13 Commits

Author SHA1 Message Date
Rob Weber
138f910d07 updated breaking change dialog (onetime) 2019-08-26 15:43:56 -05:00
Rob Weber
1d3b2f58ab updated version - out of beta 2019-08-26 15:43:44 -05:00
Rob
865416977d Update for Leia (#117)
* updated addon.xml for Krypton

* default log level is always debug now

* added screenshots per krypton format

* started new way of defining backup directories

* reconfigured simple backup process

* added an advanced backup editor and combined settings.xml scripts into a launcher

* added strings for advanced editor

* there was a function to do this

* match excluded with regex

* updated def for the addons set

* directory has to end in slash to use exists()

* added a backup set chooser on restore

* added string for restore browser

* utilize details to show root folder and icons

* save non translated paths, better cross platform support

* revert dropbox python 2.6 changes

* start of #132

* can't have duplicate ids

* updated strings

* closes #132

* added a disclaimer for breaking changes

* split backup and restore into separate functions

* updated scripting to pass in list of sets to restore

* beta version

* added 2 min delay in startup - part of #147

* forgot to remove debug message

* change to wait for abort in case someone tries to close Kodi

* add retroplayer game saves to default file list

* display restore points with most recent on top

* remove length check, breaking change with this version means old archives are no longer compatible

* format restore list according to regional settings

* this function isn't used anymore, legacy of old file manager

* use images folder as default

* added note about compatibility

* added utils function for regional date, use for scheduler notifications as well

* add/remove include and exclude directories to a set

* paths should have / at the end

* show path relative to root

* if in advanced mode allow jumping to editor from launch screen

* check that path is within root folder of set

* cannot have duplicate set names or rules regarding folders within a set

* put strings in correct lang file

* beta version bump

* accidentally deleted string id

* change exclude criteria. Regex was not matching in complex cases

* make sure the dest folder (backup set root) exists before writing to it

* modify select display to show recursive value for included folders

* use a context menu here

* added ability to toggle recursion of sub folders

* beta 3

* added support doc

* wrong branch

* don't need this import anymore

* don't need these imports

* part of #133
2019-08-26 15:40:15 -05:00
Rob
d703374792 Update issue templates 2019-08-26 14:46:28 -05:00
Rob Weber
c057f66a1e add support file 2019-08-26 14:06:16 -05:00
Rob Weber
ee2f38e865 missed a print statement 2019-08-19 15:34:32 -05:00
Rob Weber
4513eb67f9 fixed error handling for python 2.7+ 2019-08-19 15:23:48 -05:00
Rob Weber
d0b1d6bb34 fix language codes 2019-08-19 14:50:52 -05:00
Rob Weber
34529471a7 use monitor.abortRequested() 2019-08-19 14:26:15 -05:00
Rob Weber
c44d71b8b4 updated path 2019-08-19 14:21:40 -05:00
Rob Weber
bc91dfe2a2 updated croniter to match libraryautoupdate (newest) 2019-08-19 14:20:37 -05:00
Rob Weber
27fbadebda fixed addon.xml per guidlines on wiki 2019-08-19 14:18:17 -05:00
Rob Weber
32d2fde51c added timeout on startup to allow time to sync 2019-08-09 09:59:57 -05:00
35 changed files with 1664 additions and 1154 deletions

19
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,19 @@
---
name: Bug report
about: Found a problem?
title: ''
labels: bug
assignees: ''
---
Before working on a new issue I'm probably going to ask you all this stuff anyway, probably just easier to provide it now.....
**Describe the problem**
A clear and concise description of what the bug is.
**Platform and Kodi version**
What OS and Kodi version are you using? (Rpi, Android, etc)
**Link to Debug Log**
Don't just post the whole thing here, use [https://paste.kodi.tv](https://paste.kodi.tv)

15
.github/SUPPORT.md vendored Normal file
View File

@@ -0,0 +1,15 @@
# Support
There are a few different ways to get support depending on your situation.
## Need Instructions?
The quikest way to get familiar with the Backup addon is to [read the wiki](https://github.com/robweber/xbmcbackup/wiki).
## Have A Question?
If you can't find what you're looking for in the wiki, head on over to the [Kodi forums thread](https://forum.kodi.tv/showthread.php?tid=129499) for this project. Ask your question there and try to get some help.
## Unexpected Behavior or Found a Bug?
If the addon just isn't working or you have a reproducable issue that could be resolve, [start an Issue](https://github.com/robweber/xbmcbackup/issues/new).

View File

@@ -1,5 +1,7 @@
# Backup Addon
__Kodi Version Compatibility:__ Kodi 17.x (Krypton) and greater
## About
I've had to recover my database, thumbnails, and source configuration enough times that I just wanted a quick easy way to back them up. That is what this addon is meant to do.
@@ -17,6 +19,11 @@ For more specific information please check out the [wiki on Github](https://gith
* [FAQ](https://github.com/robweber/xbmcbackup/wiki/FAQ)
## Attributions
Icon files from Open Iconic — www.useiconic.com/open

151
addon.xml
View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.xbmcbackup"
name="Backup" version="1.1.3" provider-name="robweber">
name="Backup" version="1.5.0" provider-name="robweber">
<requires>
<!-- jarvis -->
<import addon="xbmc.python" version="2.24.0"/>
<import addon="xbmc.python" version="2.25.0"/>
<import addon="script.module.httplib2" version="0.8.0" />
<import addon="script.module.oauth2client" version="4.1.2" />
<import addon="script.module.uritemplate" version="0.6" />
@@ -16,76 +16,89 @@
</extension>
<extension point="xbmc.service" library="scheduler.py" start="startup" />
<extension point="xbmc.addon.metadata">
<summary lang="ar">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary>
<summary lang="be">Backup and restore your XBMC database and configuration files in the event of a crash or file corruption.</summary>
<summary lang="bg">Добавката може да създава резервно копие на XBMC и възстановява (след това) Вашата база от данни и настройки, в случай на необходимост.</summary>
<summary lang="ca">Feu còpies de seguretat i restaureu la vostra base de dades de l'XBMC i dels fitxers de configuració en el cas de fallada o corrupció dels fitxers.</summary>
<summary lang="cs">Zálohování a obnovení XBMC databáze a konfiguračních souborů v případě chyby nebo poškození souborů.</summary>
<summary lang="da">Sikkerhedskopiér og genskab din XBMC database og konfigurationsfiler i tilfælde af et nedbrud eller en ødelagt fil.</summary>
<summary lang="de">Die XBMC Datenbank sichern und bei Dateiverlust oder Beschädigung wiederherstellen.</summary>
<summary lang="el">Δημιουργήστε αντίγραφα ασφαλείας της βάσης δεδομένων και των ρυθμίσεων του XBMC για την πιθανότητα σφαλμάτων ή καταστροφής αρχείων.</summary>
<summary lang="en">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary>
<summary lang="en_NZ">Backup and restore your XBMC database and configuration files in the event of a crash or file corruption.</summary>
<summary lang="en_US">Backup and restore your XBMC database and configuration files in the event of a crash or file corruption.</summary>
<summary lang="es">Haz copia de seguridad de tu base de datos y configuración y recupera todo en caso de fallo.</summary>
<summary lang="es_MX">Respalda y restaura tu base de datos y archivos de configuración de XBMC dado el evento de un cuelgue o corrupción de archivos. </summary>
<summary lang="eu">XBMC datu-basea eta konfigurazio fitxategien babes-kopia egin kraskatze edo fitxategi hondamena saihesteko</summary>
<summary lang="fr">Sauvegarder et restaurer vos bases de données XBMC et vos fichiers de configuration en cas de crash ou de fichiers corrompus.</summary>
<summary lang="fr_CA">Sauvegarder et restaurer votre base de données et vos fichiers de configuration XBMC dans le cas d'un plantage ou d'une corruption de fichier.</summary>
<summary lang="gl">Crear copia de seguranza e restaurar a base de datos e ficheiros de configuración de XBMC no caso dun fallo ou corrupción de ficheiros.</summary>
<summary lang="he">גיבוי ושחזור מסד הנתונים וקבצי ההגדרות של XBMC במקרה של קריסה או קבצים פגומים.</summary>
<summary lang="hr">Sigurnosno kopirajte i obnovite vašu XBMC bazu podataka i datoteke podešavanja prilikom rušenja ili oštećenja datoteka.</summary>
<summary lang="hu">Biztonsági mentés készítése az XBMC adatbázisról és a beállítófájlokról vagy állítsa helyre azokat egy rendszerösszeomlás vagy adatvesztés után.</summary>
<summary lang="id">Cadangkan dan kembalikan basis data XBMC Anda beserta berkas konfigurasi apabila terjadi kerusakan atau korupsi berkas.</summary>
<summary lang="it">Effettua il backup o ripristina il tuo database di XBMC e i file di configurazione qualora si verifichi una chiusura imprevista o un danneggiamento dei file.</summary>
<summary lang="ja">システムのクラッシュやファイル破損に備えて、XBMC のデータベースや設定ファイルをバックアップ・リストアできます。</summary>
<summary lang="ko">XBMC 데이터베이스와 설정 파일을 백업하고 복구합니다.</summary>
<summary lang="lt">Atsarginė kopija katra atkuria jūsų XBMC duomenų bazę ir konfigūracijos failus avarijos ar failo sugadinimo metu.</summary>
<summary lang="nl">Back-up en herstel je XBMC database en configuratiebestanden in geval van een crash of bestandscorruptie.</summary>
<summary lang="no">Sikkerhetskopier og gjenopprett dine XBMC-databaser og -konfigurasjonsfiler i tilfelle et krasj eller filkorrupsjon.</summary>
<summary lang="pl">Stwórz kopię bezpieczeństwa oraz przywróć twoją bazę danych XBMC łącznie z plikami konfiguracyjnymi, w przypadku awarii lub uszkodzenia plików.</summary>
<summary lang="pt">Crie cópias de segurança da base de dados do XBMC e dos ficheiros de configuração. Pode restaurar o conteúdo se ocorrer um crash ou corrupção de ficheiros.</summary>
<summary lang="pt_BR">Faça backup e restaure o banco de dados do XBMC e seus arquivos de configuração, no caso de falha ou corrupção de arquivo.</summary>
<summary lang="ru">Сохраняйте и восстанавливайте базу данных и конфигурационные файлы XBMC, чтоб не допустить потерю данных в случае аварии или повреждений файлов.</summary>
<summary lang="sk">Zálohovanie a obnova XBMC databázy a konfiguračných súborov pre prípad havárie alebo poškodenia súboru.</summary>
<summary lang="sv">Ta backupp av eller återställ din XBMC-databas och konfigurationsfiler i händelse av en krash eller filkorruption.</summary>
<summary lang="zh">备份和恢复 XBMC 数据库和配置文件,以防范系统崩溃或文件损坏问题。</summary>
<description lang="ar">أسبق لك ان أضعت تخصيصاتك المفضله ورغبت لو كان بإمكانك نسخهم إحتياطياً ؟ اﻷن بات بإمكانك ذلك. يمكنك إستخراج قاعده بياناتك وقوائم التشغيل والملحقات وتخصيصاتك المفضله وغيره الى اى مصدر خارجى قابل للكتابه من قِبَل إكس بى إم سى او مباشرتاً الى نظام تخزين سحابى. يمكنك تفعيل النسخ اﻹحتياطى عند الحاجه او جدولته مُسبقاً</description>
<description lang="be">Ever hosed your XBMC configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by XBMC or directly to Dropbox cloud storage. Backups can be run on demand or via a scheduler. </description>
<description lang="bg">Някога да сте губили всички настройки, които сте правили по XBMC? А разполагахте ли с резервно копие? Е сега можете да създавате резервни копия само с едно кликване. Можете да изнасяте базата от данни, плейлистите, миниатюрите, добавките и други, на всяко място до което XBMC има права за писане или директно в Dropbox. Можете да настроите и автоматично създаване на копия през определен интервал от време.</description>
<description lang="ca">Alguna vegada s'ha carregat la seva configuració de l'XBMC i ha desitjat tenir una còpia de seguretat? Ara pot fer-ho amb un simple clic. Pot exportar la seva base de dades, llista de reproducció, miniatures, complements i altres detalls de la configuració a qualsevol font que pugui ser escrita per l'XBMC o directament a l'emmagatzematge en el núvol Dropbox. Les còpies de seguretat es poden executar sota demanda o per mitjà d'un planificador.</description>
<description lang="da">Har du prøvet at slette din XBMC opsætning, og ønsket at du havde haft sikkerhedskopi? Nu kan du få det med et enkelt klik. Du kan eksportere din database, afspilninglister, miniaturebilleder, addons og andre opsætningsdetaljer til enhver kilde, som er skrivbar for XBMC eller direkt til Dropbox cloud lager. Sikkerhedskopier kan køres manuelt eller via en tidsplan.</description>
<description lang="de">Jemals deine XBMC Konfiguration zerschossen und dir dann gewünscht, dass ein Backup existiert? Jetzt kannst du eine Sicherung mit nur einem Klick erzeugen. Du kannst deine Datenbanen, Playlisten, Thumbnails, Addons und andere Details zu einem Ort deiner Wahl sichern.</description>
<description lang="el">Σας έτυχε ποτέ να χάσετε τις ρυθμίσεις του XBMC και να εύχεστε να είχατε αντίγραφο ασφαλείας; Πλέον μπορείτε με ένα απλό κλικ. Μπορείτε να εξάγετε τη βάση δεδομένων, τις λίστες αναπαραγωγής, τις μικρογραφίες, τα πρόσθετα και άλλες λεπτομέρειες της εγκατάστασης σε οποιαδήποτε πηγή στην οποία μπορεί να γράψει το XBMC, ή απευθείας στο λογαριασμό σας στο Dropbox. Τα αντίγραφα μπορούν να γίνονται κατ' επιλογή ή μέσω προγραμματισμού.</description>
<description lang="en">Ever hosed your Kodi configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by XBMC or directly to Dropbox cloud storage. Backups can be run on demand or via a scheduler. </description>
<description lang="en_NZ">Ever hosed your XBMC configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by XBMC or directly to Dropbox cloud storage. Backups can be run on demand or via a scheduler. </description>
<description lang="en_US">Ever hosed your XBMC configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by Kodi or directly to Dropbox cloud storage. Backups can be run on demand or via a scheduler. </description>
<description lang="es">¿Alguna vez te has cargado la configuración de XBMC y habrías deseado tener una copia de seguridad? Ahora puedes tenerla con un único click. Exporta tus base de datos, listas de reproducción, miniaturas, addons y resto de configuraciones a cualquier fuente accesible por XBMC o a tu almacenamiento en Dropbox. Las copias de seguridad pueden programarse o realizarse bajo demanda.</description>
<description lang="es_MX">¿Alguna vez haz echado a perder tu configuración de XBMC y haz deseado tener un respaldo? Ahora puedes tenerlo con un simple click. Puedes exportar tu base de datos, listas de reproducción, miniaturas, addons y otros detalles de configuración correspondientes a cualquier fuente que pueda escribir XBMC. Los respaldos pueden ser efectuados a pedido o mediante una programación temporal</description>
<description lang="fr">Avez-vous déjà perdu votre configuration XBMC et espéré avoir fait une sauvegarde ? Maintenant, vous pouvez le faire en un simple click. Vous pouvez exporter vos bases de données, playlists, miniatures, addons et autres fichiers de configuration vers n'importe quel endroit accessible depuis XBMC.</description>
<description lang="fr_CA">Avez-vous déjà ruiné votre configuration XBMC et souhaité avoir une sauvegarde? Vous le pouvez maintenant en un seul clic. Vous pouvez exporter vos base de données, liste de lecture, imagettes, addiciels et autres détails de configurations vers n'importe quelle source inscriptible depuis XBMC ou directement vers le stockage en nuage Dropbox. Les sauvegardes peuvent être exécutées sur demande ou à l'aide d'un ordonnanceur.</description>
<description lang="gl">De seguro que algunha vez eliminou a configuración do XBMC e desexou ter unha copia de seguranza?. Agora pode cun só clic. Pode exportar a súa base de datos, listaxes de reprodución, miniaturas, complementos e outros detalles da configuración a calquera medio escribíbel ou directamente ao Dropbox. As copias de seguranza pódense executar baixo demanda ou programadas.</description>
<description lang="he">האם אי פעם נפגמו הגדרות XBMC וייחלת שהיה לך גיבוי ? כעת אתה יכול ליצור כזה בלחיצת כפתור קלילה. ניתן לייצא את בסיס הנתונים, רשימות ההשמעה, התמונות הממוזערות, הרחבות והגדרות נוספות לכל יעד שיש ל-XBMC הרשאת כתיבה לו או ישירות לשירות אחסון הענן דרופבוקס. ניתן לתזמן מראש גיבויים או להריצם ידנית.</description>
<description lang="hr">Jeste li ikada oštetili vaša XBMC podešavanja i poželjeli ste ih obnoviti iz sigurnosne kopije? Sada to možete jednim klikom. Možete izvesti vašu bazu podataka, popis izvođenja, minijature, dodatke i ostale pojedinosti podešavanja na svaki izvor dostupan XBMC-u ili izravno na Dropbox oblak pohrane. Sigurnosno kopiranje se može pokrenuti na zahtjev ili u planiranom vremenu.</description>
<description lang="hu">Sikerült már összekutyulni az XBMC beállításait és jó lett volna egy biztonsági mentés? Most megteheti egy kattintással. Exportálhatja az adatbázisait, lejátszáslistáit, könyvjelzőit, kiegészítőit és egyéb beállításokat bármely, az XBMC által írható tárhelyre vagy közvetlenül a Dropbox felhő tárolóba. A mentések kézzel vagy időzítetten indíthatóak.</description>
<description lang="id">Pernah membuat berantakan konfigurasi XBMC Anda dan berharap Anda punya cadangannya? Sekarang Anda dapat melakukannya dengan klik mudah. Anda dapat mengekspor basis data, daftar putar, gambar kecil, addon dan rincian konfigurasi lainnya ke sumber mana saja yang dapat ditulis oleh XBMC atau langsung ke penyimpanan awan Dropbox. Pencadangan dapat dijalankan sesuai permintaan atau terjadwal.</description>
<description lang="it">Hai mai distrutto la tua configurazione di XBMC ma non ne avevi una copia di backup? Ora puoi farlo con un semplice click. Puoi esportare il tuo database, le playlist, le anteprime, gli add-on ed altre configurazioni su ogni percorso accessibile da XBMC o direttamente su Dropbox. I backup si possono fare a richiesta o possono essere pianificati.</description>
<description lang="ja">XBMCの設定が消えてしまい、バックアップをとっておけば... と思ったことはありますかこれからは1クリックで簡単にバックアップできます。データベース、プレイリスト、サムネール、アドオン、その他設定項目を、XBMC が書き込み可能なメディアに書き出せます。Dropbox クラウドストレージにも直接書き出せます。スケジューラによる自動バックアップと、オンデマンドでのバックアップの両方が使えます。</description>
<description lang="ko">XBMC 설정 백업본이 있었으면 하고 원했던 적이 있습니까? 이제 한 번의 클릭으로 가능합니다. 데이터베이스, 재생목록, 썸네일, 애드온과 기타 세부 설정을 어디에나 내보내거나 직접 Dropbox 에 저장할 수 있습니다. 백업은 수동 또는 예약으로 실행할 수 있습니다.</description>
<description lang="lt">Kada naujinate ir konfiguruojate savo XBMC ar susimastėte, kad jums reikalinga atsarginė kopija? Dabar galite tai atlikti vienu spustelėjimu. Savo duomenų bazes, atkūrimą, miniatiūras, priedus ir kitas konfigūracijos failus galite eksportuoti iš bet kokio šaltinio. Atsarginė(-ės) kopija(-os) gali būti paleistos pareikalavus arba per tvarkaraštį.</description>
<description lang="nl">Ooit je XBMC configuratie verknalt en gewenst dat je een backup had? Nu kan dat met een simpele klik. Je kunt je bibliotheek, afspeellijsten, miniaturen, addons en andere configuratie optes exporteren naar elke bron die door XBMC beschrijfbaar is of direct naar een Dropbox cloud opslag. Backups kunnen op verzoek of via een rooster gedraaid worden.</description>
<description lang="no">Har du noen gang ødelagt XBMC-installasjonen din og ønsket at du hadde en sikkerhetskopi? Det kan du nå med et enkelt trykk. Du kan eksportere din database, spillelister, miniatyrer, utvidelser og andre konfigurasjonsdetaljer til enhver kilde som er tilgjengelig for XBMC eller til Dropbox. Du kan lage sikkerhetskopier ved behov eller med en timeplan.</description>
<description lang="pl">Straciłeś kiedyś swoją konfigurację XBMC i marzyłeś o kopii zapasowej? Teraz już możesz i to w prosty sposób. Możesz eksportować swoją bazę, listy odtwarzania, miniatury, wtyczki oraz wiele więcej do dowolnego źródła bezpośrednio z XBMC. Kopia bezpieczeństwa może być uruchomiona na żądanie lub wg harmonogramu.</description>
<description lang="pt">Já arruinou a sua configuração do XBMC e desejou ter feito uma cópia de segurança? Agora pode, com apenas um clique. Exporte a base de dados, listas de reprodução, miniaturas, add-ons e outras configurações para qualquer fonte acedível pelo XBMC. As cópias de segurança podem ser executadas manualmente ou por temporizador.</description>
<description lang="pt_BR">Sempre se preocupou com sua configuração do XBMC e desejou ter um backup? Agora você pode com um simples clique. Você pode exportar seu banco de dados, listas de reprodução, miniaturas, addons e outros detalhes de configuração para qualquer fonte gravável pelo XBMC ou diretamente ao armazenamento na nuvem Dropbox. Os backups podem ser executados sob demanda ou por agendamento.</description>
<description lang="ru">Хотите получить резервную копию настроек XBMC? Теперь можете это сделать одним щелчком мыши. Вы можете выгрузить вашу базу данных, плейлисты, эскизы, дополнения и другую нужную Вам информацию и сохранить её с помощью XBMC или выгрузить в облачное хранилище Dropbox. Резервную копию можно сделать по требованию или запускать по расписанию.</description>
<description lang="sk">Už ste niekedy poškodili konfiguráciu XBMC a priali si mať zálohu? Teraz môžete - na jeden klik. Môžete exportovať Vašu databázu, playlist, náhľady, doplnky a konfigurácie na ktorýkoľvek zdroj zapisovateľný XBMC. Zálohy môžu byť púšťané na požiadanie alebo plánovačom. </description>
<description lang="sv">Har du någonsin tappat bort din XBMC konfiguration och önskat att du hade en backup? Nu kan du enkelt med ett klick. Du kan exportera din databas, spellista, minityrer, tillägg och andra konfigurationsdetaljer till valfri källa som är skrivbar för XBMC. Backupper kan köras på begäran eller via scheman.</description>
<description lang="zh">你是否经常折腾你的 XBMC,因而希望能够有个备份?你可以把资料库、播放列表、缩略图、插件和其他配置细节导出到 XBMC 可以写入的任意位置。备份可以按需运行或通过计划任务执行。</description>
<summary lang="ar_SA">إنسخ إحتياطياً قاعده بيانات إكس بى إم سى وملفات اﻹعدادات فى حاله وقوع إنهيار مع إمكانيه اﻹسترجاع</summary>
<summary lang="be_BY">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary>
<summary lang="bg_BG">Добавката може да създава резервно копие на Kodi и възстановява (след това) Вашата база от данни и настройки, в случай на необходимост.</summary>
<summary lang="ca_ES">Feu còpies de seguretat i restaureu la vostra base de dades de l'Kodi i dels fitxers de configuració en el cas de fallada o corrupció dels fitxers.</summary>
<summary lang="cs_CZ">Zálohování a obnovení Kodi databáze a konfiguračních souborů v případě chyby nebo poškození souborů.</summary>
<summary lang="da_DK">Sikkerhedskopiér og genskab din Kodi database og konfigurationsfiler i tilfælde af et nedbrud eller en ødelagt fil.</summary>
<summary lang="de_DE">Die Kodi Datenbank sichern und bei Dateiverlust oder Beschädigung wiederherstellen.</summary>
<summary lang="el_GR">Δημιουργήστε αντίγραφα ασφαλείας της βάσης δεδομένων και των ρυθμίσεων του Kodi για την πιθανότητα σφαλμάτων ή καταστροφής αρχείων.</summary>
<summary lang="en_GB">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary>
<summary lang="en_NZ">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary>
<summary lang="en_US">Backup and restore your Kodi database and configuration files in the event of a crash or file corruption.</summary>
<summary lang="es_ES">Haz copia de seguridad de tu base de datos y configuración y recupera todo en caso de fallo.</summary>
<summary lang="es_MX">Respalda y restaura tu base de datos y archivos de configuración de Kodi dado el evento de un cuelgue o corrupción de archivos. </summary>
<summary lang="eu_ES">Kodi datu-basea eta konfigurazio fitxategien babes-kopia egin kraskatze edo fitxategi hondamena saihesteko</summary>
<summary lang="fr_FR">Sauvegarder et restaurer vos bases de données Kodi et vos fichiers de configuration en cas de crash ou de fichiers corrompus.</summary>
<summary lang="fr_CA">Sauvegarder et restaurer votre base de données et vos fichiers de configuration Kodi dans le cas d'un plantage ou d'une corruption de fichier.</summary>
<summary lang="gl_ES">Crear copia de seguranza e restaurar a base de datos e ficheiros de configuración de Kodi no caso dun fallo ou corrupción de ficheiros.</summary>
<summary lang="he_IL">גיבוי ושחזור מסד הנתונים וקבצי ההגדרות של Kodi במקרה של קריסה או קבצים פגומים.</summary>
<summary lang="hr_HR">Sigurnosno kopirajte i obnovite vašu Kodi bazu podataka i datoteke podešavanja prilikom rušenja ili oštećenja datoteka.</summary>
<summary lang="hu_HU">Biztonsági mentés készítése az Kodi adatbázisról és a beállítófájlokról vagy állítsa helyre azokat egy rendszerösszeomlás vagy adatvesztés után.</summary>
<summary lang="id_ID">Cadangkan dan kembalikan basis data Kodi Anda beserta berkas konfigurasi apabila terjadi kerusakan atau korupsi berkas.</summary>
<summary lang="it_IT">Effettua il backup o ripristina il tuo database di Kodi e i file di configurazione qualora si verifichi una chiusura imprevista o un danneggiamento dei file.</summary>
<summary lang="ja_JP">システムのクラッシュやファイル破損に備えて、Kodi のデータベースや設定ファイルをバックアップ・リストアできます。</summary>
<summary lang="ko_KR">Kodi 데이터베이스와 설정 파일을 백업하고 복구합니다.</summary>
<summary lang="lt_LT">Atsarginė kopija katra atkuria jūsų Kodi duomenų bazę ir konfigūracijos failus avarijos ar failo sugadinimo metu.</summary>
<summary lang="nl_NL">Back-up en herstel je Kodi database en configuratiebestanden in geval van een crash of bestandscorruptie.</summary>
<summary lang="nb_NO">Sikkerhetskopier og gjenopprett dine Kodi-databaser og -konfigurasjonsfiler i tilfelle et krasj eller filkorrupsjon.</summary>
<summary lang="pl_PL">Stwórz kopię bezpieczeństwa oraz przywróć twoją bazę danych Kodi łącznie z plikami konfiguracyjnymi, w przypadku awarii lub uszkodzenia plików.</summary>
<summary lang="pt_PT">Crie cópias de segurança da base de dados do Kodi e dos ficheiros de configuração. Pode restaurar o conteúdo se ocorrer um crash ou corrupção de ficheiros.</summary>
<summary lang="pt_BR">Faça backup e restaure o banco de dados do Kodi e seus arquivos de configuração, no caso de falha ou corrupção de arquivo.</summary>
<summary lang="ru_RU">Сохраняйте и восстанавливайте базу данных и конфигурационные файлы Kodi, чтоб не допустить потерю данных в случае аварии или повреждений файлов.</summary>
<summary lang="sk_SK">Zálohovanie a obnova Kodi databázy a konfiguračných súborov pre prípad havárie alebo poškodenia súboru.</summary>
<summary lang="sv_SE">Ta backupp av eller återställ din Kodi-databas och konfigurationsfiler i händelse av en krash eller filkorruption.</summary>
<summary lang="zh_CN">备份和恢复 Kodi 数据库和配置文件,以防范系统崩溃或文件损坏问题。</summary>
<description lang="ar_SA">أسبق لك ان أضعت تخصيصاتك المفضله ورغبت لو كان بإمكانك نسخهم إحتياطياً ؟ اﻷن بات بإمكانك ذلك. يمكنك إستخراج قاعده بياناتك وقوائم التشغيل والملحقات وتخصيصاتك المفضله وغيره الى اى مصدر خارجى قابل للكتابه من قِبَل إكس بى إم سى او مباشرتاً الى نظام تخزين سحابى. يمكنك تفعيل النسخ اﻹحتياطى عند الحاجه او جدولته مُسبقاً</description>
<description lang="be_BY">Ever hosed your Kodi configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by Kodi or directly to Dropbox cloud storage. Backups can be run on demand or via a scheduler. </description>
<description lang="bg_BG">Някога да сте губили всички настройки, които сте правили по Kodi? А разполагахте ли с резервно копие? Е сега можете да създавате резервни копия само с едно кликване. Можете да изнасяте базата от данни, плейлистите, миниатюрите, добавките и други, на всяко място до което Kodi има права за писане или директно в Dropbox. Можете да настроите и автоматично създаване на копия през определен интервал от време.</description>
<description lang="ca_ES">Alguna vegada s'ha carregat la seva configuració de l'Kodi i ha desitjat tenir una còpia de seguretat? Ara pot fer-ho amb un simple clic. Pot exportar la seva base de dades, llista de reproducció, miniatures, complements i altres detalls de la configuració a qualsevol font que pugui ser escrita per l'Kodi o directament a l'emmagatzematge en el núvol Dropbox. Les còpies de seguretat es poden executar sota demanda o per mitjà d'un planificador.</description>
<description lang="da_DK">Har du prøvet at slette din Kodi opsætning, og ønsket at du havde haft sikkerhedskopi? Nu kan du få det med et enkelt klik. Du kan eksportere din database, afspilninglister, miniaturebilleder, addons og andre opsætningsdetaljer til enhver kilde, som er skrivbar for Kodi eller direkt til Dropbox cloud lager. Sikkerhedskopier kan køres manuelt eller via en tidsplan.</description>
<description lang="de_DE">Jemals deine Kodi Konfiguration zerschossen und dir dann gewünscht, dass ein Backup existiert? Jetzt kannst du eine Sicherung mit nur einem Klick erzeugen. Du kannst deine Datenbanen, Playlisten, Thumbnails, Addons und andere Details zu einem Ort deiner Wahl sichern.</description>
<description lang="el_GR">Σας έτυχε ποτέ να χάσετε τις ρυθμίσεις του Kodi και να εύχεστε να είχατε αντίγραφο ασφαλείας; Πλέον μπορείτε με ένα απλό κλικ. Μπορείτε να εξάγετε τη βάση δεδομένων, τις λίστες αναπαραγωγής, τις μικρογραφίες, τα πρόσθετα και άλλες λεπτομέρειες της εγκατάστασης σε οποιαδήποτε πηγή στην οποία μπορεί να γράψει το Kodi, ή απευθείας στο λογαριασμό σας στο Dropbox. Τα αντίγραφα μπορούν να γίνονται κατ' επιλογή ή μέσω προγραμματισμού.</description>
<description lang="en_GB">Ever hosed your Kodi configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by Kodi or directly to Dropbox cloud storage. Backups can be run on demand or via a scheduler. </description>
<description lang="en_NZ">Ever hosed your Kodi configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by Kodi or directly to Dropbox cloud storage. Backups can be run on demand or via a scheduler. </description>
<description lang="en_US">Ever hosed your Kodi configuration and wished you'd had a backup? Now you can with one easy click. You can export your database, playlist, thumbnails, addons and other configuration details to any source writeable by Kodi or directly to Dropbox cloud storage. Backups can be run on demand or via a scheduler. </description>
<description lang="es_ES">¿Alguna vez te has cargado la configuración de Kodi y habrías deseado tener una copia de seguridad? Ahora puedes tenerla con un único click. Exporta tus base de datos, listas de reproducción, miniaturas, addons y resto de configuraciones a cualquier fuente accesible por Kodi o a tu almacenamiento en Dropbox. Las copias de seguridad pueden programarse o realizarse bajo demanda.</description>
<description lang="es_MX">¿Alguna vez haz echado a perder tu configuración de Kodi y haz deseado tener un respaldo? Ahora puedes tenerlo con un simple click. Puedes exportar tu base de datos, listas de reproducción, miniaturas, addons y otros detalles de configuración correspondientes a cualquier fuente que pueda escribir Kodi. Los respaldos pueden ser efectuados a pedido o mediante una programación temporal</description>
<description lang="fr_FR">Avez-vous déjà perdu votre configuration Kodi et espéré avoir fait une sauvegarde ? Maintenant, vous pouvez le faire en un simple click. Vous pouvez exporter vos bases de données, playlists, miniatures, addons et autres fichiers de configuration vers n'importe quel endroit accessible depuis Kodi.</description>
<description lang="fr_CA">Avez-vous déjà ruiné votre configuration Kodi et souhaité avoir une sauvegarde? Vous le pouvez maintenant en un seul clic. Vous pouvez exporter vos base de données, liste de lecture, imagettes, addiciels et autres détails de configurations vers n'importe quelle source inscriptible depuis Kodi ou directement vers le stockage en nuage Dropbox. Les sauvegardes peuvent être exécutées sur demande ou à l'aide d'un ordonnanceur.</description>
<description lang="gl_ES">De seguro que algunha vez eliminou a configuración do Kodi e desexou ter unha copia de seguranza?. Agora pode cun só clic. Pode exportar a súa base de datos, listaxes de reprodución, miniaturas, complementos e outros detalles da configuración a calquera medio escribíbel ou directamente ao Dropbox. As copias de seguranza pódense executar baixo demanda ou programadas.</description>
<description lang="he_IL">האם אי פעם נפגמו הגדרות Kodi וייחלת שהיה לך גיבוי ? כעת אתה יכול ליצור כזה בלחיצת כפתור קלילה. ניתן לייצא את בסיס הנתונים, רשימות ההשמעה, התמונות הממוזערות, הרחבות והגדרות נוספות לכל יעד שיש ל-Kodi הרשאת כתיבה לו או ישירות לשירות אחסון הענן דרופבוקס. ניתן לתזמן מראש גיבויים או להריצם ידנית.</description>
<description lang="hr_HR">Jeste li ikada oštetili vaša Kodi podešavanja i poželjeli ste ih obnoviti iz sigurnosne kopije? Sada to možete jednim klikom. Možete izvesti vašu bazu podataka, popis izvođenja, minijature, dodatke i ostale pojedinosti podešavanja na svaki izvor dostupan Kodi-u ili izravno na Dropbox oblak pohrane. Sigurnosno kopiranje se može pokrenuti na zahtjev ili u planiranom vremenu.</description>
<description lang="hu_HU">Sikerült már összekutyulni az Kodi beállításait és jó lett volna egy biztonsági mentés? Most megteheti egy kattintással. Exportálhatja az adatbázisait, lejátszáslistáit, könyvjelzőit, kiegészítőit és egyéb beállításokat bármely, az Kodi által írható tárhelyre vagy közvetlenül a Dropbox felhő tárolóba. A mentések kézzel vagy időzítetten indíthatóak.</description>
<description lang="id_ID">Pernah membuat berantakan konfigurasi Kodi Anda dan berharap Anda punya cadangannya? Sekarang Anda dapat melakukannya dengan klik mudah. Anda dapat mengekspor basis data, daftar putar, gambar kecil, addon dan rincian konfigurasi lainnya ke sumber mana saja yang dapat ditulis oleh Kodi atau langsung ke penyimpanan awan Dropbox. Pencadangan dapat dijalankan sesuai permintaan atau terjadwal.</description>
<description lang="it_IT">Hai mai distrutto la tua configurazione di Kodi ma non ne avevi una copia di backup? Ora puoi farlo con un semplice click. Puoi esportare il tuo database, le playlist, le anteprime, gli add-on ed altre configurazioni su ogni percorso accessibile da Kodi o direttamente su Dropbox. I backup si possono fare a richiesta o possono essere pianificati.</description>
<description lang="ja_JP">Kodiの設定が消えてしまい、バックアップをとっておけば... と思ったことはありますかこれからは1クリックで簡単にバックアップできます。データベース、プレイリスト、サムネール、アドオン、その他設定項目を、Kodi が書き込み可能なメディアに書き出せます。Dropbox クラウドストレージにも直接書き出せます。スケジューラによる自動バックアップと、オンデマンドでのバックアップの両方が使えます。</description>
<description lang="ko_KR">Kodi 설정 백업본이 있었으면 하고 원했던 적이 있습니까? 이제 한 번의 클릭으로 가능합니다. 데이터베이스, 재생목록, 썸네일, 애드온과 기타 세부 설정을 어디에나 내보내거나 직접 Dropbox 에 저장할 수 있습니다. 백업은 수동 또는 예약으로 실행할 수 있습니다.</description>
<description lang="lt_LT">Kada naujinate ir konfiguruojate savo Kodi ar susimastėte, kad jums reikalinga atsarginė kopija? Dabar galite tai atlikti vienu spustelėjimu. Savo duomenų bazes, atkūrimą, miniatiūras, priedus ir kitas konfigūracijos failus galite eksportuoti iš bet kokio šaltinio. Atsarginė(-ės) kopija(-os) gali būti paleistos pareikalavus arba per tvarkaraštį.</description>
<description lang="nl_NL">Ooit je Kodi configuratie verknalt en gewenst dat je een backup had? Nu kan dat met een simpele klik. Je kunt je bibliotheek, afspeellijsten, miniaturen, addons en andere configuratie optes exporteren naar elke bron die door Kodi beschrijfbaar is of direct naar een Dropbox cloud opslag. Backups kunnen op verzoek of via een rooster gedraaid worden.</description>
<description lang="nb_NO">Har du noen gang ødelagt Kodi-installasjonen din og ønsket at du hadde en sikkerhetskopi? Det kan du nå med et enkelt trykk. Du kan eksportere din database, spillelister, miniatyrer, utvidelser og andre konfigurasjonsdetaljer til enhver kilde som er tilgjengelig for Kodi eller til Dropbox. Du kan lage sikkerhetskopier ved behov eller med en timeplan.</description>
<description lang="pl_PL">Straciłeś kiedyś swoją konfigurację Kodi i marzyłeś o kopii zapasowej? Teraz już możesz i to w prosty sposób. Możesz eksportować swoją bazę, listy odtwarzania, miniatury, wtyczki oraz wiele więcej do dowolnego źródła bezpośrednio z Kodi. Kopia bezpieczeństwa może być uruchomiona na żądanie lub wg harmonogramu.</description>
<description lang="pt_PT">Já arruinou a sua configuração do Kodi e desejou ter feito uma cópia de segurança? Agora pode, com apenas um clique. Exporte a base de dados, listas de reprodução, miniaturas, add-ons e outras configurações para qualquer fonte acedível pelo Kodi. As cópias de segurança podem ser executadas manualmente ou por temporizador.</description>
<description lang="pt_BR">Sempre se preocupou com sua configuração do Kodi e desejou ter um backup? Agora você pode com um simples clique. Você pode exportar seu banco de dados, listas de reprodução, miniaturas, addons e outros detalhes de configuração para qualquer fonte gravável pelo Kodi ou diretamente ao armazenamento na nuvem Dropbox. Os backups podem ser executados sob demanda ou por agendamento.</description>
<description lang="ru_RU">Хотите получить резервную копию настроек Kodi? Теперь можете это сделать одним щелчком мыши. Вы можете выгрузить вашу базу данных, плейлисты, эскизы, дополнения и другую нужную Вам информацию и сохранить её с помощью Kodi или выгрузить в облачное хранилище Dropbox. Резервную копию можно сделать по требованию или запускать по расписанию.</description>
<description lang="sk_SK">Už ste niekedy poškodili konfiguráciu Kodi a priali si mať zálohu? Teraz môžete - na jeden klik. Môžete exportovať Vašu databázu, playlist, náhľady, doplnky a konfigurácie na ktorýkoľvek zdroj zapisovateľný Kodi. Zálohy môžu byť púšťané na požiadanie alebo plánovačom. </description>
<description lang="sv_SE">Har du någonsin tappat bort din Kodi konfiguration och önskat att du hade en backup? Nu kan du enkelt med ett klick. Du kan exportera din databas, spellista, minityrer, tillägg och andra konfigurationsdetaljer till valfri källa som är skrivbar för Kodi. Backupper kan köras på begäran eller via scheman.</description>
<description lang="zh_CN">你是否经常折腾你的 Kodi,因而希望能够有个备份?你可以把资料库、播放列表、缩略图、插件和其他配置细节导出到 Kodi 可以写入的任意位置。备份可以按需运行或通过计划任务执行。</description>
<language></language>
<platform>all</platform>
<license>The MIT License</license>
<forum>http://forum.xbmc.org/showthread.php?tid=129499</forum>
<forum>https://forum.kodi.tv/showthread.php?tid=129499</forum>
<source>https://github.com/robweber/xbmcbackup</source>
<email></email>
<assets>
<icon>resources/images/icon.png</icon>
<screenshot>resources/images/screenshot1.png</screenshot>
<screenshot>resources/images/screenshot2.png</screenshot>
<screenshot>resources/images/screenshot3.png</screenshot>
<screenshot>resources/images/screenshot4.png</screenshot>
</assets>
<news>Version 1.1.4
- added file chunk support for dropbox uploads
- fixed settings duplicate ids, thanks aster-anto
- added scheduler delay to assist with time sync (rpi mostly)
</news>
</extension>
</addon>

View File

@@ -1,37 +0,0 @@
import sys
import urlparse
import xbmcgui
import resources.lib.utils as utils
from resources.lib.authorizers import DropboxAuthorizer,GoogleDriveAuthorizer
def get_params():
param = {}
try:
for i in sys.argv:
args = i
if(args.startswith('?')):
args = args[1:]
param.update(dict(urlparse.parse_qsl(args)))
except:
pass
return param
params = get_params()
#drobpox
if(params['type'] == 'dropbox'):
authorizer = DropboxAuthorizer()
if(authorizer.authorize()):
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30027) + ' ' + utils.getString(30106))
else:
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30107) + ' ' + utils.getString(30027))
#google drive
elif(params['type'] == 'google_drive'):
authorizer = GoogleDriveAuthorizer()
if(authorizer.authorize()):
xbmcgui.Dialog().ok("Backup",utils.getString(30098) + ' ' + utils.getString(30106))
else:
xbmcgui.Dialog().ok("Backup",utils.getString(30107) + ' ' + utils.getString(30098))

View File

@@ -1,7 +1,12 @@
Version 1.5.0
Overhaul of file selection and restore procedures. Breaking Change with previous versions PR117
Version 1.1.3
added file chunk support for dropbox uploads
fixed settings duplicate ids, thanks aster-anto
added scheduler delay to assist with time sync (rpi mostly)
Version 1.1.2

View File

@@ -1,77 +1,91 @@
import urlparse
import xbmcgui
import resources.lib.utils as utils
from resources.lib.backup import XbmcBackup
def get_params():
param = {}
if(len(sys.argv) > 1):
for i in sys.argv:
args = i
if(args.startswith('?')):
args = args[1:]
param.update(dict(urlparse.parse_qsl(args)))
return param
#the program mode
mode = -1
params = get_params()
if("mode" in params):
if(params['mode'] == 'backup'):
mode = 0
elif(params['mode'] == 'restore'):
mode = 1
#if mode wasn't passed in as arg, get from user
if(mode == -1):
#figure out if this is a backup or a restore from the user
mode = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30023),[utils.getString(30016),utils.getString(30017),utils.getString(30099)])
#check if program should be run
if(mode != -1):
#run the profile backup
backup = XbmcBackup()
if(mode == 2):
#open the settings dialog
utils.openSettings()
elif(backup.remoteConfigured()):
if(mode == backup.Restore):
#get list of valid restore points
restorePoints = backup.listBackups()
pointNames = []
folderNames = []
for aDir in restorePoints:
pointNames.append(aDir[1])
folderNames.append(aDir[0])
selectedRestore = -1
if("archive" in params):
#check that the user give archive exists
if(params['archive'] in folderNames):
#set the index
selectedRestore = folderNames.index(params['archive'])
utils.log(str(selectedRestore) + " : " + params['archive'])
else:
utils.showNotification(utils.getString(30045))
utils.log(params['archive'] + ' is not a valid restore point')
else:
#allow user to select the backup to restore from
selectedRestore = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30021),pointNames)
if(selectedRestore != -1):
backup.selectRestore(restorePoints[selectedRestore][0])
backup.run(mode)
else:
#can't go any further
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045))
utils.openSettings()
import sys, urlparse
import xbmc, xbmcgui
import resources.lib.utils as utils
from resources.lib.backup import XbmcBackup
def get_params():
param = {}
if(len(sys.argv) > 1):
for i in sys.argv:
args = i
if(args.startswith('?')):
args = args[1:]
param.update(dict(urlparse.parse_qsl(args)))
return param
#the program mode
mode = -1
params = get_params()
if("mode" in params):
if(params['mode'] == 'backup'):
mode = 0
elif(params['mode'] == 'restore'):
mode = 1
#if mode wasn't passed in as arg, get from user
if(mode == -1):
#by default, Backup,Restore,Open Settings
options = [utils.getString(30016),utils.getString(30017),utils.getString(30099)]
#find out if we're using the advanced editor
if(int(utils.getSetting('backup_selection_type')) == 1):
options.append(utils.getString(30125))
#figure out if this is a backup or a restore from the user
mode = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30023),options)
#check if program should be run
if(mode != -1):
#run the profile backup
backup = XbmcBackup()
if(mode == 2):
#open the settings dialog
utils.openSettings()
elif(mode == 3 and int(utils.getSetting('backup_selection_type')) == 1):
#open the advanced editor
xbmc.executebuiltin('RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_editor)')
elif(backup.remoteConfigured()):
if(mode == backup.Restore):
#get list of valid restore points
restorePoints = backup.listBackups()
pointNames = []
folderNames = []
for aDir in restorePoints:
pointNames.append(aDir[1])
folderNames.append(aDir[0])
selectedRestore = -1
if("archive" in params):
#check that the user give archive exists
if(params['archive'] in folderNames):
#set the index
selectedRestore = folderNames.index(params['archive'])
utils.log(str(selectedRestore) + " : " + params['archive'])
else:
utils.showNotification(utils.getString(30045))
utils.log(params['archive'] + ' is not a valid restore point')
else:
#allow user to select the backup to restore from
selectedRestore = xbmcgui.Dialog().select(utils.getString(30010) + " - " + utils.getString(30021),pointNames)
if(selectedRestore != -1):
backup.selectRestore(restorePoints[selectedRestore][0])
if('sets' in params):
backup.restore(selectedSets=params['sets'].split('|'))
else:
backup.restore()
else:
backup.backup()
else:
#can't go any further
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30045))
utils.openSettings()

