From: Jorge Antonio Diaz-Benito Soriano Date: Tue, 28 Oct 2014 18:13:03 +0000 (+0100) Subject: Merge branch 'us4_view_text_files' of github.com:owncloudChalmers/android into us4_vi... X-Git-Tag: oc-android-1.8~17^2~8^2~14 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/073e7e46bd27f957f37f3df4a5c09decc1d8ef70?hp=46ff60a9e760a760b478609bac68d45f8728488f Merge branch 'us4_view_text_files' of github.com:owncloudChalmers/android into us4_view_text_files Conflicts: src/com/owncloud/android/datamodel/OCFile.java src/com/owncloud/android/ui/activity/FileDisplayActivity.java --- diff --git a/.gitignore b/.gitignore index 98923747..a218d0b9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ # generated files bin/ +build/ +*.iml gen/ target/ diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2671cce2..a5990afd 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -18,8 +18,8 @@ along with this program. If not, see . --> + android:versionCode="10600100" + android:versionName="1.6.1" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/oc_jb_workaround/AndroidManifest.xml b/oc_jb_workaround/AndroidManifest.xml index 9fa39b30..ada508ca 100644 --- a/oc_jb_workaround/AndroidManifest.xml +++ b/oc_jb_workaround/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="0100019" + android:versionName="1.0.19" > . --> - + diff --git a/res/values-af-rZA/strings.xml b/res/values-af-rZA/strings.xml index 6ea41913..17d68853 100644 --- a/res/values-af-rZA/strings.xml +++ b/res/values-af-rZA/strings.xml @@ -1,6 +1,8 @@ Instellings + Hulp Gebruikersnaam Wagwoord diff --git a/res/values-ak/strings.xml b/res/values-ak/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-ak/strings.xml +++ b/res/values-ak/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-am-rET/strings.xml b/res/values-am-rET/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-am-rET/strings.xml +++ b/res/values-am-rET/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 37cee6ad..266507d5 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -11,6 +11,8 @@ إعدادات تفاصيل أرسل + عام المزيد حسابات @@ -250,4 +252,5 @@ حسابات كلمة مرور خاطئة اختيار + الأمان diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index b037311c..d3a9060b 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -11,6 +11,8 @@ Quraşdırmalar Detallar Göndər + Ümumi Daha da Hesablar diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 42e20789..0e52b53f 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -1,6 +1,8 @@ Налады + Так Не Добра diff --git a/res/values-bg-rBG/strings.xml b/res/values-bg-rBG/strings.xml index a00871f2..5497ee5a 100644 --- a/res/values-bg-rBG/strings.xml +++ b/res/values-bg-rBG/strings.xml @@ -11,6 +11,14 @@ Настройки Детайли Изпращане + Сортиране + Сортирай по + + А-Я + Нови - Стари + + Общи Още Профили @@ -238,6 +246,7 @@ Преглед на изображението Изображението не може да бъде показано %1$s не може да бъде копиран в локалната папка %2$s + Местоположение на качване За съжаление споделянето не е включено на сървъра ви. Моля, свържете се с администратора. Неуспешен опит за споделяне. Моля, провери дали файла съществува. @@ -278,4 +287,6 @@ Файлът вече съществува в отдалечената папка. Настъпи грешка при опита за преместване на този файл или папка. за да преместиш този файл + Незабавно качване + Сигурност diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml index 74739f5d..a5cf5425 100644 --- a/res/values-bn-rBD/strings.xml +++ b/res/values-bn-rBD/strings.xml @@ -11,6 +11,8 @@ নিয়ামকসমূহ বিস্তারিত পাঠাও + সাধারণ বেশী একাউন্ট @@ -259,10 +261,10 @@ সার্ভারে এই ফাইলটি আর প্রাপ্তব্য নয় একাউন্ট একাউন্ট যোগ কর - নিরাপদ সংযোগকে একটি অনিরাপদ পথে দিকবদল করা হয়েছে ভুল কুটশব্দ সরাও এখানে কিছু নেই। একটি ফোল্ডার যোগ করতে পারেন! বেছে নিন সরাতে ব্যার্থ হলো। ফাইলটি রয়েছে কিনা দেখুন। + নিরাপত্তা diff --git a/res/values-bn-rIN/strings.xml b/res/values-bn-rIN/strings.xml index a49555a8..3b69168f 100644 --- a/res/values-bn-rIN/strings.xml +++ b/res/values-bn-rIN/strings.xml @@ -5,6 +5,8 @@ ফাইলস নতুন ফোল্ডার সেটিংস + অঙ্কিত করা ইউজারনেম ফাইলস diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index 30f5ce22..4c2f8692 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -1,5 +1,7 @@ Nova fascikla + diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 9c1d158e..c5d91d54 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -11,6 +11,8 @@ Configuració Detalls Envia + General Més Comptes @@ -257,8 +259,8 @@ El fitxer ja no està disponible en el servidor Comptes Afegeix compte - La connexió segura està essent redirigida a través d\'una ruta insegura Es requereix autenticació Contrasenya incorrecta Escull + Seguretat diff --git a/res/values-cs-rCZ/strings.xml b/res/values-cs-rCZ/strings.xml index 71b84c9e..a9db9c5b 100644 --- a/res/values-cs-rCZ/strings.xml +++ b/res/values-cs-rCZ/strings.xml @@ -11,13 +11,21 @@ Nastavení Podrobnosti Odeslat + Seřadit + Seřadit podle + + A-Z + Nejnovější - nejstarší + + Obecné Více Účty Spravovat účty - PIN aplikace + PIN do aplikace Chraňte svého klienta - Okamžité nahrání obrázků + Okamžité nahrávání obrázků Okamžitě nahrávat vytvořené fotografie Okamžité nahrávání videa Okamžitě odesílat nahrané video @@ -27,7 +35,7 @@ Zobrazuje zaznamenané logy Smazat historii Nápověda - Doporučit příteli + Doporučit přátelům Odezva Imprint Zkuste %1$s na vašem smartphonu! @@ -226,7 +234,7 @@ 389 KB 2012/05/18 12:23 PM 12:23:45 - Odesílat obrázky pouze skrze WiFi + Odesílat obrázky pouze přes WiFi Nahrávat videa pouze přes WiFi /InstantUpload Konflikt při aktualizaci @@ -237,6 +245,7 @@ Náhled obrázku Obrázek nelze zobrazit %1$s nelze zkopírovat do místního adresáře %2$s + Cesta pro nahrání Je nám líto, ale sdílení není na vašem serveru povoleno. Kontaktujte svého administrátora. Nelze sdílet. Zkontrolujte prosím že soubor existuje @@ -262,7 +271,7 @@ administrátora. Tento soubor již není dostupný na serveru Účty Přidat účet - Zabezpečené spojení je přesměrováváno nezabezpečenou trasou. + Bezpečné spojení je přesměrováno na nezabezpečenou trasu. Logy Odeslat historii Logy aplikace ownCloud pro Android @@ -277,4 +286,6 @@ administrátora. Soubor již v cílovém adresáři existuje Při pokusu o přesun tohoto souboru či složky nastala chyba pro přesun tohoto souboru + Okamžitá odesílání + Zabezpečení diff --git a/res/values-cy-rGB/strings.xml b/res/values-cy-rGB/strings.xml index daace9cd..80cc3c93 100644 --- a/res/values-cy-rGB/strings.xml +++ b/res/values-cy-rGB/strings.xml @@ -5,6 +5,8 @@ Ffeiliau Gosodiadau Anfon + Cyffredinol Cyfrifon Rheoli Cyfrifon diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 15475299..9a275407 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -11,6 +11,14 @@ Indstillinger Detaljer Send + Sortér + Sortér efter + + A-Å + Nyeste - ældste + + Generel Mere Konti @@ -237,6 +245,7 @@ Forhåndsvisning af billede Dette billede kan ikke vises %1$s kunne ikke kopieres til %2$s lokale mappe + Sti til upload Beklager, deling er ikke slået til på din server. Kontakt venligst din administrator. Kan ikke dele. Tjek venligst om filen findes. Der opstod en fejl ved deling af denne fil eller mappe @@ -261,7 +270,7 @@ Filen er ikke længere tilgængelig på serveren Konti Tilføj konto - Sikker forbindelse videredirigeres gennem en usikker rute. + Sikker forbindelse videredirigeres til en usikker rute. Logge Send historik App-logregistreringer for ownCloud Android @@ -276,4 +285,6 @@ Filen findes allerede i destinationsmappen Der opstod en fejl under forsøg på at flytte denne mappe eller fil til at flytte denne fil + Øjeblikkelige uploads + Sikkerhed diff --git a/res/values-de-rAT/strings.xml b/res/values-de-rAT/strings.xml index f2c13bf6..8a65cc16 100644 --- a/res/values-de-rAT/strings.xml +++ b/res/values-de-rAT/strings.xml @@ -2,6 +2,8 @@ Dateien Einstellungen + Allgemein Hilfe Passwort diff --git a/res/values-de-rCH/strings.xml b/res/values-de-rCH/strings.xml index a6cb3c27..20392974 100644 --- a/res/values-de-rCH/strings.xml +++ b/res/values-de-rCH/strings.xml @@ -11,6 +11,8 @@ Einstellungen Details Senden + Allgemein Mehr Konten @@ -198,4 +200,5 @@ Konten Auswählen + Sicherheit diff --git a/res/values-de-rDE/strings.xml b/res/values-de-rDE/strings.xml index b1132871..9f0ebcf9 100644 --- a/res/values-de-rDE/strings.xml +++ b/res/values-de-rDE/strings.xml @@ -11,6 +11,14 @@ Einstellungen Details Senden + Sortieren + Sortieren nach + + A-Z + Neueste - Älteste + + Allgemein Mehr Konten @@ -238,6 +246,7 @@ Bildvorschau Dieses Bild kann nicht angezeigt werden %1$s konnte nicht in den lokalen %2$s Ordner kopiert werden + Pfad hochladen Entschuldigung, Freigaben sind auf Ihrem Server nicht aktiviert. Bitte kontaktieren Sie Ihren ⇥⇥Administrator. Teilen nicht möglich. Prüfen Sie, dass die Datei existiert @@ -278,4 +287,6 @@ Die Datei ist bereits im Zielordner vorhanden Es ist ein Fehler beim Verschieben dieser Datei oder dieses Ordners aufgetreten. um diese Datei zu verschieben + Sofortiges Hochladen + Sicherheit diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index c859bb6e..2bd365c1 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -11,6 +11,14 @@ Einstellungen Details Senden + Sortieren + Sortieren nach + + A-Z + Neueste - Älteste + + Allgemein Mehr Konten @@ -238,6 +246,7 @@ Bildvorschau Dieses Bild kann nicht angezeigt werden %1$s konnte nicht in den lokalen %2$s Ordner kopiert werden + Pfad hochladen Entschuldigung, Freigaben sind auf Deinem Server nicht aktiviert. Bitte kontaktiere Deinen ⇥⇥Administrator. Teilen nicht möglich. Prüfe, dass die Datei existiert @@ -263,7 +272,7 @@ Diese Datei steht auf dem Server nicht mehr zur Verfügung Konten Konto hinzufügen - Die gesicherte Verbindung wird durch eine ungesicherte Route geleitet. + Die gesicherte Verbindung wird auf eine unsichere Route weitergeleitet. Protokolle Verlauf senden Protokolle der ownCloud-Android-App @@ -278,4 +287,6 @@ Die Datei ist bereits im Zielordner vorhanden Es ist ein Fehler beim Verschieben der Datei oder des Ordners aufgetreten. um diese Datei zu verschieben + Sofortiges Hochladen + Sicherheit diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 8cbfca9a..67b16e2a 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -11,6 +11,8 @@ Ρυθμίσεις Λεπτομέρειες Αποστολή + Γενικά Περισσότερα Λογαριασμοί @@ -104,6 +106,7 @@ Τα περιεχόμενα των %1$d αρχείων δεν μπόρεσαν να συγχρονιστούν (%2$d διενέξεις) Ορισμένα τοπικά αρχεία ξεχάστηκαν %1$d αρχεία από τον %2$s χώρο αποθήκευσης δεν ήταν δυνατό να αντιγραφούν σε + Από την έκδοση 1.3.16, τα αρχεία που μεταφορτώθηκαν από αυτήν τη συσκευή αντιγράφηκαν στον τοπικό φάκελο %1$s για να αποτραπεί η απώλεια δεδομένων όταν ένα αρχείο είναι συγχρονισμένο με πολλαπλούς λογαριασμούς. \n\nΛόγω αυτής της αλλαγής, όλα τα αρχεία που μεταφορτώθηκαν με προηγούμενες εκδόσεις αυτής της εφαρμογής αντιγράφηκαν στον φάκελο %2$s. Ωστόσο, ένα σφάλμα εμπόδισε την ολοκλήρωση αυτής της λειτουργίας κατά το συγχρονισμό του λογαριασμού. Μπορείτε είτε να αφήσετε το(α) αρχείο(α) όπως είναι και να καταργήσετε τη σύνδεση με το %3$s ή να μετακινήσετε τα αρχεία στο φάκελο %1$s και να διατηρήσετε τη σύνδεση με το %4$s. \n\nΑπαριθμημένα πιο κάτω είναι τα τοπικά αρχεία(ο) και τα απομακρυσμένα αρχεία(ο) στο %5$s με τα οποία συνδέονταν. Ο φάκελος %1$s δεν υπάρχει πια Μετακίνηση όλων Όλα τα αρχεία μετακινήθηκαν @@ -237,9 +240,12 @@ Προεπισκόπηση εικόνας Αυτή η εικόνα δεν μπορεί να προβληθεί Το %1$s δεν μπόρεσε να αντιγραφεί στον τοπικό φάκελο %2$s + Διαδρομή Μεταφόρτωσης Λυπούμαστε, ο διαμοιρασμός δεν επιτρέπεται στο διακομιστή σας. Παρακαλούμε επικοινωνείστε με το διαχειριστή σας. + Αδύνατη η κοινή χρήση. Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει Ένα σφάλμα προέκυψε κατά την προσπάθεια διαμοιρασμού αυτού του αρχείου ή φακέλου + Αδύνατη η διακοπή κοινής χρήσης. Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει Ένα σφάλμα προέκυψε κατά τη διάρκεια ακύρωσης διαμοιρασμού αυτού του αρχείου ή φακέλου Αποστολή Αντιγραφή συνδέσμου @@ -260,14 +266,21 @@ Αυτό το αρχείο δεν είναι πια διαθέσιμο στο διακομιστή Λογαριασμοί Προσθήκη λογαριασμού - Ασφαλής σύνδεση ανακατευθύνεται μέσω μιας μη ασφαλούς διαδρομής. + Ασφαλής σύνδεση ανακατευθύνεται σε μια μη ασφαλή διαδρομή. Αρχεία καταγραφών Αποστολή ιστορικού + αρχεία καταγραφής της εφαρμογής ownCloud Android Φόρτωση δεδομένων.... Απαιτείται πιστοποίηση Εσφαλμένο συνθηματικό Μετακίνηση + Δεν υπάρχει τίποτα εδώ. Μπορείτε να προσθέσετε ένα φάκελο! Επιλέξτε + Αδύνατη η μετακίνηση. Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει + Δεν είναι δυνατό να μετακινηθεί ο φάκελος σε έναν απογονικό Το αρχείο υπάρχει ήδη στο φάκελο προορισμού + Ένα σφάλμα προέκυψε κατά την προσπάθεια μετακίνησης αυτού του αρχείου ή φακέλου για μετακίνηση αυτού του αρχείου + Στιγμιαίες Μεταφορτώσεις + Ασφάλεια diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index fd812c64..4ef373d2 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -11,6 +11,14 @@ Settings Details Send + Sort + Sort by + + A-Z + Newest - Oldest + + General More Accounts @@ -238,6 +246,7 @@ Image preview This image cannot be shown %1$s could not be copied to %2$s local folder + Upload Path Sorry, sharing is not enabled on your server. Please contact your administrator. Unable to share. Please check whether the file exists @@ -263,7 +272,7 @@ The file is no longer available on the server Accounts Add account - Secure connection is redirected through an unsecured route. + Secure connection is redirected to an unsecured route. Logs Send History ownCloud Android app logs @@ -278,4 +287,6 @@ The file exists already in the destination folder An error occurred whilst trying to move this file or folder to move this file + Instant Uploads + Security diff --git a/res/values-en-rNZ/strings.xml b/res/values-en-rNZ/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-en-rNZ/strings.xml +++ b/res/values-en-rNZ/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-eo/strings.xml b/res/values-eo/strings.xml index 065e59ab..dd653401 100644 --- a/res/values-eo/strings.xml +++ b/res/values-eo/strings.xml @@ -10,6 +10,8 @@ Agordo Detaloj Sendi + Ĝeneralo Pli Kontoj @@ -186,4 +188,5 @@ Aŭtentiĝo nepras Malĝusta pasvorto Elekti + Sekuro diff --git a/res/values-es-rAR/strings.xml b/res/values-es-rAR/strings.xml index 09fdb53b..74e2f64a 100644 --- a/res/values-es-rAR/strings.xml +++ b/res/values-es-rAR/strings.xml @@ -11,6 +11,8 @@ Configuración Detalles Mandar + General Más Cuentas @@ -241,4 +243,5 @@ Autentificación requerida Clave incorrecta Elegir + Seguridad diff --git a/res/values-es-rBO/strings.xml b/res/values-es-rBO/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-es-rBO/strings.xml +++ b/res/values-es-rBO/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-es-rCL/strings.xml b/res/values-es-rCL/strings.xml index c9510725..f3ad1805 100644 --- a/res/values-es-rCL/strings.xml +++ b/res/values-es-rCL/strings.xml @@ -7,6 +7,8 @@ Nuevo directorio Configuración detalles + General Cuentas Administrar Cuentas diff --git a/res/values-es-rCO/strings.xml b/res/values-es-rCO/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-es-rCO/strings.xml +++ b/res/values-es-rCO/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-es-rCR/strings.xml b/res/values-es-rCR/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-es-rCR/strings.xml +++ b/res/values-es-rCR/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-es-rEC/strings.xml b/res/values-es-rEC/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-es-rEC/strings.xml +++ b/res/values-es-rEC/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-es-rMX/strings.xml b/res/values-es-rMX/strings.xml index 3772ff7a..78ee8b40 100644 --- a/res/values-es-rMX/strings.xml +++ b/res/values-es-rMX/strings.xml @@ -11,6 +11,8 @@ Ajustes Detalles Enviar + General Más Cuentas @@ -215,4 +217,5 @@ Cuentas Contraseña incorrecta Seleccionar + Seguridad diff --git a/res/values-es-rPE/strings.xml b/res/values-es-rPE/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-es-rPE/strings.xml +++ b/res/values-es-rPE/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-es-rPY/strings.xml b/res/values-es-rPY/strings.xml index 56fe6665..26dde5f1 100644 --- a/res/values-es-rPY/strings.xml +++ b/res/values-es-rPY/strings.xml @@ -1,6 +1,8 @@ Archivos + Archivos diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-es-rUY/strings.xml b/res/values-es-rUY/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-es-rUY/strings.xml +++ b/res/values-es-rUY/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index b9765b60..d78ff809 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -11,6 +11,14 @@ Configuración Detalles Enviar + Ordenar + Ordenar por + + A-Z + Más nuevo - Más viejo + + General Más Cuentas @@ -238,6 +246,7 @@ Previsualización de imagen No se puede mostrar la imagen %1$s se pudo copiar a la carpeta local %2$s + Ruta de subida La función Compartir no está activada en su servidor. Contacte a su administrador. No se puede compartir. Revise si el archivo existe @@ -263,7 +272,7 @@ Este archivo ya no se encuentra en el servidor Cuentas Agregar cuenta - La conexión segura está siendo redirigida por una ruta insegura. + La conexión segura está siendo desviada por una ruta insegura. Logs Enviar historial Logs de las apps ownCloud Android @@ -278,4 +287,6 @@ El archivo ya existe en la carpeta de destino Hubo un error al tratar de mover este archivo o carpeta para mover este archivo + Subidas instantáneas + Seguridad diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml index c1e6c6bc..2a23f187 100644 --- a/res/values-et-rEE/strings.xml +++ b/res/values-et-rEE/strings.xml @@ -11,6 +11,8 @@ Seaded Üksikasjad Saada + Üldine Rohkem Kontod @@ -242,6 +244,7 @@ Allpool on loend kohalikest failidest ning serveris asuvatest failidest %5$s, mi Pildi eelvaade Seda pilti ei saa näidata %1$s ei suudetud kopeerida kohalikku kataloogi %2$s + Üleslaadimise rada Vabandust, server ei toeta jagamist. Palun kontakteeru ⇥⇥administraatoriga. Jagamine ebaõnnestus. Palun kontrolli, kas fail on olemas @@ -267,7 +270,7 @@ Allpool on loend kohalikest failidest ning serveris asuvatest failidest %5$s, mi Fail ei ole serveris enam kättesaadav Kontod Lisa konto - Turvalist ühendust suunatakse läbi turvamata ühenduse. + Turvaline ühendus suunatakse läbi turvamata ühenduse. Logid Saada ajalugu ownCloud Android rakenduse logid @@ -282,4 +285,6 @@ Allpool on loend kohalikest failidest ning serveris asuvatest failidest %5$s, mi See fail on juba sihtkaustas olemas Selle faili või kausta liigutamisel tekkis tõrge selle faili liigutamiseks + Kohesed üleslaadimised + Turvalisus diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml index a9461fc0..4afe2756 100644 --- a/res/values-eu-rES/strings.xml +++ b/res/values-eu-rES/strings.xml @@ -1,5 +1,7 @@ + Deskargatu Ezeztatu diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 3edade43..e3d598ec 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -11,6 +11,8 @@ Ezarpenak Xehetasunak Bidali + Orokorra Gehiago Kontuak @@ -258,8 +260,8 @@ Mesedez, baimendu berriz Fitxategia jadanik ez dago eskuragarri zerbitzarian Kontuak Gehitu kontua - Konexio segurua birbideratu da segurua ez den bide batetik. Autentikazioa beharrezkoa Pasahitz okerra Aukeratu + Segurtasuna diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 200f5e2f..80348366 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -11,6 +11,8 @@ تنظیمات جزئیات ارسال + عمومی بیش‌تر حساب‌ها @@ -232,4 +234,5 @@ احراز هویت مورد نیاز است رمز عبور اشتباه است انتخاب کردن + امنیت diff --git a/res/values-fi-rFI/strings.xml b/res/values-fi-rFI/strings.xml index 9419f95f..18e972fb 100644 --- a/res/values-fi-rFI/strings.xml +++ b/res/values-fi-rFI/strings.xml @@ -11,6 +11,14 @@ Asetukset Tiedot Lähetä + Lajittele + Lajittelujärjestys + + A-Ö + Uusimmasta vanhimpaan + + Yleiset Enemmän Tilit @@ -218,6 +226,7 @@ Älä lähetä Kuvan esikatselu Tätä kuvaa ei voi näyttää + Lähetyspolku Jakaminen ei ole käytössä palvelimellasi. Ota yhteys ylläpitäjään. Virhe tiedoston tai kansion jakamista yrittäessä @@ -240,7 +249,6 @@ Tämä tiedosto ei ole enää palvelimella käytettävissä Tilit Lisää tili - Salattu yhteys on ohjattu uudelleen salaamatonta reittiä pitkin. Lokit Lähetä historia ownCloudin Android-sovelluksen lokit @@ -253,4 +261,6 @@ Siirto ei onnistu. Tarkista, ettei tiedostoa ole jo olemassa Tiedosto on jo olemassa kohdekansiossa Tämän tiedoston tai kansion siirtoa yrittäessä tapahtui virhe + Välittömät lähetykset + Tietoturva diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 8bb1a0e8..90c54a90 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -4,13 +4,21 @@ version %1$s Actualiser le compte Téléverser - Contenu d\'une autre application + Contenu d\'autres applications Fichiers Ouvrir avec Nouveau dossier Paramètres Détails Envoyer + Trier + Trier par + + A-Z + Plus récent - Plus ancien + + Général Plus Comptes @@ -165,7 +173,7 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq Impossible d\'établir la connexion Connexion sécurisée établie Nom d\'utilisateur ou mot de passe incorrect - Echec d\'autorisation + Échec d\'autorisation Accès refusé par le serveur d\'autorisation État inattendu ; veuillez entrer à nouveau l\'URL du serveur Votre autorisation a expiré. Merci de vous authentifier à nouveau @@ -243,6 +251,7 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq Prévisualisation de l\'image Cette image ne peut pas être affichée %1$s n\'a pas pu être copié dans le dossier local %2$s + Chemin d\'accès pour le téléversement Désolé, le partage n\'est pas disponible sur votre serveur. Contactez votre administrateur, s\'il vous plait. Impossible de partager. Vérifiez que le fichier est bien présent Une erreur est survenue lors de la tentative de partage de ce fichier ou répertoire @@ -267,7 +276,7 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq Ce fichier n’est plus disponible sur le serveur Comptes Ajouter un compte - Le connexion sécurisée est redirigée vers une route non-sécurisée. + La connexion sécurisée est redirigée via une route non-sécurisée. Journaux Envoyer l\'historique Journaux de l\'application Android ownCloud @@ -282,4 +291,6 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq Le fichier existe déjà dans le dossier de destination Une erreur est survenue lors de la tentative de déplacement de ce fichier ou dossier de déplacer ce fichier + Téléchargements instantanés + Sécurité diff --git a/res/values-fy-rNL/strings.xml b/res/values-fy-rNL/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-fy-rNL/strings.xml +++ b/res/values-fy-rNL/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index 65344d2f..3479d359 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -11,6 +11,8 @@ Preferencias Detalles Enviar + Xeral Máis Contas @@ -259,9 +261,9 @@ O ficheiro xa non está dispoñíbel no servidor Contas Engadir unha conta - A conexión segura está a ser redirixida a través dunha ruta non segura. Requírese autenticación Contrasinal incorrecto Mover Escoller + Seguranza diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-he/strings.xml b/res/values-he/strings.xml index cd30af29..9bc427b6 100644 --- a/res/values-he/strings.xml +++ b/res/values-he/strings.xml @@ -11,6 +11,8 @@ הגדרות פרטים שליחה + כללי יותר חשבונות @@ -260,6 +262,6 @@ הקובץ אינו זמין יותר על השרת חשבונות הוספת חשבון - חיבור מאובטח מנותב דרך נתיב לא מאובטח בחירה + אבטחה diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index 6d0355c7..06c5d2b9 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -11,6 +11,8 @@ सेटिंग्स विवरण भेजें + सामान्य और अधिक खाते diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 60b52f05..2c986dd5 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -5,6 +5,8 @@ Nova mapa Postavke Pošaljite + Općenito više Korisnićki računi @@ -36,4 +38,5 @@ Potrebna autentikacija Pogrešna lozinka Izaberi + Sigurnost diff --git a/res/values-hu-rHU/strings.xml b/res/values-hu-rHU/strings.xml index a0b16a04..18f000c9 100644 --- a/res/values-hu-rHU/strings.xml +++ b/res/values-hu-rHU/strings.xml @@ -11,6 +11,8 @@ Beállítások Részletek Küldjük el + Általános Több Fiókok @@ -242,4 +244,5 @@ Felhasználóazonosítás szükséges Hibás jelszó Válasszon + Biztonság diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index eeb78fe8..7994bf47 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -1,5 +1,7 @@ + Բեռնել diff --git a/res/values-ia/strings.xml b/res/values-ia/strings.xml index 0d79fbde..9bcfedfd 100644 --- a/res/values-ia/strings.xml +++ b/res/values-ia/strings.xml @@ -5,6 +5,8 @@ Nove dossier Configurationes Invia + General Plus Adjuta diff --git a/res/values-id/strings.xml b/res/values-id/strings.xml index f7cfeaa7..1ed5c03a 100644 --- a/res/values-id/strings.xml +++ b/res/values-id/strings.xml @@ -6,17 +6,29 @@ Unggah Konten dari apl lain Berkas - Bukan dengan + Buka dengan Folder baru - pengaturan + Pengaturan Rincian Kirim - umum + Urutkan + Urutan + + A-Z + Terbaru - Terlawas + + + Umum Lainnya Akun Kelola Akun PIN Apl Lindungi klien Anda + Unggah gambar cepat + Unggah gambar yang diambil kamera dengan cepat + Unggah video cepat + Unggah video yang direkam kamera dengan cepat Aktifkan Pencatatan Ini digunakan untuk mencatat masalah Riwayat Catatan @@ -25,8 +37,9 @@ Bantuan Rekomendasikan ke teman Umpan balik - Imprint - Coba %1$s pada smartphone Anda! + Jejak + Cobalah %1$s pada ponsel cerdas Anda! + Saya ingin mengajak Anda untuk menggunakan %1$s di ponsel cerdas Anda!\nUnduh gratis disini: %2$s Periksa Server Alamat server https://… Nama Pengguna @@ -35,6 +48,7 @@ Berkas Sambungkan Unggah + Pilih folder unggah: Tidak ada akun yang ditemukan Belum ada akun %1$s pada perangkat Anda. Silahkan buat akun terlebih dahulu. Pengaturan @@ -44,6 +58,8 @@ %1$s tidak diizinkan mengakses konten berbagi Mengunggah Tidak ada apa-apa di sini. Unggah sesuatu! + Memuat... + Tidak ada satupun berkas dalam folder ini. Sentuh pada berkas untuk menampilkan informasi tambahan Ukuran: Tipe: @@ -53,6 +69,7 @@ Segarkan berkas Berkas diubah namanya menjadi %1$s saat pengunggahan Bagikan tautan + Batal bagikan tautan Ya Tidak Oke @@ -75,6 +92,7 @@ %1$s berhasil diunggah Gagal mengunggah Unggah %1$s tidak selesai + Unggah gagal, Anda perlu masuk ulang Mengunduh... %1$d%% Mengunduh %2$s Berhasil mengunduh @@ -82,19 +100,26 @@ Gagal Mengunduh Mengunduh %1$s tidak selesai Belum diunduh + Gagal mengunduh, Anda perlu masuk kembali Pilih akun Sinkronisasi gagal + Sinkronisasi gagal, Anda perlu masuk kembali Sinkronisasi %1$s tidak selesai Sandi salah untuk %1$s Konflik ditemukan + %1$d berkas kept-in-sync tidak dapat disinkronkan + Berkas kept-in-sync gagal Konten berkas %1$d tidak dapat disinkronasikan (%2$d konflik) Beberapa berkas lokal terlupakan + %1$d berkas diluar folder %2$s tidak dapat disalin kedalamnya + Sejak versi 1.3.16, berkas-berkas yang diunggah dari piranti ini akan disalin kedalam folder %1$s lokal untuk mencagah kehilangan data ketika berkas tunggal disinkronkan dengan akun lebih dari satu.\n\nAkibat perubahan ini, semua berkas yang diunggah di versi aplikasi sebelumnya disalin kedalam folder %2$s. Namun, sebuah kesalahan mencegah penyelesaian operasi ini saat sinkronisasi berlangsung akun. Anda boleh meninggalkan berkas seperti ini dan menghapus tautan ke %3$s atau memindahkan berkas kedalam folder %1$s dan membiarkan tautan ke %4$s.\n\nYang tampak dibawah adalah berkas lokal, dan berkas remote didalam %5$s yang dihubungkan dengannya. Folder %1$s tidak ada lagi Pindahkan semua Semua berkas sudah dipindahkan Beberapa berkas tidak dapat dipindahkan Lokal: %1$s Jauh: %1$s + Ruang tidak cukup untuk menyalin berkas terpilih kedalam folder %1$s. Apakah Anda ingin memindahkannya saja? Silakan masukkan PIN Apl Masukkan PIN Apl PIN akan selalu diminta setiap kali apl dijalankan @@ -107,6 +132,7 @@ Pemutar musik %1$s %1$s (dimainkan) %1$s (sedang dimuat) + %1$s pemutaran selesai Tidak ditemukan berkas media Tidak ada akun yang diberikan Brkas tidak didalam akun yang sah @@ -122,6 +148,7 @@ Tombol mundur Tombol main dan jeda Tombol maju + Mendapatkan otorisasi... Mencoba untuk masuk... Tidak ada koneksi internet Sambungan aman tidak tersedia @@ -150,9 +177,14 @@ Menyambungkan ke server otentikasi... Server tidak mendukung medote otentikasi ini %1$s tidak mendukung banyak akun + Server Anda tidak membalas id pengguna dengan banar, Sialakn hubungi Administrator + + Tidak dapat mengotentikasi pada server ini Biarkan berkas tetap terbaru Ubah nama Hapus + Apakah Anda yakin ingin menghapus %1$s? + Apakah Anda yakin ingin menghapus %1$s dan isinya? Lokal saja Konten lokal saja Hapus dari server @@ -164,10 +196,13 @@ Mengubah nama tidak selesai Berkas jauh tidak dapat diperiksa Isi berkas sudah diselaraskan + Folder tidak dapat dibuat Karakter yang dilarang: / \\ < > : \" | ? * - Tunggu sejenak + Nama berkas tidak boleh kosong + Tunggu sebentar Masalah tidak terduga, silahkan pilih berkas dari apl yang berbeda Tidak ada berkas yang terpilih + Kirim taukan ke Masuk dengan oAuth2 Menyambungkan ke server oAuth2... Identitas situs tidak dapat diverfikasi @@ -192,6 +227,8 @@ Untuk: Tanda tangan: Algoritma: + Sertifikat tidak dapat ditampilkan. + - Tidak ada informasi tantang terror Ini adalah placeholder placeholder.txt Gambar PNG @@ -199,6 +236,7 @@ 18/05/2012 12:23 PM 12:23:45 Hanya unggah gambar via WiFi + Hanya unggah video via WiFi /UnggahInstan Perbarui benturan Berkas jauh %s tidak sinkron dengan berkas lokal. Melanjutkan akan menggantikan konten berkas di server. @@ -206,10 +244,49 @@ Timpa Jangan mengunggah Pratilik gambar + Gambar ini tidak dapat ditampilkan + %1$s tidak dapat disalin ke folder lokal %2$s + Jalur Lokasi Unggah + Maaf, berbagi tidak diaktifkan pada server Anda. Silakan hubungi + administrator Anda. + Tidak dapat berbagi. Mohon periksa apakah berkas ada + Terjadi kesalahan saat mencoba membagikan berkas atau folder ini + Tidak dapat menghapus berbagi. Mohon periksa apakah berkas ada + Terjadi kesalahan saat mencoba menghapus berbagi berkas dan folder ini Kirim + Salin tautan + Disalin ke papan klip + Kesalahan fatal: tidak dapat melakukan operasi + Terjadi kesalahan saat menghubungkan dengan server. + Terjadi kesalahan saat menunggu balasan server, operasi tidak dapat diselesaikan + Terjadi kesalahan saat menunggu balasan server, operasi tidak dapat diselesaikan + Operasi tidak dapat diselesaikan, server tidak tersedia + Anda tidak memiliki izin %s + untuk mengubah nama berkas ini + untuk menghapus berkas ini + untuk membagikan berkas ini + untuk batal membagikan berkas ini + untuk membuat berkas + untuk mengunggah kedalam folder ini + Berkas tidak lagi tersedia pada server Akun + Tambah akun + Sambungan aman dialihkan ke rute yang tidak aman. + Log + Kirim Riwayat + Log apl ownCloud Android + Memuat data... Diperlukan otentikasi Sandi salah + Pindah + Tdak ada apapun disini. Anda dapat menambahkan sebuah folder! Pilih + Tidak dapat memindahkan. Silakan periksa apakah berkas ada + Tidak mungkin untuk memindahkan folder kedalam turunannya + Berkas sudah ada didalam folder tujuan + Terjadi kesalahan saat mencoba memindahkan berkas atau folder ini + untuk memindahkan berkas ini + Unggah Cepat + Keamanan diff --git a/res/values-io/strings.xml b/res/values-io/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-io/strings.xml +++ b/res/values-io/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index d6eaedad..81109d51 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -4,6 +4,8 @@ Skrár Stillingar Senda + Meira Hjálp Notendanafn diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index d4507602..4a1c5bda 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -11,6 +11,14 @@ Impostazioni Dettagli Invia + Ordina + Ordina per + + A-Z + Più recente - Più datato + + Generale Altro Account @@ -238,6 +246,7 @@ Anteprima dell\'immagine Questa immagine non può essere mostrata %1$s non può essere copiato nella cartella locale %2$s + Percorso di caricamento Spiacenti, la condivisione non è abilitata sul tuo server. Contatta il tuo amministratore. Impossibile condividere. Assicurati che il file esista @@ -263,7 +272,7 @@ Il file non è più disponibile sul server Account Aggiungi account - La connessione sicura è rediretta attraverso un percorso non sicuro. + La connessione sicura è rediretta su un percorso non sicuro. Registri Invia cronologia Registri applicazione ownCloud Android @@ -278,4 +287,6 @@ Il file esiste già nella cartella di destinazione Si è verificato un errore durante il tentativo di spostare il file o la cartella per spostare questo file + Caricamenti istantanei + Protezione diff --git a/res/values-ja-rJP/strings.xml b/res/values-ja-rJP/strings.xml index 996d16c3..67c1e6e2 100644 --- a/res/values-ja-rJP/strings.xml +++ b/res/values-ja-rJP/strings.xml @@ -11,6 +11,10 @@ 設定 詳細 送信 + ソート + ソート: + 一般 もっと見る アカウント @@ -264,7 +268,6 @@ ファイルはサーバー上で利用できません アカウント アカウントを追加 - 暗号化されていない接続を経て、暗号化接続へリダイレクトされました。 ログ ログを送信 ownCloud Android アプリログ @@ -279,4 +282,5 @@ そのファイルは、宛先フォルダに既に存在しています。 このファイルまたはフォルダーを移動する際にエラーが発生しました このファイルを移動 + セキュリティ diff --git a/res/values-jv/strings.xml b/res/values-jv/strings.xml index 084a4e3e..0eaa7454 100644 --- a/res/values-jv/strings.xml +++ b/res/values-jv/strings.xml @@ -1,5 +1,7 @@ + Njipuk diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml index b23ca473..51ce611f 100644 --- a/res/values-ka-rGE/strings.xml +++ b/res/values-ka-rGE/strings.xml @@ -6,6 +6,8 @@ ახალი ფოლდერი პარამეტრები გაგზავნა + ზოგადი უფრო მეტი ანგარიში @@ -149,4 +151,5 @@ ანგარიში არჩევა + უსაფრთხოება diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index 3c4f3cd3..b31d4831 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -6,6 +6,8 @@ ការកំណត់ ព័ត៌មាន​លម្អិត ផ្ញើ + ទូទៅ ច្រើន​ទៀត គណនី @@ -83,4 +85,5 @@ គណនី ខុស​ពាក្យ​សម្ងាត់ ជ្រើស + សុវត្ថិភាព diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index b8313dbc..726c4d52 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -11,6 +11,8 @@ 설정 세부내용 보내기 + 일반 더 중요함 계정 @@ -217,4 +219,5 @@ 인증 필요함 잘못된 암호 선택 + 보안 diff --git a/res/values-ku-rIQ/strings.xml b/res/values-ku-rIQ/strings.xml index 2e843f2d..c342c643 100644 --- a/res/values-ku-rIQ/strings.xml +++ b/res/values-ku-rIQ/strings.xml @@ -3,6 +3,8 @@ بارکردن په‌ڕگەکان ده‌ستكاری + گشتی هەژمارەکان یارمەتی diff --git a/res/values-lb/strings.xml b/res/values-lb/strings.xml index 19b003ec..cde6e1a7 100644 --- a/res/values-lb/strings.xml +++ b/res/values-lb/strings.xml @@ -7,6 +7,8 @@ Astellungen Detailer Schécken + Allgemeng Méi Accounten diff --git a/res/values-lt-rLT/strings.xml b/res/values-lt-rLT/strings.xml index 5dfaffa4..c9ac6e35 100644 --- a/res/values-lt-rLT/strings.xml +++ b/res/values-lt-rLT/strings.xml @@ -11,6 +11,8 @@ Nustatymai Informacija Siųsti + Bendras Daugiau Paskyros @@ -234,4 +236,5 @@ Paskyros Neteisingas slaptažodis Pasirinkite + Saugumas diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 41a1872f..581b79cd 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -6,6 +6,8 @@ Jauna mape Iestatījumi Sūtīt + Vispārīgi Vairāk Konti @@ -143,4 +145,5 @@ Konti Izvēlieties + Drošība diff --git a/res/values-mg/strings.xml b/res/values-mg/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-mg/strings.xml +++ b/res/values-mg/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index d7a62301..d8edec41 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -7,6 +7,8 @@ Параметри Детали: Прати + Општо Повеќе Сметки @@ -122,4 +124,5 @@ Потребна е автентификација Погрешна лозинка Избери + Безбедност diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml index 75c6f22f..3ee64f03 100644 --- a/res/values-ml-rIN/strings.xml +++ b/res/values-ml-rIN/strings.xml @@ -1,6 +1,8 @@ ഫയലുകൾ + ഫയലുകൾ diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml index af534d2e..808ab65d 100644 --- a/res/values-ms-rMY/strings.xml +++ b/res/values-ms-rMY/strings.xml @@ -3,6 +3,8 @@ Muat naik Fail-fail Set + Umum Lanjutan Akaun diff --git a/res/values-mt-rMT/strings.xml b/res/values-mt-rMT/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-mt-rMT/strings.xml +++ b/res/values-mt-rMT/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index 7f998bf3..2d882a6b 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -1,6 +1,8 @@ ဖိုင်များ + အကူအညီ သုံးစွဲသူအမည် စကားဝှက် diff --git a/res/values-nb-rNO/strings.xml b/res/values-nb-rNO/strings.xml index dc60a356..2ad2386e 100644 --- a/res/values-nb-rNO/strings.xml +++ b/res/values-nb-rNO/strings.xml @@ -11,6 +11,8 @@ Innstillinger Detaljer Send + Generelt Mer Kontoer @@ -263,7 +265,6 @@ Filen finnes ikke på serveren lenger Kontoer Legg til en konto - Sikker tilkobling videresendes gjennom en usikker rute. Logger Send historikk logger for ownCloud Android app @@ -278,4 +279,5 @@ Filen finnes allerede i målmappen En feil oppstod ved flytting av denne filen eller mappen å flytte denne filen + Sikkerhet diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 2f12347a..ee3f87db 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -11,6 +11,14 @@ Instellingen Details Versturen + Sorteren + Sorteer op + + A-Z + Nieuwste - Oudste + + Algemeen Meer Accounts @@ -241,6 +249,7 @@ Hieronder staan de lokale bestanden en de externe bestanden in %5$s waar ze naar Afbeelding voorbeeld Deze afbeelding kan niet worden getoond %1$s kon niet worden gekopieerd naar de %2$s lokale map + Upload pad Sorry, delen is niet mogelijk op uw server. Neem contact op met uw beheerder. Kan dit niet delen. Controleer of dit bestand wel bestaat. @@ -266,7 +275,7 @@ Hieronder staan de lokale bestanden en de externe bestanden in %5$s waar ze naar Dit bestand is niet langer beschikbaar op de server Accounts Toevoegen account - De beveiligde verbinding is omgeleid via een onveilige route. + De beveiligde verbinding is omgeleid naar een onveilige route. Logs Verstuur geschiedenis ownCloud Android app logs @@ -281,4 +290,6 @@ Hieronder staan de lokale bestanden en de externe bestanden in %5$s waar ze naar Het bestand bestaat al in de doelmap Er trad een fout op bij uw poging dit bestand of deze map te verplaatsen om dit bestand te verplaatsen + Directe uploads + Beveiliging diff --git a/res/values-nn-rNO/strings.xml b/res/values-nn-rNO/strings.xml index 8c15264f..d9a37a83 100644 --- a/res/values-nn-rNO/strings.xml +++ b/res/values-nn-rNO/strings.xml @@ -11,6 +11,8 @@ Innstillingar Detaljar Send + Generelt Meir Kontoar @@ -129,4 +131,5 @@ Kontoar Feil passord Vel + Tryggleik diff --git a/res/values-oc/strings.xml b/res/values-oc/strings.xml index 84ca4981..0d3a45ba 100644 --- a/res/values-oc/strings.xml +++ b/res/values-oc/strings.xml @@ -3,6 +3,8 @@ Amontcarga Fichièrs Configuracion + General Mai d\'aquò Comptes diff --git a/res/values-or-rIN/strings.xml b/res/values-or-rIN/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-or-rIN/strings.xml +++ b/res/values-or-rIN/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index dc0b9e78..b84b0576 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -10,6 +10,8 @@ ਸੈਟਿੰਗ ਵੇਰਵ ਭੇਜੋ + ਆਮ ਅਕਾਊਂਟ ਲਾਗ ਰੱਖਣਾ ਚਾਲੂ diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 9fd718a0..0e84c681 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -11,6 +11,14 @@ Ustawienia Szczegóły Wyślij + Sortuj + Sortuj według + + A-Z + Nowsze - Starsze + + Ogólne Więcej Konta @@ -238,6 +246,7 @@ Podgląd Ten obrazek nie może zostać wyświetlony %1$s nie może zostać skopiowany do lokalnego folderu %2$s + Katalog wysyłania Przepraszamy, ale współdzielenie nie jest włączone na Twoim serwerze. Proszę skontaktuj się z administratorem. Nie można udostępnić. Proszę sprawdzić, czy plik istnieje @@ -266,6 +275,7 @@ Bezpieczne połączenie jest przekierowywane przez niezabezpieczone trasy. Logi Wyślij historię + Logi aplikacji ownCloud Android Ładuję dane... Wymagana autoryzacja Złe hasło @@ -277,4 +287,6 @@ Plik istnieje już w folderze docelowym Pojawił się błąd podczas próby przeniesienia tego pliku lub folderu aby przenieść ten plik + Automatyczne wysyłanie + Bezpieczeństwo diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml index 71df1607..ea2e5971 100644 --- a/res/values-pt-rBR/strings.xml +++ b/res/values-pt-rBR/strings.xml @@ -11,6 +11,14 @@ Configurações Detalhes Enviar + Classificar + Classificar por + + A-Z + Mais Recente - Mais Antigo + + Geral Mais Contas @@ -238,6 +246,7 @@ Pré-visualização da imagem Esta imagem não pode ser mostrada %1$s não pôde ser copiado para pasta local %2$s + Enviar Caminho Desculpe, o compartilhamento não está habilitado em seu servidor. Entre em contato com seu ⇥⇥ administrador. Não é possível compartilhar. Por favor verifique se o arquivo existe @@ -263,7 +272,7 @@ Este arquivo não mais está disponível neste servidor Contas Adicionar uma conta - A conexão segura está redirecionada através de uma rota insegura. + Conexão segura esta redirecionada para uma rota não segura. Logs Enviar Histórico Logs do aplicativo ownCloud Android @@ -278,4 +287,6 @@ O arquivo já existe na pasta de destino Ocorreu um erro ao tentar mover este arquivo ou pasta mover este arquivo + Envios Instantâneos + Segurança diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index dcf87789..1012a167 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -11,6 +11,14 @@ Definições Detalhes Enviar + Ordenar + Ordenar por + + A-Z + Mais Recente - Mais Antigo + + Geral Mais Contas @@ -31,6 +39,7 @@ Resposta Imprint Experimente %1$s no seu smartphone! + Quero convidar-te a usares %1$s no teu smartphone!\nFaz download aqui: %2$s Verificar Servidor Endereço do servidor https://.. Nome de Utilizador @@ -236,9 +245,11 @@ Pré-Visualização da imagem Esta imagem não pode ser mostrada Não foi possível copiar %1$s para a pasta local %2$s + Caminho de Upload Lamentamos mas não é possível partilhar através do seu servidor. Por favor contacte o seu administrador. Não é possivel partilhar. Por favor verifique se o ficheiro existe Ocorreu um erro enquanto tentava partilhar este ficheiro ou pasta + Não é possível retirar a partilha. Verifique se o ficheiro existe Ocorreu um erro enquanto retirava a partilha deste ficheiro ou pasta Enviar Copiar ligação @@ -259,14 +270,21 @@ O ficheiro não está mais disponível no servidor Contas Adicionar conta - Uma ligação segura foi redireccionada por uma rota insegura. + Ligação segura é redireccionada para um caminho inseguro. + Logs Enviar Histórico + Logs da app ownCloud Android A carregar os dados... Autenticação necessária Password errada Mover Não está aqui nada. Pode adicionar uma pasta! Escolha + Não é possível mover. Verifique se o ficheiro existe + Não é possível mover esta pasta deste modo O ficheiro já existe na pasta de destino + Um erro ocorreu ao tentar mover este ficheiro ou pasta para mover este ficheiro + Uploads Instantâneos + Segurança diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index c3e20eee..c043dff8 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -11,6 +11,8 @@ Setări Detalii Expediază + General Mai mult Conturi @@ -259,4 +261,5 @@ Conturi Parolă greșită Alege + Securitate diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 49c3e3cc..326f51a9 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -11,6 +11,8 @@ Настройки Подробно Отправить + Основные Больше Учётные записи @@ -263,7 +265,6 @@ Этот файл больше недоступен на сервере Учётные записи Добавить учетную запись - Безопасное соединение перенаправлено через небезопасный маршрут. Журналы История Отправлений Журналы Андроид-приложения ownCloud @@ -278,4 +279,5 @@ Файл уже существует в папке назначения Произошла ошибка при попытке перемещения этого файла или папки переместить этот файл + Безопасность diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml index e64f5775..4a3e9e81 100644 --- a/res/values-si-rLK/strings.xml +++ b/res/values-si-rLK/strings.xml @@ -3,6 +3,8 @@ උඩුගත කිරීම ගොනු සිටුවම් + සාමාන්‍යයෙන් වැඩි ගිණුම් diff --git a/res/values-sk-rSK/strings.xml b/res/values-sk-rSK/strings.xml index 78e09425..2ab0c280 100644 --- a/res/values-sk-rSK/strings.xml +++ b/res/values-sk-rSK/strings.xml @@ -11,6 +11,8 @@ Nastavenia Podrobnosti Odoslať + Všeobecné Viac Účty @@ -259,9 +261,9 @@ Súbor už na serveri nie je dostupný Účty Pridať účet - Zabezpečené spojenie je presmerované nezabezpečenou cestou. Vyžaduje sa overenie Nesprávne heslo Presunúť Vybrať + Zabezpečenie diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index da73b33b..b4d46663 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -11,6 +11,14 @@ Nastavitve Podrobnosti Pošlji + Razvrsti + Razvrsti po + + Naraščajoče A – Z + Novejše – Starejše + + Splošno Več Računi @@ -238,6 +246,7 @@ Predogled slike Te slike ni mogoče prikazati Datoteke %1$s ni mogoče kopirati v krajevno mapo %2$s + Pot za pošiljanje Souporaba je na strežniku onemogočena. Možnost lahko spreminjajo le uporabniki s skrbniškimi dovoljenji. Souporaba ni mogoča. Preverite, ali datoteka obstaja. @@ -278,4 +287,6 @@ Datoteka v ciljni mapi že obstaja. Prišlo je do napake med premikanjem datoteke v mapo med premikanjem datoteke + Takojšnje pošiljanje v oblak + Varnost diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index def1b9b8..798c4b39 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -5,6 +5,8 @@ Dosje e\'re Parametrat Dërgo + Përgjithshme Më tepër Llogarit @@ -72,4 +74,5 @@ Llogarit Fjalëkalim i gabuar Zgjidh + Siguria diff --git a/res/values-sr-rSP/strings.xml b/res/values-sr-rSP/strings.xml index 973a5489..daac3e01 100644 --- a/res/values-sr-rSP/strings.xml +++ b/res/values-sr-rSP/strings.xml @@ -5,6 +5,8 @@ Podešavanja Detaljnije Pošalji + Opšte Nalozi Upravljaj nalozima diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 5384da08..5aadabb9 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -5,6 +5,8 @@ Датотеке Поставке Пошаљи + Опште Више Налози @@ -109,4 +111,5 @@ Налози Одабери + Безбедност diff --git a/res/values-su/strings.xml b/res/values-su/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-su/strings.xml +++ b/res/values-su/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index be006747..a649518b 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -11,6 +11,8 @@ Inställningar Detaljer Skicka + Allmänt Mer Konton @@ -30,8 +32,8 @@ Rekommendera till en vän Feedback Imprint - Försök %1$s på din smarttelefon! - Jag skulle vilja bjuda in dig till att använda %1$s på din smartphone!\nLadda ner här: %2$s + Prova %1$s på din smartphone! + Jag skullje vilja bjuda in dig till att prova %1$s på din smartphone!\nLadda ner appen från Google Play här: %2$s Kontrollera Server Serveradress https://... Användarnamn @@ -268,4 +270,5 @@ Välj Gick inte att flytta. Vänligen kontrollera att filen existerar att flytta den här filen + Säkerhet diff --git a/res/values-sw-rKE/strings.xml b/res/values-sw-rKE/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-sw-rKE/strings.xml +++ b/res/values-sw-rKE/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml index 9cce7aeb..5a27ffd0 100644 --- a/res/values-ta-rIN/strings.xml +++ b/res/values-ta-rIN/strings.xml @@ -11,6 +11,8 @@ அமைப்புகள் விவரங்கள் அனுப்பவும் + பொது மேலும் கணக்குகள் diff --git a/res/values-ta-rLK/strings.xml b/res/values-ta-rLK/strings.xml index 8dd0c780..3c3e167a 100644 --- a/res/values-ta-rLK/strings.xml +++ b/res/values-ta-rLK/strings.xml @@ -5,6 +5,8 @@ கோப்புகள் அமைப்புகள் விவரங்கள் + பொதுவான மேலதிக கணக்குகள் diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index ac049826..834de750 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -3,6 +3,8 @@ కొత్త సంచయం అమరికలు పంపించు + మరిన్ని సహాయం వాడుకరి పేరు diff --git a/res/values-tg-rTJ/strings.xml b/res/values-tg-rTJ/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-tg-rTJ/strings.xml +++ b/res/values-tg-rTJ/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-th-rTH/strings.xml b/res/values-th-rTH/strings.xml index 0b141a18..6ed3f19c 100644 --- a/res/values-th-rTH/strings.xml +++ b/res/values-th-rTH/strings.xml @@ -7,6 +7,8 @@ ตั้งค่า รายละเอียด ส่ง + ทั่วไป มาก บัญชี diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 72a15891..656397b7 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -11,6 +11,14 @@ Ayarlar Ayrıntılar Gönder + Sırala + Şuna göre sırala + + A-Z + Yeniden - Eskiye + + Genel Daha fazla Hesaplar @@ -238,6 +246,7 @@ Resim önizleme Bu resim gösterilemiyor %1$s, %2$s yerel klasörüne kopyalanamadı + Yükleme Yolu Üzgünüz, paylaşım sunucunuzda etkin değil. Lütfen yöneticinizle iletişime geçin. Paylaşma başarısız. Lütfen dosyanın mevcut olup olmadığını denetleyin @@ -263,7 +272,7 @@ Bu dosya artık sunucuda mevcut değil Hesaplar Hesap ekle - Güvenli bağlantı, güvenli olmayan bir rotaya yönlendiriliyor. + Güvenli bağlantı, güvenli olmayan bir rotaya yönlendirildi. Günlükler Geçmişi Gönder ownCloud Android uygulama kayıtları @@ -278,4 +287,6 @@ Dosya zaten hedef klasörde mevcut Bu dosya veya klasörü taşımaya çalışılırken bir hata oluştu bu dosyayı taşımak için + Anında Yüklemeler + Güvenlik diff --git a/res/values-ug/strings.xml b/res/values-ug/strings.xml index b126d897..12a54928 100644 --- a/res/values-ug/strings.xml +++ b/res/values-ug/strings.xml @@ -5,6 +5,8 @@ يېڭى قىسقۇچ تەڭشەكلەر يوللا + ئادەتتىكى تېخىمۇ كۆپ ھېساباتلار @@ -41,4 +43,5 @@ يوللا ھېساباتلار + بىخەتەرلىك diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 68185743..22ab60a4 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -1,26 +1,54 @@ + %1$s Android App + версія %1$s + Оновити account Відвантажити Вміст із інших програм Файли + Відкрити за допомогою Нова тека Налаштування Деталі Надіслати + Сортувати + Сортувати за + + А-Я + Новіші-Старіші + + Основне Більше Облікові записи Управління обліковими записами App програмний PIN Захист Вашог App клієнта + Миттєві зображення + Миттєві зображення з камери + Миттєві відео + Миттєві відео з камери + Ввімкнути журнал + Використовується для реєстрації помилок + Журнал + Тут показані записи журналу + Видалити історію записів Допомога + Порадити товаришу Зворотній зв\'язок Відбиток + Спробуйте %1$s на своєму смартфоні! + Пропоную вам користуватися %1$s на вашому смартфоні!\nЗавантажити можна за посиланням: %2$s + Перевірити сервер + Адреса серверу https://… Ім\'я користувача Пароль + Вперше в %1$s? Файли З\'єднати Відвантажити + Оберіть теку для завантаження: Не знайдено облікового запису На Вашому пристрої відсутні облікові записи %1$s. Будь ласка, спочатку створіть запис. Налаштування @@ -30,14 +58,18 @@ %1$s не може отримати доступ до спільного контенту Завантаження Тут нічого немає. Відвантажте що-небудь! + Завантаження... + В цій теці немає файлів. Натисніть на файлі для відображення додаткової інформації Розмір: Тип: Створено: Змінено: Завантажити + Оновити файл Файл був переіменований в %1$s протягом вивантаження Опублікувати посилання + Видалити посилання Так Ні OK @@ -46,6 +78,7 @@ Відмінити Зберегти & Вихід Помилка + Завантаження... Невідома помилка Про Змінити пароль @@ -59,25 +92,34 @@ %1$s було успішно завантажено Помилка завантаження Завантаження %1$s не може завершитись + Завантажити не вдалося, необхідно повторити вхід Зкачування … %1$d%% Зкачування %2$s Успішно зкачано %1$s успішно завантажено Завантаження не вдалося Завантаження %1$s не вдається завершити + Ще не завантажене + Зберегти не вдалося, необхідно повторити вхід Оберіть обліковий запис Помилка синхронізації + Синхронізація не вдалася, необхідно повторити вхід Синхронізація %1$s не вдалась + Невірний пароль для %1$s Конфліктів знайдено %1$d файли, які мають бути синхронізованими не можуть синхронізуватися Синхронізувати файли не вдалося Зміст %1$d файлів не може бути синхронізований (%2$d конфліктів) Деякі локальні файли були забуті + Неможливо скопіювати %1$d файли з теки %2$s + \"Починаючи з версії 1.3.16, файли, завантажені з цього пристрою копіюються в локальну теку %1$s для запобігання втрати даних під час синхронізації одного файлу з кількома обліковими записами.\n\nТому всі файли, завантажені в попередніх версіях цього додатку були скопійовані в теку %2$s. Однак, під час синхронізації сталася помилка. Ви можете залишити файл(и) як є та видалити посилання на %3$s, або перемістити файл(и) в директорію %1$s і зберегти посилання на %4$s.\n\nНижче наведені локальні та віддалені файли у %5$s з якою вони були пов\'язані. + Тека %1$s білше не існує Перемістити все Всі файли були переміщені Деякі файли не можуть бути переміщені Локально: %1$s Віддалено: %1$s + Недостатньо місця для копіювання обраних файлів у теку %1$s. Чи бажаєте ви перемістити їх замість копіювання? Будь ласка, введіть свій програмний PIN Введіть програмний PIN PIN необхідно буде вводити щоразу при запуску цієї програми @@ -87,24 +129,62 @@ Не вірний App програмний PIN App програмний PIN видалено App програмний PIN збережено + %1$s музичний плеєр + %1$s (відтворення) + %1$s (завантаження) + %1$s відтворення завершене + Медіа-файлів не знайдено + Обліковий запис не налаштований + Файл в невірному обліковому записі + Кодек не підтримується + Медіа-файл не читається + Медіа-файл невірно закодований + Вийшов час на спробу відтворення + Неможливо потоком відтворити файл + Медіа-файл неможливо відтворити вбудованим програвачем + Помилка безпеки при відтворені %1$s + Помилка вводу при відтворені %1$s + Несподівана помилка при відтворені %1$s + Перемтка назад + Відтворення або пауза + Перемотка вперед + Виконується вхід... Спроба входу… Відсутнє підключення до мережі Безпечне з\'єднання не доступне. З\'єднання встановлено Перевірка з\'єднання… Не вірні налаштування сервер + Такий обліковий запис вже існує на пристрої + Введений користувач не відповідає обліковому запису Виникла невідома помилка! Не вдалося знайти хост Не знайдено примірник сервер Сервер занадто довго не відповідає Пошкоджений URL Помилка SSL ініціалізації + Неможливо перевірити SSL-сертифікат сервера Не вдалося визначити версію сервер серверу Не вдалося встановити з\'єднання Встановлено захищене з\'єднання + Невірне ім\'я користувача або пароль + Невдала авторизація + Доступ заборонений сервером авторизації + Несподівана відповідь; будь ласка, введіть адресу сервера знову + Час авторизації минув. Будь ласка, увійдіть знову + Будь ласка, введіть пароль + Час сесії минув. Будь ласка, підключіться знов + Підключення до серверу аутентифікації... + Сервер не підтримує обраний метод аутентифікації + %1$s не підтримує одночасно декілька облікових записів + Ваш сервер не повертає коректний ідентифікатор користувача, будь ласка зверніться до адміністратора +⇥ + Аутентифікація на цьому сервері неможлива Оновлювати файл Перейменувати Видалити + Ви дійсно бажаєте видалити %1$s? + Ви дійсно бажаєте видалити %1$s та весь вміст? Лише локально Лише локальний зміст Видалити із серверу @@ -116,9 +196,15 @@ Перейменування не вдалося Неможливо перевірити віддалений файл Зміст файлу вже синхронізовано + Не вдалося створити теку + Заборонені символи: / \\ < > : \" | ? * + Ім\'я файлу не може бути порожнім. Зачекайте хвилинку Несподівані проблеми ; будь ласка, спробуйте використати іншу програму для вибору файлу Не обрано файл + Надіслати посилання... + Увійти через oAuth2 + Підключення до серверу oAuth2... Не вдалося перевірити ідентифікацію сайта - Не довірений сертифікат серверу - Сертифікат серверу втратив чинність @@ -141,17 +227,65 @@ До: Підпис: Алгоритм: + Не вдалося показати сертифікат. + - Інформація про помилку відсутня Це заповнювач + placeholder.txt + PNG зображення + 389 КБ + 2012/05/18 12:23 PM + 12:23:45 Завантажувати зображення тільки через WiFi + Завантажувати відео тільки через WiFi /InstantUpload Конфлікт оновлення Віддалений файл %s не синхронізовано з локальним. Продовження процедури замінить вміст файлу на сервері. Залишити обидва Замінити Не завантажувати + Попередній перегляд зображення + Не вдалося показати зображення + %1$s неможливо скопіювати до %2$s + Завантажити шлях + На жаль, обмін не включений на вашому сервері. Будь ласка, зв\'яжіться з вашим адмінистратором. + Неможливо поділитися. Будь ласка, перевірте, чи існує файл + Виникла помилка при спробі поділитися файлом або текою + Неможливо заборонити доступ. Будь ласка, перевірте, чи існує файл + Виникла помилка при спробі заборонити доступ до файлу або теки Надіслати + Копіювати посилання Скопійовано в буфер обміну + Критична помилка: виконання операції неможливе + Виникла помилка при підключені до сервера. + Під час очікування на сервер виникла помилка, операцію неможливо завершити + Під час очікування на сервер виникла помилка, операцію неможливо завершити + Неможливо завершити операцію, сервер недоступний + У вас немає повноважень %s + на перейменування цього файла + на видалення цього файла + для надання доступу до файла + для закриття доступу до файла + для створення файла + для завантаження в цю теку + Файл більше не доступний на сервері Облікові записи + Додати обліковий запис + Безпечне підключення перенаправляється через незабезпечений маршрут. + Журнали + Надіслати історію + Журнали Android-додатка ownCloud + Завантаження даних... + Потрібна аутентифікація + Невірний пароль + Перемістити + Тут нічого немає. Ви можете додати теку! Обрати + Неможливо перемістити. Будь ласка, перевірте, чи існує файл + Неможливо перемістити теку до теки-нащадка + Файл вже існує в теці призначення + Виникла помилка при спробі перемістити файл або теку + перемістити цей файл + Миттєво завантаження + Безпека diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml index 1532b7ac..a90a3021 100644 --- a/res/values-ur-rPK/strings.xml +++ b/res/values-ur-rPK/strings.xml @@ -2,6 +2,8 @@ سیٹینگز بھجیں + مزید مدد یوزر نیم diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index 56e55a1d..69623e19 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -1,4 +1,6 @@ + diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 122092d1..7e5fe75a 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -11,6 +11,8 @@ Cài đặt Chi tiết Gởi + Tổng hợp hơn Tài khoản diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index c744b68d..1b6eab69 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -11,6 +11,8 @@ 设置 详细信息 发送 + 常规 更多 账号 @@ -263,7 +265,6 @@ 该文件在服务器上不可用 账号 添加账号 - 安全连接是通过一个非安全路由定向的。 日志 发送历史 ownCloud安卓客户端日志 @@ -278,4 +279,5 @@ 该文件已经存在在目标文件夹 尝试移动该文件或文件夹时发生错误 移动该文件 + 安全 diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index bd5f2e13..7e5ae525 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -7,6 +7,8 @@ 新資料夾 設定 傳送 + 一般 更多 帳號 diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index aca365e0..04938d38 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -11,6 +11,8 @@ 設定 詳細資料 寄出 + 一般 更多 帳號 @@ -31,6 +33,7 @@ 反饋 法律聲明 在您的手機中試用%1$s! + 我想邀請您在您的手機上使用 %1$s ! 可以由這兒下載: %2$s 檢查伺服器 伺服器位址 https://... 使用者名稱 @@ -49,6 +52,8 @@ %1$s 並沒有被允許存取分享的內容 上傳中 這裡還沒有東西,上傳一些吧! + 載入中… + 這個目錄中沒有任何檔案. 在檔案上輕觸來顯示更多資訊。 容量: 類型: @@ -92,6 +97,7 @@ 下傳失敗, 您需要重新登入 選擇帳號 同步失敗 + 同步作業失敗, 您需要重新登入 同步 %1$s 未完成 無效的密碼 %1$s 出現衝突 @@ -100,12 +106,14 @@ %1$d 未被同步 (%2$d 衝突) 有些本地端的檔案已遺失 %1$d 檔案超過 %2$s 資料夾可能不能複製進去 + 在 1.3.16 版之前, 檔案上傳時會先複製到本地的 %1$s 目錄以避免在多帳戶內同步造成遺失.\n\n由於這個改變, 所以在之前版本上傳的檔案被複製到 %2$s 目錄中. 為了避免同步發生問題. 你可以保留那些檔案並刪除連結 %3$s, 或搬移檔案到 %1$s 目錄並取得連結到 %4$s.\n\n下面列表是本地檔案, 與被連結遠端檔案 %5$s. 資料夾 %1$s 不存在 移動全部 所有文件已被移動 部份文件無法被移動 本地: %1$s 遠端: %1$s + 無足夠的空間可以複製檔案到 %1$s 目錄. 是否使用移動的方式來處理? 請輸入您的 App 密碼 輸入您的 App 密碼 這個密碼在你每次啟動這程式時都會被要求輸入 @@ -232,9 +240,12 @@ 圖片預覽 無法顯示圖片 %1$s 無法被複製到本地目錄 %2$s + 上傳目錄 很抱歉, 您的伺服器並未開啟分享的功能. 請聯絡您的 伺服器管理員. + 無法分享這個檔案或目錄. 請檢查它們是否存在 在分享檔案或目錄時發生了錯誤 + 無法取消分享這個檔案或目錄. 請檢查它們是否存在 在取消分享檔案或目錄時發生了錯誤 寄出 複製連結 @@ -245,8 +256,31 @@ 在等待伺服器回應時發生了錯誤, 這個操作將無法被完成 這個操作無法完成, 無法使用伺服器 + 您沒有權限 %s + 重新命名檔案 + 刪除檔案 + 分享檔案 + 取消分享檔案 + 建立檔案 + 上傳這個目錄 + 這個檔案已經不存在於伺服器中 帳號 + 新增帳號 + 安全連線被轉向到一個非安全的連線 + 紀錄 + 傳送歷史記錄 + ownCloud Android 應用程式記錄 + 資料載入中... 必須驗證 密碼錯誤 + 移動 + 找不到任何檔案. 你可以新增一個目錄! 選擇 + 無法搬移. 請檢查該檔案是否存在 + 把一個目錄搬移到其底下的子目錄是不可能的 + 這個檔案已經存在於目的目錄中 + 在移動檔案或目錄時發生了錯誤 + 移動這個檔案 + 即時上傳 + 安全性 diff --git a/res/values/strings.xml b/res/values/strings.xml index 4c7cf34d..af5a684d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -12,6 +12,14 @@ Settings Details Send + Sort + Sort by + + A-Z + Newest - Oldest + + General More Accounts @@ -251,6 +259,7 @@ This image cannot be shown %1$s could not be copied to %2$s local folder + Upload Path Sorry, sharing is not enabled on your server. Please contact your administrator. @@ -283,7 +292,7 @@ Accounts Add account - Secure connection is redirected through an unsecured route. + Secure connection is redirected to an unsecured route. Logs Send History @@ -302,4 +311,7 @@ An error occurred while trying to move this file or folder to move this file + Instant Uploads + Security + diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 945e853c..3b8b3e81 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -21,35 +21,41 @@ - - - + + - - - - - + + + + + + + + + @@ -65,4 +71,4 @@ - \ No newline at end of file + diff --git a/setup_env.sh b/setup_env.sh index 364d2739..8c9af071 100755 --- a/setup_env.sh +++ b/setup_env.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash -e #Repository diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java index 9a47bd78..e04239df 100644 --- a/src/com/owncloud/android/MainApp.java +++ b/src/com/owncloud/android/MainApp.java @@ -19,6 +19,7 @@ package com.owncloud.android; import android.app.Application; import android.content.Context; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy; import com.owncloud.android.lib.common.utils.Log_OC; @@ -55,6 +56,9 @@ public class MainApp extends Application { OwnCloudClientManagerFactory.setDefaultPolicy(Policy.ALWAYS_NEW_CLIENT); } + // initialise thumbnails cache on background thread + new ThumbnailsCacheManager.InitDiskCacheTask().execute(); + if (BuildConfig.DEBUG) { String dataFolder = getDataFolder(); diff --git a/src/com/owncloud/android/authentication/AuthenticatorActivity.java b/src/com/owncloud/android/authentication/AuthenticatorActivity.java index d88b8dc2..0f7892ee 100644 --- a/src/com/owncloud/android/authentication/AuthenticatorActivity.java +++ b/src/com/owncloud/android/authentication/AuthenticatorActivity.java @@ -85,6 +85,7 @@ import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; import com.owncloud.android.ui.dialog.SamlWebViewDialog; import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener; +import com.owncloud.android.utils.DisplayUtils; /** * This Activity is used to add an ownCloud account to the App @@ -246,13 +247,17 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { if (mAccount != null) { boolean oAuthRequired = (mAccountMgr.getUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2) != null); - boolean samlWebSsoRequired = - (mAccountMgr.getUserData(mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null); + boolean samlWebSsoRequired = ( + mAccountMgr.getUserData( + mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO + ) != null + ); mAuthTokenType = chooseAuthTokenType(oAuthRequired, samlWebSsoRequired); } else { boolean oAuthSupported = AUTH_ON.equals(getString(R.string.auth_method_oauth2)); - boolean samlWebSsoSupported = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso)); + boolean samlWebSsoSupported = + AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso)); mAuthTokenType = chooseAuthTokenType(oAuthSupported, samlWebSsoSupported); } } @@ -321,7 +326,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { if (savedInstanceState == null) { if (mAccount != null) { mServerInfo.mBaseUrl = mAccountMgr.getUserData(mAccount, Constants.KEY_OC_BASE_URL); - mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith("https://"); // TODO do this in a setter for mBaseUrl + // TODO do next in a setter for mBaseUrl + mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith("https://"); String ocVersion = mAccountMgr.getUserData(mAccount, Constants.KEY_OC_VERSION); if (ocVersion != null) { mServerInfo.mVersion = new OwnCloudVersion(ocVersion); @@ -351,7 +357,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { /// step 2 - set properties of UI elements (text, visibility, enabled...) mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput); - mHostUrlInput.setText(mServerInfo.mBaseUrl); + // Convert IDN to Unicode + mHostUrlInput.setText(DisplayUtils.convertIdn(mServerInfo.mBaseUrl, false)); if (mAction != ACTION_CREATE) { /// lock things that should not change mHostUrlInput.setEnabled(false); @@ -405,8 +412,12 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { @Override public boolean onTouch(View view, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) && - mHostUrlInput.hasFocus()) { + if ( + AccountTypeUtils.getAuthTokenTypeSamlSessionCookie( + MainApp.getAccountType() + ).equals(mAuthTokenType) && + mHostUrlInput.hasFocus() + ) { checkOcServer(); } } @@ -534,8 +545,9 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { /** * Saves relevant state before {@link #onPause()} * - * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the - * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()} + * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, + * intended to defer the processing of the redirection caught in + * {@link #onNewIntent(Intent)} until {@link #onResume()} * * See {@link #loadSavedInstanceState(Bundle)} */ @@ -574,11 +586,11 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { /** - * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request - * is caught here. + * The redirection triggered by the OAuth authentication server as response to the + * GET AUTHORIZATION request is caught here. * - * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the - * AndroidManifest.xml file. + * To make this possible, this activity needs to be qualified with android:launchMode = + * "singleTask" in the AndroidManifest.xml file. */ @Override protected void onNewIntent (Intent intent) { @@ -591,8 +603,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { /** - * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and - * deferred in {@link #onNewIntent(Intent)}, is processed here. + * The redirection triggered by the OAuth authentication server as response to the + * GET AUTHORIZATION, and deferred in {@link #onNewIntent(Intent)}, is processed here. */ @Override protected void onResume() { @@ -727,13 +739,18 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { showRefreshButton(false); if (uri.length() != 0) { + // Handle internationalized domain names + uri = DisplayUtils.convertIdn(uri, true); mServerStatusText = R.string.auth_testing_connection; mServerStatusIcon = R.drawable.progress_small; showServerStatus(); Intent getServerInfoIntent = new Intent(); getServerInfoIntent.setAction(OperationsService.ACTION_GET_SERVER_INFO); - getServerInfoIntent.putExtra(OperationsService.EXTRA_SERVER_URL, uri); + getServerInfoIntent.putExtra( + OperationsService.EXTRA_SERVER_URL, + normalizeUrlSuffix(uri) + ); if (mOperationsServiceBinder != null) { mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent); } else { @@ -777,7 +794,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { } private boolean isPasswordVisible() { - return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); } private void hidePasswordButton() { @@ -785,12 +803,16 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { } private void showPassword() { - mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + mPasswordInput.setInputType( + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + ); showViewPasswordButton(); } private void hidePassword() { - mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + mPasswordInput.setInputType( + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD + ); showViewPasswordButton(); } @@ -822,9 +844,13 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { return; } - if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()). + equals(mAuthTokenType)) { + startOauthorization(); - } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()). + equals(mAuthTokenType)) { + startSamlBasedFederatedSingleSignOnAuthorization(); } else { checkBasicAuthorization(); @@ -879,10 +905,18 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { // GET AUTHORIZATION request Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim()); Uri.Builder uriBuilder = uri.buildUpon(); - uriBuilder.appendQueryParameter(OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)); - uriBuilder.appendQueryParameter(OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)); - uriBuilder.appendQueryParameter(OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)); - uriBuilder.appendQueryParameter(OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type) + ); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri) + ); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id) + ); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope) + ); uri = uriBuilder.build(); Log_OC.d(TAG, "Starting browser to view " + uri.toString()); Intent i = new Intent(Intent.ACTION_VIEW, uri); @@ -927,7 +961,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { } else if (operation instanceof ExistenceCheckRemoteOperation) { //Log_OC.wtf(TAG, "received detection response through callback" ); - if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()). + equals(mAuthTokenType)) { onSamlBasedFederatedSingleSignOnAuthorizationStart(result); } else { @@ -1080,16 +1115,20 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { url = "http://" + url; } } - - url = trimUrlWebdav(url); - - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - } - + + url = normalizeUrlSuffix(url); } return (url != null ? url : ""); } + + + private String normalizeUrlSuffix(String url) { + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + url = trimUrlWebdav(url); + return url; + } // TODO remove, if possible @@ -1299,7 +1338,6 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { @SuppressWarnings("unchecked") Map tokens = (Map)(result.getData().get(0)); mAuthToken = tokens.get(OAuth2Constants.KEY_ACCESS_TOKEN); - //mAuthToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN); Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken); accessRootFolderRemoteOperation("", ""); @@ -1358,7 +1396,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { showRefreshButton(true); mOkButton.setEnabled(false); - // very special case (TODO: move to a common place for all the remote operations) (dangerous here?) + // very special case (TODO: move to a common place for all the remote operations) if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { showUntrustedCertDialog(result); } @@ -1374,23 +1412,27 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { /** - * Sets the proper response to get that the Account Authenticator that started this activity saves - * a new authorization token for mAccount. + * Sets the proper response to get that the Account Authenticator that started this activity + * saves a new authorization token for mAccount. */ private void updateToken() { Bundle response = new Bundle(); response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type); - if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()). + equals(mAuthTokenType)) { response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); - // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention + // the next line is necessary, notifications are calling directly to the + // AuthenticatorActivity to update, without AccountManager intervention mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); - } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()). + equals(mAuthTokenType)) { response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); - // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention + // the next line is necessary; by now, notifications are calling directly to the + // AuthenticatorActivity to update, without AccountManager intervention mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); } else { @@ -1411,8 +1453,10 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { */ private boolean createAccount() { /// create and save new ownCloud account - boolean isOAuth = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType); - boolean isSaml = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType); + boolean isOAuth = AccountTypeUtils. + getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType); + boolean isSaml = AccountTypeUtils. + getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType); Uri uri = Uri.parse(mServerInfo.mBaseUrl); String username = mUsernameInput.getText().toString().trim(); @@ -1434,9 +1478,12 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { mAccount = newAccount; if (isOAuth || isSaml) { - mAccountMgr.addAccountExplicitly(mAccount, "", null); // with external authorizations, the password is never input in the app + // with external authorizations, the password is never input in the app + mAccountMgr.addAccountExplicitly(mAccount, "", null); } else { - mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null); + mAccountMgr.addAccountExplicitly( + mAccount, mPasswordInput.getText().toString(), null + ); } /// add the new account as default in preferences, if there is none already @@ -1449,7 +1496,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { } /// prepare result to return to the Authenticator - // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done + // TODO check again what the Authenticator makes with it; probably has the same + // effect as addAccountExplicitly, but it's not well done final Intent intent = new Intent(); intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType()); intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); @@ -1459,9 +1507,14 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { if (isOAuth || isSaml) { mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); } - /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA - mAccountMgr.setUserData(mAccount, Constants.KEY_OC_VERSION, mServerInfo.mVersion.getVersion()); - mAccountMgr.setUserData(mAccount, Constants.KEY_OC_BASE_URL, mServerInfo.mBaseUrl); + /// add user data to the new account; TODO probably can be done in the last parameter + // addAccountExplicitly, or in KEY_USERDATA + mAccountMgr.setUserData( + mAccount, Constants.KEY_OC_VERSION, mServerInfo.mVersion.getVersion() + ); + mAccountMgr.setUserData( + mAccount, Constants.KEY_OC_BASE_URL, mServerInfo.mBaseUrl + ); if (isSaml) { mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE"); @@ -1483,7 +1536,9 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { * @param view 'Account register' button */ public void onRegisterClick(View view) { - Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.welcome_link_url))); + Intent register = new Intent( + Intent.ACTION_VIEW, Uri.parse(getString(R.string.welcome_link_url)) + ); setResult(RESULT_CANCELED); startActivity(register); } @@ -1583,18 +1638,21 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { /** * Called when the 'action' button in an IME is pressed ('enter' in software keyboard). * - * Used to trigger the authentication check when the user presses 'enter' after writing the password, - * or to throw the server test when the only field on screen is the URL input field. + * Used to trigger the authentication check when the user presses 'enter' after writing the + * password, or to throw the server test when the only field on screen is the URL input field. */ @Override public boolean onEditorAction(TextView inputField, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null && inputField.equals(mPasswordInput)) { + if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null && + inputField.equals(mPasswordInput)) { if (mOkButton.isEnabled()) { mOkButton.performClick(); } - } else if (actionId == EditorInfo.IME_ACTION_NEXT && inputField != null && inputField.equals(mHostUrlInput)) { - if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { + } else if (actionId == EditorInfo.IME_ACTION_NEXT && inputField != null && + inputField.equals(mHostUrlInput)) { + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()). + equals(mAuthTokenType)) { checkOcServer(); } } @@ -1622,8 +1680,10 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { final int x = (int) event.getX(); final int y = (int) event.getY(); final Rect bounds = rightDrawable.getBounds(); - if (x >= (view.getRight() - bounds.width() - fuzz) && x <= (view.getRight() - view.getPaddingRight() + fuzz) - && y >= (view.getPaddingTop() - fuzz) && y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) { + if ( x >= (view.getRight() - bounds.width() - fuzz) && + x <= (view.getRight() - view.getPaddingRight() + fuzz) && + y >= (view.getPaddingTop() - fuzz) && + y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) { return onDrawableTouch(event); } @@ -1672,7 +1732,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { @Override public boolean onTouchEvent(MotionEvent event) { - if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) && + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()). + equals(mAuthTokenType) && mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) { checkOcServer(); } @@ -1683,13 +1744,16 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { /** * Show untrusted cert dialog */ - public void showUntrustedCertDialog(X509Certificate x509Certificate, SslError error, SslErrorHandler handler) { + public void showUntrustedCertDialog( + X509Certificate x509Certificate, SslError error, SslErrorHandler handler + ) { // Show a dialog with the certificate info SslUntrustedCertDialog dialog = null; if (x509Certificate == null) { dialog = SslUntrustedCertDialog.newInstanceForEmptySslError(error, handler); } else { - dialog = SslUntrustedCertDialog.newInstanceForFullSslError(x509Certificate, error, handler); + dialog = SslUntrustedCertDialog. + newInstanceForFullSslError(x509Certificate, error, handler); } FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); @@ -1703,7 +1767,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { */ private void showUntrustedCertDialog(RemoteOperationResult result) { // Show a dialog with the certificate info - SslUntrustedCertDialog dialog = SslUntrustedCertDialog.newInstanceForFullSslError((CertificateCombinedException)result.getException()); + SslUntrustedCertDialog dialog = SslUntrustedCertDialog. + newInstanceForFullSslError((CertificateCombinedException)result.getException()); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.addToBackStack(null); @@ -1717,7 +1782,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { public void onSavedCertificate() { Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG); if (fd == null) { - // if SAML dialog is not shown, the SslDialog was shown due to an SSL error in the server check + // if SAML dialog is not shown, + // the SslDialog was shown due to an SSL error in the server check checkOcServer(); } } @@ -1767,7 +1833,9 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { @Override public void onServiceConnected(ComponentName component, IBinder service) { - if (component.equals(new ComponentName(AuthenticatorActivity.this, OperationsService.class))) { + if (component.equals( + new ComponentName(AuthenticatorActivity.this, OperationsService.class) + )) { //Log_OC.wtf(TAG, "Operations service connected"); mOperationsServiceBinder = (OperationsServiceBinder) service; @@ -1781,7 +1849,9 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { @Override public void onServiceDisconnected(ComponentName component) { - if (component.equals(new ComponentName(AuthenticatorActivity.this, OperationsService.class))) { + if (component.equals( + new ComponentName(AuthenticatorActivity.this, OperationsService.class) + )) { Log_OC.e(TAG, "Operations service crashed"); mOperationsServiceBinder = null; } @@ -1797,7 +1867,8 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { public void createAuthenticationDialog(WebView webView, HttpAuthHandler handler) { // Show a dialog with the certificate info - CredentialsDialogFragment dialog = CredentialsDialogFragment.newInstanceForCredentials(webView, handler); + CredentialsDialogFragment dialog = + CredentialsDialogFragment.newInstanceForCredentials(webView, handler); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.addToBackStack(null); @@ -1805,7 +1876,11 @@ SsoWebViewClientListener, OnSslUntrustedCertListener { dialog.show(ft, CREDENTIALS_DIALOG_TAG); if (!mIsFirstAuthAttempt) { - Toast.makeText(getApplicationContext(), getText(R.string.saml_authentication_wrong_pass), Toast.LENGTH_LONG).show(); + Toast.makeText( + getApplicationContext(), + getText(R.string.saml_authentication_wrong_pass), + Toast.LENGTH_LONG + ).show(); } else { mIsFirstAuthAttempt = false; } diff --git a/src/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/com/owncloud/android/datamodel/FileDataStorageManager.java index 7fb1604b..e7895e49 100644 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -151,7 +151,7 @@ public class FileDataStorageManager { public Vector getFolderImages(OCFile folder) { Vector ret = new Vector(); if (folder != null) { - // TODO better implementation, filtering in the access to database (if possible) instead of here + // TODO better implementation, filtering in the access to database instead of here Vector tmp = getFolderContent(folder); OCFile current = null; for (int i=0; i updatedFiles, Collection filesToRemove) { + public void saveFolder( + OCFile folder, Collection updatedFiles, Collection filesToRemove + ) { - Log_OC.d(TAG, "Saving folder " + folder.getRemotePath() + " with " + updatedFiles.size() + " children and " + filesToRemove.size() + " files to remove"); + Log_OC.d(TAG, "Saving folder " + folder.getRemotePath() + " with " + updatedFiles.size() + + " children and " + filesToRemove.size() + " files to remove"); - ArrayList operations = new ArrayList(updatedFiles.size()); + ArrayList operations = + new ArrayList(updatedFiles.size()); // prepare operations to insert or update files to save in the given folder for (OCFile file : updatedFiles) { ContentValues cv = new ContentValues(); cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); - cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); + cv.put( + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, + file.getModificationTimestampAtLastSyncForData() + ); cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); @@ -302,29 +312,40 @@ public class FileDataStorageManager { } else { // adding a new file - operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build()); + operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI). + withValues(cv).build()); } } // prepare operations to remove files in the given folder - String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?"; + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + + ProviderTableMeta.FILE_PATH + "=?"; String [] whereArgs = null; for (OCFile file : filesToRemove) { if (file.getParentId() == folder.getFileId()) { whereArgs = new String[]{mAccount.name, file.getRemotePath()}; //Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, "" + file.getFileId()); if (file.isFolder()) { - operations.add(ContentProviderOperation - .newDelete(ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, file.getFileId())).withSelection(where, whereArgs) - .build()); - // TODO remove local folder + operations.add(ContentProviderOperation.newDelete( + ContentUris.withAppendedId( + ProviderTableMeta.CONTENT_URI_DIR, file.getFileId() + ) + ).withSelection(where, whereArgs).build()); + + File localFolder = + new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); + if (localFolder.exists()) { + removeLocalFolder(localFolder); + } } else { - operations.add(ContentProviderOperation - .newDelete(ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, file.getFileId())).withSelection(where, whereArgs) - .build()); + operations.add(ContentProviderOperation.newDelete( + ContentUris.withAppendedId( + ProviderTableMeta.CONTENT_URI_FILE, file.getFileId() + ) + ).withSelection(where, whereArgs).build()); + if (file.isDown()) { new File(file.getStoragePath()).delete(); - // TODO move the deletion of local contents after success of deletions } } } @@ -333,9 +354,12 @@ public class FileDataStorageManager { // update metadata of folder ContentValues cv = new ContentValues(); cv.put(ProviderTableMeta.FILE_MODIFIED, folder.getModificationTimestamp()); - cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, folder.getModificationTimestampAtLastSyncForData()); + cv.put( + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, + folder.getModificationTimestampAtLastSyncForData() + ); cv.put(ProviderTableMeta.FILE_CREATION, folder.getCreationTimestamp()); - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, 0); // FileContentProvider calculates the right size + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, 0); cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, folder.getMimetype()); cv.put(ProviderTableMeta.FILE_NAME, folder.getFileName()); cv.put(ProviderTableMeta.FILE_PARENT, folder.getParentId()); @@ -409,18 +433,21 @@ public class FileDataStorageManager { // Log_OC.d(TAG, "Updating size of " + id); // if (getContentResolver() != null) { // getContentResolver().update(ProviderTableMeta.CONTENT_URI_DIR, -// new ContentValues(), // won't be used, but cannot be null; crashes in KLP +// new ContentValues(), + // won't be used, but cannot be null; crashes in KLP // ProviderTableMeta._ID + "=?", // new String[] { String.valueOf(id) }); // } else { // try { // getContentProviderClient().update(ProviderTableMeta.CONTENT_URI_DIR, -// new ContentValues(), // won't be used, but cannot be null; crashes in KLP +// new ContentValues(), + // won't be used, but cannot be null; crashes in KLP // ProviderTableMeta._ID + "=?", // new String[] { String.valueOf(id) }); // // } catch (RemoteException e) { -// Log_OC.e(TAG, "Exception in update of folder size through compatibility patch " + e.getMessage()); +// Log_OC.e( +// TAG, "Exception in update of folder size through compatibility patch " + e.getMessage()); // } // } // } else { @@ -437,9 +464,12 @@ public class FileDataStorageManager { } else { if (removeDBData) { - //Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId()); - Uri file_uri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, file.getFileId()); - String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?"; + Uri file_uri = ContentUris.withAppendedId( + ProviderTableMeta.CONTENT_URI_FILE, + file.getFileId() + ); + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + + ProviderTableMeta.FILE_PATH + "=?"; String [] whereArgs = new String[]{mAccount.name, file.getRemotePath()}; int deleted = 0; if (getContentProviderClient() != null) { @@ -481,8 +511,10 @@ public class FileDataStorageManager { } private boolean removeFolderInDb(OCFile folder) { - Uri folder_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, ""+ folder.getFileId()); // URI for recursive deletion - String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?"; + Uri folder_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, "" + + folder.getFileId()); // URI for recursive deletion + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + + ProviderTableMeta.FILE_PATH + "=?"; String [] whereArgs = new String[]{mAccount.name, folder.getRemotePath()}; int deleted = 0; if (getContentProviderClient() != null) { @@ -552,43 +584,67 @@ public class FileDataStorageManager { public void moveFolder(OCFile folder, String newPath) { // TODO check newPath - if (folder != null && folder.isFolder() && folder.fileExists() && !OCFile.ROOT_PATH.equals(folder.getFileName())) { + if ( folder != null && folder.isFolder() && + folder.fileExists() && !OCFile.ROOT_PATH.equals(folder.getFileName()) + ) { /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir') Cursor c = null; if (getContentProviderClient() != null) { try { - c = getContentProviderClient().query(ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", - new String[] { mAccount.name, folder.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); + c = getContentProviderClient().query ( + ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + + ProviderTableMeta.FILE_PATH + " LIKE ? ", + new String[] { mAccount.name, folder.getRemotePath() + "%" }, + ProviderTableMeta.FILE_PATH + " ASC " + ); } catch (RemoteException e) { Log_OC.e(TAG, e.getMessage()); } } else { - c = getContentResolver().query(ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", - new String[] { mAccount.name, folder.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); + c = getContentResolver().query ( + ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + + ProviderTableMeta.FILE_PATH + " LIKE ? ", + new String[] { mAccount.name, folder.getRemotePath() + "%" }, + ProviderTableMeta.FILE_PATH + " ASC " + ); } /// 2. prepare a batch of update operations to change all the descendants - ArrayList operations = new ArrayList(c.getCount()); + ArrayList operations = + new ArrayList(c.getCount()); int lengthOfOldPath = folder.getRemotePath().length(); String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name); int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath; if (c.moveToFirst()) { do { - ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object + ContentValues cv = new ContentValues(); // keep the constructor in the loop OCFile child = createFileInstance(c); - cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath)); - if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) { - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath)); + cv.put( + ProviderTableMeta.FILE_PATH, + newPath + child.getRemotePath().substring(lengthOfOldPath) + ); + if ( child.getStoragePath() != null && + child.getStoragePath().startsWith(defaultSavePath) ) { + cv.put( + ProviderTableMeta.FILE_STORAGE_PATH, + defaultSavePath + newPath + + child.getStoragePath().substring(lengthOfOldStoragePath) + ); } - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + operations.add( + ContentProviderOperation. + newUpdate(ProviderTableMeta.CONTENT_URI). withValues(cv). - withSelection( ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(child.getFileId()) }) - .build()); + withSelection( + ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(child.getFileId()) } + ). + build() + ); } while (c.moveToNext()); } c.close(); @@ -603,10 +659,12 @@ public class FileDataStorageManager { } } catch (OperationApplicationException e) { - Log_OC.e(TAG, "Fail to update descendants of " + folder.getFileId() + " in database", e); + Log_OC.e(TAG, "Fail to update descendants of " + + folder.getFileId() + " in database", e); } catch (RemoteException e) { - Log_OC.e(TAG, "Fail to update desendants of " + folder.getFileId() + " in database", e); + Log_OC.e(TAG, "Fail to update desendants of " + + folder.getFileId() + " in database", e); } } @@ -851,7 +909,9 @@ public class FileDataStorageManager { file.setStoragePath(c.getString(c .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH))); if (file.getStoragePath() == null) { - // try to find existing file and bind it with current account; - with the current update of SynchronizeFolderOperation, this won't be necessary anymore after a full synchronization of the account + // try to find existing file and bind it with current account; + // with the current update of SynchronizeFolderOperation, this won't be + // necessary anymore after a full synchronization of the account File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); if (f.exists()) { file.setStoragePath(f.getAbsolutePath()); @@ -930,13 +990,16 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.OCSHARES_SHARED_DATE, share.getSharedDate()); cv.put(ProviderTableMeta.OCSHARES_EXPIRATION_DATE, share.getExpirationDate()); cv.put(ProviderTableMeta.OCSHARES_TOKEN, share.getToken()); - cv.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, share.getSharedWithDisplayName()); + cv.put( + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, + share.getSharedWithDisplayName() + ); cv.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, share.isFolder() ? 1 : 0); cv.put(ProviderTableMeta.OCSHARES_USER_ID, share.getUserId()); cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getIdRemoteShared()); cv.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, mAccount.name); - if (shareExists(share.getIdRemoteShared())) { // for renamed files; no more delete and create + if (shareExists(share.getIdRemoteShared())) { // for renamed files overriden = true; if (getContentResolver() != null) { @@ -1038,7 +1101,9 @@ public class FileDataStorageManager { share.setIsFolder(c.getInt( c.getColumnIndex(ProviderTableMeta.OCSHARES_IS_DIRECTORY)) == 1 ? true : false); share.setUserId(c.getLong(c.getColumnIndex(ProviderTableMeta.OCSHARES_USER_ID))); - share.setIdRemoteShared(c.getLong(c.getColumnIndex(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED))); + share.setIdRemoteShared( + c.getLong(c.getColumnIndex(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED)) + ); } return share; @@ -1090,7 +1155,9 @@ public class FileDataStorageManager { } else { try { - getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs); + getContentProviderClient().update( + ProviderTableMeta.CONTENT_URI, cv, where, whereArgs + ); } catch (RemoteException e) { Log_OC.e(TAG, "Exception in cleanSharedFiles" + e.getMessage()); @@ -1102,7 +1169,8 @@ public class FileDataStorageManager { ContentValues cv = new ContentValues(); cv.put(ProviderTableMeta.FILE_SHARE_BY_LINK, false); cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, ""); - String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PARENT + "=?"; + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + + ProviderTableMeta.FILE_PARENT + "=?"; String [] whereArgs = new String[] { mAccount.name , String.valueOf(folder.getFileId()) }; if (getContentResolver() != null) { @@ -1110,7 +1178,9 @@ public class FileDataStorageManager { } else { try { - getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs); + getContentProviderClient().update( + ProviderTableMeta.CONTENT_URI, cv, where, whereArgs + ); } catch (RemoteException e) { Log_OC.e(TAG, "Exception in cleanSharedFilesInFolder " + e.getMessage()); @@ -1127,7 +1197,9 @@ public class FileDataStorageManager { } else { try { - getContentProviderClient().delete(ProviderTableMeta.CONTENT_URI_SHARE, where, whereArgs); + getContentProviderClient().delete( + ProviderTableMeta.CONTENT_URI_SHARE, where, whereArgs + ); } catch (RemoteException e) { Log_OC.e(TAG, "Exception in cleanShares" + e.getMessage()); @@ -1138,7 +1210,8 @@ public class FileDataStorageManager { public void saveShares(Collection shares) { cleanShares(); if (shares != null) { - ArrayList operations = new ArrayList(shares.size()); + ArrayList operations = + new ArrayList(shares.size()); // prepare operations to insert or update files to save in the given folder for (OCShare share : shares) { @@ -1152,7 +1225,10 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.OCSHARES_SHARED_DATE, share.getSharedDate()); cv.put(ProviderTableMeta.OCSHARES_EXPIRATION_DATE, share.getExpirationDate()); cv.put(ProviderTableMeta.OCSHARES_TOKEN, share.getToken()); - cv.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, share.getSharedWithDisplayName()); + cv.put( + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, + share.getSharedWithDisplayName() + ); cv.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, share.isFolder() ? 1 : 0); cv.put(ProviderTableMeta.OCSHARES_USER_ID, share.getUserId()); cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getIdRemoteShared()); @@ -1160,15 +1236,23 @@ public class FileDataStorageManager { if (shareExists(share.getIdRemoteShared())) { // updating an existing file - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI_SHARE). + operations.add( + ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI_SHARE). withValues(cv). - withSelection( ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?", - new String[] { String.valueOf(share.getIdRemoteShared()) }) - .build()); + withSelection( + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?", + new String[] { String.valueOf(share.getIdRemoteShared()) } + ). + build() + ); } else { // adding a new file - operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI_SHARE).withValues(cv).build()); + operations.add( + ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI_SHARE). + withValues(cv). + build() + ); } } @@ -1176,10 +1260,13 @@ public class FileDataStorageManager { if (operations.size() > 0) { @SuppressWarnings("unused") ContentProviderResult[] results = null; - Log_OC.d(TAG, "Sending " + operations.size() + " operations to FileContentProvider"); + Log_OC.d(TAG, "Sending " + operations.size() + + " operations to FileContentProvider"); try { if (getContentResolver() != null) { - results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); + results = getContentResolver().applyBatch( + MainApp.getAuthority(), operations + ); } else { results = getContentProviderClient().applyBatch(operations); @@ -1200,13 +1287,17 @@ public class FileDataStorageManager { cleanSharedFiles(); if (sharedFiles != null) { - ArrayList operations = new ArrayList(sharedFiles.size()); + ArrayList operations = + new ArrayList(sharedFiles.size()); // prepare operations to insert or update files to save in the given folder for (OCFile file : sharedFiles) { ContentValues cv = new ContentValues(); cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); - cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); + cv.put( + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, + file.getModificationTimestampAtLastSyncForData() + ); cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); @@ -1218,27 +1309,40 @@ public class FileDataStorageManager { } cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData()); + cv.put( + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, + file.getLastSyncDateForData() + ); cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0); cv.put(ProviderTableMeta.FILE_ETAG, file.getEtag()); cv.put(ProviderTableMeta.FILE_SHARE_BY_LINK, file.isShareByLink() ? 1 : 0); cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, file.getPublicLink()); cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions()); cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId()); - cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail() ? 1 : 0); + cv.put( + ProviderTableMeta.FILE_UPDATE_THUMBNAIL, + file.needsUpdateThumbnail() ? 1 : 0 + ); boolean existsByPath = fileExists(file.getRemotePath()); if (existsByPath || fileExists(file.getFileId())) { // updating an existing file - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + operations.add( + ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). withValues(cv). - withSelection( ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }) - .build()); + withSelection( + ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(file.getFileId()) } + ).build() + ); } else { // adding a new file - operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build()); + operations.add( + ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI). + withValues(cv). + build() + ); } } @@ -1246,10 +1350,13 @@ public class FileDataStorageManager { if (operations.size() > 0) { @SuppressWarnings("unused") ContentProviderResult[] results = null; - Log_OC.d(TAG, "Sending " + operations.size() + " operations to FileContentProvider"); + Log_OC.d(TAG, "Sending " + operations.size() + + " operations to FileContentProvider"); try { if (getContentResolver() != null) { - results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); + results = getContentResolver().applyBatch( + MainApp.getAuthority(), operations + ); } else { results = getContentProviderClient().applyBatch(operations); @@ -1268,7 +1375,8 @@ public class FileDataStorageManager { public void removeShare(OCShare share){ Uri share_uri = ProviderTableMeta.CONTENT_URI_SHARE; - String where = ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?"; + String where = ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?" + " AND " + + ProviderTableMeta.FILE_PATH + "=?"; String [] whereArgs = new String[]{mAccount.name, share.getPath()}; if (getContentProviderClient() != null) { try { @@ -1325,7 +1433,10 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.OCSHARES_SHARED_DATE, share.getSharedDate()); cv.put(ProviderTableMeta.OCSHARES_EXPIRATION_DATE, share.getExpirationDate()); cv.put(ProviderTableMeta.OCSHARES_TOKEN, share.getToken()); - cv.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, share.getSharedWithDisplayName()); + cv.put( + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, + share.getSharedWithDisplayName() + ); cv.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, share.isFolder() ? 1 : 0); cv.put(ProviderTableMeta.OCSHARES_USER_ID, share.getUserId()); cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getIdRemoteShared()); @@ -1334,7 +1445,8 @@ public class FileDataStorageManager { /* if (shareExists(share.getIdRemoteShared())) { // updating an existing share resource - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI_SHARE). + operations.add( + ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI_SHARE). withValues(cv). withSelection( ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?", new String[] { String.valueOf(share.getIdRemoteShared()) }) @@ -1343,7 +1455,11 @@ public class FileDataStorageManager { } else { */ // adding a new share resource - operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI_SHARE).withValues(cv).build()); + operations.add( + ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI_SHARE). + withValues(cv). + build() + ); //} } } @@ -1372,18 +1488,23 @@ public class FileDataStorageManager { } - private ArrayList prepareRemoveSharesInFolder(OCFile folder, ArrayList preparedOperations) { + private ArrayList prepareRemoveSharesInFolder( + OCFile folder, ArrayList preparedOperations + ) { if (folder != null) { - String where = ProviderTableMeta.OCSHARES_PATH + "=?" + " AND " + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?"; + String where = ProviderTableMeta.OCSHARES_PATH + "=?" + " AND " + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?"; String [] whereArgs = new String[]{ "", mAccount.name }; Vector files = getFolderContent(folder); for (OCFile file : files) { whereArgs[0] = file.getRemotePath(); - preparedOperations.add(ContentProviderOperation.newDelete(ProviderTableMeta.CONTENT_URI_SHARE) - .withSelection(where, whereArgs) - .build()); + preparedOperations.add( + ContentProviderOperation.newDelete(ProviderTableMeta.CONTENT_URI_SHARE). + withSelection(where, whereArgs). + build() + ); } } return preparedOperations; diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index bd8e0d4e..d568ea3c 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -26,6 +26,8 @@ import com.owncloud.android.lib.common.utils.Log_OC; import java.io.File; +import third_parties.daveKoeller.AlphanumComparator; + public class OCFile implements Parcelable, Comparable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @@ -458,7 +460,7 @@ public class OCFile implements Parcelable, Comparable { } else if (another.isFolder()) { return 1; } - return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); + return new AlphanumComparator().compare(this, another); } @Override diff --git a/src/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/src/com/owncloud/android/datamodel/ThumbnailsCacheManager.java new file mode 100644 index 00000000..e75404ef --- /dev/null +++ b/src/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -0,0 +1,265 @@ +/* ownCloud Android client application + * Copyright (C) 2012-2014 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.datamodel; + +import java.io.File; +import java.lang.ref.WeakReference; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.ThumbnailUtils; +import android.os.AsyncTask; +import android.util.TypedValue; +import android.widget.ImageView; + +import com.owncloud.android.MainApp; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.adapter.DiskLruImageCache; +import com.owncloud.android.utils.BitmapUtils; +import com.owncloud.android.utils.DisplayUtils; + +/** + * Manager for concurrent access to thumbnails cache. + * + * @author Tobias Kaminsky + * @author David A. Velasco + */ +public class ThumbnailsCacheManager { + + private static final String TAG = ThumbnailsCacheManager.class.getSimpleName(); + + private static final String CACHE_FOLDER = "thumbnailCache"; + + private static final Object mThumbnailsDiskCacheLock = new Object(); + private static DiskLruImageCache mThumbnailCache = null; + private static boolean mThumbnailCacheStarting = true; + + private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB + private static final CompressFormat mCompressFormat = CompressFormat.JPEG; + private static final int mCompressQuality = 70; + + public static Bitmap mDefaultImg = + BitmapFactory.decodeResource( + MainApp.getAppContext().getResources(), + DisplayUtils.getResourceId("image/png", "default.png") + ); + + + public static class InitDiskCacheTask extends AsyncTask { + @Override + protected Void doInBackground(File... params) { + synchronized (mThumbnailsDiskCacheLock) { + mThumbnailCacheStarting = true; + if (mThumbnailCache == null) { + try { + // Check if media is mounted or storage is built-in, if so, + // try and use external cache dir; otherwise use internal cache dir + final String cachePath = + MainApp.getAppContext().getExternalCacheDir().getPath() + + File.separator + CACHE_FOLDER; + Log_OC.d(TAG, "create dir: " + cachePath); + final File diskCacheDir = new File(cachePath); + mThumbnailCache = new DiskLruImageCache( + diskCacheDir, + DISK_CACHE_SIZE, + mCompressFormat, + mCompressQuality + ); + } catch (Exception e) { + Log_OC.d(TAG, "Thumbnail cache could not be opened ", e); + mThumbnailCache = null; + } + } + mThumbnailCacheStarting = false; // Finished initialization + mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads + } + return null; + } + } + + + public static void addBitmapToCache(String key, Bitmap bitmap) { + synchronized (mThumbnailsDiskCacheLock) { + if (mThumbnailCache != null) { + mThumbnailCache.put(key, bitmap); + } + } + } + + + public static Bitmap getBitmapFromDiskCache(String key) { + synchronized (mThumbnailsDiskCacheLock) { + // Wait while disk cache is started from background thread + while (mThumbnailCacheStarting) { + try { + mThumbnailsDiskCacheLock.wait(); + } catch (InterruptedException e) {} + } + if (mThumbnailCache != null) { + return (Bitmap) mThumbnailCache.getBitmap(key); + } + } + return null; + } + + + public static boolean cancelPotentialWork(OCFile file, ImageView imageView) { + final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final OCFile bitmapData = bitmapWorkerTask.mFile; + // If bitmapData is not yet set or it differs from the new data + if (bitmapData == null || bitmapData != file) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; + } + + public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + public static class ThumbnailGenerationTask extends AsyncTask { + private final WeakReference mImageViewReference; + private OCFile mFile; + private FileDataStorageManager mStorageManager; + + public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager) { + // Use a WeakReference to ensure the ImageView can be garbage collected + mImageViewReference = new WeakReference(imageView); + if (storageManager == null) + throw new IllegalArgumentException("storageManager must not be NULL"); + mStorageManager = storageManager; + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(OCFile... params) { + Bitmap thumbnail = null; + + try { + mFile = params[0]; + final String imageKey = String.valueOf(mFile.getRemoteId()); + + // Check disk cache in background thread + thumbnail = getBitmapFromDiskCache(imageKey); + + // Not found in disk cache + if (thumbnail == null || mFile.needsUpdateThumbnail()) { + // Converts dp to pixel + Resources r = MainApp.getAppContext().getResources(); + int px = (int) Math.round(TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 150, r.getDisplayMetrics() + )); + + if (mFile.isDown()){ + Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile( + mFile.getStoragePath(), px, px); + + if (bitmap != null) { + thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px); + + // Add thumbnail to cache + addBitmapToCache(imageKey, thumbnail); + + mFile.setNeedsUpdateThumbnail(false); + mStorageManager.saveFile(mFile); + } + + } + } + + } catch (Throwable t) { + // the app should never break due to a problem with thumbnails + Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t); + if (t instanceof OutOfMemoryError) { + System.gc(); + } + } + + return thumbnail; + } + + protected void onPostExecute(Bitmap bitmap){ + if (isCancelled()) { + bitmap = null; + } + + if (mImageViewReference != null && bitmap != null) { + final ImageView imageView = mImageViewReference.get(); + final ThumbnailGenerationTask bitmapWorkerTask = + getBitmapWorkerTask(imageView); + if (this == bitmapWorkerTask && imageView != null) { + if (imageView.getTag().equals(mFile.getFileId())) { + imageView.setImageBitmap(bitmap); + } + } + } + } + } + + + public static class AsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncDrawable( + Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask + ) { + + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference(bitmapWorkerTask); + } + + public ThumbnailGenerationTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + + /** + * Remove from cache the remoteId passed + * @param fileRemoteId: remote id of mFile passed + */ + public static void removeFileFromCache(String fileRemoteId){ + synchronized (mThumbnailsDiskCacheLock) { + if (mThumbnailCache != null) { + mThumbnailCache.removeKey(fileRemoteId); + } + mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads + } + } + +} diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index cb0f12ce..fdc35f8d 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -398,6 +398,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis file.setMimetype(mCurrentDownload.getMimeType()); file.setStoragePath(mCurrentDownload.getSavePath()); file.setFileLength((new File(mCurrentDownload.getSavePath()).length())); + file.setRemoteId(mCurrentDownload.getFile().getRemoteId()); mStorageManager.saveFile(file); } diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index 5b7185ac..ad2a2cbe 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -630,7 +630,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe // coincidence; nothing else is needed, the storagePath is right // in the instance returned by mCurrentUpload.getFile() } - + file.setNeedsUpdateThumbnail(true); mStorageManager.saveFile(file); } @@ -641,6 +641,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe file.setModificationTimestamp(remoteFile.getModifiedTimestamp()); file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp()); // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available + file.setRemoteId(remoteFile.getRemoteId()); } private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, diff --git a/src/com/owncloud/android/operations/CreateFolderOperation.java b/src/com/owncloud/android/operations/CreateFolderOperation.java index b0e7ed9d..4df8b3df 100644 --- a/src/com/owncloud/android/operations/CreateFolderOperation.java +++ b/src/com/owncloud/android/operations/CreateFolderOperation.java @@ -84,21 +84,36 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper } } - /** * Save new directory in local database */ public void saveFolderInDB() { - OCFile newDir = new OCFile(mRemotePath); - newDir.setMimetype("DIR"); - long parentId = getStorageManager().getFileByPath(FileStorageUtils.getParentPath(mRemotePath)).getFileId(); - newDir.setParentId(parentId); - newDir.setModificationTimestamp(System.currentTimeMillis()); - getStorageManager().saveFile(newDir); + if (mCreateFullPath && getStorageManager(). + getFileByPath(FileStorageUtils.getParentPath(mRemotePath)) == null){// When parent + // of remote path + // is not created + String[] subFolders = mRemotePath.split("/"); + String composedRemotePath = "/"; - Log_OC.d(TAG, "Create directory " + mRemotePath + " in Database"); + // For each antecesor folders create them recursively + for (int i=0; i mForgottenLocalFiles; /** 'True' means that this operation is part of a full account synchronization */ private boolean mSyncFullAccount; - /** 'True' means that Share resources bound to the files into the folder should be refreshed also */ + /** 'True' means that Share resources bound to the files into should be refreshed also */ private boolean mIsShareSupported; - /** 'True' means that the remote folder changed from last synchronization and should be fetched */ + /** 'True' means that the remote folder changed and should be fetched */ private boolean mRemoteFolderChanged; /** 'True' means that Etag will be ignored */ @@ -116,11 +121,14 @@ public class SynchronizeFolderOperation extends RemoteOperation { /** * Creates a new instance of {@link SynchronizeFolderOperation}. * - * @param remoteFolderPath Remote folder to synchronize. + * @param folder Folder to synchronize. * @param currentSyncTime Time stamp for the synchronization process in progress. - * @param localFolderId Identifier in the local database of the folder to synchronize. - * @param updateFolderProperties 'True' means that the properties of the folder should be updated also, not just its content. - * @param syncFullAccount 'True' means that this operation is part of a full account synchronization. + * @param syncFullAccount 'True' means that this operation is part of a full account + * synchronization. + * @param isShareSupported 'True' means that the server supports the sharing API. + * @param ignoreEtag 'True' means that the content of the remote folder should + * be fetched and updated even though the 'eTag' did not + * change. * @param dataStorageManager Interface with the local database. * @param account ownCloud account where the folder is located. * @param context Application context. @@ -159,7 +167,8 @@ public class SynchronizeFolderOperation extends RemoteOperation { } /** - * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is complete. + * Returns the list of files and folders contained in the synchronized folder, + * if called after synchronization is complete. * * @return List of files and folders contained in the synchronized folder. */ @@ -194,7 +203,9 @@ public class SynchronizeFolderOperation extends RemoteOperation { } if (!mSyncFullAccount) { - sendLocalBroadcast(EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result); + sendLocalBroadcast( + EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result + ); } if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) { @@ -202,7 +213,9 @@ public class SynchronizeFolderOperation extends RemoteOperation { } if (!mSyncFullAccount) { - sendLocalBroadcast(EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result); + sendLocalBroadcast( + EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result + ); } return result; @@ -235,12 +248,14 @@ public class SynchronizeFolderOperation extends RemoteOperation { if (!mIgnoreETag) { // check if remote and local folder are different - mRemoteFolderChanged = !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag())); + mRemoteFolderChanged = + !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag())); } result = new RemoteOperationResult(ResultCode.OK); - Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + (mRemoteFolderChanged ? "changed" : "not changed")); + Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + + (mRemoteFolderChanged ? "changed" : "not changed")); } else { // check failed @@ -248,9 +263,11 @@ public class SynchronizeFolderOperation extends RemoteOperation { removeLocalFolder(); } if (result.isException()) { - Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + result.getLogMessage(), result.getException()); + Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + + result.getLogMessage(), result.getException()); } else { - Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + result.getLogMessage()); + Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + + result.getLogMessage()); } } @@ -267,7 +284,8 @@ public class SynchronizeFolderOperation extends RemoteOperation { if (result.isSuccess()) { synchronizeData(result.getData(), client); if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { - result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); + // should be a different result code, but will do the job } } else { if (result.getCode() == ResultCode.FILE_NOT_FOUND) @@ -281,7 +299,13 @@ public class SynchronizeFolderOperation extends RemoteOperation { private void removeLocalFolder() { if (mStorageManager.fileExists(mLocalFolder.getFileId())) { String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); - mStorageManager.removeFolder(mLocalFolder, true, (mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath))); + mStorageManager.removeFolder( + mLocalFolder, + true, + ( mLocalFolder.isDown() && + mLocalFolder.getStoragePath().startsWith(currentSavePath) + ) + ); } } @@ -296,7 +320,7 @@ public class SynchronizeFolderOperation extends RemoteOperation { * * @param client Client instance to the remote server where the data were * retrieved. - * @return 'True' when any change was made in the local data, 'false' otherwise. + * @return 'True' when any change was made in the local data, 'false' otherwise */ private void synchronizeData(ArrayList folderAndFiles, OwnCloudClient client) { // get 'fresh data' from the database @@ -307,7 +331,8 @@ public class SynchronizeFolderOperation extends RemoteOperation { remoteFolder.setParentId(mLocalFolder.getParentId()); remoteFolder.setFileId(mLocalFolder.getFileId()); - Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + " changed - starting update of local data "); + Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + + " changed - starting update of local data "); List updatedFiles = new Vector(folderAndFiles.size() - 1); List filesToSyncContents = new Vector(); @@ -327,30 +352,38 @@ public class SynchronizeFolderOperation extends RemoteOperation { remoteFile.setParentId(mLocalFolder.getFileId()); /// retrieve local data for the read file - //localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath()); + // localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath()); localFile = localFilesMap.remove(remoteFile.getRemotePath()); - /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in the server side) + /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server) remoteFile.setLastSyncDateForProperties(mCurrentSyncTime); if (localFile != null) { // some properties of local state are kept unmodified remoteFile.setFileId(localFile.getFileId()); remoteFile.setKeepInSync(localFile.keepInSync()); remoteFile.setLastSyncDateForData(localFile.getLastSyncDateForData()); - remoteFile.setModificationTimestampAtLastSyncForData(localFile.getModificationTimestampAtLastSyncForData()); + remoteFile.setModificationTimestampAtLastSyncForData( + localFile.getModificationTimestampAtLastSyncForData() + ); remoteFile.setStoragePath(localFile.getStoragePath()); - remoteFile.setEtag(localFile.getEtag()); // eTag will not be updated unless contents are synchronized (Synchronize[File|Folder]Operation with remoteFile as parameter) + // eTag will not be updated unless contents are synchronized + // (Synchronize[File|Folder]Operation with remoteFile as parameter) + remoteFile.setEtag(localFile.getEtag()); if (remoteFile.isFolder()) { - remoteFile.setFileLength(localFile.getFileLength()); // TODO move operations about size of folders to FileContentProvider + remoteFile.setFileLength(localFile.getFileLength()); + // TODO move operations about size of folders to FileContentProvider } remoteFile.setPublicLink(localFile.getPublicLink()); remoteFile.setShareByLink(localFile.isShareByLink()); } else { - remoteFile.setEtag(""); // remote eTag will not be updated unless contents are synchronized (Synchronize[File|Folder]Operation with remoteFile as parameter) + // remote eTag will not be updated unless contents are synchronized + // (Synchronize[File|Folder]Operation with remoteFile as parameter) + remoteFile.setEtag(""); } /// check and fix, if needed, local storage path - checkAndFixForeignStoragePath(remoteFile); // fixing old policy - now local files must be copied into the ownCloud local folder + checkAndFixForeignStoragePath(remoteFile); // policy - local files are COPIED + // into the ownCloud local folder; searchForLocalFileInDefaultPath(remoteFile); // legacy /// prepare content synchronization for kept-in-sync files @@ -368,7 +401,7 @@ public class SynchronizeFolderOperation extends RemoteOperation { updatedFiles.add(remoteFile); } - // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) + // save updated contents in local database mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values()); // request for the synchronization of file contents AFTER saving current remote properties @@ -378,27 +411,32 @@ public class SynchronizeFolderOperation extends RemoteOperation { } /** - * Performs a list of synchronization operations, determining if a download or upload is needed or - * if exists conflict due to changes both in local and remote contents of the each file. + * Performs a list of synchronization operations, determining if a download or upload is needed + * or if exists conflict due to changes both in local and remote contents of the each file. * - * If download or upload is needed, request the operation to the corresponding service and goes on. + * If download or upload is needed, request the operation to the corresponding service and goes + * on. * * @param filesToSyncContents Synchronization operations to execute. * @param client Interface to the remote ownCloud server. */ - private void startContentSynchronizations(List filesToSyncContents, OwnCloudClient client) { + private void startContentSynchronizations( + List filesToSyncContents, OwnCloudClient client + ) { RemoteOperationResult contentsResult = null; for (SynchronizeFileOperation op: filesToSyncContents) { - contentsResult = op.execute(mStorageManager, mContext); // returns without waiting for upload or download finishes + contentsResult = op.execute(mStorageManager, mContext); // async if (!contentsResult.isSuccess()) { if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { mConflictsFound++; } else { mFailsInFavouritesFound++; if (contentsResult.getException() != null) { - Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); + Log_OC.e(TAG, "Error while synchronizing favourites : " + + contentsResult.getLogMessage(), contentsResult.getException()); } else { - Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage()); + Log_OC.e(TAG, "Error while synchronizing favourites : " + + contentsResult.getLogMessage()); } } } // won't let these fails break the synchronization process @@ -430,11 +468,11 @@ public class SynchronizeFolderOperation extends RemoteOperation { /** - * Checks the storage path of the OCFile received as parameter. If it's out of the local ownCloud folder, - * tries to copy the file inside it. + * Checks the storage path of the OCFile received as parameter. + * If it's out of the local ownCloud folder, tries to copy the file inside it. * - * If the copy fails, the link to the local file is nullified. The account of forgotten files is kept in - * {@link #mForgottenLocalFiles} + * If the copy fails, the link to the local file is nullified. The account of forgotten + * files is kept in {@link #mForgottenLocalFiles} *) * @param file File to check and fix. */ @@ -456,7 +494,9 @@ public class SynchronizeFolderOperation extends RemoteOperation { File expectedParent = expectedFile.getParentFile(); expectedParent.mkdirs(); if (!expectedParent.isDirectory()) { - throw new IOException("Unexpected error: parent directory could not be created"); + throw new IOException( + "Unexpected error: parent directory could not be created" + ); } expectedFile.createNewFile(); if (!expectedFile.isFile()) { @@ -480,12 +520,14 @@ public class SynchronizeFolderOperation extends RemoteOperation { try { if (in != null) in.close(); } catch (Exception e) { - Log_OC.d(TAG, "Weird exception while closing input stream for " + storagePath + " (ignoring)", e); + Log_OC.d(TAG, "Weird exception while closing input stream for " + + storagePath + " (ignoring)", e); } try { if (out != null) out.close(); } catch (Exception e) { - Log_OC.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e); + Log_OC.d(TAG, "Weird exception while closing output stream for " + + expectedPath + " (ignoring)", e); } } } @@ -497,7 +539,8 @@ public class SynchronizeFolderOperation extends RemoteOperation { RemoteOperationResult result = null; // remote request - GetRemoteSharesForFileOperation operation = new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true); + GetRemoteSharesForFileOperation operation = + new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true); result = operation.execute(client); if (result.isSuccess()) { @@ -532,13 +575,17 @@ public class SynchronizeFolderOperation extends RemoteOperation { /** - * Sends a message to any application component interested in the progress of the synchronization. + * Sends a message to any application component interested in the progress + * of the synchronization. * * @param event - * @param dirRemotePath Remote path of a folder that was just synchronized (with or without success) + * @param dirRemotePath Remote path of a folder that was just synchronized + * (with or without success) * @param result */ - private void sendLocalBroadcast(String event, String dirRemotePath, RemoteOperationResult result) { + private void sendLocalBroadcast( + String event, String dirRemotePath, RemoteOperationResult result + ) { Log_OC.d(TAG, "Send broadcast " + event); Intent intent = new Intent(event); intent.putExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME, mAccount.name); diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 9792f3e9..a94454ce 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -22,13 +22,12 @@ import java.util.ArrayList; import java.util.HashMap; import com.owncloud.android.R; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.shares.ShareType; - - import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; @@ -165,13 +164,14 @@ public class FileContentProvider extends ContentProvider { int count = 0; switch (mUriMatcher.match(uri)) { case SINGLE_FILE: - /*Cursor c = query(db, uri, null, where, whereArgs, null); - String remotePath = "(unexisting)"; + Cursor c = query(db, uri, null, where, whereArgs, null); + String remoteId = ""; if (c != null && c.moveToFirst()) { - remotePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)); + remoteId = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID)); + //ThumbnailsCacheManager.removeFileFromCache(remoteId); } - Log_OC.d(TAG, "Removing FILE " + remotePath); - */ + Log_OC.d(TAG, "Removing FILE " + remoteId); + count = db.delete(ProviderTableMeta.FILE_TABLE_NAME, ProviderTableMeta._ID + "=" @@ -197,16 +197,28 @@ public class FileContentProvider extends ContentProvider { Cursor children = query(uri, null, null, null, null); if (children != null && children.moveToFirst()) { long childId; - boolean isDir; + boolean isDir; //String remotePath; while (!children.isAfterLast()) { childId = children.getLong(children.getColumnIndex(ProviderTableMeta._ID)); - isDir = "DIR".equals(children.getString(children.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE))); + isDir = "DIR".equals(children.getString( + children.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE) + )); //remotePath = children.getString(children.getColumnIndex(ProviderTableMeta.FILE_PATH)); if (isDir) { - count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId), null, null); + count += delete( + db, + ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId), + null, + null + ); } else { - count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId), null, null); + count += delete( + db, + ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId), + null, + null + ); } children.moveToNext(); } @@ -240,7 +252,6 @@ public class FileContentProvider extends ContentProvider { } return count; } - @Override public String getType(Uri uri) { @@ -257,7 +268,6 @@ public class FileContentProvider extends ContentProvider { @Override public Uri insert(Uri uri, ContentValues values) { - //Log_OC.d(TAG, "Inserting " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this); Uri newUri = null; SQLiteDatabase db = mDbHelper.getWritableDatabase(); db.beginTransaction(); @@ -277,23 +287,31 @@ public class FileContentProvider extends ContentProvider { case SINGLE_FILE: String remotePath = values.getAsString(ProviderTableMeta.FILE_PATH); String accountName = values.getAsString(ProviderTableMeta.FILE_ACCOUNT_OWNER); - String[] projection = new String[] {ProviderTableMeta._ID, ProviderTableMeta.FILE_PATH, ProviderTableMeta.FILE_ACCOUNT_OWNER }; - String where = ProviderTableMeta.FILE_PATH + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?"; + String[] projection = new String[] { + ProviderTableMeta._ID, ProviderTableMeta.FILE_PATH, + ProviderTableMeta.FILE_ACCOUNT_OWNER + }; + String where = ProviderTableMeta.FILE_PATH + "=? AND " + + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?"; String[] whereArgs = new String[] {remotePath, accountName}; Cursor doubleCheck = query(db, uri, projection, where, whereArgs, null); - if (doubleCheck == null || !doubleCheck.moveToFirst()) { // ugly patch; serious refactorization is needed to reduce work in FileDataStorageManager and bring it to FileContentProvider + // ugly patch; serious refactorization is needed to reduce work in + // FileDataStorageManager and bring it to FileContentProvider + if (doubleCheck == null || !doubleCheck.moveToFirst()) { long rowId = db.insert(ProviderTableMeta.FILE_TABLE_NAME, null, values); if (rowId > 0) { - Uri insertedFileUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId); - //Log_OC.d(TAG, "Inserted " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this); + Uri insertedFileUri = + ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId); return insertedFileUri; } else { - //Log_OC.d(TAG, "Error while inserting " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this); throw new SQLException("ERROR " + uri); } } else { // file is already inserted; race condition, let's avoid a duplicated entry - Uri insertedFileUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, doubleCheck.getLong(doubleCheck.getColumnIndex(ProviderTableMeta._ID))); + Uri insertedFileUri = ContentUris.withAppendedId( + ProviderTableMeta.CONTENT_URI_FILE, + doubleCheck.getLong(doubleCheck.getColumnIndex(ProviderTableMeta._ID)) + ); doubleCheck.close(); return insertedFileUri; @@ -302,22 +320,35 @@ public class FileContentProvider extends ContentProvider { case SHARES: String path = values.getAsString(ProviderTableMeta.OCSHARES_PATH); String accountNameShare= values.getAsString(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER); - String[] projectionShare = new String[] {ProviderTableMeta._ID, ProviderTableMeta.OCSHARES_PATH, ProviderTableMeta.OCSHARES_ACCOUNT_OWNER }; - String whereShare = ProviderTableMeta.OCSHARES_PATH + "=? AND " + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?"; + String[] projectionShare = new String[] { + ProviderTableMeta._ID, ProviderTableMeta.OCSHARES_PATH, + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + }; + String whereShare = ProviderTableMeta.OCSHARES_PATH + "=? AND " + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?"; String[] whereArgsShare = new String[] {path, accountNameShare}; Uri insertedShareUri = null; - Cursor doubleCheckShare = query(db, uri, projectionShare, whereShare, whereArgsShare, null); - if (doubleCheckShare == null || !doubleCheckShare.moveToFirst()) { // ugly patch; serious refactorization is needed to reduce work in FileDataStorageManager and bring it to FileContentProvider + Cursor doubleCheckShare = + query(db, uri, projectionShare, whereShare, whereArgsShare, null); + // ugly patch; serious refactorization is needed to reduce work in + // FileDataStorageManager and bring it to FileContentProvider + if (doubleCheckShare == null || !doubleCheckShare.moveToFirst()) { long rowId = db.insert(ProviderTableMeta.OCSHARES_TABLE_NAME, null, values); if (rowId >0) { - insertedShareUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, rowId); + insertedShareUri = + ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, rowId); } else { throw new SQLException("ERROR " + uri); } } else { // file is already inserted; race condition, let's avoid a duplicated entry - insertedShareUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, doubleCheckShare.getLong(doubleCheckShare.getColumnIndex(ProviderTableMeta._ID))); + insertedShareUri = ContentUris.withAppendedId( + ProviderTableMeta.CONTENT_URI_SHARE, + doubleCheckShare.getLong( + doubleCheckShare.getColumnIndex(ProviderTableMeta._ID) + ) + ); doubleCheckShare.close(); } updateFilesTableAccordingToShareInsertion(db, uri, values); @@ -330,11 +361,17 @@ public class FileContentProvider extends ContentProvider { } - private void updateFilesTableAccordingToShareInsertion(SQLiteDatabase db, Uri uri, ContentValues shareValues) { + private void updateFilesTableAccordingToShareInsertion( + SQLiteDatabase db, Uri uri, ContentValues shareValues + ) { ContentValues fileValues = new ContentValues(); - fileValues.put(ProviderTableMeta.FILE_SHARE_BY_LINK, - ShareType.PUBLIC_LINK.getValue() == shareValues.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE)? 1 : 0); - String whereShare = ProviderTableMeta.FILE_PATH + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?"; + fileValues.put( + ProviderTableMeta.FILE_SHARE_BY_LINK, + ShareType.PUBLIC_LINK.getValue() == + shareValues.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE)? 1 : 0 + ); + String whereShare = ProviderTableMeta.FILE_PATH + "=? AND " + + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?"; String[] whereArgsShare = new String[] { shareValues.getAsString(ProviderTableMeta.OCSHARES_PATH), shareValues.getAsString(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER) @@ -362,7 +399,14 @@ public class FileContentProvider extends ContentProvider { @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder + ) { + Cursor result = null; SQLiteDatabase db = mDbHelper.getReadableDatabase(); db.beginTransaction(); @@ -375,7 +419,15 @@ public class FileContentProvider extends ContentProvider { return result; } - private Cursor query(SQLiteDatabase db, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + private Cursor query( + SQLiteDatabase db, + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder + ) { + SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder(); sqlQuery.setTables(ProviderTableMeta.FILE_TABLE_NAME); @@ -429,7 +481,6 @@ public class FileContentProvider extends ContentProvider { @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - //Log_OC.d(TAG, "Updating " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this); int count = 0; SQLiteDatabase db = mDbHelper.getWritableDatabase(); db.beginTransaction(); @@ -445,14 +496,24 @@ public class FileContentProvider extends ContentProvider { - private int update(SQLiteDatabase db, Uri uri, ContentValues values, String selection, String[] selectionArgs) { + private int update( + SQLiteDatabase db, + Uri uri, + ContentValues values, + String selection, + String[] selectionArgs + ) { switch (mUriMatcher.match(uri)) { case DIRECTORY: return 0; //updateFolderSize(db, selectionArgs[0]); case SHARES: - return db.update(ProviderTableMeta.OCSHARES_TABLE_NAME, values, selection, selectionArgs); + return db.update( + ProviderTableMeta.OCSHARES_TABLE_NAME, values, selection, selectionArgs + ); default: - return db.update(ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs); + return db.update( + ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs + ); } } @@ -510,8 +571,10 @@ public class FileContentProvider extends ContentProvider { */ @Override - public ContentProviderResult[] applyBatch (ArrayList operations) throws OperationApplicationException { - Log_OC.d("FileContentProvider", "applying batch in provider " + this + " (temporary: " + isTemporary() + ")" ); + public ContentProviderResult[] applyBatch (ArrayList operations) + throws OperationApplicationException { + Log_OC.d("FileContentProvider", "applying batch in provider " + this + + " (temporary: " + isTemporary() + ")" ); ContentProviderResult[] results = new ContentProviderResult[operations.size()]; int i=0; @@ -600,12 +663,13 @@ public class FileContentProvider extends ContentProvider { db.beginTransaction(); try { db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + - " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER " + - " DEFAULT 0"); + " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + + " INTEGER " + " DEFAULT 0"); // assume there are not local changes pending to upload db.execSQL("UPDATE " + ProviderTableMeta.FILE_TABLE_NAME + - " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + System.currentTimeMillis() + + " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + + System.currentTimeMillis() + " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL"); upgraded = true; @@ -619,11 +683,12 @@ public class FileContentProvider extends ContentProvider { db.beginTransaction(); try { db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + - " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER " + - " DEFAULT 0"); + " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + + " INTEGER " + " DEFAULT 0"); db.execSQL("UPDATE " + ProviderTableMeta.FILE_TABLE_NAME + - " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + ProviderTableMeta.FILE_MODIFIED + + " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + + ProviderTableMeta.FILE_MODIFIED + " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL"); upgraded = true; @@ -633,7 +698,8 @@ public class FileContentProvider extends ContentProvider { } } if (!upgraded) - Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + + ", newVersion == " + newVersion); if (oldVersion < 5 && newVersion >= 5) { Log_OC.i("SQL", "Entering in the #4 ADD in onUpgrade"); @@ -650,7 +716,8 @@ public class FileContentProvider extends ContentProvider { } } if (!upgraded) - Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + + ", newVersion == " + newVersion); if (oldVersion < 6 && newVersion >= 6) { Log_OC.i("SQL", "Entering in the #5 ADD in onUpgrade"); @@ -689,7 +756,8 @@ public class FileContentProvider extends ContentProvider { } } if (!upgraded) - Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + + ", newVersion == " + newVersion); if (oldVersion < 7 && newVersion >= 7) { Log_OC.i("SQL", "Entering in the #7 ADD in onUpgrade"); @@ -710,7 +778,8 @@ public class FileContentProvider extends ContentProvider { } } if (!upgraded) - Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + + ", newVersion == " + newVersion); if (oldVersion < 8 && newVersion >= 8) { Log_OC.i("SQL", "Entering in the #8 ADD in onUpgrade"); @@ -727,7 +796,8 @@ public class FileContentProvider extends ContentProvider { } } if (!upgraded) - Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + + ", newVersion == " + newVersion); } } diff --git a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java index b095981e..33e24003 100644 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -264,7 +264,7 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { mCurrentSyncTime, true, mIsShareSupported, - true, + false, getStorageManager(), getAccount(), getContext() diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 1bf8365a..627d40cf 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -85,6 +85,7 @@ import com.owncloud.android.operations.SynchronizeFolderOperation; import com.owncloud.android.operations.UnshareLinkOperation; import com.owncloud.android.services.observer.FileObserverService; import com.owncloud.android.syncadapter.FileSyncAdapter; +import com.owncloud.android.ui.adapter.FileListListAdapter; import com.owncloud.android.ui.dialog.CreateFolderDialogFragment; import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener; @@ -481,6 +482,40 @@ public class FileDisplayActivity extends HookActivity implements dialog.show(getSupportFragmentManager(), "createdirdialog"); break; } + case R.id.action_sort: { + SharedPreferences appPreferences = PreferenceManager + .getDefaultSharedPreferences(this); + + // Read sorting order, default to sort by name ascending + Integer sortOrder = appPreferences + .getInt("sortOrder", FileListListAdapter.SORT_NAME); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.actionbar_sort_title) + .setSingleChoiceItems(R.array.actionbar_sortby, sortOrder, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + + switch (which) { + case 0: + sortByName(true); + break; + case 1: + sortByDate(false); + break; + +// TODO re-enable when server-side folder size calculation is available +// case 2: +// sortBySize(false); +// break; + } + + dialog.dismiss(); + + } + }); + builder.create().show(); + break; + } case R.id.action_sync_account: { startSynchronization(); break; @@ -1790,4 +1825,16 @@ public class FileDisplayActivity extends HookActivity implements } } } + + private void sortByDate(boolean ascending) { + getListOfFilesFragment().sortByDate(ascending); + } + + private void sortBySize(boolean ascending) { + getListOfFilesFragment().sortBySize(ascending); + } + + private void sortByName(boolean ascending) { + getListOfFilesFragment().sortByName(ascending); + } } diff --git a/src/com/owncloud/android/ui/activity/MoveActivity.java b/src/com/owncloud/android/ui/activity/MoveActivity.java index 2d6824c8..8a254705 100644 --- a/src/com/owncloud/android/ui/activity/MoveActivity.java +++ b/src/com/owncloud/android/ui/activity/MoveActivity.java @@ -31,7 +31,6 @@ import android.content.res.Resources.NotFoundException; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; -import android.support.v4.widget.SwipeRefreshLayout; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; @@ -64,7 +63,7 @@ import com.owncloud.android.utils.ErrorMessageAdapter; import com.owncloud.android.lib.common.utils.Log_OC; public class MoveActivity extends HookActivity implements FileFragment.ContainerActivity, - OnClickListener, SwipeRefreshLayout.OnRefreshListener { + OnClickListener, OnEnforceableRefreshListener { public static final String EXTRA_CURRENT_FOLDER = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CURRENT_FOLDER"; public static final String EXTRA_TARGET_FILE = UploadFilesActivity.class.getCanonicalName() + "EXTRA_TARGET_FILE"; @@ -554,16 +553,23 @@ public class MoveActivity extends HookActivity implements FileFragment.Container } - @Override public void onRefresh() { + refreshList(true); + } + + @Override + public void onRefresh(boolean enforced) { + refreshList(enforced); + } + + private void refreshList(boolean ignoreETag) { OCFileListFragment listOfFiles = getListOfFilesFragment(); if (listOfFiles != null) { OCFile folder = listOfFiles.getCurrentFile(); if (folder != null) { - startSyncFolderOperation(folder, true); + startSyncFolderOperation(folder, ignoreETag); } } } - } diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java index 9ac6e27b..20330931 100644 --- a/src/com/owncloud/android/ui/activity/Preferences.java +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -74,6 +74,7 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa private final Handler mHandler = new Handler(); private String mAccountName; private boolean mShowContextMenu = false; + private String mUploadPath; @SuppressWarnings("deprecation") @@ -87,7 +88,9 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa actionBar.setIcon(DisplayUtils.getSeasonalIconId()); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setTitle(R.string.actionbar_settings); - + + loadInstantUploadPath(); + // Load the accounts category for adding the list of accounts mAccountsPrefCategory = (PreferenceCategory) findPreference("accounts_category"); @@ -101,7 +104,7 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa if (obj != null && obj instanceof LongClickableCheckBoxPreference) { mShowContextMenu = true; - mAccountName = obj.toString(); + mAccountName = ((LongClickableCheckBoxPreference) obj).getKey(); Preferences.this.openContextMenu(listView); @@ -239,6 +242,16 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa preferenceCategory.removePreference(pImprint); } } + + Preference pInstantUploadPathApp = (Preference) findPreference("instant_upload_path"); + + pInstantUploadPathApp.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + mUploadPath = updateInstantUploadPath(newValue.toString()); + return true; + } + }); /* About App */ pAboutApp = (Preference) findPreference("about_app"); @@ -255,6 +268,12 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa } @Override + protected void onPause() { + saveInstantUploadPathOnPreferences(); + super.onPause(); + } + + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { // Filter for only showing contextual menu when long press on the @@ -387,7 +406,8 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa for (Account a : accounts) { LongClickableCheckBoxPreference accountPreference = new LongClickableCheckBoxPreference(this); accountPreference.setKey(a.name); - accountPreference.setTitle(a.name); + // Handle internationalized domain names + accountPreference.setTitle(DisplayUtils.convertIdn(a.name, false)); mAccountsPrefCategory.addPreference(accountPreference); // Check the current account that is being used @@ -462,4 +482,47 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa } + /** + * Update the upload path checking that it is a correct path + * @param uploadPath: path write by user + * @return String: uploadPath + */ + private String updateInstantUploadPath(String uploadPath) { + String slashString = "/"; + + // If slashes are duplicated, replace them for only one slash + uploadPath = uploadPath.replaceAll("/+", slashString); + + // Remove last slash from path + if (uploadPath.length() > 0 && uploadPath.charAt(uploadPath.length()-1) == slashString.charAt(0)) { + uploadPath = uploadPath.substring(0, uploadPath.length()-1); + } + + if (uploadPath.isEmpty()) { // Set default instant upload path + uploadPath = getString(R.string.instant_upload_path); + }else { + if (!uploadPath.startsWith(slashString)) { // Add initial slash on path if necessary + uploadPath = slashString.concat(uploadPath); + } + } + return uploadPath; + } + + /** + * Load upload path set on preferences + */ + private void loadInstantUploadPath() { + SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + mUploadPath = appPrefs.getString("instant_upload_path", getString(R.string.instant_upload_path)); + } + + /** + * Save the "Instant Upload Path" on preferences + */ + private void saveInstantUploadPathOnPreferences() { + SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor editor = appPrefs.edit(); + editor.putString("instant_upload_path", mUploadPath); + editor.commit(); + } } diff --git a/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java b/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java index d22dc971..93efdf1c 100644 --- a/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java +++ b/src/com/owncloud/android/ui/adapter/DiskLruImageCache.java @@ -1,3 +1,20 @@ +/* ownCloud Android client application + * Copyright (C) 2012-2014 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.adapter; import java.io.BufferedInputStream; @@ -7,14 +24,10 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; -import android.util.Log; import com.jakewharton.disklrucache.DiskLruCache; import com.owncloud.android.BuildConfig; @@ -28,16 +41,14 @@ public class DiskLruImageCache { private static final int CACHE_VERSION = 1; private static final int VALUE_COUNT = 1; private static final int IO_BUFFER_SIZE = 8 * 1024; - private static final Pattern CAPITAL_LETTERS = Pattern.compile("[A-Z]"); - - private StringBuffer mValidKeyBuffer = new StringBuffer(64); - private StringBuffer mConversionBuffer = new StringBuffer(2).append('_'); - private static final String TAG = "DiskLruImageCache"; + private static final String TAG = DiskLruImageCache.class.getSimpleName(); + + //public DiskLruImageCache( Context context,String uniqueName, int diskCacheSize, + public DiskLruImageCache( + File diskCacheDir, int diskCacheSize, CompressFormat compressFormat, int quality + ) throws IOException { - public DiskLruImageCache( Context context,String uniqueName, int diskCacheSize, - CompressFormat compressFormat, int quality ) throws IOException { - final File diskCacheDir = getDiskCacheDir(context, uniqueName ); mDiskCache = DiskLruCache.open( diskCacheDir, CACHE_VERSION, VALUE_COUNT, diskCacheSize ); @@ -58,17 +69,6 @@ public class DiskLruImageCache { } } - private File getDiskCacheDir(Context context, String uniqueName) { - - // Check if media is mounted or storage is built-in, if so, try and use external cache dir - // otherwise use internal cache dir - final String cachePath = context.getExternalCacheDir().getPath(); - - Log_OC.d(TAG, "create dir: " + cachePath + File.separator + uniqueName); - - return new File(cachePath + File.separator + uniqueName); - } - public void put( String key, Bitmap data ) { DiskLruCache.Editor editor = null; @@ -83,17 +83,17 @@ public class DiskLruImageCache { mDiskCache.flush(); editor.commit(); if ( BuildConfig.DEBUG ) { - Log.d( "cache_test_DISK_", "image put on disk cache " + validKey ); + Log_OC.d( "cache_test_DISK_", "image put on disk cache " + validKey ); } } else { editor.abort(); if ( BuildConfig.DEBUG ) { - Log.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + validKey ); + Log_OC.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + validKey ); } } } catch (IOException e) { if ( BuildConfig.DEBUG ) { - Log.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + validKey ); + Log_OC.d( "cache_test_DISK_", "ERROR on: image put on disk cache " + validKey ); } try { if ( editor != null ) { @@ -131,7 +131,8 @@ public class DiskLruImageCache { } if ( BuildConfig.DEBUG ) { - Log.d("cache_test_DISK_", bitmap == null ? "not found" : "image read from disk " + validKey); + Log_OC.d("cache_test_DISK_", bitmap == null ? + "not found" : "image read from disk " + validKey); } return bitmap; @@ -160,7 +161,7 @@ public class DiskLruImageCache { public void clearCache() { if ( BuildConfig.DEBUG ) { - Log.d( "cache_test_DISK_", "disk cache CLEARED"); + Log_OC.d( "cache_test_DISK_", "disk cache CLEARED"); } try { mDiskCache.delete(); @@ -174,16 +175,20 @@ public class DiskLruImageCache { } private String convertToValidKey(String key) { - Matcher capitalLettersMatcher = CAPITAL_LETTERS.matcher(key); - mValidKeyBuffer.delete(0, mValidKeyBuffer.length()); - mConversionBuffer.delete(1, mConversionBuffer.length()); - - while (capitalLettersMatcher.find()) { - mConversionBuffer.replace(1, 2, capitalLettersMatcher.group(0).toLowerCase()); - capitalLettersMatcher.appendReplacement(mValidKeyBuffer, mConversionBuffer.toString()); - } - capitalLettersMatcher.appendTail(mValidKeyBuffer); - return mValidKeyBuffer.toString(); + return Integer.toString(key.hashCode()); } + /** + * Remove passed key from cache + * @param key + */ + public void removeKey( String key ) { + String validKey = convertToValidKey(key); + try { + mDiskCache.remove(validKey); + Log_OC.d(TAG, "removeKey from cache: " + validKey); + } catch (IOException e) { + e.printStackTrace(); + } + } } \ No newline at end of file diff --git a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java index ffdad175..9a2a0d3a 100644 --- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@ -17,21 +17,18 @@ */ package com.owncloud.android.ui.adapter; + import java.io.File; -import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.Comparator; import java.util.Vector; +import third_parties.daveKoeller.AlphanumComparator; import android.accounts.Account; import android.content.Context; -import android.content.res.Resources; +import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.media.ThumbnailUtils; -import android.os.AsyncTask; -import android.util.TypedValue; +import android.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -45,13 +42,14 @@ import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; +import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncDrawable; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.ComponentsGetter; -import com.owncloud.android.utils.BitmapUtils; import com.owncloud.android.utils.DisplayUtils; - +import com.owncloud.android.utils.FileStorageUtils; + /** * This Adapter populates a ListView with all files and folders in an ownCloud @@ -61,11 +59,9 @@ import com.owncloud.android.utils.DisplayUtils; * @author Tobias Kaminsky * @author David A. Velasco */ -public class FileListListAdapter extends BaseAdapter implements ListAdapter { +public class FileListListAdapter extends BaseAdapter implements ListAdapter { private final static String PERMISSION_SHARED_WITH_ME = "S"; - private static final String TAG = FileListListAdapter.class.getSimpleName(); - private Context mContext; private OCFile mFile = null; private Vector mFiles = null; @@ -74,15 +70,13 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { private FileDataStorageManager mStorageManager; private Account mAccount; private ComponentsGetter mTransferServiceGetter; + private Integer mSortOrder; + public static final Integer SORT_NAME = 0; + public static final Integer SORT_DATE = 1; + public static final Integer SORT_SIZE = 2; + private Boolean mSortAscending; + private SharedPreferences mAppPreferences; - private final Object thumbnailDiskCacheLock = new Object(); - private DiskLruImageCache mThumbnailCache; - private boolean mThumbnailCacheStarting = true; - private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB - private static final CompressFormat mCompressFormat = CompressFormat.JPEG; - private static final int mCompressQuality = 70; - private Bitmap defaultImg; - public FileListListAdapter( boolean justFolders, Context context, @@ -92,144 +86,20 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { mJustFolders = justFolders; mContext = context; mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); - mTransferServiceGetter = transferServiceGetter; - defaultImg = BitmapFactory.decodeResource(mContext.getResources(), - DisplayUtils.getResourceId("image/png", "default.png")); + mTransferServiceGetter = transferServiceGetter; - // Initialise disk cache on background thread - new InitDiskCacheTask().execute(); - } - - class InitDiskCacheTask extends AsyncTask { - @Override - protected Void doInBackground(File... params) { - synchronized (thumbnailDiskCacheLock) { - try { - mThumbnailCache = new DiskLruImageCache(mContext, "thumbnailCache", - DISK_CACHE_SIZE, mCompressFormat, mCompressQuality); - } catch (Exception e) { - Log_OC.d(TAG, "Thumbnail cache could not be opened ", e); - mThumbnailCache = null; - } - mThumbnailCacheStarting = false; // Finished initialization - thumbnailDiskCacheLock.notifyAll(); // Wake any waiting threads - } - return null; - } - } - - static class AsyncDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; - - public AsyncDrawable(Resources res, Bitmap bitmap, - ThumbnailGenerationTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = - new WeakReference(bitmapWorkerTask); - } - - public ThumbnailGenerationTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } - - class ThumbnailGenerationTask extends AsyncTask { - private final WeakReference imageViewReference; - private OCFile file; - + mAppPreferences = PreferenceManager + .getDefaultSharedPreferences(mContext); - public ThumbnailGenerationTask(ImageView imageView) { - // Use a WeakReference to ensure the ImageView can be garbage collected - imageViewReference = new WeakReference(imageView); - } - - // Decode image in background. - @Override - protected Bitmap doInBackground(OCFile... params) { - Bitmap thumbnail = null; - - try { - file = params[0]; - final String imageKey = String.valueOf(file.getRemoteId()); - - // Check disk cache in background thread - thumbnail = getBitmapFromDiskCache(imageKey); - - // Not found in disk cache - if (thumbnail == null || file.needsUpdateThumbnail()) { - // Converts dp to pixel - Resources r = mContext.getResources(); - int px = (int) Math.round(TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 150, r.getDisplayMetrics() - )); - - if (file.isDown()){ - Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile( - file.getStoragePath(), px, px); - - if (bitmap != null) { - thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px); - - // Add thumbnail to cache - addBitmapToCache(imageKey, thumbnail); - - file.setNeedsUpdateThumbnail(false); - mStorageManager.saveFile(file); - } - - } - } - - } catch (Throwable t) { - // the app should never break due to a problem with thumbnails - Log_OC.e(TAG, "Generation of thumbnail for " + file + " failed", t); - if (t instanceof OutOfMemoryError) { - System.gc(); - } - } - - return thumbnail; - } + // Read sorting order, default to sort by name ascending + mSortOrder = mAppPreferences + .getInt("sortOrder", 0); + mSortAscending = mAppPreferences.getBoolean("sortAscending", true); - protected void onPostExecute(Bitmap bitmap){ - if (isCancelled()) { - bitmap = null; - } - - if (imageViewReference != null && bitmap != null) { - final ImageView imageView = imageViewReference.get(); - final ThumbnailGenerationTask bitmapWorkerTask = - getBitmapWorkerTask(imageView); - if (this == bitmapWorkerTask && imageView != null) { - imageView.setImageBitmap(bitmap); - } - } - } - } - - public void addBitmapToCache(String key, Bitmap bitmap) { - synchronized (thumbnailDiskCacheLock) { - if (mThumbnailCache != null) { - mThumbnailCache.put(key, bitmap); - } - } + // initialise thumbnails cache on background thread + new ThumbnailsCacheManager.InitDiskCacheTask().execute(); } - - public Bitmap getBitmapFromDiskCache(String key) { - synchronized (thumbnailDiskCacheLock) { - // Wait while disk cache is started from background thread - while (mThumbnailCacheStarting) { - try { - thumbnailDiskCacheLock.wait(); - } catch (InterruptedException e) {} - } - if (mThumbnailCache != null) { - return (Bitmap) mThumbnailCache.getBitmap(key); - } - } - return null; - } - + @Override public boolean areAllItemsEnabled() { return true; @@ -280,6 +150,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { fileName.setText(name); ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); + fileIcon.setTag(file.getFileId()); ImageView sharedIconV = (ImageView) view.findViewById(R.id.sharedIcon); ImageView sharedWithMeIconV = (ImageView) view.findViewById(R.id.sharedWithMeIcon); sharedWithMeIconV.setVisibility(View.GONE); @@ -334,18 +205,28 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { } // get Thumbnail if file is image - if (file.isImage()){ + if (file.isImage() && file.getRemoteId() != null){ // Thumbnail in Cache? - Bitmap thumbnail = getBitmapFromDiskCache(String.valueOf(file.getRemoteId())); + Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( + String.valueOf(file.getRemoteId()) + ); if (thumbnail != null && !file.needsUpdateThumbnail()){ fileIcon.setImageBitmap(thumbnail); } else { // generate new Thumbnail - if (cancelPotentialWork(file, fileIcon)) { - final ThumbnailGenerationTask task = - new ThumbnailGenerationTask(fileIcon); - final AsyncDrawable asyncDrawable = - new AsyncDrawable(mContext.getResources(), defaultImg, task); + if (ThumbnailsCacheManager.cancelPotentialWork(file, fileIcon)) { + final ThumbnailsCacheManager.ThumbnailGenerationTask task = + new ThumbnailsCacheManager.ThumbnailGenerationTask( + fileIcon, mStorageManager + ); + if (thumbnail == null) { + thumbnail = ThumbnailsCacheManager.mDefaultImg; + } + final AsyncDrawable asyncDrawable = new AsyncDrawable( + mContext.getResources(), + thumbnail, + task + ); fileIcon.setImageDrawable(asyncDrawable); task.execute(file); } @@ -361,8 +242,14 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { } } else { - fileSizeV.setVisibility(View.INVISIBLE); - //fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); + // TODO Re-enable when server supports folder-size calculation +// if (FileStorageUtils.getDefaultSavePathFor(mAccount.name, file) != null){ +// fileSizeV.setVisibility(View.VISIBLE); +// fileSizeV.setText(getFolderSizeHuman(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file))); +// } else { + fileSizeV.setVisibility(View.INVISIBLE); +// } + lastModV.setVisibility(View.VISIBLE); lastModV.setText( DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp()) @@ -395,36 +282,47 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { return view; } - - public static boolean cancelPotentialWork(OCFile file, ImageView imageView) { - final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + /** + * Local Folder size in human readable format + * + * @param path + * String + * @return Size in human readable format + */ + private String getFolderSizeHuman(String path) { - if (bitmapWorkerTask != null) { - final OCFile bitmapData = bitmapWorkerTask.file; - // If bitmapData is not yet set or it differs from the new data - if (bitmapData == null || bitmapData != file) { - // Cancel previous task - bitmapWorkerTask.cancel(true); - } else { - // The same work is already in progress - return false; - } + File dir = new File(path); + + if (dir.exists()) { + long bytes = getFolderSize(dir); + return DisplayUtils.bytesToHumanReadable(bytes); } - // No task associated with the ImageView, or an existing task was cancelled - return true; + + return "0 B"; } - - private static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } + /** + * Local Folder size + * @param dir File + * @return Size in bytes + */ + private long getFolderSize(File dir) { + if (dir.exists()) { + long result = 0; + File[] fileList = dir.listFiles(); + for(int i = 0; i < fileList.length; i++) { + if(fileList[i].isDirectory()) { + result += getFolderSize(fileList[i]); + } else { + result += fileList[i].length(); + } + } + return result; + } + return 0; + } + @Override public int getViewTypeCount() { return 1; @@ -461,6 +359,26 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { } else { mFiles = null; } + + sortDirectory(); + } + + /** + * Sorts all filenames, regarding last user decision + */ + private void sortDirectory(){ + switch (mSortOrder){ + case 0: + sortByName(mSortAscending); + break; + case 1: + sortByDate(mSortAscending); + break; + case 2: + sortBySize(mSortAscending); + break; + } + notifyDataSetChanged(); } @@ -496,4 +414,106 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { && file.getPermissions() != null && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME)); } + + /** + * Sorts list by Date + * @param sortAscending true: ascending, false: descending + */ + private void sortByDate(boolean sortAscending){ + final Integer val; + if (sortAscending){ + val = 1; + } else { + val = -1; + } + + Collections.sort(mFiles, new Comparator() { + public int compare(OCFile o1, OCFile o2) { + if (o1.isFolder() && o2.isFolder()) { + Long obj1 = o1.getModificationTimestamp(); + return val * obj1.compareTo(o2.getModificationTimestamp()); + } + else if (o1.isFolder()) { + return -1; + } else if (o2.isFolder()) { + return 1; + } else if (o1.getModificationTimestamp() == 0 || o2.getModificationTimestamp() == 0){ + return 0; + } else { + Long obj1 = o1.getModificationTimestamp(); + return val * obj1.compareTo(o2.getModificationTimestamp()); + } + } + }); + } + + /** + * Sorts list by Size + * @param sortAscending true: ascending, false: descending + */ + private void sortBySize(boolean sortAscending){ + final Integer val; + if (sortAscending){ + val = 1; + } else { + val = -1; + } + + Collections.sort(mFiles, new Comparator() { + public int compare(OCFile o1, OCFile o2) { + if (o1.isFolder() && o2.isFolder()) { + Long obj1 = getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o1))); + return val * obj1.compareTo(getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o2)))); + } + else if (o1.isFolder()) { + return -1; + } else if (o2.isFolder()) { + return 1; + } else if (o1.getFileLength() == 0 || o2.getFileLength() == 0){ + return 0; + } else { + Long obj1 = o1.getFileLength(); + return val * obj1.compareTo(o2.getFileLength()); + } + } + }); + } + + /** + * Sorts list by Name + * @param sortAscending true: ascending, false: descending + */ + private void sortByName(boolean sortAscending){ + final Integer val; + if (sortAscending){ + val = 1; + } else { + val = -1; + } + + Collections.sort(mFiles, new Comparator() { + public int compare(OCFile o1, OCFile o2) { + if (o1.isFolder() && o2.isFolder()) { + return val * o1.getRemotePath().toLowerCase().compareTo(o2.getRemotePath().toLowerCase()); + } else if (o1.isFolder()) { + return -1; + } else if (o2.isFolder()) { + return 1; + } + return val * new AlphanumComparator().compare(o1, o2); + } + }); + } + + public void setSortOrder(Integer order, boolean ascending) { + SharedPreferences.Editor editor = mAppPreferences.edit(); + editor.putInt("sortOrder", order); + editor.putBoolean("sortAscending", ascending); + editor.commit(); + + mSortOrder = order; + mSortAscending = ascending; + + sortDirectory(); + } } diff --git a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java index 2ae730fb..dc118269 100644 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -388,5 +388,17 @@ public class OCFileListFragment extends ExtendedListFragment { mFile = directory; } } + + public void sortByName(boolean descending) { + mAdapter.setSortOrder(FileListListAdapter.SORT_NAME, descending); + } + + public void sortByDate(boolean descending) { + mAdapter.setSortOrder(FileListListAdapter.SORT_DATE, descending); + } + + public void sortBySize(boolean descending) { + mAdapter.setSortOrder(FileListListAdapter.SORT_SIZE, descending); + } } diff --git a/src/com/owncloud/android/ui/preview/FileDownloadFragment.java b/src/com/owncloud/android/ui/preview/FileDownloadFragment.java index b8489b59..98bbda38 100644 --- a/src/com/owncloud/android/ui/preview/FileDownloadFragment.java +++ b/src/com/owncloud/android/ui/preview/FileDownloadFragment.java @@ -32,6 +32,7 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageButton; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -126,6 +127,13 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene ((ImageButton)mView.findViewById(R.id.cancelBtn)).setOnClickListener(this); + ((LinearLayout)mView.findViewById(R.id.fileDownloadLL)).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + ((PreviewImageActivity) getActivity()).toggleFullScreen(); + } + }); + if (mError) { setButtonsForRemote(); } else { diff --git a/src/com/owncloud/android/ui/preview/ImageViewCustom.java b/src/com/owncloud/android/ui/preview/ImageViewCustom.java new file mode 100644 index 00000000..ad851404 --- /dev/null +++ b/src/com/owncloud/android/ui/preview/ImageViewCustom.java @@ -0,0 +1,70 @@ +package com.owncloud.android.ui.preview; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import com.owncloud.android.lib.common.utils.Log_OC; + +public class ImageViewCustom extends ImageView { + + private static final boolean IS_ICS_OR_HIGHER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + + private Bitmap mBitmap; + + + public ImageViewCustom(Context context) { + super(context); + } + + public ImageViewCustom(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ImageViewCustom(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @SuppressLint("NewApi") + @Override + protected void onDraw(Canvas canvas) { + + if(IS_ICS_OR_HIGHER && checkIfMaximumBitmapExceed(canvas)) { + // Set layer type to software one for avoiding exceed + // and problems in visualization + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + + super.onDraw(canvas); + } + + /** + * Checks if current bitmaps exceed the maximum OpenGL texture size limit + * @param bitmap + * @return boolean + */ + @SuppressLint("NewApi") + private boolean checkIfMaximumBitmapExceed(Canvas canvas) { + Log_OC.d("OC", "Canvas maximum: " + canvas.getMaximumBitmapWidth() + " - " + canvas.getMaximumBitmapHeight()); + if (mBitmap!= null && (mBitmap.getWidth() > canvas.getMaximumBitmapWidth() + || mBitmap.getHeight() > canvas.getMaximumBitmapHeight())) { + return true; + } + + return false; + } + + /** + * Set current bitmap + * @param bitmap + */ + public void setBitmap (Bitmap bitmap) { + mBitmap = bitmap; + } + +} diff --git a/src/com/owncloud/android/ui/preview/PreviewImageFragment.java b/src/com/owncloud/android/ui/preview/PreviewImageFragment.java index 8e3afaf5..4dd5c436 100644 --- a/src/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -16,6 +16,12 @@ */ package com.owncloud.android.ui.preview; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; import java.lang.ref.WeakReference; import android.accounts.Account; @@ -40,7 +46,6 @@ import android.widget.TextView; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; -import com.ortiz.touch.TouchImageView; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.FileMenuFilter; @@ -48,6 +53,8 @@ import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; import com.owncloud.android.ui.dialog.RemoveFileDialogFragment; import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.utils.TouchImageViewCustom; + /** @@ -60,12 +67,13 @@ import com.owncloud.android.ui.fragment.FileFragment; * @author David A. Velasco */ public class PreviewImageFragment extends FileFragment { + public static final String EXTRA_FILE = "FILE"; public static final String EXTRA_ACCOUNT = "ACCOUNT"; private View mView; private Account mAccount; - private TouchImageView mImageView; + private TouchImageViewCustom mImageView; private TextView mMessageView; private ProgressBar mProgressWheel; @@ -124,7 +132,7 @@ public class PreviewImageFragment extends FileFragment { Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); mView = inflater.inflate(R.layout.preview_image_fragment, container, false); - mImageView = (TouchImageView) mView.findViewById(R.id.image); + mImageView = (TouchImageViewCustom) mView.findViewById(R.id.image); mImageView.setVisibility(View.GONE); mImageView.setOnClickListener(new OnClickListener() { @Override @@ -306,6 +314,7 @@ public class PreviewImageFragment extends FileFragment { public void onDestroy() { if (mBitmap != null) { mBitmap.recycle(); + System.gc(); } super.onDestroy(); } @@ -327,7 +336,7 @@ public class PreviewImageFragment extends FileFragment { * * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. */ - private final WeakReference mImageViewRef; + private final WeakReference mImageViewRef; /** * Weak reference to the target {@link TextView} where error messages will be written. @@ -356,65 +365,27 @@ public class PreviewImageFragment extends FileFragment { * * @param imageView Target {@link ImageView} where the bitmap will be loaded into. */ - public BitmapLoader(ImageView imageView, TextView messageView, ProgressBar progressWheel) { - mImageViewRef = new WeakReference(imageView); + public BitmapLoader(ImageViewCustom imageView, TextView messageView, ProgressBar progressWheel) { + mImageViewRef = new WeakReference(imageView); mMessageViewRef = new WeakReference(messageView); mProgressWheelRef = new WeakReference(progressWheel); } - @SuppressWarnings("deprecation") - @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20 - @Override + @Override protected Bitmap doInBackground(String... params) { Bitmap result = null; if (params.length != 1) return result; String storagePath = params[0]; try { - // set desired options that will affect the size of the bitmap - BitmapFactory.Options options = new Options(); - options.inScaled = true; - options.inPurgeable = true; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - options.inPreferQualityOverSpeed = false; - } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - options.inMutable = false; - } - // make a false load of the bitmap - just to be able to read outWidth, outHeight and outMimeType - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(storagePath, options); - - int width = options.outWidth; - int height = options.outHeight; - int scale = 1; - - Display display = getActivity().getWindowManager().getDefaultDisplay(); - Point size = new Point(); - int screenWidth; - int screenHeight; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { - display.getSize(size); - screenWidth = size.x; - screenHeight = size.y; - } else { - screenWidth = display.getWidth(); - screenHeight = display.getHeight(); - } - if (width > screenWidth) { - // second try to scale down the image , this time depending upon the screen size - scale = (int) Math.floor((float)width / screenWidth); - } - if (height > screenHeight) { - scale = Math.max(scale, (int) Math.floor((float)height / screenHeight)); - } - options.inSampleSize = scale; + File picture = new File(storagePath); - // really load the bitmap - options.inJustDecodeBounds = false; // the next decodeFile call will be real - result = BitmapFactory.decodeFile(storagePath, options); - //Log_OC.d(TAG, "Image loaded - width: " + options.outWidth + ", loaded height: " + options.outHeight); + if (picture != null) { + //Decode file into a bitmap in real size for being able to make zoom on the image + result = BitmapFactory.decodeStream(new FlushedInputStream + (new BufferedInputStream(new FileInputStream(picture)))); + } if (result == null) { mErrorMessageId = R.string.preview_image_error_unknown_format; @@ -422,8 +393,15 @@ public class PreviewImageFragment extends FileFragment { } } catch (OutOfMemoryError e) { - mErrorMessageId = R.string.preview_image_error_unknown_format; Log_OC.e(TAG, "Out of memory occured for file " + storagePath, e); + + // If out of memory error when loading image, try to load it scaled + result = loadScaledImage(storagePath); + + if (result == null) { + mErrorMessageId = R.string.preview_image_error_unknown_format; + Log_OC.e(TAG, "File could not be loaded as a bitmap: " + storagePath); + } } catch (NoSuchFieldError e) { mErrorMessageId = R.string.common_error_unknown; @@ -446,11 +424,13 @@ public class PreviewImageFragment extends FileFragment { showErrorMessage(); } } - + + @SuppressLint("InlinedApi") private void showLoadedImage(Bitmap result) { if (mImageViewRef != null) { - final ImageView imageView = mImageViewRef.get(); + final ImageViewCustom imageView = mImageViewRef.get(); if (imageView != null) { + imageView.setBitmap(result); imageView.setImageBitmap(result); imageView.setVisibility(View.VISIBLE); mBitmap = result; @@ -511,8 +491,87 @@ public class PreviewImageFragment extends FileFragment { container.finish(); } - public TouchImageView getImageView() { + public TouchImageViewCustom getImageView() { return mImageView; } - + + static class FlushedInputStream extends FilterInputStream { + public FlushedInputStream(InputStream inputStream) { + super(inputStream); + } + + @Override + public long skip(long n) throws IOException { + long totalBytesSkipped = 0L; + while (totalBytesSkipped < n) { + long bytesSkipped = in.skip(n - totalBytesSkipped); + if (bytesSkipped == 0L) { + int byteValue = read(); + if (byteValue < 0) { + break; // we reached EOF + } else { + bytesSkipped = 1; // we read one byte + } + } + totalBytesSkipped += bytesSkipped; + } + return totalBytesSkipped; + } + } + + /** + * Load image scaled + * @param storagePath: path of the image + * @return Bitmap + */ + @SuppressWarnings("deprecation") + private Bitmap loadScaledImage(String storagePath) { + + Log_OC.d(TAG, "Loading image scaled"); + + // set desired options that will affect the size of the bitmap + BitmapFactory.Options options = new Options(); + options.inScaled = true; + options.inPurgeable = true; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + options.inPreferQualityOverSpeed = false; + } + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + options.inMutable = false; + } + // make a false load of the bitmap - just to be able to read outWidth, outHeight and outMimeType + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(storagePath, options); + + int width = options.outWidth; + int height = options.outHeight; + int scale = 1; + + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point size = new Point(); + int screenWidth; + int screenHeight; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { + display.getSize(size); + screenWidth = size.x; + screenHeight = size.y; + } else { + screenWidth = display.getWidth(); + screenHeight = display.getHeight(); + } + + if (width > screenWidth) { + // second try to scale down the image , this time depending upon the screen size + scale = (int) Math.floor((float)width / screenWidth); + } + if (height > screenHeight) { + scale = Math.max(scale, (int) Math.floor((float)height / screenHeight)); + } + options.inSampleSize = scale; + + // really load the bitmap + options.inJustDecodeBounds = false; // the next decodeFile call will be real + return BitmapFactory.decodeFile(storagePath, options); + + } } diff --git a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java index ea412ced..7d6489b2 100644 --- a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -29,6 +29,7 @@ import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.IBinder; @@ -372,13 +373,15 @@ public class PreviewMediaFragment extends FileFragment implements mVideoPreview.setOnErrorListener(mVideoHelper); } + @SuppressWarnings("static-access") private void playVideo() { // create and prepare control panel for the user mMediaController.setMediaPlayer(mVideoPreview); // load the video file in the video player ; // when done, VideoHelper#onPrepared() will be called - mVideoPreview.setVideoPath(getFile().getStoragePath()); + Uri uri = Uri.parse(getFile().getStoragePath()); + mVideoPreview.setVideoPath(uri.encode(getFile().getStoragePath())); } diff --git a/src/com/owncloud/android/utils/DisplayUtils.java b/src/com/owncloud/android/utils/DisplayUtils.java index 682d2be0..8c4c492b 100644 --- a/src/com/owncloud/android/utils/DisplayUtils.java +++ b/src/com/owncloud/android/utils/DisplayUtils.java @@ -18,6 +18,7 @@ package com.owncloud.android.utils; +import java.net.IDN; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -25,6 +26,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Set; +import android.annotation.TargetApi; +import android.os.Build; + import com.owncloud.android.R; /** @@ -235,4 +239,35 @@ public class DisplayUtils { return R.drawable.icon; } } + + /** + * Converts an internationalized domain name (IDN) in an URL to and from ASCII/Unicode. + * @param url the URL where the domain name should be converted + * @param toASCII if true converts from Unicode to ASCII, if false converts from ASCII to Unicode + * @return the URL containing the converted domain name + */ + @TargetApi(Build.VERSION_CODES.GINGERBREAD) + public static String convertIdn(String url, boolean toASCII) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { + // Find host name after '//' or '@' + int hostStart = 0; + if (url.indexOf("//") != -1) { + hostStart = url.indexOf("//") + "//".length(); + } else if (url.indexOf("@") != -1) { + hostStart = url.indexOf("@") + "@".length(); + } + + int hostEnd = url.substring(hostStart).indexOf("/"); + // Handle URL which doesn't have a path (path is implicitly '/') + hostEnd = (hostEnd == -1 ? url.length() : hostStart + hostEnd); + + String host = url.substring(hostStart, hostEnd); + host = (toASCII ? IDN.toASCII(host) : IDN.toUnicode(host)); + + return url.substring(0, hostStart) + host + url.substring(hostEnd); + } else { + return url; + } + } } diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index 58dda0da..3895821d 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -26,6 +26,8 @@ import com.owncloud.android.lib.resources.files.RemoteFile; import android.annotation.SuppressLint; import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.net.Uri; import android.os.Environment; import android.os.StatFs; @@ -73,7 +75,9 @@ public class FileStorageUtils { } public static String getInstantUploadFilePath(Context context, String fileName) { - String uploadPath = context.getString(R.string.instant_upload_path); + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); + String uploadPathdef = context.getString(R.string.instant_upload_path); + String uploadPath = pref.getString("instant_upload_path", uploadPathdef); String value = uploadPath + OCFile.PATH_SEPARATOR + (fileName == null ? "" : fileName); return value; } @@ -120,4 +124,4 @@ public class FileStorageUtils { return file; } -} \ No newline at end of file +} diff --git a/src/com/owncloud/android/utils/TouchImageViewCustom.java b/src/com/owncloud/android/utils/TouchImageViewCustom.java new file mode 100644 index 00000000..a0f7b792 --- /dev/null +++ b/src/com/owncloud/android/utils/TouchImageViewCustom.java @@ -0,0 +1,1276 @@ +/* + * TouchImageView.java + * By: Michael Ortiz + * Updated By: Patrick Lackemacher + * Updated By: Babay88 + * Updated By: @ipsilondev + * Updated By: hank-cp + * Updated By: singpolyma + * ------------------- + * Extends Android ImageView to include pinch zooming, panning, fling and double tap zoom. + */ + +package com.owncloud.android.utils; + +import com.owncloud.android.ui.preview.ImageViewCustom; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.OverScroller; +import android.widget.Scroller; + +public class TouchImageViewCustom extends ImageViewCustom { + private static final String DEBUG = "DEBUG"; + + // + // SuperMin and SuperMax multipliers. Determine how much the image can be + // zoomed below or above the zoom boundaries, before animating back to the + // min/max zoom boundary. + // + private static final float SUPER_MIN_MULTIPLIER = .75f; + private static final float SUPER_MAX_MULTIPLIER = 1.25f; + + // + // Scale of image ranges from minScale to maxScale, where minScale == 1 + // when the image is stretched to fit view. + // + private float normalizedScale; + + // + // Matrix applied to image. MSCALE_X and MSCALE_Y should always be equal. + // MTRANS_X and MTRANS_Y are the other values used. prevMatrix is the matrix + // saved prior to the screen rotating. + // + private Matrix matrix, prevMatrix; + + private static enum State { NONE, DRAG, ZOOM, FLING, ANIMATE_ZOOM }; + private State state; + + private float minScale; + private float maxScale; + private float superMinScale; + private float superMaxScale; + private float[] m; + + private Context context; + private Fling fling; + + private ScaleType mScaleType; + + private boolean imageRenderedAtLeastOnce; + private boolean onDrawReady; + + private ZoomVariables delayedZoomVariables; + + // + // Size of view and previous view size (ie before rotation) + // + private int viewWidth, viewHeight, prevViewWidth, prevViewHeight; + + // + // Size of image when it is stretched to fit view. Before and After rotation. + // + private float matchViewWidth, matchViewHeight, prevMatchViewWidth, prevMatchViewHeight; + + private ScaleGestureDetector mScaleDetector; + private GestureDetector mGestureDetector; + private GestureDetector.OnDoubleTapListener doubleTapListener = null; + private OnTouchListener userTouchListener = null; + private OnTouchImageViewListener touchImageViewListener = null; + + public TouchImageViewCustom(Context context) { + super(context); + sharedConstructing(context); + } + + public TouchImageViewCustom(Context context, AttributeSet attrs) { + super(context, attrs); + sharedConstructing(context); + } + + public TouchImageViewCustom(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + sharedConstructing(context); + } + + private void sharedConstructing(Context context) { + super.setClickable(true); + this.context = context; + mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); + mGestureDetector = new GestureDetector(context, new GestureListener()); + matrix = new Matrix(); + prevMatrix = new Matrix(); + m = new float[9]; + normalizedScale = 1; + if (mScaleType == null) { + mScaleType = ScaleType.FIT_CENTER; + } + minScale = 1; + maxScale = 3; + superMinScale = SUPER_MIN_MULTIPLIER * minScale; + superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; + setImageMatrix(matrix); + setScaleType(ScaleType.MATRIX); + setState(State.NONE); + onDrawReady = false; + super.setOnTouchListener(new PrivateOnTouchListener()); + } + + @Override + public void setOnTouchListener(View.OnTouchListener l) { + userTouchListener = l; + } + + public void setOnTouchImageViewListener(OnTouchImageViewListener l) { + touchImageViewListener = l; + } + + public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener l) { + doubleTapListener = l; + } + + @Override + public void setImageResource(int resId) { + super.setImageResource(resId); + savePreviousImageValues(); + fitImageToView(); + } + + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + savePreviousImageValues(); + fitImageToView(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + savePreviousImageValues(); + fitImageToView(); + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + savePreviousImageValues(); + fitImageToView(); + } + + @Override + public void setScaleType(ScaleType type) { + if (type == ScaleType.FIT_START || type == ScaleType.FIT_END) { + throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); + } + if (type == ScaleType.MATRIX) { + super.setScaleType(ScaleType.MATRIX); + + } else { + mScaleType = type; + if (onDrawReady) { + // + // If the image is already rendered, scaleType has been called programmatically + // and the TouchImageView should be updated with the new scaleType. + // + setZoom(this); + } + } + } + + @Override + public ScaleType getScaleType() { + return mScaleType; + } + + /** + * Returns false if image is in initial, unzoomed state. False, otherwise. + * @return true if image is zoomed + */ + public boolean isZoomed() { + return normalizedScale != 1; + } + + /** + * Return a Rect representing the zoomed image. + * @return rect representing zoomed image + */ + public RectF getZoomedRect() { + if (mScaleType == ScaleType.FIT_XY) { + throw new UnsupportedOperationException("getZoomedRect() not supported with FIT_XY"); + } + PointF topLeft = transformCoordTouchToBitmap(0, 0, true); + PointF bottomRight = transformCoordTouchToBitmap(viewWidth, viewHeight, true); + + float w = getDrawable().getIntrinsicWidth(); + float h = getDrawable().getIntrinsicHeight(); + return new RectF(topLeft.x / w, topLeft.y / h, bottomRight.x / w, bottomRight.y / h); + } + + /** + * Save the current matrix and view dimensions + * in the prevMatrix and prevView variables. + */ + private void savePreviousImageValues() { + if (matrix != null && viewHeight != 0 && viewWidth != 0) { + matrix.getValues(m); + prevMatrix.setValues(m); + prevMatchViewHeight = matchViewHeight; + prevMatchViewWidth = matchViewWidth; + prevViewHeight = viewHeight; + prevViewWidth = viewWidth; + } + } + + @Override + public Parcelable onSaveInstanceState() { + Bundle bundle = new Bundle(); + bundle.putParcelable("instanceState", super.onSaveInstanceState()); + bundle.putFloat("saveScale", normalizedScale); + bundle.putFloat("matchViewHeight", matchViewHeight); + bundle.putFloat("matchViewWidth", matchViewWidth); + bundle.putInt("viewWidth", viewWidth); + bundle.putInt("viewHeight", viewHeight); + matrix.getValues(m); + bundle.putFloatArray("matrix", m); + bundle.putBoolean("imageRendered", imageRenderedAtLeastOnce); + return bundle; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + Bundle bundle = (Bundle) state; + normalizedScale = bundle.getFloat("saveScale"); + m = bundle.getFloatArray("matrix"); + prevMatrix.setValues(m); + prevMatchViewHeight = bundle.getFloat("matchViewHeight"); + prevMatchViewWidth = bundle.getFloat("matchViewWidth"); + prevViewHeight = bundle.getInt("viewHeight"); + prevViewWidth = bundle.getInt("viewWidth"); + imageRenderedAtLeastOnce = bundle.getBoolean("imageRendered"); + super.onRestoreInstanceState(bundle.getParcelable("instanceState")); + return; + } + + super.onRestoreInstanceState(state); + } + + @Override + protected void onDraw(Canvas canvas) { + onDrawReady = true; + imageRenderedAtLeastOnce = true; + if (delayedZoomVariables != null) { + setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX, delayedZoomVariables.focusY, delayedZoomVariables.scaleType); + delayedZoomVariables = null; + } + super.onDraw(canvas); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + savePreviousImageValues(); + } + + /** + * Get the max zoom multiplier. + * @return max zoom multiplier. + */ + public float getMaxZoom() { + return maxScale; + } + + /** + * Set the max zoom multiplier. Default value: 3. + * @param max max zoom multiplier. + */ + public void setMaxZoom(float max) { + maxScale = max; + superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; + } + + /** + * Get the min zoom multiplier. + * @return min zoom multiplier. + */ + public float getMinZoom() { + return minScale; + } + + /** + * Get the current zoom. This is the zoom relative to the initial + * scale, not the original resource. + * @return current zoom multiplier. + */ + public float getCurrentZoom() { + return normalizedScale; + } + + /** + * Set the min zoom multiplier. Default value: 1. + * @param min min zoom multiplier. + */ + public void setMinZoom(float min) { + minScale = min; + superMinScale = SUPER_MIN_MULTIPLIER * minScale; + } + + /** + * Reset zoom and translation to initial state. + */ + public void resetZoom() { + normalizedScale = 1; + fitImageToView(); + } + + /** + * Set zoom to the specified scale. Image will be centered by default. + * @param scale + */ + public void setZoom(float scale) { + setZoom(scale, 0.5f, 0.5f); + } + + /** + * Set zoom to the specified scale. Image will be centered around the point + * (focusX, focusY). These floats range from 0 to 1 and denote the focus point + * as a fraction from the left and top of the view. For example, the top left + * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). + * @param scale + * @param focusX + * @param focusY + */ + public void setZoom(float scale, float focusX, float focusY) { + setZoom(scale, focusX, focusY, mScaleType); + } + + /** + * Set zoom to the specified scale. Image will be centered around the point + * (focusX, focusY). These floats range from 0 to 1 and denote the focus point + * as a fraction from the left and top of the view. For example, the top left + * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). + * @param scale + * @param focusX + * @param focusY + * @param scaleType + */ + public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) { + // + // setZoom can be called before the image is on the screen, but at this point, + // image and view sizes have not yet been calculated in onMeasure. Thus, we should + // delay calling setZoom until the view has been measured. + // + if (!onDrawReady) { + delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType); + return; + } + + if (scaleType != mScaleType) { + setScaleType(scaleType); + } + resetZoom(); + scaleImage(scale, viewWidth / 2, viewHeight / 2, true); + matrix.getValues(m); + m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f)); + m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f)); + matrix.setValues(m); + fixTrans(); + setImageMatrix(matrix); + } + + /** + * Set zoom parameters equal to another TouchImageView. Including scale, position, + * and ScaleType. + * @param TouchImageView + */ + public void setZoom(TouchImageViewCustom img) { + PointF center = img.getScrollPosition(); + setZoom(img.getCurrentZoom(), center.x, center.y, img.getScaleType()); + } + + /** + * Return the point at the center of the zoomed image. The PointF coordinates range + * in value between 0 and 1 and the focus point is denoted as a fraction from the left + * and top of the view. For example, the top left corner of the image would be (0, 0). + * And the bottom right corner would be (1, 1). + * @return PointF representing the scroll position of the zoomed image. + */ + public PointF getScrollPosition() { + Drawable drawable = getDrawable(); + if (drawable == null) { + return null; + } + int drawableWidth = drawable.getIntrinsicWidth(); + int drawableHeight = drawable.getIntrinsicHeight(); + + PointF point = transformCoordTouchToBitmap(viewWidth / 2, viewHeight / 2, true); + point.x /= drawableWidth; + point.y /= drawableHeight; + return point; + } + + /** + * Set the focus point of the zoomed image. The focus points are denoted as a fraction from the + * left and top of the view. The focus points can range in value between 0 and 1. + * @param focusX + * @param focusY + */ + public void setScrollPosition(float focusX, float focusY) { + setZoom(normalizedScale, focusX, focusY); + } + + /** + * Performs boundary checking and fixes the image matrix if it + * is out of bounds. + */ + private void fixTrans() { + matrix.getValues(m); + float transX = m[Matrix.MTRANS_X]; + float transY = m[Matrix.MTRANS_Y]; + + float fixTransX = getFixTrans(transX, viewWidth, getImageWidth()); + float fixTransY = getFixTrans(transY, viewHeight, getImageHeight()); + + if (fixTransX != 0 || fixTransY != 0) { + matrix.postTranslate(fixTransX, fixTransY); + } + } + + /** + * When transitioning from zooming from focus to zoom from center (or vice versa) + * the image can become unaligned within the view. This is apparent when zooming + * quickly. When the content size is less than the view size, the content will often + * be centered incorrectly within the view. fixScaleTrans first calls fixTrans() and + * then makes sure the image is centered correctly within the view. + */ + private void fixScaleTrans() { + fixTrans(); + matrix.getValues(m); + if (getImageWidth() < viewWidth) { + m[Matrix.MTRANS_X] = (viewWidth - getImageWidth()) / 2; + } + + if (getImageHeight() < viewHeight) { + m[Matrix.MTRANS_Y] = (viewHeight - getImageHeight()) / 2; + } + matrix.setValues(m); + } + + private float getFixTrans(float trans, float viewSize, float contentSize) { + float minTrans, maxTrans; + + if (contentSize <= viewSize) { + minTrans = 0; + maxTrans = viewSize - contentSize; + + } else { + minTrans = viewSize - contentSize; + maxTrans = 0; + } + + if (trans < minTrans) + return -trans + minTrans; + if (trans > maxTrans) + return -trans + maxTrans; + return 0; + } + + private float getFixDragTrans(float delta, float viewSize, float contentSize) { + if (contentSize <= viewSize) { + return 0; + } + return delta; + } + + private float getImageWidth() { + return matchViewWidth * normalizedScale; + } + + private float getImageHeight() { + return matchViewHeight * normalizedScale; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Drawable drawable = getDrawable(); + if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { + setMeasuredDimension(0, 0); + return; + } + + int drawableWidth = drawable.getIntrinsicWidth(); + int drawableHeight = drawable.getIntrinsicHeight(); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + viewWidth = setViewSize(widthMode, widthSize, drawableWidth); + viewHeight = setViewSize(heightMode, heightSize, drawableHeight); + + // + // Set view dimensions + // + setMeasuredDimension(viewWidth, viewHeight); + + // + // Fit content within view + // + fitImageToView(); + } + + /** + * If the normalizedScale is equal to 1, then the image is made to fit the screen. Otherwise, + * it is made to fit the screen according to the dimensions of the previous image matrix. This + * allows the image to maintain its zoom after rotation. + */ + private void fitImageToView() { + Drawable drawable = getDrawable(); + if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { + return; + } + if (matrix == null || prevMatrix == null) { + return; + } + + int drawableWidth = drawable.getIntrinsicWidth(); + int drawableHeight = drawable.getIntrinsicHeight(); + + // + // Scale image for view + // + float scaleX = (float) viewWidth / drawableWidth; + float scaleY = (float) viewHeight / drawableHeight; + + switch (mScaleType) { + case CENTER: + scaleX = scaleY = 1; + break; + + case CENTER_CROP: + scaleX = scaleY = Math.max(scaleX, scaleY); + break; + + case CENTER_INSIDE: + scaleX = scaleY = Math.min(1, Math.min(scaleX, scaleY)); + + case FIT_CENTER: + scaleX = scaleY = Math.min(scaleX, scaleY); + break; + + case FIT_XY: + break; + + default: + // + // FIT_START and FIT_END not supported + // + throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); + + } + + // + // Center the image + // + float redundantXSpace = viewWidth - (scaleX * drawableWidth); + float redundantYSpace = viewHeight - (scaleY * drawableHeight); + matchViewWidth = viewWidth - redundantXSpace; + matchViewHeight = viewHeight - redundantYSpace; + if (!isZoomed() && !imageRenderedAtLeastOnce) { + // + // Stretch and center image to fit view + // + matrix.setScale(scaleX, scaleY); + matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2); + normalizedScale = 1; + + } else { + // + // These values should never be 0 or we will set viewWidth and viewHeight + // to NaN in translateMatrixAfterRotate. To avoid this, call savePreviousImageValues + // to set them equal to the current values. + // + if (prevMatchViewWidth == 0 || prevMatchViewHeight == 0) { + savePreviousImageValues(); + } + + prevMatrix.getValues(m); + + // + // Rescale Matrix after rotation + // + m[Matrix.MSCALE_X] = matchViewWidth / drawableWidth * normalizedScale; + m[Matrix.MSCALE_Y] = matchViewHeight / drawableHeight * normalizedScale; + + // + // TransX and TransY from previous matrix + // + float transX = m[Matrix.MTRANS_X]; + float transY = m[Matrix.MTRANS_Y]; + + // + // Width + // + float prevActualWidth = prevMatchViewWidth * normalizedScale; + float actualWidth = getImageWidth(); + translateMatrixAfterRotate(Matrix.MTRANS_X, transX, prevActualWidth, actualWidth, prevViewWidth, viewWidth, drawableWidth); + + // + // Height + // + float prevActualHeight = prevMatchViewHeight * normalizedScale; + float actualHeight = getImageHeight(); + translateMatrixAfterRotate(Matrix.MTRANS_Y, transY, prevActualHeight, actualHeight, prevViewHeight, viewHeight, drawableHeight); + + // + // Set the matrix to the adjusted scale and translate values. + // + matrix.setValues(m); + } + fixTrans(); + setImageMatrix(matrix); + } + + /** + * Set view dimensions based on layout params + * + * @param mode + * @param size + * @param drawableWidth + * @return + */ + private int setViewSize(int mode, int size, int drawableWidth) { + int viewSize; + switch (mode) { + case MeasureSpec.EXACTLY: + viewSize = size; + break; + + case MeasureSpec.AT_MOST: + viewSize = Math.min(drawableWidth, size); + break; + + case MeasureSpec.UNSPECIFIED: + viewSize = drawableWidth; + break; + + default: + viewSize = size; + break; + } + return viewSize; + } + + /** + * After rotating, the matrix needs to be translated. This function finds the area of image + * which was previously centered and adjusts translations so that is again the center, post-rotation. + * + * @param axis Matrix.MTRANS_X or Matrix.MTRANS_Y + * @param trans the value of trans in that axis before the rotation + * @param prevImageSize the width/height of the image before the rotation + * @param imageSize width/height of the image after rotation + * @param prevViewSize width/height of view before rotation + * @param viewSize width/height of view after rotation + * @param drawableSize width/height of drawable + */ + private void translateMatrixAfterRotate(int axis, float trans, float prevImageSize, float imageSize, int prevViewSize, int viewSize, int drawableSize) { + if (imageSize < viewSize) { + // + // The width/height of image is less than the view's width/height. Center it. + // + m[axis] = (viewSize - (drawableSize * m[Matrix.MSCALE_X])) * 0.5f; + + } else if (trans > 0) { + // + // The image is larger than the view, but was not before rotation. Center it. + // + m[axis] = -((imageSize - viewSize) * 0.5f); + + } else { + // + // Find the area of the image which was previously centered in the view. Determine its distance + // from the left/top side of the view as a fraction of the entire image's width/height. Use that percentage + // to calculate the trans in the new view width/height. + // + float percentage = (Math.abs(trans) + (0.5f * prevViewSize)) / prevImageSize; + m[axis] = -((percentage * imageSize) - (viewSize * 0.5f)); + } + } + + private void setState(State state) { + this.state = state; + } + + public boolean canScrollHorizontallyFroyo(int direction) { + return canScrollHorizontally(direction); + } + + @Override + public boolean canScrollHorizontally(int direction) { + matrix.getValues(m); + float x = m[Matrix.MTRANS_X]; + + if (getImageWidth() < viewWidth) { + return false; + + } else if (x >= -1 && direction < 0) { + return false; + + } else if (Math.abs(x) + viewWidth + 1 >= getImageWidth() && direction > 0) { + return false; + } + + return true; + } + + /** + * Gesture Listener detects a single click or long click and passes that on + * to the view's listener. + * @author Ortiz + * + */ + private class GestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) + { + if(doubleTapListener != null) { + return doubleTapListener.onSingleTapConfirmed(e); + } + return performClick(); + } + + @Override + public void onLongPress(MotionEvent e) + { + performLongClick(); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) + { + if (fling != null) { + // + // If a previous fling is still active, it should be cancelled so that two flings + // are not run simultaenously. + // + fling.cancelFling(); + } + fling = new Fling((int) velocityX, (int) velocityY); + compatPostOnAnimation(fling); + return super.onFling(e1, e2, velocityX, velocityY); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + boolean consumed = false; + if(doubleTapListener != null) { + consumed = doubleTapListener.onDoubleTap(e); + } + if (state == State.NONE) { + float targetZoom = (normalizedScale == minScale) ? maxScale : minScale; + DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false); + compatPostOnAnimation(doubleTap); + consumed = true; + } + return consumed; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + if(doubleTapListener != null) { + return doubleTapListener.onDoubleTapEvent(e); + } + return false; + } + } + + public interface OnTouchImageViewListener { + public void onMove(); + } + + /** + * Responsible for all touch events. Handles the heavy lifting of drag and also sends + * touch events to Scale Detector and Gesture Detector. + * @author Ortiz + * + */ + private class PrivateOnTouchListener implements OnTouchListener { + + // + // Remember last point position for dragging + // + private PointF last = new PointF(); + + @Override + public boolean onTouch(View v, MotionEvent event) { + mScaleDetector.onTouchEvent(event); + mGestureDetector.onTouchEvent(event); + PointF curr = new PointF(event.getX(), event.getY()); + + if (state == State.NONE || state == State.DRAG || state == State.FLING) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + last.set(curr); + if (fling != null) + fling.cancelFling(); + setState(State.DRAG); + break; + + case MotionEvent.ACTION_MOVE: + if (state == State.DRAG) { + float deltaX = curr.x - last.x; + float deltaY = curr.y - last.y; + float fixTransX = getFixDragTrans(deltaX, viewWidth, getImageWidth()); + float fixTransY = getFixDragTrans(deltaY, viewHeight, getImageHeight()); + matrix.postTranslate(fixTransX, fixTransY); + fixTrans(); + last.set(curr.x, curr.y); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + setState(State.NONE); + break; + } + } + + setImageMatrix(matrix); + + // + // User-defined OnTouchListener + // + if(userTouchListener != null) { + userTouchListener.onTouch(v, event); + } + + // + // OnTouchImageViewListener is set: TouchImageView dragged by user. + // + if (touchImageViewListener != null) { + touchImageViewListener.onMove(); + } + + // + // indicate event was handled + // + return true; + } + } + + /** + * ScaleListener detects user two finger scaling and scales image. + * @author Ortiz + * + */ + private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + setState(State.ZOOM); + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true); + + // + // OnTouchImageViewListener is set: TouchImageView pinch zoomed by user. + // + if (touchImageViewListener != null) { + touchImageViewListener.onMove(); + } + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + super.onScaleEnd(detector); + setState(State.NONE); + boolean animateToZoomBoundary = false; + float targetZoom = normalizedScale; + if (normalizedScale > maxScale) { + targetZoom = maxScale; + animateToZoomBoundary = true; + + } else if (normalizedScale < minScale) { + targetZoom = minScale; + animateToZoomBoundary = true; + } + + if (animateToZoomBoundary) { + DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true); + compatPostOnAnimation(doubleTap); + } + } + } + + private void scaleImage(double deltaScale, float focusX, float focusY, boolean stretchImageToSuper) { + + float lowerScale, upperScale; + if (stretchImageToSuper) { + lowerScale = superMinScale; + upperScale = superMaxScale; + + } else { + lowerScale = minScale; + upperScale = maxScale; + } + + float origScale = normalizedScale; + normalizedScale *= deltaScale; + if (normalizedScale > upperScale) { + normalizedScale = upperScale; + deltaScale = upperScale / origScale; + } else if (normalizedScale < lowerScale) { + normalizedScale = lowerScale; + deltaScale = lowerScale / origScale; + } + + matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY); + fixScaleTrans(); + } + + /** + * DoubleTapZoom calls a series of runnables which apply + * an animated zoom in/out graphic to the image. + * @author Ortiz + * + */ + private class DoubleTapZoom implements Runnable { + + private long startTime; + private static final float ZOOM_TIME = 500; + private float startZoom, targetZoom; + private float bitmapX, bitmapY; + private boolean stretchImageToSuper; + private AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator(); + private PointF startTouch; + private PointF endTouch; + + DoubleTapZoom(float targetZoom, float focusX, float focusY, boolean stretchImageToSuper) { + setState(State.ANIMATE_ZOOM); + startTime = System.currentTimeMillis(); + this.startZoom = normalizedScale; + this.targetZoom = targetZoom; + this.stretchImageToSuper = stretchImageToSuper; + PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false); + this.bitmapX = bitmapPoint.x; + this.bitmapY = bitmapPoint.y; + + // + // Used for translating image during scaling + // + startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY); + endTouch = new PointF(viewWidth / 2, viewHeight / 2); + } + + @Override + public void run() { + float t = interpolate(); + double deltaScale = calculateDeltaScale(t); + scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper); + translateImageToCenterTouchPosition(t); + fixScaleTrans(); + setImageMatrix(matrix); + + // + // OnTouchImageViewListener is set: double tap runnable updates listener + // with every frame. + // + if (touchImageViewListener != null) { + touchImageViewListener.onMove(); + } + + if (t < 1f) { + // + // We haven't finished zooming + // + compatPostOnAnimation(this); + + } else { + // + // Finished zooming + // + setState(State.NONE); + } + } + + /** + * Interpolate between where the image should start and end in order to translate + * the image so that the point that is touched is what ends up centered at the end + * of the zoom. + * @param t + */ + private void translateImageToCenterTouchPosition(float t) { + float targetX = startTouch.x + t * (endTouch.x - startTouch.x); + float targetY = startTouch.y + t * (endTouch.y - startTouch.y); + PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY); + matrix.postTranslate(targetX - curr.x, targetY - curr.y); + } + + /** + * Use interpolator to get t + * @return + */ + private float interpolate() { + long currTime = System.currentTimeMillis(); + float elapsed = (currTime - startTime) / ZOOM_TIME; + elapsed = Math.min(1f, elapsed); + return interpolator.getInterpolation(elapsed); + } + + /** + * Interpolate the current targeted zoom and get the delta + * from the current zoom. + * @param t + * @return + */ + private double calculateDeltaScale(float t) { + double zoom = startZoom + t * (targetZoom - startZoom); + return zoom / normalizedScale; + } + } + + /** + * This function will transform the coordinates in the touch event to the coordinate + * system of the drawable that the imageview contain + * @param x x-coordinate of touch event + * @param y y-coordinate of touch event + * @param clipToBitmap Touch event may occur within view, but outside image content. True, to clip return value + * to the bounds of the bitmap size. + * @return Coordinates of the point touched, in the coordinate system of the original drawable. + */ + private PointF transformCoordTouchToBitmap(float x, float y, boolean clipToBitmap) { + matrix.getValues(m); + float origW = getDrawable().getIntrinsicWidth(); + float origH = getDrawable().getIntrinsicHeight(); + float transX = m[Matrix.MTRANS_X]; + float transY = m[Matrix.MTRANS_Y]; + float finalX = ((x - transX) * origW) / getImageWidth(); + float finalY = ((y - transY) * origH) / getImageHeight(); + + if (clipToBitmap) { + finalX = Math.min(Math.max(finalX, 0), origW); + finalY = Math.min(Math.max(finalY, 0), origH); + } + + return new PointF(finalX , finalY); + } + + /** + * Inverse of transformCoordTouchToBitmap. This function will transform the coordinates in the + * drawable's coordinate system to the view's coordinate system. + * @param bx x-coordinate in original bitmap coordinate system + * @param by y-coordinate in original bitmap coordinate system + * @return Coordinates of the point in the view's coordinate system. + */ + private PointF transformCoordBitmapToTouch(float bx, float by) { + matrix.getValues(m); + float origW = getDrawable().getIntrinsicWidth(); + float origH = getDrawable().getIntrinsicHeight(); + float px = bx / origW; + float py = by / origH; + float finalX = m[Matrix.MTRANS_X] + getImageWidth() * px; + float finalY = m[Matrix.MTRANS_Y] + getImageHeight() * py; + return new PointF(finalX , finalY); + } + + /** + * Fling launches sequential runnables which apply + * the fling graphic to the image. The values for the translation + * are interpolated by the Scroller. + * @author Ortiz + * + */ + private class Fling implements Runnable { + + CompatScroller scroller; + int currX, currY; + + Fling(int velocityX, int velocityY) { + setState(State.FLING); + scroller = new CompatScroller(context); + matrix.getValues(m); + + int startX = (int) m[Matrix.MTRANS_X]; + int startY = (int) m[Matrix.MTRANS_Y]; + int minX, maxX, minY, maxY; + + if (getImageWidth() > viewWidth) { + minX = viewWidth - (int) getImageWidth(); + maxX = 0; + + } else { + minX = maxX = startX; + } + + if (getImageHeight() > viewHeight) { + minY = viewHeight - (int) getImageHeight(); + maxY = 0; + + } else { + minY = maxY = startY; + } + + scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX, + maxX, minY, maxY); + currX = startX; + currY = startY; + } + + public void cancelFling() { + if (scroller != null) { + setState(State.NONE); + scroller.forceFinished(true); + } + } + + @Override + public void run() { + + // + // OnTouchImageViewListener is set: TouchImageView listener has been flung by user. + // Listener runnable updated with each frame of fling animation. + // + if (touchImageViewListener != null) { + touchImageViewListener.onMove(); + } + + if (scroller.isFinished()) { + scroller = null; + return; + } + + if (scroller.computeScrollOffset()) { + int newX = scroller.getCurrX(); + int newY = scroller.getCurrY(); + int transX = newX - currX; + int transY = newY - currY; + currX = newX; + currY = newY; + matrix.postTranslate(transX, transY); + fixTrans(); + setImageMatrix(matrix); + compatPostOnAnimation(this); + } + } + } + + @TargetApi(Build.VERSION_CODES.GINGERBREAD) + private class CompatScroller { + Scroller scroller; + OverScroller overScroller; + boolean isPreGingerbread; + + public CompatScroller(Context context) { + if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) { + isPreGingerbread = true; + scroller = new Scroller(context); + + } else { + isPreGingerbread = false; + overScroller = new OverScroller(context); + } + } + + public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { + if (isPreGingerbread) { + scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); + } else { + overScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); + } + } + + public void forceFinished(boolean finished) { + if (isPreGingerbread) { + scroller.forceFinished(finished); + } else { + overScroller.forceFinished(finished); + } + } + + public boolean isFinished() { + if (isPreGingerbread) { + return scroller.isFinished(); + } else { + return overScroller.isFinished(); + } + } + + public boolean computeScrollOffset() { + if (isPreGingerbread) { + return scroller.computeScrollOffset(); + } else { + overScroller.computeScrollOffset(); + return overScroller.computeScrollOffset(); + } + } + + public int getCurrX() { + if (isPreGingerbread) { + return scroller.getCurrX(); + } else { + return overScroller.getCurrX(); + } + } + + public int getCurrY() { + if (isPreGingerbread) { + return scroller.getCurrY(); + } else { + return overScroller.getCurrY(); + } + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private void compatPostOnAnimation(Runnable runnable) { + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { + postOnAnimation(runnable); + + } else { + postDelayed(runnable, 1000/60); + } + } + + private class ZoomVariables { + public float scale; + public float focusX; + public float focusY; + public ScaleType scaleType; + + public ZoomVariables(float scale, float focusX, float focusY, ScaleType scaleType) { + this.scale = scale; + this.focusX = focusX; + this.focusY = focusY; + this.scaleType = scaleType; + } + } + + private void printMatrixInfo() { + float[] n = new float[9]; + matrix.getValues(n); + Log.d(DEBUG, "Scale: " + n[Matrix.MSCALE_X] + " TransX: " + n[Matrix.MTRANS_X] + " TransY: " + n[Matrix.MTRANS_Y]); + } +} \ No newline at end of file diff --git a/src/third_parties/daveKoeller/AlphanumComparator.java b/src/third_parties/daveKoeller/AlphanumComparator.java new file mode 100644 index 00000000..e6bd6f38 --- /dev/null +++ b/src/third_parties/daveKoeller/AlphanumComparator.java @@ -0,0 +1,129 @@ +/* + * The Alphanum Algorithm is an improved sorting algorithm for strings + * containing numbers. Instead of sorting numbers in ASCII order like + * a standard sort, this algorithm sorts numbers in numeric order. + * + * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +package third_parties.daveKoeller; +import java.util.Comparator; + +import com.owncloud.android.datamodel.OCFile; + +/** + * This is an updated version with enhancements made by Daniel Migowski, + * Andre Bogus, and David Koelle + * + * To convert to use Templates (Java 1.5+): + * - Change "implements Comparator" to "implements Comparator" + * - Change "compare(Object o1, Object o2)" to "compare(String s1, String s2)" + * - Remove the type checking and casting in compare(). + * + * To use this class: + * Use the static "sort" method from the java.util.Collections class: + * Collections.sort(your list, new AlphanumComparator()); + */ +public class AlphanumComparator implements Comparator +{ + private final boolean isDigit(char ch) + { + return ch >= 48 && ch <= 57; + } + + /** Length of string is passed in for improved efficiency (only need to calculate it once) **/ + private final String getChunk(String s, int slength, int marker) + { + StringBuilder chunk = new StringBuilder(); + char c = s.charAt(marker); + chunk.append(c); + marker++; + if (isDigit(c)) + { + while (marker < slength) + { + c = s.charAt(marker); + if (!isDigit(c)) + break; + chunk.append(c); + marker++; + } + } else + { + while (marker < slength) + { + c = s.charAt(marker); + if (isDigit(c)) + break; + chunk.append(c); + marker++; + } + } + return chunk.toString(); + } + + public int compare(OCFile o1, OCFile o2) + { + String s1 = (String)o1.getRemotePath().toLowerCase(); + String s2 = (String)o2.getRemotePath().toLowerCase(); + + int thisMarker = 0; + int thatMarker = 0; + int s1Length = s1.length(); + int s2Length = s2.length(); + + while (thisMarker < s1Length && thatMarker < s2Length) + { + String thisChunk = getChunk(s1, s1Length, thisMarker); + thisMarker += thisChunk.length(); + + String thatChunk = getChunk(s2, s2Length, thatMarker); + thatMarker += thatChunk.length(); + + // If both chunks contain numeric characters, sort them numerically + int result = 0; + if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) + { + // Simple chunk comparison by length. + int thisChunkLength = thisChunk.length(); + result = thisChunkLength - thatChunk.length(); + // If equal, the first different number counts + if (result == 0) + { + for (int i = 0; i < thisChunkLength; i++) + { + result = thisChunk.charAt(i) - thatChunk.charAt(i); + if (result != 0) + { + return result; + } + } + } + } else + { + result = thisChunk.compareTo(thatChunk); + } + + if (result != 0) + return result; + } + + return s1Length - s2Length; + } +} diff --git a/src/third_parties/daveKoeller/lgpl-2.1.txt b/src/third_parties/daveKoeller/lgpl-2.1.txt new file mode 100644 index 00000000..4362b491 --- /dev/null +++ b/src/third_parties/daveKoeller/lgpl-2.1.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it!