64
launcher.py Normal file
View File

@@ -0,0 +1,64 @@
import sys
import urlparse
import xbmc
import xbmcgui
import xbmcvfs
import resources.lib.utils as utils
from resources.lib.authorizers import DropboxAuthorizer,GoogleDriveAuthorizer
from resources.lib.advanced_editor import AdvancedBackupEditor
#launcher for various helpful functions found in the settings.xml area
def authorize_cloud(cloudProvider):
#drobpox
if(cloudProvider == 'dropbox'):
authorizer = DropboxAuthorizer()
if(authorizer.authorize()):
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30027) + ' ' + utils.getString(30106))
else:
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30107) + ' ' + utils.getString(30027))
#google drive
elif(cloudProvider == 'google_drive'):
authorizer = GoogleDriveAuthorizer()
if(authorizer.authorize()):
xbmcgui.Dialog().ok("Backup",utils.getString(30098) + ' ' + utils.getString(30106))
else:
xbmcgui.Dialog().ok("Backup",utils.getString(30107) + ' ' + utils.getString(30098))
def remove_auth():
#triggered from settings.xml - asks if user wants to delete OAuth token information
shouldDelete = xbmcgui.Dialog().yesno(utils.getString(30093),utils.getString(30094),utils.getString(30095),autoclose=7000)
if(shouldDelete):
#delete any of the known token file types
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "tokens.txt")) #dropbox
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "google_drive.dat")) #google drive
def get_params():
param = {}
try:
for i in sys.argv:
args = i
if(args.startswith('?')):
args = args[1:]
param.update(dict(urlparse.parse_qsl(args)))
except:
pass
return param
params = get_params()
if(params['action'] == 'authorize_cloud'):
authorize_cloud(params['provider'])
elif(params['action'] == 'remove_auth'):
remove_auth()
elif(params['action'] == 'advanced_editor'):
editor = AdvancedBackupEditor()
editor.showMainScreen()
elif(params['action'] == 'advanced_copy_config'):
editor = AdvancedBackupEditor()
editor.copySimpleConfig()

View File

@@ -1,13 +0,0 @@
import xbmc
import xbmcgui
import xbmcvfs
import resources.lib.utils as utils
#triggered from settings.xml - asks if user wants to delete OAuth token information
shouldDelete = xbmcgui.Dialog().yesno(utils.getString(30093),utils.getString(30094),utils.getString(30095),autoclose=7000)
if(shouldDelete):
#delete any of the known token file types
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "tokens.txt")) #dropbox
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "google_drive.dat")) #google drive

View File

@@ -0,0 +1,105 @@
{
"addons":{
"root":"special://home/addons/",
"dirs":[
{
"type":"include",
"path":"special://home/addons/",
"recurse":true
},
{
"type":"exclude",
"path":"special://home/addons/packages/"
},
{
"type":"exclude",
"path":"special://home/addons/temp/"
}
]
},
"addon_data":{
"root":"special://home/userdata/addon_data/",
"dirs":[
{
"type":"include",
"path":"special://home/userdata/addon_data/",
"recurse":true
}
]
},
"database":{
"root":"special://home/userdata/Database/",
"dirs":[
{
"type":"include",
"path":"special://home/userdata/Database/",
"recurse":true
}
]
},
"game_saves":{
"root":"special://home/userdata/Savestates/",
"dirs":[
{
"type":"include",
"path":"special://home/userdata/Savestates/",
"recurse":true
}
]
},
"playlists":{
"root":"special://home/userdata/playlists/",
"dirs":[
{
"type":"include",
"path":"special://home/userdata/playlists/",
"recurse":true
}
]
},
"profiles":{
"root":"special://home/userdata/profiles/",
"dirs":[
{
"type":"include",
"path":"special://home/userdata/profiles/",
"recurse":true
}
]
},
"thumbnails":{
"root":"special://home/userdata/Thumbnails/",
"dirs":[
{
"type":"include",
"path":"special://home/userdata/Thumbnails/",
"recurse":true
}
]
},
"config":{
"root":"special://home/userdata/",
"dirs":[
{
"type":"include",
"path":"special://home/userdata/",
"recurse":false
},
{
"type":"include",
"path":"special://home/userdata/keymaps/",
"recurse":true
},
{
"type":"include",
"path":"special://home/userdata/peripheral_data/",
"recurse":true
},
{
"type":"include",
"path":"special://home/userdata/library/",
"recurse":true
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

View File

@@ -48,6 +48,14 @@ msgctxt "#30013"
msgid "Scheduling"
msgstr "Scheduling"
msgctxt "#30014"
msgid "Simple"
msgstr "Simple"
msgctxt "#30015"
msgid "Advanced"
msgstr "Advanced"
msgctxt "#30016"
msgid "Backup"
msgstr "Backup"
@@ -129,12 +137,12 @@ msgid "Config Files"
msgstr "Config Files"
msgctxt "#30036"
msgid "Custom Directory 1"
msgstr "Custom Directory 1"
msgid "Disclaimer"
msgstr "Disclaimer"
msgctxt "#30037"
msgid "Custom Directory 2"
msgstr "Custom Directory 2"
msgid "Canceling this menu will close and save changes"
msgstr "Canceling this menu will close and save changes"
msgctxt "#30038"
msgid "Advanced Settings Detected"
@@ -420,3 +428,131 @@ msgstr "Visit https://console.developers.google.com/"
msgctxt "#30109"
msgid "Run on startup if missed"
msgstr "Run on startup if missed"
msgctxt "#30110"
msgid "Set Name"
msgstr ""
msgctxt "#30111"
msgid "Root folder selection"
msgstr ""
msgctxt "#30112"
msgid "Browse Folder"
msgstr ""
msgctxt "#30113"
msgid "Enter Own"
msgstr ""
msgctxt "#30114"
msgid "starts in Kodi home"
msgstr ""
msgctxt "#30115"
msgid "enter path to start there"
msgstr ""
msgctxt "#30116"
msgid "Enter root path"
msgstr ""
msgctxt "#30117"
msgid "Path Error"
msgstr ""
msgctxt "#30118"
msgid "Path does not exist"
msgstr ""
msgctxt "#30119"
msgid "Select root"
msgstr ""
msgctxt "#30120"
msgid "Add Exclude Folder"
msgstr ""
msgctxt "#30121"
msgid "Root Folder"
msgstr ""
msgctxt "#30122"
msgid "Edit"
msgstr ""
msgctxt "#30123"
msgid "Delete"
msgstr ""
msgctxt "#30124"
msgid "Choose Action"
msgstr ""
msgctxt "#30125"
msgid "Advanced Editor"
msgstr ""
msgctxt "#30126"
msgid "Add Set"
msgstr ""
msgctxt "#30127"
msgid "Delete Set"
msgstr ""
msgctxt "#30128"
msgid "Are you sure you want to delete?"
msgstr ""
msgctxt "#30129"
msgid "Exclude"
msgstr ""
msgctxt "#30130"
msgid "The root folder cannot be changed"
msgstr ""
msgctxt "#30131"
msgid "Choose Sets to Restore"
msgstr ""
msgctxt "#30132"
msgid "Version 1.5.0 requires you to setup your file selections again - this is a breaking change"
msgstr ""
msgctxt "#30133"
msgid "Game Saves"
msgstr ""
msgctxt "#30134"
msgid "Include"
msgstr ""
msgctxt "#30135"
msgid "Add Include Folder"
msgstr ""
msgctxt "#30136"
msgid "Path must be within root folder"
msgstr ""
msgctxt "#30137"
msgid "This path is part of a rule already"
msgstr ""
msgctxt "#30138"
msgid "Set Name exists already"
msgstr ""
msgctxt "#30139"
msgid "Copy Simple Config"
msgstr ""
msgctxt "#30140"
msgid "This will copy the default Simple file selection to the Advanced Editor"
msgstr ""
msgctxt "#30141"
msgid "This will erase any current Advanced Editor settings"
msgstr ""

View File

@@ -129,14 +129,6 @@ msgctxt "#30035"
msgid "Config Files"
msgstr "Config Files"
msgctxt "#30036"
msgid "Custom Directory 1"
msgstr "Custom Directory 1"
msgctxt "#30037"
msgid "Custom Directory 2"
msgstr "Custom Directory 2"
msgctxt "#30038"
msgid "Advanced Settings Detected"
msgstr "Advanced Settings Detected"

View File

@@ -0,0 +1,229 @@
import json
import utils as utils
import xbmcvfs
import xbmc
import xbmcgui
class BackupSetManager:
jsonFile = xbmc.translatePath(utils.data_dir() + "custom_paths.json")
paths = None
def __init__(self):
self.paths = {}
#try and read in the custom file
self._readFile()
def addSet(self,aSet):
self.paths[aSet['name']] = {'root':aSet['root'],'dirs':[{"type":"include","path":aSet['root'],'recurse':True}]}
#save the file
self._writeFile()
def updateSet(self,name,aSet):
self.paths[name] = aSet
#save the file
self._writeFile()
def deleteSet(self,index):
#match the index to a key
keys = self.getSets()
#delete this set
del self.paths[keys[index]]
#save the file
self._writeFile()
def getSets(self):
#list all current sets by name
keys = self.paths.keys()
keys.sort()
return keys
def getSet(self,index):
keys = self.getSets();
#return the set at this index
return {'name':keys[index],'set':self.paths[keys[index]]}
def validateSetName(self,name):
return (name not in self.getSets())
def _writeFile(self):
#create the custom file
aFile = xbmcvfs.File(self.jsonFile,'w')
aFile.write(json.dumps(self.paths))
aFile.close()
def _readFile(self):
if(xbmcvfs.exists(self.jsonFile)):
#read in the custom file
aFile = xbmcvfs.File(self.jsonFile)
#load custom dirs
self.paths = json.loads(aFile.read())
aFile.close()
else:
#write a blank file
self._writeFile()
class AdvancedBackupEditor:
dialog = None
def __init__(self):
self.dialog = xbmcgui.Dialog()
def _cleanPath(self,root,path):
return path[len(root)-1:]
def _validatePath(self,root,path):
return path.startswith(root)
def createSet(self):
backupSet = None
name = self.dialog.input(utils.getString(30110),defaultt='Backup Set')
if(name != None):
#give a choice to start in home or enter a root path
enterHome = self.dialog.yesno(utils.getString(30111),line1=utils.getString(30112) + " - " + utils.getString(30114),line2=utils.getString(30113) + " - " + utils.getString(30115),nolabel=utils.getString(30112),yeslabel=utils.getString(30113))
rootFolder = 'special://home'
if(enterHome):
rootFolder = self.dialog.input(utils.getString(30116),defaultt=rootFolder)
#direcotry has to end in slash
if(rootFolder[:-1] != '/'):
rootFolder = rootFolder + '/'
#check that this path even exists
if(not xbmcvfs.exists(xbmc.translatePath(rootFolder))):
self.dialog.ok(utils.getString(30117),utils.getString(30118),rootFolder)
return None
else:
#select path to start set
rootFolder = self.dialog.browse(type=0,heading=utils.getString(30119),shares='files',defaultt=rootFolder)
backupSet = {'name':name,'root':rootFolder}
return backupSet
def editSet(self,name,backupSet):
optionSelected = ''
rootPath = backupSet['root']
utils.log(rootPath)
while(optionSelected != -1):
options = [xbmcgui.ListItem(utils.getString(30120),"Exclude a specific folder from this backup set"),xbmcgui.ListItem(utils.getString(30135),"Include a specific folder to this backup set"),xbmcgui.ListItem(rootPath,utils.getString(30121))]
for aDir in backupSet['dirs']:
if(aDir['type'] == 'exclude'):
options.append(xbmcgui.ListItem(self._cleanPath(rootPath,aDir['path']),"%s: %s" % ("Type",utils.getString(30129))))
elif(aDir['type'] == 'include'):
options.append(xbmcgui.ListItem(self._cleanPath(rootPath,aDir['path']),"%s: %s | %s: %s" % ("Type",utils.getString(30134),"Include Sub Folders",str(aDir['recurse']))))
optionSelected = self.dialog.select(utils.getString(30122) + ' ' + name,options,useDetails=True)
if(optionSelected == 0 or optionSelected == 1):
#add a folder, will equal root if cancel is hit
addFolder = self.dialog.browse(type=0,heading=utils.getString(30120),shares='files',defaultt=backupSet['root'])
if(addFolder.startswith(rootPath)):
if(not any(addFolder == aDir['path'] for aDir in backupSet['dirs'])):
#cannot add root as an exclusion
if(optionSelected == 0 and addFolder != backupSet['root']):
backupSet['dirs'].append({"path":addFolder,"type":"exclude"})
elif(optionSelected == 1):
#can add root as inclusion
backupSet['dirs'].append({"path":addFolder,"type":"include","recurse":True})
else:
#this path is already part of another include/exclude rule
self.dialog.ok(utils.getString(30117),utils.getString(30137),addFolder)
else:
#folder must be under root folder
self.dialog.ok(utils.getString(30117), utils.getString(30136),rootPath)
elif(optionSelected == 2):
self.dialog.ok(utils.getString(30121),utils.getString(30130),backupSet['root'])
elif(optionSelected > 2):
cOptions = ['Delete']
if(backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
cOptions.append('Toggle Sub Folders')
contextOption = self.dialog.contextmenu(cOptions)
if(contextOption == 0):
if(self.dialog.yesno(heading=utils.getString(30123),line1=utils.getString(30128))):
#remove folder
del backupSet['dirs'][optionSelected - 3]
elif(contextOption == 1 and backupSet['dirs'][optionSelected - 3]['type'] == 'include'):
#toggle if this folder should be recursive
backupSet['dirs'][optionSelected - 3]['recurse'] = not backupSet['dirs'][optionSelected - 3]['recurse']
return backupSet
def showMainScreen(self):
exitCondition = ""
customPaths = BackupSetManager()
#show this every time
self.dialog.ok(utils.getString(30036),utils.getString(30037))
while(exitCondition != -1):
#load the custom paths
options = [xbmcgui.ListItem(utils.getString(30126),'',utils.addon_dir() + '/resources/images/plus-icon.png')]
for index in range(0,len(customPaths.getSets())):
aSet = customPaths.getSet(index)
options.append(xbmcgui.ListItem(aSet['name'],utils.getString(30121) + ': ' + aSet['set']['root'],utils.addon_dir() + '/resources/images/folder-icon.png'))
#show the gui
exitCondition = self.dialog.select(utils.getString(30125),options,useDetails=True)
if(exitCondition >= 0):
if(exitCondition == 0):
newSet = self.createSet()
#check that the name is unique
if(customPaths.validateSetName(newSet['name'])):
customPaths.addSet(newSet)
else:
self.dialog.ok(utils.getString(30117), utils.getString(30138),newSet['name'])
else:
#bring up a context menu
menuOption = self.dialog.contextmenu([utils.getString(30122),utils.getString(30123)])
if(menuOption == 0):
#get the set
aSet = customPaths.getSet(exitCondition -1)
#edit the set
updatedSet = self.editSet(aSet['name'],aSet['set'])
#save it
customPaths.updateSet(aSet['name'],updatedSet)
elif(menuOption == 1):
if(self.dialog.yesno(heading=utils.getString(30127),line1=utils.getString(30128))):
#delete this path - subtract one because of "add" item
customPaths.deleteSet(exitCondition -1)
def copySimpleConfig(self):
#disclaimer in case the user hit this on accident
shouldContinue = self.dialog.yesno(utils.getString(30139),utils.getString(30140),utils.getString(30141))
if(shouldContinue):
source = xbmc.translatePath(utils.addon_dir() + "/resources/data/default_files.json")
dest = xbmc.translatePath(utils.data_dir() + "/custom_paths.json")
xbmcvfs.copy(source,dest)

View File

@@ -3,9 +3,18 @@ import xbmcgui
import xbmcvfs
import resources.lib.tinyurl as tinyurl
import resources.lib.utils as utils
import dropbox
from resources.lib.pydrive.auth import GoogleAuth
from resources.lib.pydrive.drive import GoogleDrive
#don't die on import error yet, these might not even get used
try:
import dropbox
except ImportError:
pass
try:
from resources.lib.pydrive.auth import GoogleAuth
from resources.lib.pydrive.drive import GoogleDrive
except ImportError:
pass
class DropboxAuthorizer:
APP_KEY = ""
@@ -58,7 +67,7 @@ class DropboxAuthorizer:
try:
user_token = flow.finish(code)
self._setToken(user_token.access_token)
except Exception,e:
except Exception as e:
utils.log("Error: %s" % (e,))
result = False

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
import re
from time import time, mktime
from datetime import datetime, date
from relativedelta import relativedelta
from .relativedelta import relativedelta
search_re = re.compile(r'^([^-]+)-([^-/]+)(/(.*))?$')
only_int_re = re.compile(r'^\d+$')
@@ -85,7 +85,7 @@ class croniter(object):
or not only_int_re.search(str(step))):
raise ValueError("[%s] is not acceptable" %expr_format)
for j in xrange(int(low), int(high)+1):
for j in range(int(low), int(high)+1):
if j % int(step) == 0:
e_list.append(j)
else:
@@ -298,11 +298,4 @@ class croniter(object):
if year % 400 == 0 or (year % 4 == 0 and year % 100 != 0):
return True
else:
return False
if __name__ == '__main__':
base = datetime(2010, 1, 25)
itr = croniter('0 0 1 * *', base)
n1 = itr.get_next(datetime)
print n1
return False

View File

@@ -511,9 +511,5 @@ def _params_to_urlencoded(params):
else:
return str(o).encode('utf-8')
#fix for python 2.6
utf8_params = {}
for k,v in six.iteritems(params):
utf8_params[encode(k)] = encode(v)
utf8_params = {encode(k): encode(v) for k, v in six.iteritems(params)}
return url_encode(utf8_params)

View File

@@ -237,12 +237,11 @@ class StoneToPythonPrimitiveSerializer(StoneSerializerBase):
def encode_map(self, validator, value):
validated_value = validator.validate(value)
#fix for python 2.6
result = {}
for key, value in validated_value.items():
result[self.encode_sub(validator.key_validator,key)] = self.encode_sub(validator.value_validator, value)
return result
return {
self.encode_sub(validator.key_validator, key):
self.encode_sub(validator.value_validator, value) for
key, value in validated_value.items()
}
def encode_nullable(self, validator, value):
if value is None:
@@ -831,12 +830,11 @@ def _decode_list(
if not isinstance(obj, list):
raise bv.ValidationError(
'expected list, got %s' % bv.generic_type_name(obj))
result = []
for item in obj:
result.append(_json_compat_obj_decode_helper(data_type.item_validator, item, alias_validators, strict,old_style, for_msgpack))
return result
return [
_json_compat_obj_decode_helper(
data_type.item_validator, item, alias_validators, strict,
old_style, for_msgpack)
for item in obj]
def _decode_map(
@@ -848,12 +846,15 @@ def _decode_map(
if not isinstance(obj, dict):
raise bv.ValidationError(
'expected dict, got %s' % bv.generic_type_name(obj))
result = {}
for key, value in obj.items():
result[_json_compat_obj_decode_helper(data_type.key_validator, key, alias_validators, strict,old_style, for_msgpack)] = _json_compat_obj_decode_helper(data_type.value_validator, value, alias_validators, strict,old_style, for_msgpack)
return result
return {
_json_compat_obj_decode_helper(
data_type.key_validator, key, alias_validators, strict,
old_style, for_msgpack):
_json_compat_obj_decode_helper(
data_type.value_validator, value, alias_validators, strict,
old_style, for_msgpack)
for key, value in obj.items()
}
def _decode_nullable(

View File

@@ -422,13 +422,10 @@ class Map(Composite):
def validate(self, val):
if not isinstance(val, dict):
raise ValidationError('%r is not a valid dict' % val)
#fix for python 2.6
result = {}
for key, value in val.items():
result[self.key_validator.validate(key)] = self.value_validator.validate(value)
return result
return {
self.key_validator.validate(key):
self.value_validator.validate(value) for key, value in val.items()
}
class Struct(Composite):

View File

@@ -1,4 +1,3 @@
import xbmc
import utils as utils
class ZipExtractor:
@@ -24,8 +23,7 @@ class ZipExtractor:
#extract the file
zipFile.extract(aFile,outLoc)
except Exception,e:
print str(e)
except Exception as e:
utils.log("Error extracting file")
result = False

View File

@@ -156,7 +156,7 @@ class GoogleAuth(ApiAttributeMixin, object):
port_number = port
try:
httpd = ClientRedirectServer((host_name, port), ClientRedirectHandler)
except socket.error, e:
except socket.error as e:
pass
else:
success = True
@@ -164,26 +164,16 @@ class GoogleAuth(ApiAttributeMixin, object):
if success:
oauth_callback = 'http://%s:%s/' % (host_name, port_number)
else:
print 'Failed to start a local webserver. Please check your firewall'
print 'settings and locally running programs that may be blocking or'
print 'using configured ports. Default ports are 8080 and 8090.'
raise AuthenticationError()
self.flow.redirect_uri = oauth_callback
authorize_url = self.GetAuthUrl()
webbrowser.open(authorize_url, new=1, autoraise=True)
print 'Your browser has been opened to visit:'
print
print ' ' + authorize_url
print
httpd.handle_request()
if 'error' in httpd.query_params:
print 'Authentication request was rejected'
raise AuthenticationRejected('User rejected authentication')
if 'code' in httpd.query_params:
return httpd.query_params['code']
else:
print 'Failed to find "code" in the query parameters of the redirect.'
print 'Try command-line authentication'
raise AuthenticationError('No code found in redirect')
@CheckAuth
@@ -195,10 +185,6 @@ class GoogleAuth(ApiAttributeMixin, object):
"""
self.flow.redirect_uri = OOB_CALLBACK_URN
authorize_url = self.GetAuthUrl()
print 'Go to the following link in your browser:'
print
print ' ' + authorize_url
print
return raw_input('Enter verification code: ').strip()
def LoadCredentials(self, backend=None):
@@ -309,7 +295,7 @@ class GoogleAuth(ApiAttributeMixin, object):
client_config_file = self.settings['client_config_file']
try:
client_type, client_info = clientsecrets.loadfile(client_config_file)
except clientsecrets.InvalidClientSecretsError, error:
except clientsecrets.InvalidClientSecretsError as error:
raise InvalidConfigError('Invalid client secrets file %s' % error)
if not client_type in (clientsecrets.TYPE_WEB,
clientsecrets.TYPE_INSTALLED):
@@ -334,7 +320,6 @@ class GoogleAuth(ApiAttributeMixin, object):
self.client_config[config] = self.settings['client_config'][config]
except KeyError:
print config
raise InvalidConfigError('Insufficient client config in settings')
def GetFlow(self):
@@ -374,7 +359,7 @@ class GoogleAuth(ApiAttributeMixin, object):
self.http = httplib2.Http()
try:
self.credentials.refresh(self.http)
except AccessTokenRefreshError, error:
except AccessTokenRefreshError as error:
raise RefreshError('Access token refresh failed: %s' % error)
def GetAuthUrl(self, keys = None):
@@ -414,9 +399,8 @@ class GoogleAuth(ApiAttributeMixin, object):
self.GetFlow()
try:
self.credentials = self.flow.step2_exchange(code)
except FlowExchangeError, e:
except FlowExchangeError as e:
raise AuthenticationError('OAuth2 code exchange failed: %s' % e)
print 'Authentication successful.'
def Authorize(self):
"""Authorizes and builds service.

View File

@@ -108,7 +108,7 @@ class GoogleDriveFile(ApiAttributeMixin, ApiResource):
"""
try:
return dict.__getitem__(self, key)
except KeyError, e:
except KeyError as e:
if self.uploaded:
raise KeyError(e)
if self.get('id'):
@@ -180,7 +180,7 @@ class GoogleDriveFile(ApiAttributeMixin, ApiResource):
if file_id:
try:
metadata = self.auth.service.files().get(fileId=file_id).execute()
except errors.HttpError, error:
except errors.HttpError as error:
raise ApiRequestError(error)
else:
self.uploaded = True
@@ -244,7 +244,7 @@ class GoogleDriveFile(ApiAttributeMixin, ApiResource):
if self.dirty['content']:
param['media_body'] = self._BuildMediaBody()
metadata = self.auth.service.files().insert(**param).execute()
except errors.HttpError, error:
except errors.HttpError as error:
raise ApiRequestError(error)
else:
self.uploaded = True
@@ -268,7 +268,7 @@ class GoogleDriveFile(ApiAttributeMixin, ApiResource):
if self.dirty['content']:
param['media_body'] = self._BuildMediaBody()
metadata = self.auth.service.files().update(**param).execute()
except errors.HttpError, error:
except errors.HttpError as error:
raise ApiRequestError(error)
else:
self.uploaded = True
@@ -290,7 +290,7 @@ class GoogleDriveFile(ApiAttributeMixin, ApiResource):
param['fileId'] = self.metadata.get('id')
try:
metadata = self.auth.service.files().patch(**param).execute()
except errors.HttpError, error:
except errors.HttpError as error:
raise ApiRequestError(error)
else:
self.UpdateMetadata(metadata)

View File

@@ -120,8 +120,7 @@ def LoadSettingsFile(filename=SETTINGS_FILE):
try:
stream = file(filename, 'r')
data = load(stream, Loader=Loader)
except (YAMLError, IOError), e:
print e
except (YAMLError, IOError) as e:
raise SettingsError(e)
return data

View File

@@ -115,7 +115,7 @@ Here is the behavior of operations with relativedelta:
if dt1 and dt2:
if not isinstance(dt1, datetime.date) or \
not isinstance(dt2, datetime.date):
raise TypeError, "relativedelta only diffs datetime/date"
raise TypeError("relativedelta only diffs datetime/date")
if type(dt1) is not type(dt2):
if not isinstance(dt1, datetime.datetime):
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
@@ -195,7 +195,7 @@ Here is the behavior of operations with relativedelta:
self.day = yday-ydayidx[idx-1]
break
else:
raise ValueError, "invalid year day (%d)" % yday
raise ValueError("invalid year day (%d)" % yday)
self._fix()
@@ -244,7 +244,7 @@ Here is the behavior of operations with relativedelta:
def __radd__(self, other):
if not isinstance(other, datetime.date):
raise TypeError, "unsupported type for add operation"
raise TypeError("unsupported type for add operation")
elif self._has_time and not isinstance(other, datetime.datetime):
other = datetime.datetime.fromordinal(other.toordinal())
year = (self.year or other.year)+self.years
@@ -290,7 +290,7 @@ Here is the behavior of operations with relativedelta:
def __add__(self, other):
if not isinstance(other, relativedelta):
raise TypeError, "unsupported type for add operation"
raise TypeError("unsupported type for add operation")
return relativedelta(years=other.years+self.years,
months=other.months+self.months,
days=other.days+self.days,
@@ -310,7 +310,7 @@ Here is the behavior of operations with relativedelta:
def __sub__(self, other):
if not isinstance(other, relativedelta):
raise TypeError, "unsupported type for sub operation"
raise TypeError("unsupported type for sub operation")
return relativedelta(years=other.years-self.years,
months=other.months-self.months,
days=other.days-self.days,
@@ -426,5 +426,5 @@ Here is the behavior of operations with relativedelta:
"hour", "minute", "second", "microsecond"]:
value = getattr(self, attr)
if value is not None:
l.append("%s=%s" % (attr, `value`))
l.append("%s=%s" % (attr, value))
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))

View File

@@ -14,11 +14,11 @@ def addon_dir():
def openSettings():
__Addon.openSettings()
def log(message,loglevel=xbmc.LOGNOTICE):
def log(message,loglevel=xbmc.LOGDEBUG):
xbmc.log(encode(__addon_id__ + "-" + __Addon.getAddonInfo('version') + ": " + message),level=loglevel)
def showNotification(message):
xbmcgui.Dialog().notification(encode(getString(30010)),encode(message),time=4000,icon=xbmc.translatePath(__Addon.getAddonInfo('path') + "/icon.png"))
xbmcgui.Dialog().notification(encode(getString(30010)),encode(message),time=4000,icon=xbmc.translatePath(__Addon.getAddonInfo('path') + "/resources/images/icon.png"))
def getSetting(name):
return __Addon.getSetting(name)
@@ -29,6 +29,14 @@ def setSetting(name,value):
def getString(string_id):
return __Addon.getLocalizedString(string_id)
def getRegionalTimestamp(date_time,dateformat=['dateshort']):
result = ''
for aFormat in dateformat:
result = result + ("%s " % date_time.strftime(xbmc.getRegion(aFormat)))
return result.strip()
def encode(string):
result = ''

View File

@@ -1,16 +1,12 @@
import utils as utils
import tinyurl as tinyurl
import xbmc
import xbmcvfs
import xbmcgui
import zipfile
import zlib
import os
import os.path
import sys
import dropbox
from dropbox.files import WriteMode,CommitInfo,UploadSessionCursor
from pydrive.drive import GoogleDrive
from authorizers import DropboxAuthorizer,GoogleDriveAuthorizer
class Vfs:
@@ -232,7 +228,7 @@ class DropboxFileSystem(Vfs):
self.client.files_upload_session_append_v2(f.read(self.MAX_CHUNK),upload_cursor)
upload_cursor.offset = f.tell()
#if no errors we're good!
#if no errors we're good!
return True
except Exception as anError:
utils.log(str(anError))

View File

@@ -4,6 +4,7 @@
<setting id="compress_backups" type="bool" label="30087" default="false" />
<setting id="backup_rotation" type="number" label="30026" default="0" />
<setting id="progress_mode" type="enum" label="30022" lvalues="30082|30083|30084" default="0" />
<setting id="upgrade_notes" type="number" label="upgrade_notes" visible="false" default="1" />
</category>
<category id="backup_path" label="30048">
<setting id="remote_selection" type="enum" lvalues="30018|30019|30027|30098" default="0" label="30025"/>
@@ -13,22 +14,22 @@
<setting id="dropbox_secret" type="text" label="30029" visible="eq(-4,2)" default="" />
<setting id="google_drive_id" type="text" label="Client ID" visible="eq(-5,3)" default="" />
<setting id="google_drive_secret" type="text" label="Client Secret" visible="eq(-6,3)" default="" />
<setting id="auth_dropbox_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/authorize_cloud.py,type=dropbox)" visible="eq(-7,2)"/>
<setting id="auth_google_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/authorize_cloud.py,type=google_drive)" visible="eq(-8,3)"/>
<setting id="remove_auth_button" type="action" label="30093" action="RunScript(special://home/addons/script.xbmcbackup/remove_auth.py)" visible="gt(-9,1)"/>
<setting id="auth_dropbox_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=authorize_cloud&provider=dropbox)" visible="eq(-7,2)"/>
<setting id="auth_google_button" type="action" label="30104" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=authorize_cloud&provider=google_drive)" visible="eq(-8,3)"/>
<setting id="remove_auth_button" type="action" label="30093" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=remove_auth)" visible="gt(-9,1)"/>
</category>
<category id="selection" label="30012">
<setting id="backup_addons" type="bool" label="30030" default="true" />
<setting id="backup_addon_data" type="bool" label="30031" default="false" />
<setting id="backup_database" type="bool" label="30032" default="true" />
<setting id="backup_playlists" type="bool" label="30033" default="true" />
<setting id="backup_profiles" type="bool" label="30080" default="false" />
<setting id="backup_thumbnails" type="bool" label="30034" default="true" />
<setting id="backup_config" type="bool" label="30035" default="true" />
<setting id="custom_dir_1_enable" type="bool" label="30036" default="false" />
<setting id="backup_custom_dir_1" type="folder" label="30018" default="" visible="eq(-1,true)"/>
<setting id="custom_dir_2_enable" type="bool" label="30037" default="false" />
<setting id="backup_custom_dir_2" type="folder" label="30018" default="" visible="eq(-1,true)"/>
<setting id="backup_selection_type" type="enum" lvalues="30014|30015" default="0" label="30023" />
<setting id="backup_addon_data" type="bool" label="30031" default="false" visible="eq(-1,0)"/>
<setting id="backup_config" type="bool" label="30035" default="true" visible="eq(-2,0)"/>
<setting id="backup_database" type="bool" label="30032" default="true" visible="eq(-3,0)"/>
<setting id="backup_game_saves" type="bool" label="30133" default="false" visible="eq(-4,0)" />
<setting id="backup_playlists" type="bool" label="30033" default="true" visible="eq(-5,0)"/>
<setting id="backup_profiles" type="bool" label="30080" default="false" visible="eq(-6,0)"/>
<setting id="backup_thumbnails" type="bool" label="30034" default="true" visible="eq(-7,0)"/>
<setting id="backup_addons" type="bool" label="30030" default="true" visible="eq(-8,0)" />
<setting id="advanced_button" type="action" label="30125" visible="eq(-9,1)" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_editor)" />
<setting id="advanced_defaults" type="action" label="30139" visible="eq(-10,1)" action="RunScript(special://home/addons/script.xbmcbackup/launcher.py,action=advanced_copy_config)" />
</category>
<category id="scheduling" label="30013">
<setting id="enable_scheduler" type="bool" label="30060" default="false" />

View File

@@ -1,189 +1,198 @@
import xbmc
import xbmcvfs
import xbmcgui
import datetime
import time
import os
import resources.lib.utils as utils
from resources.lib.croniter import croniter
from resources.lib.backup import XbmcBackup
class BackupScheduler:
monitor = None
enabled = "false"
next_run = 0
next_run_path = None
restore_point = None
def __init__(self):
self.monitor = UpdateMonitor(update_method = self.settingsChanged)
self.enabled = utils.getSetting("enable_scheduler")
self.next_run_path = xbmc.translatePath(utils.data_dir()) + 'next_run.txt'
if(self.enabled == "true"):
nr = 0
if(xbmcvfs.exists(self.next_run_path)):
fh = xbmcvfs.File(self.next_run_path)
try:
#check if we saved a run time from the last run
nr = float(fh.read())
except ValueError:
nr = 0
fh.close()
#if we missed and the user wants to play catch-up
if(0 < nr <= time.time() and utils.getSetting('schedule_miss') == 'true'):
utils.log("scheduled backup was missed, doing it now...")
progress_mode = int(utils.getSetting('progress_mode'))
if(progress_mode == 0):
progress_mode = 1 # Kodi just started, don't block it with a foreground progress bar
self.doScheduledBackup(progress_mode)
self.setup()
def setup(self):
#scheduler was turned on, find next run time
utils.log("scheduler enabled, finding next run time")
self.findNextRun(time.time())
def start(self):
#check if a backup should be resumed
resumeRestore = self._resumeCheck()
if(resumeRestore):
restore = XbmcBackup()
restore.selectRestore(self.restore_point)
#skip the advanced settings check
restore.skipAdvanced()
restore.run(XbmcBackup.Restore)
while(not xbmc.abortRequested):
if(self.enabled == "true"):
#scheduler is still on
now = time.time()
if(self.next_run <= now):
progress_mode = int(utils.getSetting('progress_mode'))
self.doScheduledBackup(progress_mode)
#check if we should shut the computer down
if(utils.getSetting("cron_shutdown") == 'true'):
#wait 10 seconds to make sure all backup processes and files are completed
time.sleep(10)
xbmc.executebuiltin('ShutDown()')
else:
#find the next run time like normal
self.findNextRun(now)
xbmc.sleep(500)
#delete monitor to free up memory
del self.monitor
def doScheduledBackup(self,progress_mode):
if(progress_mode != 2):
utils.showNotification(utils.getString(30053))
backup = XbmcBackup()
if(backup.remoteConfigured()):
if(int(utils.getSetting('progress_mode')) in [0,1]):
backup.run(XbmcBackup.Backup,True)
else:
backup.run(XbmcBackup.Backup,False)
#check if this is a "one-off"
if(int(utils.getSetting("schedule_interval")) == 0):
#disable the scheduler after this run
self.enabled = "false"
utils.setSetting('enable_scheduler','false')
else:
utils.showNotification(utils.getString(30045))
def findNextRun(self,now):
progress_mode = int(utils.getSetting('progress_mode'))
#find the cron expression and get the next run time
cron_exp = self.parseSchedule()
cron_ob = croniter(cron_exp,datetime.datetime.fromtimestamp(now))
new_run_time = cron_ob.get_next(float)
if(new_run_time != self.next_run):
self.next_run = new_run_time
utils.log("scheduler will run again on " + datetime.datetime.fromtimestamp(self.next_run).strftime('%m-%d-%Y %H:%M'))
#write the next time to a file
fh = xbmcvfs.File(self.next_run_path, 'w')
fh.write(str(self.next_run))
fh.close()
#only show when not in silent mode
if(progress_mode != 2):
utils.showNotification(utils.getString(30081) + " " + datetime.datetime.fromtimestamp(self.next_run).strftime('%m-%d-%Y %H:%M'))
def settingsChanged(self):
current_enabled = utils.getSetting("enable_scheduler")
if(current_enabled == "true" and self.enabled == "false"):
#scheduler was just turned on
self.enabled = current_enabled
self.setup()
elif (current_enabled == "false" and self.enabled == "true"):
#schedule was turn off
self.enabled = current_enabled
if(self.enabled == "true"):
#always recheck the next run time after an update
self.findNextRun(time.time())
def parseSchedule(self):
schedule_type = int(utils.getSetting("schedule_interval"))
cron_exp = utils.getSetting("cron_schedule")
hour_of_day = utils.getSetting("schedule_time")
hour_of_day = int(hour_of_day[0:2])
if(schedule_type == 0 or schedule_type == 1):
#every day
cron_exp = "0 " + str(hour_of_day) + " * * *"
elif(schedule_type == 2):
#once a week
day_of_week = utils.getSetting("day_of_week")
cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week
elif(schedule_type == 3):
#first day of month
cron_exp = "0 " + str(hour_of_day) + " 1 * *"
return cron_exp
def _resumeCheck(self):
shouldContinue = False
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "resume.txt"))):
rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"),'r')
self.restore_point = rFile.read()
rFile.close()
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "resume.txt"))
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042),utils.getString(30043),utils.getString(30044))
return shouldContinue
class UpdateMonitor(xbmc.Monitor):
update_method = None
def __init__(self,*args, **kwargs):
xbmc.Monitor.__init__(self)
self.update_method = kwargs['update_method']
def onSettingsChanged(self):
self.update_method()
BackupScheduler().start()
import xbmc
import xbmcvfs
import xbmcgui
from datetime import datetime
import time
import resources.lib.utils as utils
from resources.lib.croniter import croniter
from resources.lib.backup import XbmcBackup
UPGRADE_INT = 2 #to keep track of any upgrade notifications
class BackupScheduler:
monitor = None
enabled = "false"
next_run = 0
next_run_path = None
restore_point = None
def __init__(self):
self.monitor = UpdateMonitor(update_method = self.settingsChanged)
self.enabled = utils.getSetting("enable_scheduler")
self.next_run_path = xbmc.translatePath(utils.data_dir()) + 'next_run.txt'
if(self.enabled == "true"):
#sleep for 2 minutes so Kodi can start and time can update correctly
xbmc.Monitor().waitForAbort(120)
nr = 0
if(xbmcvfs.exists(self.next_run_path)):
fh = xbmcvfs.File(self.next_run_path)
try:
#check if we saved a run time from the last run
nr = float(fh.read())
except ValueError:
nr = 0
fh.close()
#if we missed and the user wants to play catch-up
if(0 < nr <= time.time() and utils.getSetting('schedule_miss') == 'true'):
utils.log("scheduled backup was missed, doing it now...")
progress_mode = int(utils.getSetting('progress_mode'))
if(progress_mode == 0):
progress_mode = 1 # Kodi just started, don't block it with a foreground progress bar
self.doScheduledBackup(progress_mode)
self.setup()
def setup(self):
#scheduler was turned on, find next run time
utils.log("scheduler enabled, finding next run time")
self.findNextRun(time.time())
def start(self):
#display upgrade messages if they exist
if(int(utils.getSetting('upgrade_notes')) < UPGRADE_INT):
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30132))
utils.setSetting('upgrade_notes',str(UPGRADE_INT))
#check if a backup should be resumed
resumeRestore = self._resumeCheck()
if(resumeRestore):
restore = XbmcBackup()
restore.selectRestore(self.restore_point)
#skip the advanced settings check
restore.skipAdvanced()
restore.restore()
while(not self.monitor.abortRequested()):
if(self.enabled == "true"):
#scheduler is still on
now = time.time()
if(self.next_run <= now):
progress_mode = int(utils.getSetting('progress_mode'))
self.doScheduledBackup(progress_mode)
#check if we should shut the computer down
if(utils.getSetting("cron_shutdown") == 'true'):
#wait 10 seconds to make sure all backup processes and files are completed
time.sleep(10)
xbmc.executebuiltin('ShutDown()')
else:
#find the next run time like normal
self.findNextRun(now)
xbmc.sleep(500)
#delete monitor to free up memory
del self.monitor
def doScheduledBackup(self,progress_mode):
if(progress_mode != 2):
utils.showNotification(utils.getString(30053))
backup = XbmcBackup()
if(backup.remoteConfigured()):
if(int(utils.getSetting('progress_mode')) in [0,1]):
backup.backup(True)
else:
backup.backup(False)
#check if this is a "one-off"
if(int(utils.getSetting("schedule_interval")) == 0):
#disable the scheduler after this run
self.enabled = "false"
utils.setSetting('enable_scheduler','false')
else:
utils.showNotification(utils.getString(30045))
def findNextRun(self,now):
progress_mode = int(utils.getSetting('progress_mode'))
#find the cron expression and get the next run time
cron_exp = self.parseSchedule()
cron_ob = croniter(cron_exp,datetime.fromtimestamp(now))
new_run_time = cron_ob.get_next(float)
if(new_run_time != self.next_run):
self.next_run = new_run_time
utils.log("scheduler will run again on " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run),['dateshort','time']))
#write the next time to a file
fh = xbmcvfs.File(self.next_run_path, 'w')
fh.write(str(self.next_run))
fh.close()
#only show when not in silent mode
if(progress_mode != 2):
utils.showNotification(utils.getString(30081) + " " + utils.getRegionalTimestamp(datetime.fromtimestamp(self.next_run),['dateshort','time']))
def settingsChanged(self):
current_enabled = utils.getSetting("enable_scheduler")
if(current_enabled == "true" and self.enabled == "false"):
#scheduler was just turned on
self.enabled = current_enabled
self.setup()
elif (current_enabled == "false" and self.enabled == "true"):
#schedule was turn off
self.enabled = current_enabled
if(self.enabled == "true"):
#always recheck the next run time after an update
self.findNextRun(time.time())
def parseSchedule(self):
schedule_type = int(utils.getSetting("schedule_interval"))
cron_exp = utils.getSetting("cron_schedule")
hour_of_day = utils.getSetting("schedule_time")
hour_of_day = int(hour_of_day[0:2])
if(schedule_type == 0 or schedule_type == 1):
#every day
cron_exp = "0 " + str(hour_of_day) + " * * *"
elif(schedule_type == 2):
#once a week
day_of_week = utils.getSetting("day_of_week")
cron_exp = "0 " + str(hour_of_day) + " * * " + day_of_week
elif(schedule_type == 3):
#first day of month
cron_exp = "0 " + str(hour_of_day) + " 1 * *"
return cron_exp
def _resumeCheck(self):
shouldContinue = False
if(xbmcvfs.exists(xbmc.translatePath(utils.data_dir() + "resume.txt"))):
rFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "resume.txt"),'r')
self.restore_point = rFile.read()
rFile.close()
xbmcvfs.delete(xbmc.translatePath(utils.data_dir() + "resume.txt"))
shouldContinue = xbmcgui.Dialog().yesno(utils.getString(30042),utils.getString(30043),utils.getString(30044))
return shouldContinue
class UpdateMonitor(xbmc.Monitor):
update_method = None
def __init__(self,*args, **kwargs):
xbmc.Monitor.__init__(self)
self.update_method = kwargs['update_method']
def onSettingsChanged(self):
self.update_method()
BackupScheduler().start()