path = owncloud-android-library
url = git://github.com/owncloud/android-library.git
branch = develop
+[submodule "ocdoc"]
+ path = user_manual/ocdoc
+ url = https://github.com/owncloud/documentation
+ branch = master
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.14.0'
+ classpath 'com.android.tools.build:gradle:1.0.0'
}
}
-#Wed Oct 15 10:45:44 CEST 2014
+#Mon Jan 19 09:42:11 CET 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
-Subproject commit 8261865ff24c1bfc05be19ae9364a66dac8f26c3
+Subproject commit e87f5f25ad91950d47ec9b6fa01401360cd7ec8d
<string name="actionbar_settings">Configuració</string>
<string name="actionbar_see_details">Detalls</string>
<string name="actionbar_send_file">Envia</string>
+ <string name="actionbar_sort">Ordena</string>
+ <string name="actionbar_sort_title">Ordena per</string>
+ <string-array name="actionbar_sortby">
+ <item>A-Z</item>
+ <item>Més nou - Més antic</item>
+ </string-array>
<!--TODO re-enable when server-side folder size calculation is available
<item>Biggest - Smallest</item>-->
<string name="prefs_category_general">General</string>
<string name="auth_redirect_non_secure_connection_title">Bezpečné spojení je přesměrováno na nezabezpečenou trasu.</string>
<string name="actionbar_logger">Logy</string>
<string name="log_send_history_button">Odeslat historii</string>
- <string name="log_send_no_mail_app">Nebyla nalezena žádná aplikace pro zasílání logů. Nainstalujte poštovní aplikaci!</string>
+ <string name="log_send_no_mail_app">Nebyla nalezena žádná aplikace pro odesílání logů. Nainstalujte poštovní aplikaci!</string>
<string name="log_send_mail_subject">%1$s logy aplikace pro Android</string>
<string name="log_progress_dialog_text">Načítání dat…</string>
<string name="saml_authentication_required_text">Vyžadováno přihlášení</string>
<string name="prefs_category_instant_uploading">Okamžitá odesílání</string>
<string name="prefs_category_security">Zabezpečení</string>
<string name="prefs_instant_video_upload_path_title">Cesta pro nahrávání videí</string>
+ <string name="download_folder_failed_content">Stažení adresáře %1$s nemohlo být dokončeno</string>
<string name="shared_subject_header">sdílené</string>
<string name="with_you_subject_header">s vámi</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="prefs_category_instant_uploading">Øjeblikkelige uploads</string>
<string name="prefs_category_security">Sikkerhed</string>
<string name="prefs_instant_video_upload_path_title">Sti til videoupload</string>
+ <string name="download_folder_failed_content">Download af %1$s mappe kunne ikke fuldføres</string>
<string name="shared_subject_header">delt</string>
<string name="with_you_subject_header">med dig</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="prefs_category_instant_uploading">Sofortiges Hochladen</string>
<string name="prefs_category_security">Sicherheit</string>
<string name="prefs_instant_video_upload_path_title">Verzeichnis zum Hochladen der Videos</string>
+ <string name="download_folder_failed_content">Herunterladen des %1$s - Ordners konnte nicht abgeschlossen werden</string>
<string name="shared_subject_header">geteilt</string>
<string name="with_you_subject_header">Mit Ihnen</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="prefs_category_instant_uploading">Sofortiges Hochladen</string>
<string name="prefs_category_security">Sicherheit</string>
<string name="prefs_instant_video_upload_path_title">Verzeichnis zum Hochladen der Videos</string>
+ <string name="download_folder_failed_content">Herunterladen des %1$s - Ordners konnte nicht abgeschlossen werden</string>
<string name="shared_subject_header">geteilt</string>
<string name="with_you_subject_header">Mit Dir</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="actionbar_sort">Ταξινόμηση</string>
<string name="actionbar_sort_title">Ταξινόμηση κατά</string>
<string-array name="actionbar_sortby">
- <item>A-Z</item>
+ <item>A-Ω</item>
<item>Νεότερο - Παλαιότερο</item>
</string-array>
<!--TODO re-enable when server-side folder size calculation is available
<string name="uploader_error_forbidden_content">Ο %1$s δεν επιτρέπεται να έχει πρόσβαση στο κοινόχρηστο περιεχόμενο</string>
<string name="uploader_info_uploading">Μεταφόρτωση</string>
<string name="file_list_seconds_ago">δευτερόλεπτα πριν</string>
- <string name="file_list_empty">Î\94εν Ï\85Ï\80άÏ\81Ï\87ει Ï\84ίÏ\80οÏ\84α εδÏ\8e. Î\91νεβάστε κάτι!</string>
- <string name="file_list_loading">Φόρτωση ...</string>
+ <string name="file_list_empty">Î\94εν Ï\85Ï\80άÏ\81Ï\87ει Ï\84ίÏ\80οÏ\84α εδÏ\8e. Î\9cεÏ\84αÏ\86οÏ\81Ï\84Ï\8eστε κάτι!</string>
+ <string name="file_list_loading">Φόρτωση...</string>
<string name="local_file_list_empty">Δεν υπάρχουν αρχεία σε αυτό τον φάκελο.</string>
<string name="file_list_folder">φάκελος</string>
<string name="file_list_folders">φάκελοι</string>
διαχειριστή σας.</string>
<string name="share_link_file_no_exist">Αδύνατη η κοινή χρήση. Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει</string>
<string name="share_link_file_error">Ένα σφάλμα προέκυψε κατά την προσπάθεια διαμοιρασμού αυτού του αρχείου ή φακέλου</string>
- <string name="unshare_link_file_no_exist">Αδύνατη η διακοπή κοινής χρήσης. Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει</string>
+ <string name="unshare_link_file_no_exist">Αδύνατη η διακοπή κοινής χρήσης. Παρακαλώ ελέγξτε αν το αρχείο υπάρχει</string>
<string name="unshare_link_file_error">Ένα σφάλμα προέκυψε κατά τη διάρκεια ακύρωσης διαμοιρασμού αυτού του αρχείου ή φακέλου</string>
<string name="activity_chooser_send_file_title">Αποστολή</string>
<string name="copy_link">Αντιγραφή συνδέσμου</string>
<string name="forbidden_permissions_rename">για να μετονομάσετε αυτό το αρχείο</string>
<string name="forbidden_permissions_delete">για να διαγράψετε αυτό το αρχείο</string>
<string name="share_link_forbidden_permissions">για να μοιραστείτε αυτό το αρχείο</string>
- <string name="unshare_link_forbidden_permissions">για να μη μοιÏ\81αÏ\83Ï\84είÏ\84ε αÏ\85Ï\84Ï\8c Ï\84ο αÏ\81Ï\87είο</string>
+ <string name="unshare_link_forbidden_permissions">για να διακÏ\8cÏ\88εÏ\84ε Ï\84ο διαμοιÏ\81αÏ\83μÏ\8c αÏ\85Ï\84οÏ\8d Ï\84οÏ\85 αÏ\81Ï\87είοÏ\85</string>
<string name="forbidden_permissions_create">για να δημιουργήσετε το αρχείο</string>
- <string name="uploader_upload_forbidden_permissions">για να μεταφορτώσετε σε αυτό τον κατάλογο</string>
+ <string name="uploader_upload_forbidden_permissions">για να μεταφορτώσετε σε αυτό το φάκελο</string>
<string name="downloader_download_file_not_found">Αυτό το αρχείο δεν είναι πια διαθέσιμο στο διακομιστή</string>
<string name="prefs_category_accounts">Λογαριασμοί</string>
<string name="prefs_add_account">Προσθήκη λογαριασμού</string>
<string name="auth_redirect_non_secure_connection_title">Ασφαλής σύνδεση ανακατευθύνεται σε μια μη ασφαλή διαδρομή.</string>
<string name="actionbar_logger">Αρχεία καταγραφών</string>
- <string name="log_send_history_button">Î\91Ï\80οÏ\83Ï\84ολή ιστορικού</string>
- <string name="log_send_no_mail_app">Î\94εν ενÏ\84οÏ\80ίÏ\83Ï\84ηκε εÏ\86αÏ\81μογή αÏ\80οÏ\83Ï\84ολήÏ\82 αναÏ\86οÏ\81Ï\8eν Ï\83Ï\85Ï\83Ï\84ήμαÏ\84οÏ\82. Î\95γκαÏ\84αÏ\83Ï\84ήÏ\83Ï\84ε Ï\84ο mail app!</string>
- <string name="log_send_mail_subject">%1$s αναÏ\86οÏ\81ÎÏ\82 Ï\84οÏ\85 Android app</string>
+ <string name="log_send_history_button">Î\91Ï\80οÏ\83Ï\84ολή Î\99στορικού</string>
+ <string name="log_send_no_mail_app">Î\94εν ενÏ\84οÏ\80ίÏ\83Ï\84ηκε εÏ\86αÏ\81μογή αÏ\80οÏ\83Ï\84ολήÏ\82 αναÏ\86οÏ\81Ï\8eν Ï\83Ï\85Ï\83Ï\84ήμαÏ\84οÏ\82. Î\95γκαÏ\84αÏ\83Ï\84ήÏ\83Ï\84ε Ï\84ην εÏ\86αÏ\81μογή Î\97λ. ΤαÏ\87Ï\85δÏ\81ομείοÏ\85!!</string>
+ <string name="log_send_mail_subject">%1$s αναÏ\86οÏ\81ÎÏ\82 Ï\84ηÏ\82 εÏ\86αÏ\81μογήÏ\82 Android</string>
<string name="log_progress_dialog_text">Φόρτωση δεδομένων....</string>
<string name="saml_authentication_required_text">Απαιτείται πιστοποίηση</string>
- <string name="saml_authentication_wrong_pass">Εσφαλμένο συνθηματικό</string>
+ <string name="saml_authentication_wrong_pass">Εσφαλμένος κωδικός πρόσβασης</string>
<string name="actionbar_move">Μετακίνηση</string>
<string name="file_list_empty_moving">Δεν υπάρχει τίποτα εδώ. Μπορείτε να προσθέσετε ένα φάκελο!</string>
<string name="folder_picker_choose_button_text">Επιλέξτε</string>
- <string name="move_file_not_found">Αδύνατη η μετακίνηση. Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει</string>
+ <string name="move_file_not_found">Αδύνατη η μετακίνηση. Παρακαλώ ελέγξτε αν το αρχείο υπάρχει</string>
<string name="move_file_invalid_into_descendent">Δεν είναι δυνατό να μετακινηθεί ο φάκελος σε έναν απογονικό</string>
<string name="move_file_invalid_overwrite">Το αρχείο υπάρχει ήδη στο φάκελο προορισμού</string>
<string name="move_file_error">Ένα σφάλμα προέκυψε κατά την προσπάθεια μετακίνησης αυτού του αρχείου ή φακέλου</string>
<string name="prefs_category_instant_uploading">Στιγμιαίες Μεταφορτώσεις</string>
<string name="prefs_category_security">Ασφάλεια</string>
<string name="prefs_instant_video_upload_path_title">Διαδρομή Μεταφόρτωσης Βίντεο</string>
+ <string name="download_folder_failed_content">Η λήψη του φακέλου %1$s δεν ολοκληρώθηκε με επιτυχία.</string>
<string name="shared_subject_header">μοιρασμένο </string>
+ <string name="with_you_subject_header">με εσένα</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="prefs_category_instant_uploading">Instant Uploads</string>
<string name="prefs_category_security">Security</string>
<string name="prefs_instant_video_upload_path_title">Upload Video Path</string>
+ <string name="download_folder_failed_content">Download of %1$s folder could not be completed</string>
<string name="shared_subject_header">shared</string>
<string name="with_you_subject_header">with you</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="auth_host_url">Dirección del servidor https://…</string>
<string name="auth_username">Nombre de usuario</string>
<string name="auth_password">Contraseña</string>
- <string name="auth_register">Nuevo para %1$s?</string>
+ <string name="auth_register">¿Nuevo en %1$s?</string>
<string name="sync_string_files">Archivos</string>
<string name="setup_btn_connect">Conectar</string>
<string name="uploader_btn_upload_text">Subir</string>
<string name="ssl_validator_header">La identidad del sitio no puede ser verificada</string>
<string name="ssl_validator_reason_cert_not_trusted">- El certificado del servidor no es de confianza</string>
<string name="ssl_validator_reason_cert_expired">- El certificado del servidor expiró</string>
- <string name="ssl_validator_reason_cert_not_yet_valid">- El certificado del servidor es de una fecha que aún no llega</string>
+ <string name="ssl_validator_reason_cert_not_yet_valid">- El certificado del servidor es de una fecha que aún no ha llegado</string>
<string name="ssl_validator_reason_hostname_not_verified">- La URL no coincide con el nombre de dominio del certificado</string>
<string name="ssl_validator_question">¿Confías de todas formas en este certificado?</string>
<string name="ssl_validator_not_saved">El certificado no pudo ser guardado</string>
<string name="prefs_category_accounts">Cuentas</string>
<string name="prefs_add_account">Agregar cuenta</string>
<string name="auth_redirect_non_secure_connection_title">La conexión segura está siendo desviada por una ruta insegura.</string>
- <string name="actionbar_logger">Logs</string>
+ <string name="actionbar_logger">Registros</string>
<string name="log_send_history_button">Enviar historial</string>
<string name="log_send_no_mail_app">No se ha encontrado una app para enviar logs. Instale la app mail!</string>
<string name="log_send_mail_subject">Se han encontrado %1$s logs de la app Android</string>
<string name="forbidden_permissions_move">para mover este archivo</string>
<string name="prefs_category_instant_uploading">Subidas instantáneas</string>
<string name="prefs_category_security">Seguridad</string>
- <string name="prefs_instant_video_upload_path_title">Ruta de vídeo de subida</string>
+ <string name="prefs_instant_video_upload_path_title">Guardar videos subidos en la carpeta:</string>
+ <string name="download_folder_failed_content">Descarga de la carpeta %1$s no ha podido ser completada</string>
<string name="shared_subject_header">compartido</string>
<string name="with_you_subject_header">contigo</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="downloader_download_file_not_found">Fitxategia jadanik ez dago eskuragarri zerbitzarian</string>
<string name="prefs_category_accounts">Kontuak</string>
<string name="prefs_add_account">Gehitu kontua</string>
+ <string name="log_send_mail_subject">%1$s Android aplikazioaren egunerokoak</string>
+ <string name="log_progress_dialog_text">Datuak kargatzen...</string>
<string name="saml_authentication_required_text">Autentikazioa beharrezkoa</string>
<string name="saml_authentication_wrong_pass">Pasahitz okerra</string>
<string name="actionbar_move">Mugitu</string>
<string name="prefs_category_instant_uploading">Berehalako Igoerak</string>
<string name="prefs_category_security">Segurtasuna</string>
<string name="shared_subject_header">konpartitua</string>
+ <string name="with_you_subject_header">zurekin</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="pincode_wrong">Väärä sovelluksen PIN</string>
<string name="pincode_removed">Sovelluksen PIN poistettu</string>
<string name="pincode_stored">Sovelluksen PIN-koodi tallennettu</string>
+ <string name="media_notif_ticker">%1$s-musiikkisoitin</string>
<string name="media_state_playing">%1$s (toistetaan)</string>
<string name="media_state_loading">%1$s (ladataan)</string>
<string name="media_err_nothing_to_play">Mediatiedostoa ei löytynyt</string>
<string name="media_err_no_account">Tiliä ei määritetty</string>
<string name="media_err_not_in_owncloud">Tiedosto ei ole kelvollisella tilillä</string>
+ <string name="media_err_unsupported">Mediakoodekki ei ole tuettu</string>
<string name="media_err_io">Mediatiedoston luku ei onnistunut</string>
+ <string name="media_err_malformed">Mediatiedostoa ei ole koodattu kelvollisesti</string>
<string name="media_err_timeout">Aikakatkaisu toistoa yrittäessä</string>
<string name="media_err_invalid_progressive_playback">Mediatiedostoa ei voi suoratoistaa</string>
<string name="media_err_security_ex">Turvallisuusvirhe yrittäessä toistaa kohdetta %1$s</string>
<string name="prefs_category_general">Général</string>
<string name="prefs_category_more">Plus</string>
<string name="prefs_accounts">Comptes</string>
- <string name="prefs_manage_accounts">Gestion des comptes utilisateur</string>
+ <string name="prefs_manage_accounts">Gestion des comptes</string>
<string name="prefs_pincode">Code de sécurité</string>
<string name="prefs_pincode_summary">Protéger l\'accès à l\'application</string>
<string name="prefs_instant_upload">Envoi instantané des photos</string>
<string name="common_choose_account">Choisissez un compte</string>
<string name="sync_fail_ticker">La synchronisation a échoué</string>
<string name="sync_fail_ticker_unauthorized">Échec de la synchronisation, vous devez vous reconnecter à nouveau</string>
- <string name="sync_fail_content">La synchronisation de %1$s ne peut pas être complétée</string>
- <string name="sync_fail_content_unauthorized">Mot de passe invalide pour %1$s</string>
+ <string name="sync_fail_content">La synchronisation de %1$s n\'a pu être terminée</string>
+ <string name="sync_fail_content_unauthorized">Mot de passe non valide pour %1$s</string>
<string name="sync_conflicts_in_favourites_ticker">Des conflits ont été trouvés</string>
- <string name="sync_conflicts_in_favourites_content">%1$d fichiers à garder synchronisés n\'ont put être synchronisé</string>
+ <string name="sync_conflicts_in_favourites_content">%1$d fichiers à garder synchronisés n\'ont pu être synchronisés</string>
<string name="sync_fail_in_favourites_ticker">La synchronisation des fichiers a échoué</string>
<string name="sync_fail_in_favourites_content">Le contenu de %1$d fichiers n\'a pu être synchronisé (%2$d conflits)</string>
<string name="sync_foreign_files_forgotten_ticker">Certains fichiers locaux ont été oubliés</string>
<string name="media_err_unsupported">Le codec de ce média n\'est pas pris en charge </string>
<string name="media_err_io">Le fichier média ne peut pas être lu</string>
<string name="media_err_malformed">Le fichier média n\'est pas correctement encodé</string>
- <string name="media_err_timeout">Délai dépassé pour la lecture du morceau.</string>
+ <string name="media_err_timeout">Délai dépassé pour la lecture du morceau</string>
<string name="media_err_invalid_progressive_playback">Le fichier média ne peut pas être diffusé</string>
<string name="media_err_unknown">Le fichier média ne peut être joué avec le lecteur standard</string>
<string name="media_err_security_ex">Erreur de sécurité à la lecture de %1$s</string>
<string name="prefs_category_instant_uploading">Envoi instantané</string>
<string name="prefs_category_security">Sécurité</string>
<string name="prefs_instant_video_upload_path_title">Répertoire d\'envoi des vidéos</string>
+ <string name="download_folder_failed_content">Le téléchargement du dossier %1$s n\'a pas pu être achevé complètement</string>
<string name="shared_subject_header">partagé(e)</string>
<string name="with_you_subject_header">avec vous</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="prefs_category_instant_uploading">Envío instantáneo</string>
<string name="prefs_category_security">Seguranza</string>
<string name="prefs_instant_video_upload_path_title">Enviar a ruta do vídeo</string>
+ <string name="download_folder_failed_content">A descarga do cartafol %1$s non se puido completar</string>
<string name="shared_subject_header">compartido</string>
<string name="with_you_subject_header">con vostede</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<?xml version='1.0' encoding='UTF-8'?>
<resources>
+ <string name="about_android">%1$s Android aplikacija</string>
+ <string name="about_version">verzija %1$s</string>
+ <string name="actionbar_sync">Osvježi račun</string>
<string name="actionbar_upload">Učitaj</string>
+ <string name="actionbar_upload_from_apps">Sadržaj iz drugih aplikacija</string>
<string name="actionbar_upload_files">Datoteke</string>
+ <string name="actionbar_open_with">Otvori sa</string>
<string name="actionbar_mkdir">Nova mapa</string>
<string name="actionbar_settings">Postavke</string>
+ <string name="actionbar_see_details">Detalji</string>
<string name="actionbar_send_file">Pošaljite</string>
+ <string name="actionbar_sort">Sortiraj</string>
+ <string name="actionbar_sort_title">Sortiraj po</string>
+ <string-array name="actionbar_sortby">
+ <item>A-Z</item>
+ <item>Najnoviji- Stariji</item>
+ </string-array>
<!--TODO re-enable when server-side folder size calculation is available
<item>Biggest - Smallest</item>-->
<string name="prefs_category_general">Općenito</string>
<string name="prefs_category_more">više</string>
<string name="prefs_accounts">Korisnićki računi</string>
+ <string name="prefs_manage_accounts">Upravljaj računima</string>
+ <string name="prefs_pincode">PIN aplikacije</string>
+ <string name="prefs_pincode_summary">Zaštit svog klijenta</string>
+ <string name="prefs_instant_upload">Trenutni upload slika</string>
+ <string name="prefs_instant_upload_summary">Trenutni upload slika snimljenih kamerom</string>
+ <string name="prefs_instant_video_upload">Trenutni upload videa</string>
+ <string name="prefs_instant_video_upload_summary">Trenutni upload videa snimljen kamerom</string>
<string name="prefs_help">Pomoć</string>
<string name="auth_username">Korisničko ime</string>
<string name="auth_password">Lozinka</string>
<string name="auth_trying_to_login">Trying to login…</string>
<string name="common_rename">Promjeni ime</string>
<string name="common_remove">Makni</string>
+ <string name="ssl_validator_btn_details_see">Detalji</string>
<string name="activity_chooser_send_file_title">Pošaljite</string>
<string name="empty"></string>
<string name="prefs_category_accounts">Korisnićki računi</string>
<string name="prefs_category_instant_uploading">Caricamenti istantanei</string>
<string name="prefs_category_security">Protezione</string>
<string name="prefs_instant_video_upload_path_title">Percorso di caricamento video</string>
+ <string name="download_folder_failed_content">Lo scaricamento della cartella %1$s non può essere completato</string>
<string name="shared_subject_header">condiviso</string>
<string name="with_you_subject_header">con te</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="file_list_empty">ここには何もありません。何かアップロードしてください。</string>
<string name="file_list_loading">読込中 ...</string>
<string name="local_file_list_empty">このフォルダーにはファイルがありません。</string>
- <string name="file_list_folder">フォルダ</string>
- <string name="file_list_folders">フォルダ</string>
+ <string name="file_list_folder">フォルダー</string>
+ <string name="file_list_folders">フォルダー</string>
<string name="file_list_file">ファイル</string>
<string name="file_list_files">ファイル</string>
<string name="filedetails_select_file">ファイルをタップすると追加情報が表示されます。</string>
<string name="auth_redirect_non_secure_connection_title">暗号化接続は非暗号化接続にリダイレクトされました。</string>
<string name="actionbar_logger">ログ</string>
<string name="log_send_history_button">ログを送信</string>
- <string name="log_send_no_mail_app">ログを送るアプリが見つかりませんでした。メールアプリをインストールして下さい。</string>
+ <string name="log_send_no_mail_app">ログを送信するアプリが見つかりませんでした。メールアプリをインストールしてください。</string>
<string name="log_send_mail_subject">%1$s アンドロイドアプリログ</string>
<string name="log_progress_dialog_text">読込中 ...</string>
<string name="saml_authentication_required_text">認証を必要とする</string>
<string name="saml_authentication_wrong_pass">無効なパスワード</string>
<string name="actionbar_move">移動</string>
- <string name="file_list_empty_moving">ファイルが有りません。フォルダを追加してください。</string>
+ <string name="file_list_empty_moving">何もありません。フォルダーを追加してください。</string>
<string name="folder_picker_choose_button_text">選択</string>
<string name="move_file_not_found">移動できません。ファイルがあるか確認してください。</string>
- <string name="move_file_invalid_into_descendent">ã\83\95ã\82©ã\83«ã\83\80ã\82\92å\90ã\83\95ã\82©ã\83«ã\83\80へ移動することはできません。</string>
- <string name="move_file_invalid_overwrite">そのファイルは、宛先フォルダに既に存在しています。</string>
+ <string name="move_file_invalid_into_descendent">ã\83\95ã\82©ã\83«ã\83\80ã\83¼ã\82\92å\90ã\83\95ã\82©ã\83«ã\83\80ã\83¼へ移動することはできません。</string>
+ <string name="move_file_invalid_overwrite">そのファイルは宛先フォルダーにすでに存在します。</string>
<string name="move_file_error">このファイルまたはフォルダーを移動する際にエラーが発生しました</string>
<string name="forbidden_permissions_move">このファイルを移動</string>
<string name="prefs_category_instant_uploading">自動アップロード</string>
<?xml version='1.0' encoding='UTF-8'?>
<resources>
+ <string name="about_android">%1$s កម្មវិធីអានដ្រយ</string>
+ <string name="about_version">ជំនាន់ %1$s</string>
<string name="actionbar_upload">ផ្ទុកឡើង</string>
<string name="actionbar_upload_files">ឯកសារ</string>
+ <string name="actionbar_open_with">បើកជាមួយ</string>
<string name="actionbar_mkdir">ថតថ្មី</string>
<string name="actionbar_settings">ការកំណត់</string>
<string name="actionbar_see_details">ព័ត៌មានលម្អិត</string>
<string name="actionbar_send_file">ផ្ញើ</string>
+ <string name="actionbar_sort">តម្រៀប</string>
+ <string name="actionbar_sort_title">តម្រៀបដោយ</string>
+ <string-array name="actionbar_sortby">
+ <item>A-Z</item>
+ <item>ថ្មីបំផុត-ចាស់បំផុត</item>
+ </string-array>
<!--TODO re-enable when server-side folder size calculation is available
<item>Biggest - Smallest</item>-->
<string name="prefs_category_general">ទូទៅ</string>
<string name="prefs_category_more">ច្រើនទៀត</string>
<string name="prefs_accounts">គណនី</string>
<string name="prefs_manage_accounts">គ្រប់គ្រងគណនី</string>
+ <string name="prefs_pincode">ភីនកូដ កម្មវិធី</string>
<string name="prefs_help">ជំនួយ</string>
<string name="auth_username">ឈ្មោះអ្នកប្រើ</string>
<string name="auth_password">ពាក្យសម្ងាត់</string>
<string name="common_error">កំហុស</string>
<string name="common_loading">កំពុងដំណើរការ</string>
<string name="common_error_unknown">មិនស្គាល់កំហុស</string>
+ <string name="about_title">អំពី</string>
<string name="change_password">ប្តូរពាក្យសម្ងាត់</string>
<string name="delete_account">លប់គណនី</string>
<string name="create_account">បង្កើតគណនី</string>
<string name="fd_keep_in_sync">រក្សាឯកសាររហូតដល់កាលបរិច្ឆេទ</string>
<string name="common_rename">ប្ដូរឈ្មោះ</string>
<string name="common_remove">ដកចេញ</string>
+ <string name="confirmation_remove_local">ទីកន្លែងតែមួយ</string>
+ <string name="confirmation_remove_remote">ដកចេញពីសឺវឺ</string>
+ <string name="confirmation_remove_remote_and_local">បញ្ជារ និងទីតាំង</string>
<string name="remove_success_msg">ការដកយកចេញបានជោគជ័យ</string>
<string name="remove_fail_msg">ការដកយកចេញបានបរាជ័យ</string>
<string name="rename_dialog_title">បញ្ចូលឈ្មោះថ្មី</string>
<string name="actionbar_upload">업로드</string>
<string name="actionbar_upload_from_apps">다른 앱의 콘텐츠</string>
<string name="actionbar_upload_files">파일</string>
- <string name="actionbar_open_with">로 열기</string>
+ <string name="actionbar_open_with">ë\8b¤ì\9d\8cì\9c¼ë¡\9c ì\97´ê¸°</string>
<string name="actionbar_mkdir">새 폴더</string>
<string name="actionbar_settings">설정</string>
- <string name="actionbar_see_details">ì\84¸ë¶\80ë\82´ì\9a©</string>
+ <string name="actionbar_see_details">ì\9e\90ì\84¸í\95\9c ì \95ë³´</string>
<string name="actionbar_send_file">보내기</string>
+ <string name="actionbar_sort">정렬</string>
+ <string name="actionbar_sort_title">정렬 순서</string>
+ <string-array name="actionbar_sortby">
+ <item>가나다</item>
+ <item>최신 - 이전</item>
+ </string-array>
<!--TODO re-enable when server-side folder size calculation is available
<item>Biggest - Smallest</item>-->
<string name="prefs_category_general">일반</string>
- <string name="prefs_category_more">더 중요함</string>
+ <string name="prefs_category_more">더 보기</string>
<string name="prefs_accounts">계정</string>
<string name="prefs_manage_accounts">계정 관리</string>
<string name="prefs_pincode">앱 암호</string>
<string name="prefs_pincode_summary">내 클라이언트 보호</string>
- <string name="prefs_log_title">로깅 허용</string>
- <string name="prefs_log_summary">이건 로그 문제에 사용됩니다</string>
+ <string name="prefs_instant_upload">사진 즉시 업로드</string>
+ <string name="prefs_instant_upload_summary">카메라로 찍은 사진 즉시 업로드</string>
+ <string name="prefs_instant_video_upload">동영상 즉시 업로드</string>
+ <string name="prefs_instant_video_upload_summary">카메라로 찍은 동영상 즉시 업로드</string>
+ <string name="prefs_log_title">로그 기록 사용</string>
+ <string name="prefs_log_summary">문제점을 기록하는 데 사용됩니다</string>
<string name="prefs_log_title_history">로그 기록</string>
<string name="prefs_log_summary_history">여기서 기록된 로그를 보여줍니다</string>
- <string name="prefs_log_delete_history_button">역사 삭제하기</string>
+ <string name="prefs_log_delete_history_button">과거 기록 삭제</string>
<string name="prefs_help">도움말</string>
- <string name="prefs_recommend">친구들에게 권하기</string>
+ <string name="prefs_recommend">친구에게 추천하기</string>
<string name="prefs_feedback">피드백</string>
- <string name="prefs_imprint">임프린트</string>
- <string name="recommend_subject">%1$s 을 스마트폰에서 사용해보세요!</string>
+ <string name="prefs_imprint">법적 고지</string>
+ <string name="prefs_remember_last_share_location">공유 위치 기억하기</string>
+ <string name="prefs_remember_last_upload_location_summary">마지막 공유 업로드 위치 기억하기</string>
+ <string name="recommend_subject">%1$s을(를) 스마트폰에서 사용해 보세요!</string>
+ <string name="recommend_text">%1$s을(를) 스마트폰에서 사용해 보는 것을 추천합니다!\n다운로드 링크: %2$s</string>
<string name="auth_check_server">서버 확인</string>
<string name="auth_host_url">서버 주소 https://…</string>
<string name="auth_username">사용자 이름</string>
<string name="sync_string_files">파일</string>
<string name="setup_btn_connect">접속</string>
<string name="uploader_btn_upload_text">업로드</string>
+ <string name="uploader_top_message">업로드 폴더 선택:</string>
<string name="uploader_wrn_no_account_title">계정 없음</string>
<string name="uploader_wrn_no_account_text">이 장치에 %1$s 계정이 없습니다. 먼저 계정을 설정하십시오.</string>
<string name="uploader_wrn_no_account_setup_btn_text">설정</string>
<string name="uploader_wrn_no_content_text">받은 콘텐츠가 없습니다. 업로드할 항목이 없습니다.</string>
<string name="uploader_error_forbidden_content">%1$s에서 공유된 콘텐츠에 접근할 수 없습니다</string>
<string name="uploader_info_uploading">업로드 중</string>
- <string name="file_list_seconds_ago">ì´\88 ì \84</string>
+ <string name="file_list_seconds_ago">ì´\88 ì§\80ë\82¨</string>
<string name="file_list_empty">내용이 없습니다. 업로드할 수 있습니다!</string>
+ <string name="file_list_loading">불러오는 중...</string>
+ <string name="local_file_list_empty">이 폴더에 파일이 없습니다.</string>
<string name="file_list_folder">폴더</string>
<string name="file_list_folders">폴더</string>
<string name="file_list_file">파일</string>
<string name="filedetails_created">만든 날짜:</string>
<string name="filedetails_modified">수정한 날짜:</string>
<string name="filedetails_download">다운로드</string>
- <string name="filedetails_sync_file">파일 새로고침</string>
+ <string name="filedetails_sync_file">파일 새로 고침</string>
<string name="filedetails_renamed_in_upload_msg">업로드 중 파일 이름을 %1$s(으)로 변경하였습니다</string>
<string name="action_share_file">링크 공유</string>
+ <string name="action_unshare_file">링크 공유 해제</string>
<string name="common_yes">예</string>
<string name="common_no">아니요</string>
<string name="common_ok">확인</string>
<string name="common_save_exit">저장하고 끝내기</string>
<string name="common_error">오류</string>
<string name="common_loading">불러오는 중...</string>
- <string name="common_error_unknown">알수없는 오류</string>
+ <string name="common_error_unknown">알 수 없는 오류</string>
<string name="about_title">정보</string>
<string name="change_password">암호 변경</string>
<string name="delete_account">계정 삭제</string>
<string name="uploader_upload_succeeded_ticker">업로드 성공</string>
<string name="uploader_upload_succeeded_content_single">%1$s을(를) 업로드하였습니다</string>
<string name="uploader_upload_failed_ticker">업로드 실패</string>
- <string name="uploader_upload_failed_content_single">%1$s을(를) 업로드할 수 없었습니다</string>
+ <string name="uploader_upload_failed_content_single">%1$s을(를) 업로드할 수 없습니다</string>
+ <string name="uploader_upload_failed_credentials_error">업로드가 실패하였습니다. 다시 로그인하십시오</string>
<string name="downloader_download_in_progress_ticker">다운로드 중...</string>
<string name="downloader_download_in_progress_content">%1$d%% %2$s 다운로드 중</string>
<string name="downloader_download_succeeded_ticker">다운로드 성공</string>
<string name="downloader_download_succeeded_content">%1$s을(를) 다운로드하였습니다</string>
<string name="downloader_download_failed_ticker">다운로드 실패</string>
- <string name="downloader_download_failed_content">%1$sì\9d\84(를) ë\8b¤ì\9a´ë¡\9cë\93\9cí\95 ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+ <string name="downloader_download_failed_content">%1$s을(를) 다운로드할 수 없습니다</string>
<string name="downloader_not_downloaded_yet">아직 다운로드 되지 않았습니다</string>
+ <string name="downloader_download_failed_credentials_error">다운로드가 실패하였습니다. 다시 로그인하십시오</string>
<string name="common_choose_account">계정 선택</string>
<string name="sync_fail_ticker">동기화 실패</string>
+ <string name="sync_fail_ticker_unauthorized">동기화가 실패하였습니다. 다시 로그인하십시오</string>
<string name="sync_fail_content">%1$s와(과) 동기화할 수 없었습니다</string>
- <string name="sync_fail_content_unauthorized">%1$sì\97\90 ë\8c\80í\95\9c ë¹\84ë°\80ë²\88í\98¸ê°\80 í\8b\80립니다</string>
+ <string name="sync_fail_content_unauthorized">%1$sì\9d\98 ì\95\94í\98¸ê°\80 ì\98¬ë°\94르ì§\80 ì\95\8aì\8aµ니다</string>
<string name="sync_conflicts_in_favourites_ticker">충돌하는 항목 발견됨</string>
- <string name="sync_conflicts_in_favourites_content">ë\8f\99기í\99\94ë\90\9c í\8c\8cì\9d¼ ì¤\91 %1$dê°\9c를 ë\8f\99기í\99\94í\95 ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+ <string name="sync_conflicts_in_favourites_content">동기화된 파일 중 %1$d개를 동기화할 수 없습니다</string>
<string name="sync_fail_in_favourites_ticker">파일을 동기화할 수 없었습니다</string>
- <string name="sync_fail_in_favourites_content">파일 %1$d개의 내용을 동기화할 수 없었습니다 (충돌 %2$d개)</string>
- <string name="sync_foreign_files_forgotten_ticker">몇몇 로컬 파일이 사라졌습니다.</string>
- <string name="sync_current_folder_was_removed">%1$s 폴더가 존재하지 않습니다.</string>
- <string name="foreign_files_move">모두 옮김</string>
- <string name="foreign_files_success">모든 파일 옮김</string>
- <string name="foreign_files_fail">몇몇 파일을 옮기지 못했습니다.</string>
+ <string name="sync_fail_in_favourites_content">파일 %1$d개의 내용을 동기화할 수 없습니다 (충돌 %2$d개)</string>
+ <string name="sync_foreign_files_forgotten_ticker">일부 로컬 파일이 사라졌습니다.</string>
+ <string name="sync_foreign_files_forgotten_content">폴더 %2$s의 파일 중 %1$d개를 복사할 수 없습니다</string>
+ <string name="sync_foreign_files_forgotten_explanation">버전 1.3.16부터는 하나의 파일이 여러 계정과 동기화될 때 데이터 손실을 막기 위해서 이 장치에서 업로드된 파일은 로컬 폴더 %1$s(으)로 복사됩니다.\n\n이 변경 사항 때문에 이 앱의 이전 버전에서 업로드된 모든 파일은 폴더 %2$s(으)로 복사되었습니다. 계정 동기화 중 오류가 발생하여 이 작업이 중단되었습니다. 파일을 그대로 둔 다음 %3$s(으)로 향한 링크를 삭제하거나, 파일을 직접 폴더 %1$s(으)로 이동한 다음 %4$s(으)로 향한 링크를 그대로 두십시오.\n\n아래 목록은 로컬 파일과 링크가 걸려 있는 %5$s에 있는 원격 파일입니다.</string>
+ <string name="sync_current_folder_was_removed">폴더 %1$s이(가) 더 이상 존재하지 않습니다.</string>
+ <string name="foreign_files_move">모두 이동</string>
+ <string name="foreign_files_success">모든 파일 이동됨</string>
+ <string name="foreign_files_fail">몇몇 파일을 이동할 수 없음</string>
<string name="foreign_files_local_text">로컬: %1$s</string>
<string name="foreign_files_remote_text">원격: %1$s</string>
+ <string name="upload_query_move_foreign_files">선택한 파일을 폴더 %1$s(으)로 복사할 공간이 부족합니다. 파일을 이동하시겠습니까?</string>
<string name="pincode_enter_pin_code">앱 암호를 입력하십시오</string>
<string name="pincode_configure_your_pin">앱 암호를 입력하십시오</string>
<string name="pincode_configure_your_pin_explanation">앱을 시작할 때마다 암호를 물어봅니다</string>
<string name="pincode_removed">앱 암호가 삭제되었습니다</string>
<string name="pincode_stored">앱 암호가 저장되었습니다</string>
<string name="media_notif_ticker">%1$s 음악 재생기</string>
- <string name="media_state_playing">%1$s (재생중)</string>
+ <string name="media_state_playing">%1$s (재생 중)</string>
<string name="media_state_loading">%1$s (불러오는 중)</string>
<string name="media_event_done">%1$s 재생 완료됨</string>
- <string name="media_err_nothing_to_play">미디어 파일을 찾을수 없습니다</string>
+ <string name="media_err_nothing_to_play">미디어 파일을 찾을 수 음</string>
<string name="media_err_no_account">준비된 계정이 없습니다</string>
<string name="media_err_not_in_owncloud">유효한 계정의 파일이 아닙니다</string>
<string name="media_err_unsupported">지원하지 않는 미디어 코덱</string>
- <string name="media_err_io">미디어 파일을 읽을수 </string>
+ <string name="media_err_io">미디어 파일을 읽을 수 없음</string>
<string name="media_err_malformed">미디어 파일이 제대로 인코드 되지 않았습니다</string>
<string name="media_err_timeout">재생 시도 중 시간이 초과됨</string>
- <string name="media_err_invalid_progressive_playback">미디어 파일을 스트리밍 할수 없습니다</string>
- <string name="media_err_unknown">내장된 미디어 플레이어에서는 이 미디어 파일을 재생할수 없습니다</string>
- <string name="media_err_security_ex">%1$s 를 재생하는 중에 보안오류가 발생함</string>
- <string name="media_err_io_ex">%1$s 를 재생하는 중에 입력 에러가 발생함</string>
- <string name="media_err_unexpected">%1$s 를 재생하던 중에 알수 없는 오류가 발생함</string>
- <string name="media_rewind_description">되감기 버튼</string>
- <string name="media_play_pause_description">재생 혹은 일시정지 버튼</string>
- <string name="media_forward_description">빨리감기 버튼</string>
- <string name="auth_trying_to_login">로그인 중...</string>
+ <string name="media_err_invalid_progressive_playback">미디어 파일을 스트리밍 할 수 없습니다</string>
+ <string name="media_err_unknown">내장된 미디어 플레이어에서 이 미디어 파일을 재생할 수 없습니다</string>
+ <string name="media_err_security_ex">%1$s을(를) 재생하는 중 보안 오류가 발생함</string>
+ <string name="media_err_io_ex">%1$s을(를) 재생하는 중 입력 오류가 발생함</string>
+ <string name="media_err_unexpected">%1$s을(를) 재생하는 중 알 수 없는 오류가 발생함</string>
+ <string name="media_rewind_description">되감기 단추</string>
+ <string name="media_play_pause_description">재생 혹은 일시 정지 단추</string>
+ <string name="media_forward_description">빨리감기 단추</string>
+ <string name="auth_getting_authorization">인증 정보 가져오는 중...</string>
+ <string name="auth_trying_to_login">로그인 시도 중...</string>
<string name="auth_no_net_conn_title">네트워크에 연결할 수 없습니다</string>
<string name="auth_nossl_plain_ok_title">암호화된 연결을 사용할 수 없습니다.</string>
<string name="auth_connection_established">연결됨</string>
<string name="auth_testing_connection">연결 테스트 중...</string>
<string name="auth_not_configured_title">서버 설정이 잘못됨</string>
<string name="auth_account_not_new">같은 사용자와 서버에 대한 계정이 이미 존재합니다</string>
- <string name="auth_account_not_the_same">ì\9e\85ë ¥ë\90\9c ì\82¬ì\9a©ì\9e\90ê°\80 ì\9d´ ê³\84ì \95ì\9d\98 ì\82¬ì\9a©ì\9e\90ì\99\80 ì\9d¼ì¹\98í\95\98ì§\80 ì\95\8aì\9d\8c</string>
+ <string name="auth_account_not_the_same">ì\9e\85ë ¥ë\90\9c ì\82¬ì\9a©ì\9e\90ê°\80 ì\9d´ ê³\84ì \95ì\9d\98 ì\82¬ì\9a©ì\9e\90ì\99\80 ì\9d¼ì¹\98í\95\98ì§\80 ì\95\8aì\8aµë\8b\88ë\8b¤</string>
<string name="auth_unknown_error_title">알 수 없는 오류가 발생하였습니다!</string>
<string name="auth_unknown_host_title">호스트를 찾을 수 없음</string>
<string name="auth_incorrect_path_title">서버 인스턴스를 찾을 수 없음</string>
- <string name="auth_timeout_title">ì\84\9cë²\84 ì\9d\91ë\8bµ ì\8b\9cê°\84ì\9d´ ì´\88ê³¼ë\90\98ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+ <string name="auth_timeout_title">ì\84\9cë²\84 ì\9d\91ë\8bµ ì\8b\9cê°\84ì\9d´ ì´\88ê³¼ë\90¨</string>
<string name="auth_incorrect_address_title">잘못된 URL</string>
<string name="auth_ssl_general_error_title">SSL 초기화 오류</string>
<string name="auth_ssl_unverified_server_title">SSL 서버의 신원을 확인할수 없습니다</string>
<string name="auth_bad_oc_version_title">확인할 수 없는 서버 버전</string>
<string name="auth_wrong_connection_title">연결을 수립할 수 없음</string>
<string name="auth_secure_connection">암호화된 연결 사용 중</string>
- <string name="auth_unauthorized">잘못된 로그인/암호</string>
- <string name="auth_oauth_error">권한부여가 성공적으로 이뤄지지 않았습니다</string>
- <string name="auth_oauth_error_access_denied">권한 서버로 부터 접근이 거부되었습니다</string>
- <string name="auth_wtf_reenter_URL">뜻밖의 상태; 다시 서버 주소를 입력해주십시오</string>
- <string name="auth_expired_oauth_token_toast">인증이 만료되었습니다. 다시 인증해주세요</string>
- <string name="auth_expired_basic_auth_toast">현재 암호를 </string>
- <string name="auth_expired_saml_sso_token_toast">세션이 만료되었습니다. 다시 접속해주세요</string>
- <string name="auth_connecting_auth_server">ì\9d¸ì¦\9d ì\84\9cë²\84ì\97\90 ì \91ì\86\8d하는 중...</string>
+ <string name="auth_unauthorized">잘못된 사용자 이름 및 암호</string>
+ <string name="auth_oauth_error">인증 실패</string>
+ <string name="auth_oauth_error_access_denied">인증 서버 접근 거부됨</string>
+ <string name="auth_wtf_reenter_URL">예상하지 못한 상태입니다. 서버 URL을 다시 입력해 주십시오</string>
+ <string name="auth_expired_oauth_token_toast">인증이 만료되었습니다. 다시 인증해 주십시오</string>
+ <string name="auth_expired_basic_auth_toast">현재 암호를 입력해 주십시오</string>
+ <string name="auth_expired_saml_sso_token_toast">세션이 만료되었습니다. 다시 접속해 주십시오</string>
+ <string name="auth_connecting_auth_server">ì\9d¸ì¦\9d ì\84\9cë²\84ì\97\90 ì\97°ê²°하는 중...</string>
<string name="auth_unsupported_auth_method">서버에서 이 인증 방법을 지원하지 않습니다.</string>
- <string name="auth_unsupported_multiaccount">%1$s 에서는 다중 계정을 지원하지 않습니다</string>
+ <string name="auth_unsupported_multiaccount">%1$s에서 다중 계정을 지원하지 않습니다</string>
+ <string name="auth_fail_get_user_name">서버에서 올바른 사용자 ID를 반환하지 않았습니다. 관리자에게 연락하십시오
+ </string>
+ <string name="auth_can_not_auth_against_server">이 서버에 인증할 수 없음</string>
<string name="fd_keep_in_sync">파일을 최신 정보로 유지</string>
<string name="common_rename">이름 바꾸기</string>
<string name="common_remove">삭제</string>
+ <string name="confirmation_remove_alert">%1$s을(를) 삭제하시겠습니까?</string>
+ <string name="confirmation_remove_folder_alert">%1$s 및 포함된 내용을 삭제하시겠습니까?</string>
<string name="confirmation_remove_local">로컬만</string>
<string name="confirmation_remove_folder_local">로컬 콘텐츠만</string>
<string name="confirmation_remove_remote">서버에서 삭제</string>
<string name="confirmation_remove_remote_and_local">서버와 로컬 모두</string>
- <string name="remove_success_msg">ì\84±ê³µì \81ì\9c¼ë¡\9c ì\82ì \9cí\95\98ì\98\80ì\8aµë\8b\88ë\8b¤</string>
- <string name="remove_fail_msg">ì\82ì \9cí\95 ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+ <string name="remove_success_msg">ì\84±ê³µì \81ì\9c¼ë¡\9c ì\82ì \9cí\95¨</string>
+ <string name="remove_fail_msg">ì\82ì \9cí\95 ì\88\98 ì\97\86ì\9d\8c</string>
<string name="rename_dialog_title">새 이름 입력</string>
<string name="rename_local_fail_msg">로컬 파일의 이름을 변경할 수 없습니다. 다른 이름을 입력하십시오</string>
- <string name="rename_server_fail_msg">이름을 변경할 수 없었습니다</string>
- <string name="sync_file_fail_msg">원격 파일을 확인할 수 없었습니다</string>
- <string name="sync_file_nothing_to_do_msg">파일 내용이 이미 동기화되었습니다</string>
- <string name="filename_forbidden_characters">사용할수 없는 문자들: / \\ < > : \" | ? *</string>
+ <string name="rename_server_fail_msg">이름을 변경할 수 없음</string>
+ <string name="sync_file_fail_msg">원격 파일을 확인할 수 없음</string>
+ <string name="sync_file_nothing_to_do_msg">파일 내용이 이미 동기화됨</string>
+ <string name="create_dir_fail_msg">폴더를 만들 수 없음</string>
+ <string name="filename_forbidden_characters">사용할 수 없는 문자: / \\ < > : \" | ? *</string>
+ <string name="filename_empty">파일 이름이 비어 있을 수 없음</string>
<string name="wait_a_moment">잠시 기다려 주십시오</string>
<string name="filedisplay_unexpected_bad_get_content">예상하지 못한 오류입니다. 다른 앱에서 파일을 선택하십시오</string>
<string name="filedisplay_no_file_selected">선택한 파일 없음</string>
+ <string name="activity_chooser_title">다음으로 링크 보내기...</string>
<string name="oauth_check_onoff">oAuth2로 로그인하기</string>
- <string name="oauth_login_connection">oAuth2 서버에 연결중...</string>
- <string name="ssl_validator_header">ì\82¬ì\9d´í\8a¸ ì\9d¸ì¦\9dì\84\9c를 í\99\95ì\9d¸í\95 ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+ <string name="oauth_login_connection">oAuth2 서버에 연결 중...</string>
+ <string name="ssl_validator_header">사이트 인증서를 확인할 수 없습니다</string>
<string name="ssl_validator_reason_cert_not_trusted">- 서버 인증서를 신뢰할 수 없습니다</string>
<string name="ssl_validator_reason_cert_expired">- 서버 인증서가 만료되었습니다</string>
<string name="ssl_validator_reason_cert_not_yet_valid">- 서버 인증서의 유효 기간이 시작되지 않았습니다</string>
<string name="ssl_validator_reason_hostname_not_verified">- 인증서의 URL과 입력한 URL이 일치하지 않습니다</string>
<string name="ssl_validator_question">이 인증서를 신뢰하시겠습니까?</string>
- <string name="ssl_validator_not_saved">ì\9d¸ì¦\9dì\84\9c를 ì \80ì\9e¥í\95 ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+ <string name="ssl_validator_not_saved">인증서를 저장할 수 없습니다</string>
<string name="ssl_validator_btn_details_see">자세히</string>
<string name="ssl_validator_btn_details_hide">숨기기</string>
<string name="ssl_validator_label_subject">발급 대상:</string>
<string name="ssl_validator_label_validity_to">끝:</string>
<string name="ssl_validator_label_signature">서명:</string>
<string name="ssl_validator_label_signature_algorithm">알고리즘:</string>
- <string name="placeholder_sentence">이것은 플레이스홀더입니다</string>
+ <string name="ssl_validator_null_cert">인증서를 표시할 수 없습니다.</string>
+ <string name="ssl_validator_no_info_about_error">- 오류에 대한 정보가 없습니다</string>
+ <string name="placeholder_sentence">이것은 자리 비움자입니다</string>
<string name="placeholder_filename">placeholder.txt</string>
<string name="placeholder_filetype">PNG 그림</string>
<string name="placeholder_filesize">389 KB</string>
<string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
<string name="placeholder_media_time">12:23:45</string>
- <string name="instant_upload_on_wifi">WiFi 사용 중일때만 사진 업로드</string>
+ <string name="instant_upload_on_wifi">Wi-Fi 사용 중일때만 사진 업로드</string>
+ <string name="instant_video_upload_on_wifi">Wi-Fi 사용 중일때만 동영상 업로드</string>
<string name="instant_upload_path">/InstantUpload</string>
<string name="conflict_title">업데이트 충돌</string>
<string name="conflict_message">원격 파일 %s이(가) 로컬 파일과 동기화되지 않았습니다. 계속 진행하면 서버에 있는 파일을 덮어씁니다.</string>
<string name="conflict_keep_both">모두 저장</string>
<string name="conflict_overwrite">덮어쓰기</string>
<string name="conflict_dont_upload">업로드하지 않음</string>
- <string name="preview_image_description">그림 미리보기</string>
+ <string name="preview_image_description">사진 미리 보기</string>
+ <string name="preview_image_error_unknown_format">이 사진을 미리 볼 수 없습니다</string>
+ <string name="error__upload__local_file_not_copied">%1$s을(를) 로컬 폴더 %2$s(으)로 복사할 수 없습니다</string>
+ <string name="prefs_instant_upload_path_title">업로드 경로</string>
+ <string name="share_link_no_support_share_api">서버에서 공유가 비활성화되어 있습니다. 관리자에게 연락하십시오.</string>
+ <string name="share_link_file_no_exist">공유할 수 없습니다. 파일이 있는지 확인하십시오</string>
+ <string name="share_link_file_error">이 파일이나 폴더를 공유하는 중 오류 발생</string>
+ <string name="unshare_link_file_no_exist">공유를 해제할 수 없습니다. 파일이 있는지 확인하십시오</string>
+ <string name="unshare_link_file_error">이 파일이나 폴더의 공유를 해제하는 중 오류 발생</string>
<string name="activity_chooser_send_file_title">보내기</string>
- <string name="copy_link">링크 복사</string>
+ <string name="copy_link">링크 주소 복사</string>
<string name="clipboard_text_copied">클립보드로 복사됨</string>
+ <string name="error_cant_bind_to_operations_service">치명적 오류: 작업을 진행할 수 없음</string>
+ <string name="network_error_socket_exception">서버에 연결하는 중 오류가 발생하였습니다.</string>
+ <string name="network_error_socket_timeout_exception">서버를 기다리는 중 오류가 발생하였습니다. 작업이 진행되지 않았을 수도 있습니다</string>
+ <string name="network_error_connect_timeout_exception">서버를 기다리는 중 오류가 발생하였습니다. 작업이 진행되지 않았을 수도 있습니다</string>
+ <string name="network_host_not_available">서버를 사용할 수 없어서 작업을 진행할 수 없습니다</string>
<string name="empty"></string>
+ <string name="forbidden_permissions">%s 권한이 없습니다</string>
+ <string name="forbidden_permissions_rename">이 파일의 이름을 바꿀</string>
+ <string name="forbidden_permissions_delete">이 파일을 삭제할</string>
+ <string name="share_link_forbidden_permissions">이 파일을 공유할</string>
+ <string name="unshare_link_forbidden_permissions">이 파일의 공유를 해제할</string>
+ <string name="forbidden_permissions_create">파일을 생성할</string>
+ <string name="uploader_upload_forbidden_permissions">이 폴더에 업로드할</string>
+ <string name="downloader_download_file_not_found">이 파일을 서버에서 더 이상 사용할 수 없습니다</string>
<string name="prefs_category_accounts">계정</string>
+ <string name="prefs_add_account">계정 추가</string>
+ <string name="auth_redirect_non_secure_connection_title">보안 연결이 안전하지 않은 경로로 넘어갑니다.</string>
+ <string name="actionbar_logger">로그</string>
+ <string name="log_send_history_button">과거 기록 보내기</string>
+ <string name="log_send_no_mail_app">로그를 보낼 앱이 없습니다. 메일 앱을 설치하십시오!</string>
+ <string name="log_send_mail_subject">%1$s Android 앱 로그</string>
+ <string name="log_progress_dialog_text">데이터 불러오는 중...</string>
<string name="saml_authentication_required_text">인증 필요함</string>
<string name="saml_authentication_wrong_pass">잘못된 암호</string>
+ <string name="actionbar_move">이동</string>
+ <string name="file_list_empty_moving">항목이 없습니다. 폴더를 추가할 수 있습니다!</string>
<string name="folder_picker_choose_button_text">선택</string>
+ <string name="move_file_not_found">이동할 수 없습니다. 파일이 존재하는 지 확인하십시오</string>
+ <string name="move_file_invalid_into_descendent">폴더를 하위 폴더 아래로 이동할 수 없습니다</string>
+ <string name="move_file_invalid_overwrite">파일이 이미 대상 폴더에 존재합니다</string>
+ <string name="move_file_error">이 파일이나 폴더를 이동하는 중 오류가 발생하였습니다</string>
+ <string name="forbidden_permissions_move">이 파일을 이동할</string>
+ <string name="prefs_category_instant_uploading">즉시 업로드</string>
<string name="prefs_category_security">보안</string>
+ <string name="prefs_instant_video_upload_path_title">동영상 업로드 경로</string>
<string name="shared_subject_header">공유됨</string>
+ <string name="with_you_subject_header">나와</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="activity_chooser_send_file_title">Sūtīt</string>
<string name="empty"></string>
<string name="prefs_category_accounts">Konti</string>
+ <string name="saml_authentication_wrong_pass">Nepareiza parole</string>
<string name="folder_picker_choose_button_text">Izvēlieties</string>
<string name="prefs_category_security">Drošība</string>
</resources>
<string name="actionbar_settings">Innstillinger</string>
<string name="actionbar_see_details">Detaljer</string>
<string name="actionbar_send_file">Send</string>
+ <string name="actionbar_sort">Sorter</string>
+ <string name="actionbar_sort_title">Sorter på</string>
+ <string-array name="actionbar_sortby">
+ <item>A-Z</item>
+ <item>Nyeste - Eldste</item>
+ </string-array>
<!--TODO re-enable when server-side folder size calculation is available
<item>Biggest - Smallest</item>-->
<string name="prefs_category_general">Generelt</string>
<string name="prefs_recommend">Anbefal til en venn</string>
<string name="prefs_feedback">Tilbakemelding</string>
<string name="prefs_imprint">Avtrykk</string>
+ <string name="prefs_remember_last_share_location">Husk delt plassering</string>
+ <string name="prefs_remember_last_upload_location_summary">Husk sist delt plassering for opplasting</string>
<string name="recommend_subject">Prøv %1$s på smarttelefonen din!</string>
<string name="recommend_text">Jeg ønsker å invitere deg til å bruke %1$s på smarttelefonen din!\nLast ned her: %2$s</string>
<string name="auth_check_server">Sjekk server</string>
<string name="preview_image_description">Bildeforhåndsvisning</string>
<string name="preview_image_error_unknown_format">Dette bildet kan ikke vises</string>
<string name="error__upload__local_file_not_copied">%1$s kunne ikke kopieres til lokal mappe %2$s</string>
+ <string name="prefs_instant_upload_path_title">Sti til opplasting</string>
<string name="share_link_no_support_share_api">Beklager, deling er ikke skrudd på for din tjener. Ta kontakt med
administratoren.</string>
<string name="share_link_file_no_exist">Kan ikke dele. Sjekk om filen eksisterer.</string>
<string name="downloader_download_file_not_found">Filen finnes ikke på serveren lenger</string>
<string name="prefs_category_accounts">Kontoer</string>
<string name="prefs_add_account">Legg til en konto</string>
+ <string name="auth_redirect_non_secure_connection_title">Sikker forbindelse er omdirigert til en usikker rute.</string>
<string name="actionbar_logger">Logger</string>
<string name="log_send_history_button">Send historikk</string>
+ <string name="log_send_no_mail_app">Ingen app for sending av logger funnet. Installer epost-app!</string>
+ <string name="log_send_mail_subject">%1$s Android app logger</string>
+ <string name="log_progress_dialog_text">Laster data...</string>
<string name="saml_authentication_required_text">Autentisering kreves</string>
<string name="saml_authentication_wrong_pass">Feil passord</string>
<string name="actionbar_move">Flytt</string>
<string name="move_file_invalid_overwrite">Filen finnes allerede i målmappen</string>
<string name="move_file_error">En feil oppstod ved flytting av denne filen eller mappen</string>
<string name="forbidden_permissions_move">å flytte denne filen</string>
+ <string name="prefs_category_instant_uploading">Umiddelbare opplastinger</string>
<string name="prefs_category_security">Sikkerhet</string>
+ <string name="prefs_instant_video_upload_path_title">Sti til video-opplasting</string>
<string name="shared_subject_header">delt</string>
+ <string name="with_you_subject_header">med deg</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="prefs_category_instant_uploading">Directe uploads</string>
<string name="prefs_category_security">Beveiliging</string>
<string name="prefs_instant_video_upload_path_title">Upload Video Pad</string>
+ <string name="download_folder_failed_content">Download van %1$s map kon niet worden voltooid</string>
<string name="shared_subject_header">gedeeld</string>
<string name="with_you_subject_header">met u</string>
<string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
<string name="prefs_remember_last_share_location">Lembrar localização de partilha</string>
<string name="prefs_remember_last_upload_location_summary">Lembrar da última localização de envio de partilha</string>
<string name="recommend_subject">Test %1$s no seu smartphone!</string>
- <string name="recommend_text">Quero convidar-te a usares %1$s no teu smartphone!\nFaz download aqui: %2$s</string>
+ <string name="recommend_text">Eu quero convidar-te para usares %1$s no teu smartphone!\nTransfere aqui: %2$s</string>
<string name="auth_check_server">Verificar Servidor</string>
<string name="auth_host_url">Endereço do servidor https://..</string>
<string name="auth_username">Nome de Utilizador</string>
<string name="uploader_btn_upload_text">Enviar</string>
<string name="uploader_top_message">Escolha a pasta de envio:</string>
<string name="uploader_wrn_no_account_title">A conta não foi encontrada</string>
- <string name="uploader_wrn_no_account_text">Não tem nenhuma conta %1$s no seu dispositivo. Por favor, configure primeiro uma conta.</string>
+ <string name="uploader_wrn_no_account_text">Não existe nenhuma conta %1$s no seu dispositivo. Por favor, configure primeiro uma conta.</string>
<string name="uploader_wrn_no_account_setup_btn_text">Configurar</string>
<string name="uploader_wrn_no_account_quit_btn_text">Sair</string>
<string name="uploader_wrn_no_content_title">Sem conteúdo para enviar</string>
<string name="filedetails_sync_file">Atualizar ficheiro</string>
<string name="filedetails_renamed_in_upload_msg">O ficheiro foi renomeado para %1$s durante o envio.</string>
<string name="action_share_file">Partilhar a hiperligação</string>
- <string name="action_unshare_file">Deixar de partilhar a ligação</string>
+ <string name="action_unshare_file">Deixar de partilhar a hiperligação</string>
<string name="common_yes">Sim</string>
<string name="common_no">Não</string>
<string name="common_ok">ACEITAR</string>
<string name="uploader_upload_succeeded_ticker">Envio bem sucedido</string>
<string name="uploader_upload_succeeded_content_single">%1$s foi enviado com sucesso</string>
<string name="uploader_upload_failed_ticker">Não foi possível enviar</string>
- <string name="uploader_upload_failed_content_single">O envio do ficheiro %1$s não foi concluído.</string>
+ <string name="uploader_upload_failed_content_single">Não foi possível concluir o envio de %1$s.</string>
<string name="uploader_upload_failed_credentials_error">Falha no carregamento, é necessário fazer novo login</string>
- <string name="downloader_download_in_progress_ticker">A transferir ...</string>
+ <string name="downloader_download_in_progress_ticker">A transferir...</string>
<string name="downloader_download_in_progress_content">%1$d%% A transferir %2$s</string>
<string name="downloader_download_succeeded_ticker">Transferência bem sucedida</string>
- <string name="downloader_download_succeeded_content">%1$s foi descarregado com sucesso</string>
- <string name="downloader_download_failed_ticker">Descarga falhou</string>
- <string name="downloader_download_failed_content">O descarregamento %1$s não foi possível descarregar</string>
+ <string name="downloader_download_succeeded_content">%1$s foi transferido com sucesso</string>
+ <string name="downloader_download_failed_ticker">Transferência falhada</string>
+ <string name="downloader_download_failed_content">Não foi possível concluir a transferência de %1$s</string>
<string name="downloader_not_downloaded_yet">Ainda não foi transferido</string>
<string name="downloader_download_failed_credentials_error">Não foi possível transferir, tem de iniciar a sessão novamente</string>
<string name="common_choose_account">Escolha a conta</string>
<string name="pincode_configure_your_pin_explanation">O PIN será pedido sempre que a app seja iniciada.</string>
<string name="pincode_reenter_your_pincode">Por favor, reinsira o PIN da App</string>
<string name="pincode_remove_your_pincode">Remover o seu PIN da App</string>
- <string name="pincode_mismatch">Os códigos PIN introduzidos não são iguais.</string>
- <string name="pincode_wrong">Código PIN Incorrecto.</string>
- <string name="pincode_removed">PIN da aplicação removido</string>
- <string name="pincode_stored">PIN da aplicação guardado</string>
+ <string name="pincode_mismatch">Os CÓDIGOS da APP não são iguais</string>
+ <string name="pincode_wrong">CÃ\93DIGO da App Incorreto</string>
+ <string name="pincode_removed">CÓDIGOS da App removido</string>
+ <string name="pincode_stored">CÓDIGO da App guardado</string>
<string name="media_notif_ticker">%1$s leitor de música</string>
<string name="media_state_playing">%1$s (a reproduzir)</string>
<string name="media_state_loading">%1$s (a carregar)</string>
<string name="media_err_nothing_to_play">Não foi encontrado nenhum ficheiro de média</string>
<string name="media_err_no_account">Não foi fornecida conta</string>
<string name="media_err_not_in_owncloud">O ficheiro não está numa conta válida</string>
- <string name="media_err_unsupported">Codec de média não suportado</string>
- <string name="media_err_io">Não foi possível reproduzir o ficheiro</string>
+ <string name="media_err_unsupported">Codec de multimédia não suportado</string>
+ <string name="media_err_io">Não foi possível ler o ficheiro de multimédia</string>
<string name="media_err_malformed">Ficheiro erradamente codificado (codec)</string>
<string name="media_err_timeout">O tempo de espera para jogar expirou</string>
<string name="media_err_invalid_progressive_playback">O ficheiro não pode ser reproduzido (streaming)</string>
<string name="media_err_security_ex">Erro de segurança a tentar reproduzir o ficheiro %1$s</string>
<string name="media_err_io_ex">Erro de input a tentar reproduzir %1$s</string>
<string name="media_err_unexpected">Erro inesperado a tentar reproduzir %1$s</string>
- <string name="media_rewind_description">Botão de rebobinar</string>
- <string name="media_play_pause_description">Botão Tocar/Pausa</string>
+ <string name="media_rewind_description">Botão de Retroceder</string>
+ <string name="media_play_pause_description">Botão de Reproduzir/Pausar</string>
<string name="media_forward_description">Botão de avanço rápido</string>
<string name="auth_getting_authorization">A obter autorização...</string>
- <string name="auth_trying_to_login">A tentar entrar...</string>
+ <string name="auth_trying_to_login">A tentar iniciar a sessão...</string>
<string name="auth_no_net_conn_title">Sem ligação à rede</string>
- <string name="auth_nossl_plain_ok_title">Ligação segura indisponível</string>
+ <string name="auth_nossl_plain_ok_title">Ligação segura indisponível.</string>
<string name="auth_connection_established">Ligação estabelecida</string>
<string name="auth_testing_connection">A testar a ligação...</string>
<string name="auth_not_configured_title">Configuração do servidor incorrecta.</string>
<string name="auth_account_not_new">Uma conta para este utilizador e servidor já existe no dispositivo</string>
<string name="auth_account_not_the_same">O utilizador que escreveu não coincide com o nome de utilizador desta conta</string>
<string name="auth_unknown_error_title">Ocorreu um erro desconhecido!</string>
- <string name="auth_unknown_host_title">Não é possível encontrar o servidor</string>
- <string name="auth_incorrect_path_title">Instância servidor não encontrada</string>
- <string name="auth_timeout_title">O servidor levou demasiado tempo a responder</string>
+ <string name="auth_unknown_host_title">Não foi possível encontrar o anfitrião</string>
+ <string name="auth_incorrect_path_title">Instância do servidor não encontrada</string>
+ <string name="auth_timeout_title">O servidor demorou muito tempo a responder</string>
<string name="auth_incorrect_address_title">URL errado</string>
<string name="auth_ssl_general_error_title">Inicialização de SSL falhou</string>
- <string name="auth_ssl_unverified_server_title">Não foi possível verificar a identidade SSL do servidor</string>
+ <string name="auth_ssl_unverified_server_title">Não foi possível verificar a identidade do servidor SSL</string>
<string name="auth_bad_oc_version_title">Versão do servidor não reconhecida</string>
<string name="auth_wrong_connection_title">Não consegue estabelecer ligação</string>
<string name="auth_secure_connection">Ligação segura estabelecida</string>
<string name="auth_oauth_error_access_denied">Acesso negado pelo servidor</string>
<string name="auth_wtf_reenter_URL">Estado inesperado, por favor, digite a URL do servidor novamente</string>
<string name="auth_expired_oauth_token_toast">O prazo da sua autorização expirou. Por favor renove-a</string>
- <string name="auth_expired_basic_auth_toast">Por favor, introduza a password actual</string>
+ <string name="auth_expired_basic_auth_toast">Por favor, insira a palavra-passe atual</string>
<string name="auth_expired_saml_sso_token_toast">A sua sessão expirou. Por favor autentique-se de novo</string>
<string name="auth_connecting_auth_server">A verificar a sua autenticação no servidor...</string>
<string name="auth_unsupported_auth_method">O servidor não suporta este método de autenticação</string>
<string name="prefs_category_security">Segurança</string>
<string name="prefs_instant_video_upload_path_title">Envio do Caminho do Vídeo</string>
<string name="shared_subject_header">partilhado</string>
+ <string name="with_you_subject_header">consigo</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="prefs_manage_accounts">Administrare conturi</string>
<string name="prefs_pincode">PIN-ul aplicaţiei</string>
<string name="prefs_pincode_summary">Protejaţi-vă clientul</string>
- <string name="prefs_instant_upload">Încărcare instanta de imagine</string>
- <string name="prefs_instant_upload_summary">Încărca instantaneu imagini luate de camera</string>
+ <string name="prefs_instant_upload">Încărcare instantă de imagini</string>
+ <string name="prefs_instant_upload_summary">Încarcă instantant imagini luate cu camera</string>
<string name="prefs_instant_video_upload">Încărcare instantă de videoclipuri.</string>
- <string name="prefs_instant_video_upload_summary">Încarcă videoclipuri instant, filmate cu camera.</string>
+ <string name="prefs_instant_video_upload_summary">Încarcă instant videoclipuri înregistrate cu camera</string>
<string name="prefs_log_title">Permite logarea</string>
<string name="prefs_log_summary">Acesta este folosit pentru a înregistra problemele</string>
<string name="prefs_log_title_history">Istoria logarilor</string>
<string name="placeholder_media_time">12:23:45</string>
<string name="instant_upload_on_wifi">Incarca poze doar via WiFi</string>
<string name="instant_video_upload_on_wifi">Încarcă videoclipuri doar via WiFi</string>
- <string name="instant_upload_path">/Încărcare instanta</string>
+ <string name="instant_upload_path">/Încărcare instantă</string>
<string name="conflict_title">Actualizați conflictul</string>
<string name="conflict_message">Fișierul de la distanță %s nu este sincronizat cu fișierul local. Continuand, se va înlocui conținutul fișierului de pe server.</string>
<string name="conflict_keep_both">Pastreaza amandoua</string>
<string name="file_list_empty_moving">Nu este nimic aici. Poți adăuga un director!</string>
<string name="folder_picker_choose_button_text">Alege</string>
<string name="forbidden_permissions_move">pentru a muta acest fișier</string>
+ <string name="prefs_category_instant_uploading">Încărcări instante</string>
<string name="prefs_category_security">Securitate</string>
</resources>
<?xml version='1.0' encoding='UTF-8'?>
<resources>
- <string name="about_android">%1$s Ð\9fÑ\80иложение длÑ\8f Ð\90ндÑ\80оида</string>
- <string name="about_version">Ð\92ерсия %1$s</string>
+ <string name="about_android">%1$s длÑ\8f Android</string>
+ <string name="about_version">версия %1$s</string>
<string name="actionbar_sync">Обновить учетную запись</string>
<string name="actionbar_upload">Загрузить</string>
<string name="actionbar_upload_from_apps">Содержимое из других приложений</string>
<string name="actionbar_upload_files">Файлы</string>
<string name="actionbar_open_with">Открыть с помощью</string>
- <string name="actionbar_mkdir">Новая папка</string>
+ <string name="actionbar_mkdir">Новый каталог</string>
<string name="actionbar_settings">Настройки</string>
<string name="actionbar_see_details">Подробно</string>
<string name="actionbar_send_file">Отправить</string>
<string name="actionbar_sort_title">Упорядочить по</string>
<string-array name="actionbar_sortby">
<item>А-Я</item>
- <item>Новые - Старые</item>
+ <item>Новое - Старое</item>
</string-array>
<!--TODO re-enable when server-side folder size calculation is available
<item>Biggest - Smallest</item>-->
<string name="prefs_category_more">Больше</string>
<string name="prefs_accounts">Учётные записи</string>
<string name="prefs_manage_accounts">Управление учётными записями</string>
- <string name="prefs_pincode">App PIN</string>
+ <string name="prefs_pincode">PIN приложения</string>
<string name="prefs_pincode_summary">Защитить ваш клиент</string>
- <string name="prefs_instant_upload">Ð\91Ñ\8bÑ\81Ñ\82Ñ\80ая загрузка фотографий</string>
+ <string name="prefs_instant_upload">Ð\9cгновенная загрузка фотографий</string>
<string name="prefs_instant_upload_summary">Немедленно загружать фотографии сделанные камерой</string>
- <string name="prefs_instant_video_upload">Ð\91Ñ\8bÑ\81Ñ\82Ñ\80ая загрузка видео</string>
- <string name="prefs_instant_video_upload_summary">Ð\91Ñ\8bÑ\81Ñ\82Ñ\80аÑ\8f загÑ\80Ñ\83зка видео Ñ\81 камеÑ\80Ñ\8b</string>
+ <string name="prefs_instant_video_upload">Ð\9cгновенная загрузка видео</string>
+ <string name="prefs_instant_video_upload_summary">Ð\9dемедленно загÑ\80Ñ\83жаÑ\82Ñ\8c видео Ñ\81деланнÑ\8bе камеÑ\80ой</string>
<string name="prefs_log_title">Включить журналирование</string>
<string name="prefs_log_summary">Используется для регистрации ошибок</string>
<string name="prefs_log_title_history">Журнал</string>
<string name="prefs_recommend">Рекомендовать другу</string>
<string name="prefs_feedback">Обратная связь</string>
<string name="prefs_imprint">Штамп</string>
- <string name="prefs_remember_last_share_location">Ð\97апомниÑ\82Ñ\8c Ñ\80аÑ\81положение пÑ\83бликаÑ\86ии</string>
+ <string name="prefs_remember_last_share_location">Ð\97апомниÑ\82Ñ\8c Ñ\80аÑ\81положение обÑ\89его Ñ\80еÑ\81Ñ\83Ñ\80Ñ\81а</string>
<string name="prefs_remember_last_upload_location_summary">Запомнить расположение загрузки последней публикации</string>
<string name="recommend_subject">Попробуйте %1$s на вашем смартфоне!</string>
<string name="recommend_text">Хочу предложить вам использовать %1$s на смартфоне!\nЗагрузить можно здесь: %2$s
<string name="sync_string_files">Файлы</string>
<string name="setup_btn_connect">Подключиться</string>
<string name="uploader_btn_upload_text">Загрузить</string>
- <string name="uploader_top_message">Ð\92Ñ\8bбеÑ\80еÑ\82е папкÑ\83 для загрузки</string>
+ <string name="uploader_top_message">Ð\92Ñ\8bбеÑ\80иÑ\82е каÑ\82алог для загрузки</string>
<string name="uploader_wrn_no_account_title">Учётная запись не найдена</string>
- <string name="uploader_wrn_no_account_text">Ð\9dа ваÑ\88ем Ñ\83Ñ\81Ñ\82Ñ\80ойÑ\81Ñ\82ве неÑ\82 Ñ\83Ñ\87Ñ\91Ñ\82нÑ\8bÑ\85 запиÑ\81ей %1$s. СнаÑ\87ала нÑ\83жно наÑ\81Ñ\82Ñ\80оиÑ\82Ñ\8c учётную запись.</string>
- <string name="uploader_wrn_no_account_setup_btn_text">УÑ\81Ñ\82ановка</string>
+ <string name="uploader_wrn_no_account_text">Ð\9dа ваÑ\88ем Ñ\83Ñ\81Ñ\82Ñ\80ойÑ\81Ñ\82ве неÑ\82 Ñ\83Ñ\87Ñ\91Ñ\82нÑ\8bÑ\85 запиÑ\81ей %1$s. Ð\9fожалÑ\83йÑ\81Ñ\82а наÑ\81Ñ\82Ñ\80ойÑ\82е учётную запись.</string>
+ <string name="uploader_wrn_no_account_setup_btn_text">Ð\9dаÑ\81Ñ\82Ñ\80ойка</string>
<string name="uploader_wrn_no_account_quit_btn_text">Выход</string>
<string name="uploader_wrn_no_content_title">Нет содержимого для загрузки</string>
<string name="uploader_wrn_no_content_text">Содержимое не получено. Нечего загружать.</string>
- <string name="uploader_error_forbidden_content">%1$s не имеет доступа к опубликованным данным</string>
+ <string name="uploader_error_forbidden_content">Доступ к общему ресурсу для %1$s запрещен</string>
<string name="uploader_info_uploading">Загрузка</string>
- <string name="file_list_seconds_ago">только что</string>
+ <string name="file_list_seconds_ago">пару секунд назад</string>
<string name="file_list_empty">Здесь ничего нет. Загрузите что-нибудь!</string>
<string name="file_list_loading">Загрузка...</string>
- <string name="local_file_list_empty">В данной папке нет файлов.</string>
- <string name="file_list_folder">папка</string>
- <string name="file_list_folders">папки</string>
+ <string name="local_file_list_empty">В этом каталоге нет файлов.</string>
+ <string name="file_list_folder">каÑ\82алог</string>
+ <string name="file_list_folders">каÑ\82алоги</string>
<string name="file_list_file">файл</string>
<string name="file_list_files">файлы</string>
<string name="filedetails_select_file">Нажмите на файл для отображения дополнительной информации.</string>
<string name="filedetails_sync_file">Обновить файл</string>
<string name="filedetails_renamed_in_upload_msg">Файл был переименован в %1$s во время загрузки</string>
<string name="action_share_file">Поделиться ссылкой</string>
- <string name="action_unshare_file">Удалить ссылку</string>
+ <string name="action_unshare_file">УбÑ\80ать ссылку</string>
<string name="common_yes">Да</string>
<string name="common_no">Нет</string>
<string name="common_ok">ОК</string>
<string name="common_cancel">Отмена</string>
<string name="common_save_exit">Сохранить и выйти</string>
<string name="common_error">Ошибка</string>
- <string name="common_loading">Ð\98дÑ\91Ñ\82 загÑ\80Ñ\83зка...</string>
+ <string name="common_loading">Ð\97агÑ\80Ñ\83зка ...</string>
<string name="common_error_unknown">Неизвестная ошибка</string>
<string name="about_title">О программе</string>
<string name="change_password">Сменить пароль</string>
<string name="delete_account">Удалить учётную запись</string>
<string name="create_account">Создать учётную запись</string>
- <string name="upload_chooser_title">Загрузить из...</string>
- <string name="uploader_info_dirname">Ð\98мÑ\8f папки</string>
- <string name="uploader_upload_in_progress_ticker">Загрузка...</string>
- <string name="uploader_upload_in_progress_content">%1$d%% загÑ\80Ñ\83зки %2$s</string>
+ <string name="upload_chooser_title">Загрузить из ...</string>
+ <string name="uploader_info_dirname">Ð\98мÑ\8f каÑ\82алога</string>
+ <string name="uploader_upload_in_progress_ticker">Загрузка ...</string>
+ <string name="uploader_upload_in_progress_content">%1$d%% Ð\97агÑ\80Ñ\83жаеÑ\82Ñ\81Ñ\8f %2$s</string>
<string name="uploader_upload_succeeded_ticker">Загрузка завершена</string>
<string name="uploader_upload_succeeded_content_single">%1$s был успешно загружен</string>
<string name="uploader_upload_failed_ticker">Ошибка загрузки</string>
<string name="uploader_upload_failed_content_single">Загрузка %1$s не может быть завершена</string>
- <string name="uploader_upload_failed_credentials_error">Ð\97агÑ\80Ñ\83зка не Ñ\83далаÑ\81Ñ\8c, Ð\92ам необÑ\85одимо пеÑ\80еподклÑ\8eÑ\87иÑ\82Ñ\8cÑ\81Ñ\8f</string>
- <string name="downloader_download_in_progress_ticker">Скачивание...</string>
- <string name="downloader_download_in_progress_content">%1$d%% скачивания %2$s</string>
+ <string name="uploader_upload_failed_credentials_error">Ð\97агÑ\80Ñ\83зка не Ñ\83далаÑ\81Ñ\8c, нÑ\83жно заново войÑ\82и в Ñ\81воÑ\8e Ñ\83Ñ\87еÑ\82нÑ\83Ñ\8e запиÑ\81Ñ\8c</string>
+ <string name="downloader_download_in_progress_ticker">Скачивание ...</string>
+ <string name="downloader_download_in_progress_content">%1$d%% Скачивается %2$s</string>
<string name="downloader_download_succeeded_ticker">Скачивание завершено</string>
<string name="downloader_download_succeeded_content">%1$s успешно скачан</string>
<string name="downloader_download_failed_ticker">Скачивание не удалось</string>
<string name="downloader_download_failed_content">Скачивание %1$s не может быть завершено</string>
<string name="downloader_not_downloaded_yet">Ещё не скачано</string>
- <string name="downloader_download_failed_credentials_error">СкаÑ\87ивание не Ñ\83далоÑ\81Ñ\8c, Ð\92ам необÑ\85одимо пеÑ\80еподклÑ\8eÑ\87иÑ\82Ñ\8cÑ\81Ñ\8f</string>
+ <string name="downloader_download_failed_credentials_error">СкаÑ\87ивание не Ñ\83далоÑ\81Ñ\8c, нÑ\83жно заново войÑ\82и в Ñ\81воÑ\8e Ñ\83Ñ\87еÑ\82нÑ\83Ñ\8e запиÑ\81Ñ\8c</string>
<string name="common_choose_account">Выберите учётную запись</string>
<string name="sync_fail_ticker">Синхронизация прошла неудачно</string>
- <string name="sync_fail_ticker_unauthorized">СинÑ\85Ñ\80онизаÑ\86иÑ\8f не Ñ\83далаÑ\81Ñ\8c, Ð\92ам необÑ\85одимо пеÑ\80еподклÑ\8eÑ\87иÑ\82Ñ\8cÑ\81Ñ\8f</string>
+ <string name="sync_fail_ticker_unauthorized">СинÑ\85Ñ\80онизаÑ\86иÑ\8f не Ñ\83далаÑ\81Ñ\8c, нÑ\83жно заново войÑ\82и в Ñ\81воÑ\8e Ñ\83Ñ\87еÑ\82нÑ\83Ñ\8e запиÑ\81Ñ\8c</string>
<string name="sync_fail_content">Синхронизация %1$s не может быть завершена</string>
<string name="sync_fail_content_unauthorized">Неверный пароль для %1$s</string>
<string name="sync_conflicts_in_favourites_ticker">Обнаружены конфликты</string>
- <string name="sync_conflicts_in_favourites_content">%1$d файлы не могут быть синхронизированы</string>
+ <string name="sync_conflicts_in_favourites_content">%1$d файлов не может быть синхронизировано</string>
<string name="sync_fail_in_favourites_ticker">Не удалось синхронизировать файлы</string>
<string name="sync_fail_in_favourites_content">Содержимое %1$d файлов не может быть синхронизировано (конфликтов: %2$d)</string>
- <string name="sync_foreign_files_forgotten_ticker">Несколько локальных файлов были забыты</string>
- <string name="sync_foreign_files_forgotten_content"> Не возможно скопировать %1$d файлы из %2$s папки</string>
- <string name="sync_foreign_files_forgotten_explanation">Ð\9dаÑ\87инаÑ\8f Ñ\81 веÑ\80Ñ\81ии 1.3.16, Ñ\84айлÑ\8b, загÑ\80Ñ\83жаемÑ\8bе Ñ\81 Ñ\8dÑ\82ого Ñ\83Ñ\81Ñ\82Ñ\80ойÑ\81Ñ\82ва, копиÑ\80Ñ\83Ñ\8eÑ\82Ñ\81Ñ\8f в локалÑ\8cнÑ\83Ñ\8e диÑ\80екÑ\82оÑ\80иÑ\8e %1$s, Ñ\87Ñ\82обÑ\8b пÑ\80едоÑ\82вÑ\80аÑ\82иÑ\82Ñ\8c поÑ\82еÑ\80Ñ\8e даннÑ\8bÑ\85 пÑ\80и Ñ\81инÑ\85Ñ\80онизаÑ\86ии Ñ\84айла Ñ\81 неÑ\81колÑ\8cкими Ñ\83Ñ\87Ñ\91Ñ\82нÑ\8bми запиÑ\81Ñ\8fми.\n\nÐ\9fоÑ\8dÑ\82омÑ\83 вÑ\81е Ñ\84айлÑ\8b, загÑ\80Ñ\83женнÑ\8bе пÑ\80едÑ\8bдÑ\83Ñ\89ими веÑ\80Ñ\81иÑ\8fми данного пÑ\80иложениÑ\8f, бÑ\8bли Ñ\81копиÑ\80ованÑ\8b в диÑ\80екÑ\82оÑ\80иÑ\8e %2$s. Ð\9eднако, во вÑ\80емÑ\8f Ñ\81инÑ\85Ñ\80онизаÑ\86ии Ñ\87Ñ\82о-Ñ\82о помеÑ\88ало завеÑ\80Ñ\88иÑ\82Ñ\8c Ñ\8dÑ\82Ñ\83 опеÑ\80аÑ\86иÑ\8e. ТепеÑ\80Ñ\8c можно либо оставить файлы как есть и удалить ссылку на %3$s, либо переместить их в %1$s и сохранить ссылку на %4$s.\n\nНиже перечислены локальные файлы, и соответствующие им удалённые файлы в %5$s, к которым они привязаны.</string>
+ <string name="sync_foreign_files_forgotten_ticker">Некоторые загруженные файлы не были перенесены в локальную папку </string>
+ <string name="sync_foreign_files_forgotten_content"> Невозможно скопировать %1$d файлов из папки %2$s</string>
+ <string name="sync_foreign_files_forgotten_explanation">Ð\9dаÑ\87инаÑ\8f Ñ\81 веÑ\80Ñ\81ии 1.3.16, Ñ\84айлÑ\8b, загÑ\80Ñ\83жаемÑ\8bе Ñ\81 Ñ\8dÑ\82ого Ñ\83Ñ\81Ñ\82Ñ\80ойÑ\81Ñ\82ва, копиÑ\80Ñ\83Ñ\8eÑ\82Ñ\81Ñ\8f в локалÑ\8cнÑ\8bй каÑ\82алог %1$s, Ñ\87Ñ\82обÑ\8b пÑ\80едоÑ\82вÑ\80аÑ\82иÑ\82Ñ\8c поÑ\82еÑ\80Ñ\8e даннÑ\8bÑ\85 пÑ\80и Ñ\81инÑ\85Ñ\80онизаÑ\86ии Ñ\84айла Ñ\81 неÑ\81колÑ\8cкими Ñ\83Ñ\87Ñ\91Ñ\82нÑ\8bми запиÑ\81Ñ\8fми.\n\nÐ\9fоÑ\8dÑ\82омÑ\83 вÑ\81е Ñ\84айлÑ\8b, загÑ\80Ñ\83женнÑ\8bе пÑ\80едÑ\8bдÑ\83Ñ\89ими веÑ\80Ñ\81иÑ\8fми данного пÑ\80иложениÑ\8f, бÑ\8bли Ñ\81копиÑ\80ованÑ\8b в каÑ\82алог %2$s. Ð\9eднако, во вÑ\80емÑ\8f Ñ\81инÑ\85Ñ\80онизаÑ\86ии Ñ\87Ñ\82о-Ñ\82о помеÑ\88ало завеÑ\80Ñ\88иÑ\82Ñ\8c Ñ\8dÑ\82Ñ\83 опеÑ\80аÑ\86иÑ\8e. Ð\9cожеÑ\82е оставить файлы как есть и удалить ссылку на %3$s, либо переместить их в %1$s и сохранить ссылку на %4$s.\n\nНиже перечислены локальные файлы, и соответствующие им удалённые файлы в %5$s, к которым они привязаны.</string>
<string name="sync_current_folder_was_removed">Каталог %1$s больше не существует</string>
<string name="foreign_files_move">Переместить всё</string>
<string name="foreign_files_success">Все файлы были перемещены</string>
<string name="foreign_files_fail">Некоторые файлы не могут быть перемещены</string>
- <string name="foreign_files_local_text">Локально: %1$s</string>
- <string name="foreign_files_remote_text">Удаленно: %1$s</string>
- <string name="upload_query_move_foreign_files">Ð\94лÑ\8f копиÑ\80ованиÑ\8f вÑ\8bбÑ\80аннÑ\8bÑ\85 Ñ\84айлов в папкÑ\83 %1$s недостаточно свободного места. Скопировать в другое место?</string>
- <string name="pincode_enter_pin_code">Ð\92Ñ\81Ñ\82авÑ\8cÑ\82е App PIN</string>
- <string name="pincode_configure_your_pin">Введите App PIN</string>
- <string name="pincode_configure_your_pin_explanation">PIN-код будет запрашиваться при каждом запуске приложения.</string>
- <string name="pincode_reenter_your_pincode">Повторите ввод App PIN</string>
- <string name="pincode_remove_your_pincode">Удалить App PIN</string>
- <string name="pincode_mismatch">Введённые App PIN не совпадают</string>
- <string name="pincode_wrong">Неверный App PIN</string>
- <string name="pincode_removed">App PIN удалён</string>
- <string name="pincode_stored">App PIN сохранён</string>
+ <string name="foreign_files_local_text">Локальные: %1$s</string>
+ <string name="foreign_files_remote_text">Удаленные: %1$s</string>
+ <string name="upload_query_move_foreign_files">Ð\94лÑ\8f копиÑ\80ованиÑ\8f вÑ\8bбÑ\80аннÑ\8bÑ\85 Ñ\84айлов в каÑ\82алог %1$s недостаточно свободного места. Скопировать в другое место?</string>
+ <string name="pincode_enter_pin_code">УкажиÑ\82е PIN пÑ\80иложениÑ\8f</string>
+ <string name="pincode_configure_your_pin">Введите PIN приложения</string>
+ <string name="pincode_configure_your_pin_explanation">PIN будет запрашиваться при каждом запуске приложения.</string>
+ <string name="pincode_reenter_your_pincode">Повторите ввод PIN приложения</string>
+ <string name="pincode_remove_your_pincode">Удалить PIN приложения</string>
+ <string name="pincode_mismatch">Введённые PIN не совпадают</string>
+ <string name="pincode_wrong">Неверный PIN приложения</string>
+ <string name="pincode_removed">PIN приложения удалён</string>
+ <string name="pincode_stored">PIN приложения сохранён</string>
<string name="media_notif_ticker">%1$s аудиоплеер</string>
<string name="media_state_playing">%1$s (проигрывается)</string>
<string name="media_state_loading">%1$s (загружается)</string>
<string name="media_event_done">%1$s воспроизведение завершено</string>
- <string name="media_err_nothing_to_play">Медиафайлов не найдено</string>
- <string name="media_err_no_account">Учётная запись не настроена</string>
+ <string name="media_err_nothing_to_play">Медиафайлы не найдены</string>
+ <string name="media_err_no_account">Учётная запись не указана</string>
<string name="media_err_not_in_owncloud">Файл в неверной учётной записи</string>
<string name="media_err_unsupported">Неподдерживаемый кодек</string>
<string name="media_err_io">Медиафайл не может быть прочитан</string>
<string name="media_err_malformed">Медиафайл некорректно закодирован</string>
- <string name="media_err_timeout">Ð\92Ñ\80емÑ\8f попÑ\8bÑ\82ок воÑ\81пÑ\80оизведениÑ\8f вÑ\8bÑ\88ло</string>
+ <string name="media_err_timeout">Ð\98Ñ\81Ñ\82екло вÑ\80емÑ\8f попÑ\8bÑ\82ки воÑ\81пÑ\80оизведениÑ\8f</string>
<string name="media_err_invalid_progressive_playback">Невозможно организовать потоковую передачу медиафайла</string>
<string name="media_err_unknown">Медиафайл не может быть проигран стандартным плеером</string>
<string name="media_err_security_ex">Ошибка безопасности при воспроизведении %1$s</string>
<string name="media_rewind_description">Перемотка назад</string>
<string name="media_play_pause_description">Воспроизведение или пауза</string>
<string name="media_forward_description">Перемотка вперед</string>
- <string name="auth_getting_authorization">Ð\9fÑ\80оиÑ\81Ñ\85одиÑ\82 авÑ\82оÑ\80изаÑ\86иÑ\8f.....</string>
+ <string name="auth_getting_authorization">Ð\92Ñ\8bполнÑ\8fеÑ\82Ñ\81Ñ\8f авÑ\82оÑ\80изаÑ\86иÑ\8f...</string>
<string name="auth_trying_to_login">Попытка входа...</string>
<string name="auth_no_net_conn_title">Нет подключения к сети</string>
<string name="auth_nossl_plain_ok_title">Защищённое соединение недоступно.</string>
<string name="auth_timeout_title">Сервер слишком долго не отвечает</string>
<string name="auth_incorrect_address_title">Неверный URL</string>
<string name="auth_ssl_general_error_title">Ошибка инициализации SSL</string>
- <string name="auth_ssl_unverified_server_title">Невозможно проверить SSL-сертификат сервера</string>
+ <string name="auth_ssl_unverified_server_title">Невозможно проверить SSL подлинность сервера</string>
<string name="auth_bad_oc_version_title">Неизвестная версия сервера</string>
- <string name="auth_wrong_connection_title">Невозможно установить соединение</string>
+ <string name="auth_wrong_connection_title">Не удается установить соединение</string>
<string name="auth_secure_connection">Защищённое соединение установлено</string>
<string name="auth_unauthorized">Неверное имя пользователя или пароль</string>
<string name="auth_oauth_error">Ошибка авторизации</string>
<string name="auth_oauth_error_access_denied">Сервер авторизации отказал в доступе</string>
<string name="auth_wtf_reenter_URL">Неожиданный ответ; введите адрес сервера ещё раз</string>
<string name="auth_expired_oauth_token_toast">Время авторизации истекло. Пожалуйста, авторизуйтесь снова</string>
- <string name="auth_expired_basic_auth_toast">Пожалуйста, введите пароль</string>
+ <string name="auth_expired_basic_auth_toast">Пожалуйста, укажите текущий пароль</string>
<string name="auth_expired_saml_sso_token_toast">Время сессии истекло. Пожалуйста, подключитесь снова</string>
<string name="auth_connecting_auth_server">Подключение к серверу аутентификации...</string>
<string name="auth_unsupported_auth_method">Сервер не поддерживает выбранный метод аутентификации</string>
- <string name="auth_unsupported_multiaccount">%1$s не поддерживает сразу несколько учётных записей</string>
- <string name="auth_fail_get_user_name">Ð\92аÑ\88 Ñ\81еÑ\80веÑ\80 не возвÑ\80аÑ\89аеÑ\82 коÑ\80Ñ\80екÑ\82нÑ\8bй полÑ\8cзоваÑ\82елÑ\8cÑ\81кий иденÑ\82иÑ\84икаÑ\82оÑ\80, пожалÑ\83йÑ\81Ñ\82а Ñ\81вÑ\8fжиÑ\82еÑ\81Ñ\8c Ñ\81 администратором
+ <string name="auth_unsupported_multiaccount">%1$s не поддерживает несколько учётных записей</string>
+ <string name="auth_fail_get_user_name">СеÑ\80веÑ\80 веÑ\80нÑ\83л некоÑ\80Ñ\80екÑ\82нÑ\8bй полÑ\8cзоваÑ\82елÑ\8cÑ\81кий иденÑ\82иÑ\84икаÑ\82оÑ\80. Ð\9fожалÑ\83йÑ\81Ñ\82а, Ñ\81вÑ\8fжиÑ\82еÑ\81Ñ\8c Ñ\81 ваÑ\88им администратором
⇥</string>
- <string name="auth_can_not_auth_against_server">Невозможно аутентифицироваться на этом сервере</string>
+ <string name="auth_can_not_auth_against_server">Невозможно авторизоваться на этом сервере</string>
<string name="fd_keep_in_sync">Обновлять файл</string>
<string name="common_rename">Переименовать</string>
<string name="common_remove">Удалить</string>
<string name="rename_server_fail_msg">Переименование не может быть завершено</string>
<string name="sync_file_fail_msg">Удаленный файл не может быть проверен</string>
<string name="sync_file_nothing_to_do_msg">Содержимое файла уже синхронизировано</string>
- <string name="create_dir_fail_msg">Не возможно создать папку</string>
+ <string name="create_dir_fail_msg">Невозможно создать каталог</string>
<string name="filename_forbidden_characters">Недопустимые символы: / \\ < > : \" | ? *</string>
<string name="filename_empty">Имя файла не может быть пустым</string>
<string name="wait_a_moment">Подождите немного</string>
<string name="filedisplay_unexpected_bad_get_content">Неизвестная ошибка; выберите этот файл из другого приложения</string>
<string name="filedisplay_no_file_selected">Файлы не выбраны</string>
- <string name="activity_chooser_title">Отправить ссылку...</string>
+ <string name="activity_chooser_title">Отправить ссылку ...</string>
<string name="oauth_check_onoff">Войти через oAuth2</string>
<string name="oauth_login_connection">Подключение к серверу oAuth2...</string>
<string name="ssl_validator_header">Подлинность сайта не может быть проверена</string>
<string name="ssl_validator_reason_cert_expired">- Срок действия сертификата сервера истёк</string>
<string name="ssl_validator_reason_cert_not_yet_valid">- Срок действия сертификата сервера ещё не начался</string>
<string name="ssl_validator_reason_hostname_not_verified">- URL не совпадает с именем сервера в сертификате</string>
- <string name="ssl_validator_question">Ð\92Ñ\8b Ñ\85оÑ\82иÑ\82е довеÑ\80Ñ\8fÑ\82Ñ\8c данному сертификату в любом случае?</string>
+ <string name="ssl_validator_question">Ð\94овеÑ\80Ñ\8fÑ\82Ñ\8c Ñ\8dÑ\82ому сертификату в любом случае?</string>
<string name="ssl_validator_not_saved">Сертификат не может быть сохранён</string>
<string name="ssl_validator_btn_details_see">Подробно</string>
<string name="ssl_validator_btn_details_hide">Скрыть</string>
- <string name="ssl_validator_label_subject">Кому выдано:</string>
- <string name="ssl_validator_label_issuer">Кем выдано:</string>
+ <string name="ssl_validator_label_subject">Кому выдан:</string>
+ <string name="ssl_validator_label_issuer">Кем выдан:</string>
<string name="ssl_validator_label_CN">Имя:</string>
<string name="ssl_validator_label_O">Организация:</string>
- <string name="ssl_validator_label_OU">Ð\9eÑ\80ганизаÑ\86ионное подразделение:</string>
+ <string name="ssl_validator_label_OU">Ð\9fодразделение:</string>
<string name="ssl_validator_label_C">Страна:</string>
<string name="ssl_validator_label_ST">Штат:</string>
<string name="ssl_validator_label_L">Местонахождение:</string>
<string name="ssl_validator_label_validity">Срок действия:</string>
- <string name="ssl_validator_label_validity_from">Ð\98з:</string>
- <string name="ssl_validator_label_validity_to">Ð\92:</string>
+ <string name="ssl_validator_label_validity_from">С:</string>
+ <string name="ssl_validator_label_validity_to">Ð\9fо:</string>
<string name="ssl_validator_label_signature">Подпись:</string>
<string name="ssl_validator_label_signature_algorithm">Алгоритм:</string>
<string name="ssl_validator_null_cert">Сертификат не может быть показан.</string>
- <string name="ssl_validator_no_info_about_error">- Ð\98нÑ\84оÑ\80маÑ\86ии об оÑ\88ибке неÑ\82</string>
+ <string name="ssl_validator_no_info_about_error">- Ð\9dеÑ\82 инÑ\84оÑ\80маÑ\86ии об оÑ\88ибке</string>
<string name="placeholder_sentence">Это заполнитель</string>
<string name="placeholder_filename">placeholder.txt</string>
<string name="placeholder_filetype">Изображение PNG</string>
<string name="placeholder_filesize">389 КБ</string>
<string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
<string name="placeholder_media_time">12:23:45</string>
- <string name="instant_upload_on_wifi">Ð\97агÑ\80Ñ\83жаÑ\82Ñ\8c изобÑ\80ажениÑ\8f только через Wi-Fi</string>
+ <string name="instant_upload_on_wifi">Ð\97агÑ\80Ñ\83зка изобÑ\80ажений только через Wi-Fi</string>
<string name="instant_video_upload_on_wifi">Загрузка видео только через WiFi</string>
<string name="instant_upload_path">/InstantUpload</string>
<string name="conflict_title">Конфликт обновления</string>
<string name="conflict_message">Удаленный файл %s не синхронизирован с локальным. Продолжение приведет к замене содержимого файла на сервере.</string>
<string name="conflict_keep_both">Сохранить оба</string>
- <string name="conflict_overwrite">Ð\97аменить</string>
+ <string name="conflict_overwrite">Ð\9fеÑ\80езапиÑ\81ать</string>
<string name="conflict_dont_upload">Не загружать</string>
<string name="preview_image_description">Предпросмотр</string>
<string name="preview_image_error_unknown_format">Это изображение не может быть отображено</string>
- <string name="error__upload__local_file_not_copied">%1$s не возможно скопировать в локальною папку %2$s </string>
+ <string name="error__upload__local_file_not_copied">%1$s невозможно скопировать в локальный каталог %2$s </string>
<string name="prefs_instant_upload_path_title">Путь для загрузки</string>
- <string name="share_link_no_support_share_api">К сожалению, на вашем сервере отключен совместный доступ. Пожалуйста, свяжитесь с вашим администратором.</string>
- <string name="share_link_file_no_exist">Невозможно добавить в общий доступ. Пожалуйста, проверьте, существует ли файл</string>
- <string name="share_link_file_error">Ошибка предоставления общего доступа к этому файлу или каталогу</string>
- <string name="unshare_link_file_no_exist">Невозможно убрать из общего доступа. Пожалуйста, проверьте, существует ли файл</string>
- <string name="unshare_link_file_error">Ошибка удаления общего доступа к этому файлу или каталогу</string>
+ <string name="share_link_no_support_share_api">Механизм общего доступа не включен на данном сервере. Пожалуйста, свяжитесь с вашим
+⇥⇥администратором.</string>
+ <string name="share_link_file_no_exist">Невозможно поделиться. Убедитесь, что файл существует</string>
+ <string name="share_link_file_error">При попытке поделиться этим файлом или каталогом произошла ошибка</string>
+ <string name="unshare_link_file_no_exist">Невозможно закрыть доступ. Убедитесь что файл существует</string>
+ <string name="unshare_link_file_error">При попытке закрыть доступ к этому файлу или каталогу произошла ошибка</string>
<string name="activity_chooser_send_file_title">Отправить</string>
<string name="copy_link">Копировать ссылку</string>
<string name="clipboard_text_copied">Скопировано в буфер обмена</string>
- <string name="error_cant_bind_to_operations_service">Ð\9aÑ\80иÑ\82иÑ\87еÑ\81каÑ\8f оÑ\88ибка: невозможно вÑ\8bполниÑ\82Ñ\8c опеÑ\80аÑ\86ии</string>
+ <string name="error_cant_bind_to_operations_service">Ð\9aÑ\80иÑ\82иÑ\87еÑ\81каÑ\8f оÑ\88ибка: невозможно вÑ\8bполниÑ\82Ñ\8c дейÑ\81Ñ\82виÑ\8f</string>
<string name="network_error_socket_exception">При подключении к серверу возникла ошибка</string>
- <string name="network_error_socket_timeout_exception">Ð\92о вÑ\80емÑ\8f ожиданиÑ\8f Ñ\81еÑ\80веÑ\80а возникла оÑ\88ибка, опеÑ\80аÑ\86иÑ\8f не можеÑ\82 бÑ\8bÑ\82Ñ\8c завеÑ\80Ñ\88ена</string>
- <string name="network_error_connect_timeout_exception">Ð\92о вÑ\80емÑ\8f ожиданиÑ\8f Ñ\81еÑ\80веÑ\80а возникла оÑ\88ибка, опеÑ\80аÑ\86иÑ\8f не можеÑ\82 бÑ\8bÑ\82Ñ\8c завеÑ\80Ñ\88ена</string>
- <string name="network_host_not_available">Ð\9eпеÑ\80аÑ\86иÑ\8f не можеÑ\82 бÑ\8bÑ\82Ñ\8c завеÑ\80Ñ\88ена, сервер недоступен</string>
+ <string name="network_error_socket_timeout_exception">Ð\92о вÑ\80емÑ\8f ожиданиÑ\8f Ñ\81еÑ\80веÑ\80а пÑ\80оизоÑ\88ла оÑ\88ибка, дейÑ\81Ñ\82вие не можеÑ\82 бÑ\8bÑ\82Ñ\8c вÑ\8bполнено</string>
+ <string name="network_error_connect_timeout_exception">Ð\92о вÑ\80емÑ\8f ожиданиÑ\8f Ñ\81еÑ\80веÑ\80а пÑ\80оизоÑ\88ла оÑ\88ибка, дейÑ\81Ñ\82вие не можеÑ\82 бÑ\8bÑ\82Ñ\8c вÑ\8bполнено</string>
+ <string name="network_host_not_available">Ð\94ейÑ\81Ñ\82вие не можеÑ\82 бÑ\8bÑ\82Ñ\8c вÑ\8bполнено, сервер недоступен</string>
<string name="empty"></string>
- <string name="forbidden_permissions">У ваÑ\81 неÑ\82 доÑ\81Ñ\82Ñ\83па %s</string>
- <string name="forbidden_permissions_rename">пеÑ\80еименоваÑ\82Ñ\8c Ñ\8dÑ\82оÑ\82 Ñ\84айл</string>
- <string name="forbidden_permissions_delete">удалить этот файл</string>
- <string name="share_link_forbidden_permissions">опÑ\83бликоваÑ\82Ñ\8c Ñ\8dÑ\82оÑ\82 Ñ\84айл</string>
- <string name="unshare_link_forbidden_permissions">оÑ\82мениÑ\82Ñ\8c пÑ\83бликаÑ\86иÑ\8e Ñ\8dÑ\82ого Ñ\84айла</string>
- <string name="forbidden_permissions_create">создать файл</string>
- <string name="uploader_upload_forbidden_permissions">загÑ\80Ñ\83зиÑ\82Ñ\8c в Ñ\8dÑ\82Ñ\83 папкÑ\83</string>
+ <string name="forbidden_permissions">У ваÑ\81 неÑ\82 пÑ\80ав %s</string>
+ <string name="forbidden_permissions_rename">длÑ\8f пеÑ\80еименованиÑ\8f Ñ\8dÑ\82ого Ñ\84айла</string>
+ <string name="forbidden_permissions_delete">для удаления этого файла</string>
+ <string name="share_link_forbidden_permissions">длÑ\8f оÑ\82кÑ\80Ñ\8bÑ\82иÑ\8f доÑ\81Ñ\82Ñ\83па к Ñ\8dÑ\82омÑ\83 Ñ\84айлÑ\83</string>
+ <string name="unshare_link_forbidden_permissions">длÑ\8f закÑ\80Ñ\8bÑ\82иÑ\8f доÑ\81Ñ\82Ñ\83па к Ñ\8dÑ\82омÑ\83 Ñ\84айлÑ\83</string>
+ <string name="forbidden_permissions_create">для создания файла</string>
+ <string name="uploader_upload_forbidden_permissions">длÑ\8f загÑ\80Ñ\83зки в Ñ\8dÑ\82оÑ\82 каÑ\82алог</string>
<string name="downloader_download_file_not_found">Этот файл больше недоступен на сервере</string>
<string name="prefs_category_accounts">Учётные записи</string>
<string name="prefs_add_account">Добавить учетную запись</string>
- <string name="auth_redirect_non_secure_connection_title">Ð\97аÑ\89иÑ\89Ñ\91нное Ñ\81оединение пеÑ\80енапÑ\80авлено по незаÑ\89иÑ\89Ñ\91нному маршруту</string>
+ <string name="auth_redirect_non_secure_connection_title">Ð\97аÑ\89иÑ\89Ñ\91нное Ñ\81оединение пеÑ\80енапÑ\80авлено по небезопаÑ\81ному маршруту</string>
<string name="actionbar_logger">Журналы</string>
<string name="log_send_history_button">История Отправлений</string>
<string name="log_send_no_mail_app">Приложение для отправки журнала не найдено. Установите почтовое приложение!</string>
- <string name="log_send_mail_subject">Журналы приложения %1$s для Android</string>
+ <string name="log_send_mail_subject">Журналы %1$s для Android</string>
<string name="log_progress_dialog_text">Загрузка данных…</string>
<string name="saml_authentication_required_text">Требуется аутентификация </string>
<string name="saml_authentication_wrong_pass">Неправильный пароль</string>
<string name="actionbar_move">Переместить</string>
- <string name="file_list_empty_moving">Ð\97деÑ\81Ñ\8c ниÑ\87его неÑ\82. Ð\92Ñ\8b можеÑ\82е добавиÑ\82Ñ\8c папкÑ\83!</string>
+ <string name="file_list_empty_moving">Ð\97деÑ\81Ñ\8c ниÑ\87его неÑ\82. Ð\92Ñ\8b можеÑ\82е добавиÑ\82Ñ\8c каÑ\82алог!</string>
<string name="folder_picker_choose_button_text">Выбрать</string>
- <string name="move_file_not_found">Ð\9dевозможно пеÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c. Ð\9fожалÑ\83йÑ\81Ñ\82а, пÑ\80овеÑ\80Ñ\8cÑ\82е, Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 ли Ñ\84айл</string>
- <string name="move_file_invalid_into_descendent">Ð\9dевозможно пеÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c папкÑ\83 в папкÑ\83-поÑ\82омок</string>
- <string name="move_file_invalid_overwrite">Файл Ñ\83же Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 в папке назначения</string>
- <string name="move_file_error">Ð\9fÑ\80оизоÑ\88ла оÑ\88ибка пÑ\80и попÑ\8bÑ\82ке пеÑ\80емеÑ\89ениÑ\8f Ñ\8dÑ\82ого Ñ\84айла или папки</string>
- <string name="forbidden_permissions_move">пеÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c Ñ\8dÑ\82оÑ\82 Ñ\84айл</string>
+ <string name="move_file_not_found">Ð\9dевозможно пеÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c. УбедиÑ\82еÑ\81Ñ\8c, Ñ\87Ñ\82о Ñ\84айл Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82</string>
+ <string name="move_file_invalid_into_descendent">Ð\9dевозможно пеÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c каÑ\82алог в его подкаÑ\82алог</string>
+ <string name="move_file_invalid_overwrite">Файл Ñ\83же Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 в каÑ\82алоге назначения</string>
+ <string name="move_file_error">Ð\9fÑ\80оизоÑ\88ла оÑ\88ибка пÑ\80и попÑ\8bÑ\82ке пеÑ\80емеÑ\89ениÑ\8f Ñ\8dÑ\82ого Ñ\84айла или каÑ\82алога</string>
+ <string name="forbidden_permissions_move">длÑ\8f пеÑ\80емеÑ\89ениÑ\8f Ñ\8dÑ\82ого Ñ\84айла</string>
<string name="prefs_category_instant_uploading">Мгновенные загрузки</string>
<string name="prefs_category_security">Безопасность</string>
<string name="prefs_instant_video_upload_path_title">Путь для загрузки Видео</string>
- <string name="shared_subject_header">Общие</string>
+ <string name="download_folder_failed_content">Загрузка папки %1$s не может быть завершена</string>
+ <string name="shared_subject_header">общие</string>
<string name="with_you_subject_header">с вами</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="auth_redirect_non_secure_connection_title">Zabezpečené pripojenie je presmerované na nezabezpečenú trasu.</string>
<string name="actionbar_logger">Logy</string>
<string name="log_send_history_button">Odoslať históriu</string>
+ <string name="log_send_no_mail_app">Nebola nájdená aplikácia pre odosielanie log protokolov. Nainštalujte si mailovú aplikáciu!</string>
+ <string name="log_send_mail_subject">%1$s Android app logs</string>
+ <string name="log_progress_dialog_text">Načítavam dáta...</string>
<string name="saml_authentication_required_text">Vyžaduje sa overenie</string>
<string name="saml_authentication_wrong_pass">Nesprávne heslo</string>
<string name="actionbar_move">Presunúť</string>
<string name="forbidden_permissions_move">pre presun tohoto súboru</string>
<string name="prefs_category_instant_uploading">Okamžité nahratie</string>
<string name="prefs_category_security">Zabezpečenie</string>
+ <string name="prefs_instant_video_upload_path_title">Cesta pre nahrávanie videí</string>
+ <string name="download_folder_failed_content">Sťahovanie %1$s priečinka nebolo dokončené</string>
<string name="shared_subject_header">zdieľané</string>
+ <string name="with_you_subject_header">s vami</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<resources>
<string name="actionbar_upload">Pošalji</string>
<string name="actionbar_upload_files">Fajlovi</string>
+ <string name="actionbar_mkdir">Novi direktorijum</string>
<string name="actionbar_settings">Podešavanja</string>
<string name="actionbar_see_details">Detaljnije</string>
<string name="actionbar_send_file">Pošalji</string>
<string name="filedetails_size">Veličina:</string>
<string name="filedetails_type">Tip:</string>
<string name="filedetails_download">Preuzmi</string>
+ <string name="action_share_file">Podeli prečicu</string>
<string name="common_yes">Da</string>
<string name="common_no">Ne</string>
<string name="common_ok">Ok</string>
+ <string name="common_cancel_upload">Otkaži otpremanje</string>
<string name="common_cancel">Otkaži</string>
<string name="common_error">Greška</string>
+ <string name="common_error_unknown">Nepoznata greška</string>
<string name="change_password">Izmeni lozinku</string>
<string name="delete_account">Ukloni nalog</string>
<string name="create_account">Novi nalog</string>
+ <string name="uploader_info_dirname">Ime fascikle</string>
<string name="uploader_upload_in_progress_ticker">Otpremanje...</string>
<string name="uploader_upload_succeeded_ticker">Uspešno otpremljeno</string>
<string name="uploader_upload_failed_ticker">Otpremanje nije uspelo</string>
<string name="actionbar_settings">Inställningar</string>
<string name="actionbar_see_details">Detaljer</string>
<string name="actionbar_send_file">Skicka</string>
+ <string name="actionbar_sort">Sortera</string>
+ <string name="actionbar_sort_title">Sortera efter</string>
+ <string-array name="actionbar_sortby">
+ <item>A-Ö</item>
+ <item>Nyast - Äldst</item>
+ </string-array>
<!--TODO re-enable when server-side folder size calculation is available
<item>Biggest - Smallest</item>-->
<string name="prefs_category_general">Allmänt</string>
<string name="prefs_recommend">Rekommendera till en vän</string>
<string name="prefs_feedback">Feedback</string>
<string name="prefs_imprint">Imprint</string>
+ <string name="prefs_remember_last_share_location">Kom ihåg plats för delat</string>
+ <string name="prefs_remember_last_upload_location_summary">Kom ihåg senaste uppladdningsplats vid dela</string>
<string name="recommend_subject">Prova %1$s på din smartphone!</string>
<string name="recommend_text">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</string>
<string name="auth_check_server">Kontrollera Server</string>
<string name="sync_fail_in_favourites_content">Innehållet i %1$d filer kunde inte synkas (%2$d konflikter)</string>
<string name="sync_foreign_files_forgotten_ticker">Vissa lokala filer glömdes</string>
<string name="sync_foreign_files_forgotten_content">%1$d filer från %2$s mappar kunde inte kopieras till</string>
+ <string name="sync_foreign_files_forgotten_explanation">Från och med version 1.3.16 kommer filer uppladdade från denna enhet kopieras in till lokal %1$s mapp för att förhindra dataförlust när en enskild fil synkroniseras med flera konton.\n\nPå grund av denna ändring kommer alla filer uppladdade i tidigare versioner av denna applikation kopieras in till %2$s mapp. Dock förhindrade ett fel slutförandet av denna operation under konto-synkronisering. Du kan antingen lämna filerna som de är och ta bort länken till %3$s, eller flytta filerna in till %1$s mapp och behålla länken till %4$s.\n\nNedan listas de lokala filerna och de fjrran filerna i %5$s som de länkades till.</string>
<string name="sync_current_folder_was_removed">Mappen %1$s existerar inte längre</string>
<string name="foreign_files_move">Flytta allt</string>
<string name="foreign_files_success">Alla filer flyttades</string>
<string name="preview_image_description">Förhandsvisa bild</string>
<string name="preview_image_error_unknown_format">Denna bild kan inte visas</string>
<string name="error__upload__local_file_not_copied">%1$s kunde inte kopieras till %2$s lokal mapp</string>
+ <string name="prefs_instant_upload_path_title">Uppladdnings-sökväg</string>
<string name="share_link_no_support_share_api">Ledsen, delning är inte aktiverat på din server. Vänligen kontakta din
administratör.</string>
+ <string name="share_link_file_no_exist">Lyckades ej dela. Vänligen kontrollera om filen eisterar</string>
<string name="share_link_file_error">Ett fel uppstod vid försök att dela denna fil eller mapp</string>
+ <string name="unshare_link_file_no_exist">Lyckades ej sluta dela. Vänligen kontrollera om filen existerar</string>
<string name="unshare_link_file_error">Ett fel uppstod vid försök att sluta dela denna fil eller mapp</string>
<string name="activity_chooser_send_file_title">Skicka</string>
<string name="copy_link">Kopiera länk</string>
<string name="downloader_download_file_not_found">Filen är inte längre tillgänglig på servern</string>
<string name="prefs_category_accounts">Konton</string>
<string name="prefs_add_account">Lägg till konto</string>
+ <string name="auth_redirect_non_secure_connection_title">Säker anslutning är omdirigerad till en osäker väg.</string>
+ <string name="actionbar_logger">Loggar</string>
+ <string name="log_send_history_button">Skickat historik</string>
+ <string name="log_send_no_mail_app">Ingen app för att skicka loggar hittades. Installera mail appen!</string>
+ <string name="log_send_mail_subject">%1$s Android app logs</string>
+ <string name="log_progress_dialog_text">Laddar data...</string>
<string name="saml_authentication_required_text">Autentisering krävs</string>
<string name="saml_authentication_wrong_pass">Fel lösenord</string>
<string name="actionbar_move">Flytta</string>
<string name="file_list_empty_moving">Ingenting här. Du kan skapa en mapp!</string>
<string name="folder_picker_choose_button_text">Välj</string>
<string name="move_file_not_found">Gick inte att flytta. Vänligen kontrollera att filen existerar</string>
+ <string name="move_file_invalid_into_descendent">Det är inte möjligt att flytta mappen in i underliggande struktur</string>
+ <string name="move_file_invalid_overwrite">Filen existerar redan i destinationsmappen</string>
+ <string name="move_file_error">Ett fel uppstod vid försök att flytta denna fil eller mapp</string>
<string name="forbidden_permissions_move">att flytta den här filen</string>
+ <string name="prefs_category_instant_uploading">Direktuppladning</string>
<string name="prefs_category_security">Säkerhet</string>
+ <string name="prefs_instant_video_upload_path_title">Uppladdnings-sökväg för video</string>
<string name="shared_subject_header">delad</string>
+ <string name="with_you_subject_header">med dig</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="auth_redirect_non_secure_connection_title">Güvenli bağlantı, güvenli olmayan bir rotaya yönlendirildi.</string>
<string name="actionbar_logger">Günlükler</string>
<string name="log_send_history_button">Geçmişi Gönder</string>
- <string name="log_send_no_mail_app">Günlükleri göndermek için uygulama bulunamadı. E-posta uygulamasını yükleyin!</string>
+ <string name="log_send_no_mail_app">Kayıtları göndermek için uygulama bulunamadı. E-posta uygulamasını yükleyin!</string>
<string name="log_send_mail_subject">%1$s Android uygulama kayıtları</string>
- <string name="log_progress_dialog_text">Yükleniyor...</string>
+ <string name="log_progress_dialog_text">Veri yükleniyor...</string>
<string name="saml_authentication_required_text">Kimlik doğrulama gerekli</string>
<string name="saml_authentication_wrong_pass">Hatalı parola</string>
<string name="actionbar_move">Taşı</string>
<string name="prefs_category_instant_uploading">Anında Yüklemeler</string>
<string name="prefs_category_security">Güvenlik</string>
<string name="prefs_instant_video_upload_path_title">Video Yükleme Yolu</string>
+ <string name="download_folder_failed_content">%1$s klasörün indirilmesi tamamlanamadı</string>
<string name="shared_subject_header">paylaşılan</string>
+ <string name="with_you_subject_header">sizinle</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="prefs_recommend">Порадити товаришу</string>
<string name="prefs_feedback">Зворотній зв\'язок</string>
<string name="prefs_imprint">Відбиток</string>
+ <string name="prefs_remember_last_share_location">Запам\'ятати позицію</string>
+ <string name="prefs_remember_last_upload_location_summary">Запам\'ятати останній опублікований шлях завантаження</string>
<string name="recommend_subject">Спробуйте %1$s на своєму смартфоні!</string>
<string name="recommend_text">Пропоную вам користуватися %1$s на вашому смартфоні!\nЗавантажити можна за посиланням: %2$s</string>
<string name="auth_check_server">Перевірити сервер</string>
<string name="auth_redirect_non_secure_connection_title">Безпечне підключення перенаправляється через незабезпечений маршрут.</string>
<string name="actionbar_logger">Журнали</string>
<string name="log_send_history_button">Надіслати історію</string>
+ <string name="log_send_no_mail_app">Немає додатку для відправки журналів. Встановіть поштовий додаток!</string>
+ <string name="log_send_mail_subject">%1$s Android лог додатку</string>
+ <string name="log_progress_dialog_text">Завантаження даних...</string>
<string name="saml_authentication_required_text">Потрібна аутентифікація</string>
<string name="saml_authentication_wrong_pass">Невірний пароль</string>
<string name="actionbar_move">Перемістити</string>
<string name="forbidden_permissions_move">перемістити цей файл</string>
<string name="prefs_category_instant_uploading">Миттєво завантаження</string>
<string name="prefs_category_security">Безпека</string>
+ <string name="prefs_instant_video_upload_path_title">Шлях завантаження відео</string>
<string name="shared_subject_header">спільне</string>
+ <string name="with_you_subject_header">з тобою</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
--- /dev/null
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--TODO re-enable when server-side folder size calculation is available
+ <item>Biggest - Smallest</item>-->
+ <string name="empty"></string>
+</resources>
<string name="prefs_category_security">安全性</string>
<string name="prefs_instant_video_upload_path_title">影片上傳路徑</string>
<string name="shared_subject_header">以分享的</string>
+ <string name="with_you_subject_header">與你</string>
+ <string name="subject_token">%1$s %2$s >>%3$s<< %4$s</string>
</resources>
<string name="prefs_category_security">Security</string>
<string name="prefs_instant_video_upload_path_title">Upload Video Path</string>
+ <string name="download_folder_failed_content">Download of %1$s folder could not be completed</string>
<string name="shared_subject_header">shared</string>
<string name="with_you_subject_header">with you</string>
android:summary="@string/prefs_pincode_summary"/>
</PreferenceCategory>
- <PreferenceCategory android:title="@string/prefs_category_instant_uploading">
- <com.owncloud.android.ui.PreferenceWithLongSummary
- android:title="@string/prefs_instant_upload_path_title"
- android:key="instant_upload_path" />
- <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_uploading"
+ <PreferenceCategory android:title="@string/prefs_category_instant_uploading" android:key="instant_uploading_category">
+ <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_uploading"
android:title="@string/prefs_instant_upload"
android:summary="@string/prefs_instant_upload_summary"/>
- <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:dependency="instant_uploading"
- android:disableDependentsState="true"
+ <com.owncloud.android.ui.PreferenceWithLongSummary
+ android:title="@string/prefs_instant_upload_path_title"
+ android:key="instant_upload_path" />
+ <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle
android:title="@string/instant_upload_on_wifi"
android:key="instant_upload_on_wifi"/>
+ <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_video_uploading"
+ android:title="@string/prefs_instant_video_upload"
+ android:summary="@string/prefs_instant_video_upload_summary" />
<com.owncloud.android.ui.PreferenceWithLongSummary
android:title="@string/prefs_instant_video_upload_path_title"
android:key="instant_video_upload_path" />
- <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_video_uploading"
- android:title="@string/prefs_instant_video_upload"
- android:summary="@string/prefs_instant_video_upload_summary"/>
- <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:dependency="instant_video_uploading"
- android:disableDependentsState="true"
+ <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle
android:title="@string/instant_video_upload_on_wifi"
android:key="instant_video_upload_on_wifi"/>
<!-- DISABLED FOR RELEASE UNTIL FIXED
\r
if (mOperationsServiceBinder != null) {\r
//Log_OC.wtf(TAG, "getting access token..." );\r
- mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent);\r
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);\r
}\r
}\r
\r
normalizeUrlSuffix(uri)\r
);\r
if (mOperationsServiceBinder != null) {\r
- mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent);\r
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);\r
} else {\r
Log_OC.wtf(TAG, "Server check tried with OperationService unbound!" );\r
}\r
\r
if (mOperationsServiceBinder != null) {\r
//Log_OC.wtf(TAG, "starting existenceCheckRemoteOperation..." );\r
- mWaitingForOpId = mOperationsServiceBinder.newOperation(existenceCheckIntent);\r
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(existenceCheckIntent);\r
}\r
}\r
\r
\r
if (mOperationsServiceBinder != null) {\r
//Log_OC.wtf(TAG, "starting getRemoteUserNameOperation..." );\r
- mWaitingForOpId = mOperationsServiceBinder.newOperation(getUserNameIntent);\r
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getUserNameIntent);\r
}\r
}\r
\r
cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
+ cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading());
boolean sameRemotePath = fileExists(file.getRemotePath());
if (sameRemotePath ||
cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
+ cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading());
boolean existsByPath = fileExists(file.getRemotePath());
if (existsByPath || fileExists(file.getFileId())) {
file.setRemoteId(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID)));
file.setNeedsUpdateThumbnail(c.getInt(
c.getColumnIndex(ProviderTableMeta.FILE_UPDATE_THUMBNAIL)) == 1 ? true : false);
+ file.setDownloading(c.getInt(
+ c.getColumnIndex(ProviderTableMeta.FILE_IS_DOWNLOADING)) == 1 ? true : false);
}
return file;
ProviderTableMeta.FILE_UPDATE_THUMBNAIL,
file.needsUpdateThumbnail() ? 1 : 0
);
+ cv.put(
+ ProviderTableMeta.FILE_IS_DOWNLOADING,
+ file.isDownloading() ? 1 : 0
+ );
boolean existsByPath = fileExists(file.getRemotePath());
if (existsByPath || fileExists(file.getFileId())) {
private boolean mNeedsUpdateThumbnail;
+ private boolean mIsDownloading;
+
/**
* Create new {@link OCFile} with given path.
mPermissions = source.readString();
mRemoteId = source.readString();
mNeedsUpdateThumbnail = source.readInt() == 0;
+ mIsDownloading = source.readInt() == 0;
}
dest.writeString(mPermissions);
dest.writeString(mRemoteId);
dest.writeInt(mNeedsUpdateThumbnail ? 1 : 0);
+ dest.writeInt(mIsDownloading ? 1 : 0);
}
/**
mPermissions = null;
mRemoteId = null;
mNeedsUpdateThumbnail = false;
+ mIsDownloading = false;
}
/**
this.mRemoteId = remoteId;
}
+ public boolean isDownloading() {
+ return mIsDownloading;
+ }
+
+ public void setDownloading(boolean isDownloading) {
+ this.mIsDownloading = isDownloading;
+ }
+
+ public boolean isSynchronizing() {
+ // TODO real implementation
+ return false;
+ }
}
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.media.ExifInterface;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.AsyncTask;
public static Bitmap mDefaultImg =
BitmapFactory.decodeResource(
MainApp.getAppContext().getResources(),
- DisplayUtils.getResourceId("image/png", "default.png")
+ DisplayUtils.getFileTypeIconId("image/png", "default.png")
);
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<OCFile, Void, Bitmap> {
+ public static class ThumbnailGenerationTask extends AsyncTask<Object, Void, Bitmap> {
private final WeakReference<ImageView> mImageViewReference;
private static Account mAccount;
- private OCFile mFile;
+ private Object mFile;
private FileDataStorageManager mStorageManager;
-
+
+
public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager, Account account) {
- // Use a WeakReference to ensure the ImageView can be garbage collected
+ // Use a WeakReference to ensure the ImageView can be garbage collected
mImageViewReference = new WeakReference<ImageView>(imageView);
if (storageManager == null)
throw new IllegalArgumentException("storageManager must not be NULL");
mAccount = account;
}
- // Decode image in background.
+ public ThumbnailGenerationTask(ImageView imageView) {
+ // Use a WeakReference to ensure the ImageView can be garbage collected
+ mImageViewReference = new WeakReference<ImageView>(imageView);
+ }
+
@Override
- protected Bitmap doInBackground(OCFile... params) {
+ protected Bitmap doInBackground(Object... params) {
Bitmap thumbnail = null;
-
+
try {
if (mAccount != null) {
AccountManager accountMgr = AccountManager.get(MainApp.getAppContext());
-
+
mServerVersion = accountMgr.getUserData(mAccount, Constants.KEY_OC_VERSION);
OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, MainApp.getAppContext());
mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
getClientFor(ocAccount, MainApp.getAppContext());
}
-
+
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(r.getDimension(R.dimen.file_icon_size));
-
- if (mFile.isDown()){
- Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
- mFile.getStoragePath(), px, px);
-
- if (bitmap != null) {
- thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
-
- // Rotate image, obeying exif tag
- thumbnail = BitmapUtils.rotateImage(thumbnail, mFile.getStoragePath());
-
- // Add thumbnail to cache
- addBitmapToCache(imageKey, thumbnail);
- mFile.setNeedsUpdateThumbnail(false);
- mStorageManager.saveFile(mFile);
- }
-
- } else {
- // Download thumbnail from server
- if (mClient != null && mServerVersion != null) {
- OwnCloudVersion serverOCVersion = new OwnCloudVersion(mServerVersion);
- if (serverOCVersion.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS)) >= 0) {
- try {
- int status = -1;
-
- String uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
- px + "/" + px + Uri.encode(mFile.getRemotePath(), "/");
- Log_OC.d("Thumbnail", "URI: " + uri);
- GetMethod get = new GetMethod(uri);
- status = mClient.executeMethod(get);
- if (status == HttpStatus.SC_OK) {
- byte[] bytes = get.getResponseBody();
- Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
- thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
-
- // Add thumbnail to cache
- if (thumbnail != null) {
- addBitmapToCache(imageKey, thumbnail);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- } else {
- Log_OC.d(TAG, "Server too old");
- }
- }
- }
+ if (mFile instanceof OCFile) {
+ thumbnail = doOCFileInBackground();
+ } else if (mFile instanceof File) {
+ thumbnail = doFileInBackground();
+ } else {
+ // do nothing
}
-
- } 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();
+
+ }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);
+ final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
- if (imageView.getTag().equals(mFile.getFileId())) {
+ String tagId = "";
+ if (mFile instanceof OCFile){
+ tagId = String.valueOf(((OCFile)mFile).getFileId());
+ } else if (mFile instanceof File){
+ tagId = String.valueOf(((File)mFile).hashCode());
+ }
+ if (String.valueOf(imageView.getTag()).equals(tagId)) {
imageView.setImageBitmap(bitmap);
}
}
}
}
+
+ /**
+ * Add thumbnail to cache
+ * @param imageKey: thumb key
+ * @param bitmap: image for extracting thumbnail
+ * @param path: image path
+ * @param px: thumbnail dp
+ * @return Bitmap
+ */
+ private Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int px){
+
+ Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
+
+ // Rotate image, obeying exif tag
+ thumbnail = BitmapUtils.rotateImage(thumbnail,path);
+
+ // Add thumbnail to cache
+ addBitmapToCache(imageKey, thumbnail);
+
+ return thumbnail;
+ }
+
+ /**
+ * Converts size of file icon from dp to pixel
+ * @return int
+ */
+ private int getThumbnailDimension(){
+ // Converts dp to pixel
+ Resources r = MainApp.getAppContext().getResources();
+ return (int) Math.round(r.getDimension(R.dimen.file_icon_size));
+ }
+
+ private Bitmap doOCFileInBackground() {
+ Bitmap thumbnail = null;
+ OCFile file = (OCFile)mFile;
+
+ 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()) {
+
+ int px = getThumbnailDimension();
+
+ if (file.isDown()) {
+ Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
+ file.getStoragePath(), px, px);
+
+ if (bitmap != null) {
+ thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), px);
+
+ file.setNeedsUpdateThumbnail(false);
+ mStorageManager.saveFile(file);
+ }
+
+ } else {
+ // Download thumbnail from server
+ if (mClient != null && mServerVersion != null) {
+ OwnCloudVersion serverOCVersion = new OwnCloudVersion(mServerVersion);
+ if (serverOCVersion.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS)) >= 0) {
+ try {
+ int status = -1;
+
+ String uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
+ px + "/" + px + Uri.encode(file.getRemotePath(), "/");
+ Log_OC.d("Thumbnail", "URI: " + uri);
+ GetMethod get = new GetMethod(uri);
+ status = mClient.executeMethod(get);
+ if (status == HttpStatus.SC_OK) {
+ byte[] bytes = get.getResponseBody();
+ Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+ thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
+
+ // Add thumbnail to cache
+ if (thumbnail != null) {
+ addBitmapToCache(imageKey, thumbnail);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ Log_OC.d(TAG, "Server too old");
+ }
+ }
+ }
+ }
+
+ return thumbnail;
+
+ }
+
+ private Bitmap doFileInBackground() {
+ Bitmap thumbnail = null;
+ File file = (File)mFile;
+
+ final String imageKey = String.valueOf(file.hashCode());
+
+ // Check disk cache in background thread
+ thumbnail = getBitmapFromDiskCache(imageKey);
+
+ // Not found in disk cache
+ if (thumbnail == null) {
+
+ int px = getThumbnailDimension();
+
+ Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
+ file.getAbsolutePath(), px, px);
+
+ if (bitmap != null) {
+ thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px);
+ }
+ }
+ return thumbnail;
+ }
+
}
-
-
+
+ public static boolean cancelPotentialWork(Object file, ImageView imageView) {
+ final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Object 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 AsyncDrawable extends BitmapDrawable {
private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
public AsyncDrawable(
Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
- ) {
-
+ ) {
+
super(res, bitmap);
bitmapWorkerTaskReference =
- new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
+ new WeakReference<ThumbnailGenerationTask>(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
- }
- }
-
}
db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN attempt INTEGER;");
}
db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN message TEXT;");
-
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ //downgrading is the exception, so deleting and re-creating is acceptable.
+ //otherwise exception will be thrown (cannot downgrade) and oc app will crash.
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_INSTANT_UPLOAD + ";");
+ onCreate(db);
}
}
}
public class ProviderMeta {\r
\r
public static final String DB_NAME = "filelist";\r
- public static final int DB_VERSION = 8;\r
+ public static final int DB_VERSION = 9;\r
\r
private ProviderMeta() {\r
}\r
public static final String FILE_PERMISSIONS = "permissions";\r
public static final String FILE_REMOTE_ID = "remote_id";\r
public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail";\r
+ public static final String FILE_IS_DOWNLOADING= "is_downloading";\r
\r
public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME\r
+ " collate nocase asc";\r
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.services.OperationsService;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.activity.ComponentsGetter;
/**
*
* @param targetFile {@link OCFile} target of the action to filter in the {@link Menu}.
* @param account ownCloud {@link Account} holding targetFile.
- * @param cg Accessor to app components, needed to get access the
- * {@link FileUploader} and {@link FileDownloader} services.
+ * @param cg Accessor to app components, needed to access the
+ * {@link FileUploader} and {@link FileDownloader} services
* @param context Android {@link Context}, needed to access build setup resources.
*/
public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, Context context) {
boolean uploading = false;
if (mComponentsGetter != null && mFile != null && mAccount != null) {
FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder();
- downloading = downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile);
+ downloading = (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile));
+ OperationsServiceBinder opsBinder = mComponentsGetter.getOperationsServiceBinder();
+ downloading |= (opsBinder != null && opsBinder.isSynchronizing(mAccount, mFile.getRemotePath()));
FileUploaderBinder uploaderBinder = mComponentsGetter.getFileUploaderBinder();
- uploading = uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile);
+ uploading = (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile));
}
/// decision is taken for each possible action on a file in the menu
// DOWNLOAD
- if (mFile == null || mFile.isFolder() || mFile.isDown() || downloading || uploading) {
+ if (mFile == null || mFile.isDown() || downloading || uploading) {
toHide.add(R.id.action_download_file);
} else {
// CANCEL DOWNLOAD
- if (mFile == null || !downloading || mFile.isFolder()) {
+ if (mFile == null || !downloading) {
toHide.add(R.id.action_cancel_download);
} else {
toShow.add(R.id.action_cancel_download);
/* ownCloud Android client application
- * Copyright (C) 2012-2014 ownCloud Inc.
+ * Copyright (C) 2012-2015 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,
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
service.putExtra(OperationsService.EXTRA_SEND_INTENT, sendIntent);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
} else {
Log_OC.wtf(TAG, "Trying to open a NULL OCFile");
service.setAction(OperationsService.ACTION_UNSHARE);
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
public void syncFile(OCFile file) {
- // Sync file
- Intent service = new Intent(mFileActivity, OperationsService.class);
- service.setAction(OperationsService.ACTION_SYNC_FILE);
- service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
- service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
- service.putExtra(OperationsService.EXTRA_SYNC_FILE_CONTENTS, true);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
- mFileActivity.showLoadingDialog();
+ if (!file.isFolder()){
+ Intent intent = new Intent(mFileActivity, OperationsService.class);
+ intent.setAction(OperationsService.ACTION_SYNC_FILE);
+ intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+ intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+ intent.putExtra(OperationsService.EXTRA_SYNC_FILE_CONTENTS, true);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(intent);
+ mFileActivity.showLoadingDialog();
+
+ } else {
+ Intent intent = new Intent(mFileActivity, OperationsService.class);
+ intent.setAction(OperationsService.ACTION_SYNC_FOLDER);
+ intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+ intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+ mFileActivity.startService(intent);
+ }
}
-
public void renameFile(OCFile file, String newFilename) {
// RenameFile
Intent service = new Intent(mFileActivity, OperationsService.class);
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
service.putExtra(OperationsService.EXTRA_NEWNAME, newFilename);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
}
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
service.putExtra(OperationsService.EXTRA_REMOVE_ONLY_LOCAL, onlyLocalCopy);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
}
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, remotePath);
service.putExtra(OperationsService.EXTRA_CREATE_FULL_PATH, createFullPath);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
}
-
+ /**
+ * Cancel the transference in downloads (files/folders) and file uploads
+ * @param file OCFile
+ */
public void cancelTransference(OCFile file) {
Account account = mFileActivity.getAccount();
+ if (file.isFolder()) {
+ OperationsService.OperationsServiceBinder opsBinder = mFileActivity.getOperationsServiceBinder();
+ if (opsBinder != null) {
+ opsBinder.cancel(account, file);
+ }
+ }
+
+ // for both files and folders
FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
- FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
+ FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
+ downloaderBinder.cancel(account, file);
+
+ // TODO - review why is this here, and solve in a better way
// Remove etag for parent, if file is a keep_in_sync
if (file.keepInSync()) {
- OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
- parent.setEtag("");
- mFileActivity.getStorageManager().saveFile(parent);
+ OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
+ parent.setEtag("");
+ mFileActivity.getStorageManager().saveFile(parent);
}
-
- downloaderBinder.cancel(account, file);
-
+
} else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
uploaderBinder.cancel(account, file);
}
service.putExtra(OperationsService.EXTRA_NEW_PARENT_PATH, newfile.getRemotePath());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, currentFile.getRemotePath());
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
}
/* ownCloud Android client application
* Copyright (C) 2012 Bartek Przybylski
- * Copyright (C) 2012-2013 ownCloud Inc.
+ * Copyright (C) 2012-2015 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,
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AuthenticatorActivity;
import android.os.Message;
import android.os.Process;
import android.support.v4.app.NotificationCompat;
+import android.util.Pair;
public class FileDownloader extends Service implements OnDatatransferProgressListener {
public static final String EXTRA_ACCOUNT = "ACCOUNT";
public static final String EXTRA_FILE = "FILE";
-
+
private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";
public static final String EXTRA_FILE_PATH = "FILE_PATH";
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
+ public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
private static final String TAG = "FileDownloader";
private ServiceHandler mServiceHandler;
private IBinder mBinder;
private OwnCloudClient mDownloadClient = null;
- private Account mLastAccount = null;
+ private Account mCurrentAccount = null;
private FileDataStorageManager mStorageManager;
- private ConcurrentMap<String, DownloadFileOperation> mPendingDownloads = new ConcurrentHashMap<String, DownloadFileOperation>();
+ private IndexedForest<DownloadFileOperation> mPendingDownloads = new IndexedForest<DownloadFileOperation>();
+
private DownloadFileOperation mCurrentDownload = null;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mNotificationBuilder;
private int mLastPercent;
-
+
public static String getDownloadAddedMessage() {
- return FileDownloader.class.getName().toString() + DOWNLOAD_ADDED_MESSAGE;
+ return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
}
public static String getDownloadFinishMessage() {
- return FileDownloader.class.getName().toString() + DOWNLOAD_FINISH_MESSAGE;
- }
-
- /**
- * Builds a key for mPendingDownloads from the account and file to download
- *
- * @param account Account where the file to download is stored
- * @param file File to download
- */
- private String buildRemoteName(Account account, OCFile file) {
- return account.name + file.getRemotePath();
+ return FileDownloader.class.getName() + DOWNLOAD_FINISH_MESSAGE;
}
-
/**
* Service initialization
/**
* Entry point to add one or several files to the queue of downloads.
- *
- * New downloads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working
- * although the caller activity goes away.
+ *
+ * New downloads are added calling to startService(), resulting in a call to this method.
+ * This ensures the service will keep on working although the caller activity goes away.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if ( !intent.hasExtra(EXTRA_ACCOUNT) ||
!intent.hasExtra(EXTRA_FILE)
- /*!intent.hasExtra(EXTRA_FILE_PATH) ||
- !intent.hasExtra(EXTRA_REMOTE_PATH)*/
) {
Log_OC.e(TAG, "Not enough information provided in intent");
return START_NOT_STICKY;
- }
- Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
- OCFile file = intent.getParcelableExtra(EXTRA_FILE);
-
- AbstractList<String> requestedDownloads = new Vector<String>(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection)
- String downloadKey = buildRemoteName(account, file);
- try {
- DownloadFileOperation newDownload = new DownloadFileOperation(account, file);
- mPendingDownloads.putIfAbsent(downloadKey, newDownload);
- newDownload.addDatatransferProgressListener(this);
- newDownload.addDatatransferProgressListener((FileDownloaderBinder)mBinder);
- requestedDownloads.add(downloadKey);
- sendBroadcastNewDownload(newDownload);
-
- } catch (IllegalArgumentException e) {
- Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
- return START_NOT_STICKY;
- }
-
- if (requestedDownloads.size() > 0) {
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- msg.obj = requestedDownloads;
- mServiceHandler.sendMessage(msg);
+ } else {
+ final Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+ final OCFile file = intent.getParcelableExtra(EXTRA_FILE);
+
+ /*Log_OC.v(
+ "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Received request to download file"
+ );*/
+
+ AbstractList<String> requestedDownloads = new Vector<String>();
+ try {
+ DownloadFileOperation newDownload = new DownloadFileOperation(account, file);
+ newDownload.addDatatransferProgressListener(this);
+ newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
+ Pair<String, String> putResult = mPendingDownloads.putIfAbsent(
+ account, file.getRemotePath(), newDownload
+ );
+ String downloadKey = putResult.first;
+ requestedDownloads.add(downloadKey);
+ /*Log_OC.v(
+ "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Download on " + file.getRemotePath() + " added to queue"
+ );*/
+
+ // Store file on db with state 'downloading'
+ /*
+ TODO - check if helps with UI responsiveness, letting only folders use FileDownloaderBinder to check
+ FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
+ file.setDownloading(true);
+ storageManager.saveFile(file);
+ */
+
+ sendBroadcastNewDownload(newDownload, putResult.second);
+
+ } catch (IllegalArgumentException e) {
+ Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
+ return START_NOT_STICKY;
+ }
+
+ if (requestedDownloads.size() > 0) {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = requestedDownloads;
+ mServiceHandler.sendMessage(msg);
+ }
+ //}
}
return START_NOT_STICKY;
}
-
-
+
+
/**
- * Provides a binder object that clients can use to perform operations on the queue of downloads, excepting the addition of new files.
- *
+ * Provides a binder object that clients can use to perform operations on the queue of downloads,
+ * excepting the addition of new files.
+ *
* Implemented to perform cancellation, pause and resume of existing downloads.
*/
@Override
return false; // not accepting rebinding (default behaviour)
}
-
+
/**
* Binder to let client components to perform operations on the queue of downloads.
- *
+ *
* It provides by itself the available operations.
*/
public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener {
/**
- * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder} instance
+ * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder}
+ * instance.
*/
- private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();
-
-
+ private Map<Long, OnDatatransferProgressListener> mBoundListeners =
+ new HashMap<Long, OnDatatransferProgressListener>();
+
+
/**
* Cancels a pending or current download of a remote file.
- *
- * @param account Owncloud account where the remote file is stored.
+ *
+ * @param account ownCloud account where the remote file is stored.
* @param file A file in the queue of pending downloads
*/
public void cancel(Account account, OCFile file) {
- DownloadFileOperation download = null;
- synchronized (mPendingDownloads) {
- download = mPendingDownloads.remove(buildRemoteName(account, file));
- }
+ /*Log_OC.v(
+ "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Received request to cancel download of " + file.getRemotePath()
+ );
+ Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Removing download of " + file.getRemotePath());*/
+ Pair<DownloadFileOperation, String> removeResult = mPendingDownloads.remove(account, file.getRemotePath());
+ DownloadFileOperation download = removeResult.first;
if (download != null) {
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Canceling returned download of " + file.getRemotePath());*/
download.cancel();
+ } else {
+ if (mCurrentDownload != null && mCurrentAccount != null &&
+ mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
+ account.name.equals(mCurrentAccount.name)) {
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Canceling current sync as descendant: " + mCurrentDownload.getRemotePath());*/
+ mCurrentDownload.cancel();
+ }
}
}
/**
- * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download.
+ * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or
+ * waiting to download.
*
- * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download.
+ * If 'file' is a directory, returns 'true' if any of its descendant files is downloading or
+ * waiting to download.
*
- * @param account Owncloud account where the remote file is stored.
+ * @param account ownCloud account where the remote file is stored.
* @param file A file that could be in the queue of downloads.
*/
public boolean isDownloading(Account account, OCFile file) {
if (account == null || file == null) return false;
- String targetKey = buildRemoteName(account, file);
- synchronized (mPendingDownloads) {
- if (file.isFolder()) {
- // this can be slow if there are many downloads :(
- Iterator<String> it = mPendingDownloads.keySet().iterator();
- boolean found = false;
- while (it.hasNext() && !found) {
- found = it.next().startsWith(targetKey);
- }
- return found;
- } else {
- return (mPendingDownloads.containsKey(targetKey));
- }
- }
+ return (mPendingDownloads.contains(account, file.getRemotePath()));
}
*
* @param listener Object to notify about progress of transfer.
* @param account ownCloud account holding the file of interest.
- * @param file {@link OCfile} of interest for listener.
+ * @param file {@link OCFile} of interest for listener.
*/
- public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+ public void addDatatransferProgressListener (
+ OnDatatransferProgressListener listener, Account account, OCFile file
+ ) {
if (account == null || file == null || listener == null) return;
- String targetKey = buildRemoteName(account, file);
- mBoundListeners.put(targetKey, listener);
+ //String targetKey = buildKey(account, file.getRemotePath());
+ mBoundListeners.put(file.getFileId(), listener);
}
*
* @param listener Object to notify about progress of transfer.
* @param account ownCloud account holding the file of interest.
- * @param file {@link OCfile} of interest for listener.
+ * @param file {@link OCFile} of interest for listener.
*/
- public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+ public void removeDatatransferProgressListener (
+ OnDatatransferProgressListener listener, Account account, OCFile file
+ ) {
if (account == null || file == null || listener == null) return;
- String targetKey = buildRemoteName(account, file);
- if (mBoundListeners.get(targetKey) == listener) {
- mBoundListeners.remove(targetKey);
+ //String targetKey = buildKey(account, file.getRemotePath());
+ Long fileId = file.getFileId();
+ if (mBoundListeners.get(fileId) == listener) {
+ mBoundListeners.remove(fileId);
}
}
@Override
public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
String fileName) {
- String key = buildRemoteName(mCurrentDownload.getAccount(), mCurrentDownload.getFile());
- OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
+ //String key = buildKey(mCurrentDownload.getAccount(), mCurrentDownload.getFile().getRemotePath());
+ OnDatatransferProgressListener boundListener = mBoundListeners.get(mCurrentDownload.getFile().getFileId());
if (boundListener != null) {
boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
}
if (msg.obj != null) {
Iterator<String> it = requestedDownloads.iterator();
while (it.hasNext()) {
- mService.downloadFile(it.next());
+ String next = it.next();
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Handling download file " + next);*/
+ mService.downloadFile(next);
}
}
mService.stopSelf(msg.arg1);
* @param downloadKey Key to access the download to perform, contained in mPendingDownloads
*/
private void downloadFile(String downloadKey) {
-
- synchronized(mPendingDownloads) {
- mCurrentDownload = mPendingDownloads.get(downloadKey);
- }
-
+
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Getting download of " + downloadKey);*/
+ mCurrentDownload = mPendingDownloads.get(downloadKey);
+
if (mCurrentDownload != null) {
notifyDownloadStart(mCurrentDownload);
RemoteOperationResult downloadResult = null;
try {
/// prepare client object to send the request to the ownCloud server
- if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) {
- mLastAccount = mCurrentDownload.getAccount();
- mStorageManager =
- new FileDataStorageManager(mLastAccount, getContentResolver());
- OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this);
- mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
- getClientFor(ocAccount, this);
- }
+ if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentDownload.getAccount())) {
+ mCurrentAccount = mCurrentDownload.getAccount();
+ mStorageManager = new FileDataStorageManager(
+ mCurrentAccount,
+ getContentResolver()
+ );
+ } // else, reuse storage manager from previous operation
+
+ // always get client from client manager, to get fresh credentials in case of update
+ OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this);
+ mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+ getClientFor(ocAccount, this);
+
/// perform the download
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Executing download of " + mCurrentDownload.getRemotePath());*/
downloadResult = mCurrentDownload.execute(mDownloadClient);
if (downloadResult.isSuccess()) {
saveDownloadedFile();
}
} catch (AccountsException e) {
- Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+ Log_OC.e(TAG, "Error while trying to get authorization for " + mCurrentAccount.name, e);
downloadResult = new RemoteOperationResult(e);
} catch (IOException e) {
- Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+ Log_OC.e(TAG, "Error while trying to get authorization for " + mCurrentAccount.name, e);
downloadResult = new RemoteOperationResult(e);
} finally {
- synchronized(mPendingDownloads) {
- mPendingDownloads.remove(downloadKey);
- }
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Removing payload " + mCurrentDownload.getRemotePath());*/
+
+ Pair<DownloadFileOperation, String> removeResult =
+ mPendingDownloads.removePayload(mCurrentAccount, mCurrentDownload.getRemotePath());
+
+ /// notify result
+ notifyDownloadResult(mCurrentDownload, downloadResult);
+
+ sendBroadcastDownloadFinished(mCurrentDownload, downloadResult, removeResult.second);
}
-
- /// notify result
- notifyDownloadResult(mCurrentDownload, downloadResult);
-
- sendBroadcastDownloadFinished(mCurrentDownload, downloadResult);
}
}
mStorageManager.triggerMediaScan(file.getStoragePath());
}
+ /**
+ * Update the OC File after a unsuccessful download
+ */
+ private void updateUnsuccessfulDownloadedFile() {
+ OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
+ file.setDownloading(false);
+ mStorageManager.saveFile(file);
+ }
+
/**
* Creates a status notification to show the download progress
* Callback method to update the progress bar in the status notification.
*/
@Override
- public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath) {
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath)
+ {
int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
if (percent != mLastPercent) {
mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
// let the user update credentials with one click
Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, download.getAccount());
- updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN);
+ updateAccountCredentials.putExtra(
+ AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
+ );
updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
.setContentIntent(PendingIntent.getActivity(
this, (int) System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT));
- mDownloadClient = null; // grant that future retries on the same account will get the fresh credentials
-
} else {
// TODO put something smart in showDetailsIntent
Intent showDetailsIntent = new Intent();
this, (int) System.currentTimeMillis(), showDetailsIntent, 0));
}
- mNotificationBuilder.setContentText(ErrorMessageAdapter.getErrorCauseMessage(downloadResult, download, getResources()));
+ mNotificationBuilder.setContentText(
+ ErrorMessageAdapter.getErrorCauseMessage(downloadResult, download, getResources())
+ );
mNotificationManager.notify(tickerId, mNotificationBuilder.build());
// Remove success notification
/**
* Sends a broadcast when a download finishes in order to the interested activities can update their view
*
- * @param download Finished download operation
- * @param downloadResult Result of the download operation
+ * @param download Finished download operation
+ * @param downloadResult Result of the download operation
+ * @param unlinkedFromRemotePath Path in the downloads tree where the download was unlinked from
*/
- private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) {
+ private void sendBroadcastDownloadFinished(
+ DownloadFileOperation download,
+ RemoteOperationResult downloadResult,
+ String unlinkedFromRemotePath) {
Intent end = new Intent(getDownloadFinishMessage());
end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
end.putExtra(ACCOUNT_NAME, download.getAccount().name);
end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+ if (unlinkedFromRemotePath != null) {
+ end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
+ }
sendStickyBroadcast(end);
}
/**
* Sends a broadcast when a new download is added to the queue.
*
- * @param download Added download operation
+ * @param download Added download operation
+ * @param linkedToRemotePath Path in the downloads tree where the download was linked to
*/
- private void sendBroadcastNewDownload(DownloadFileOperation download) {
+ private void sendBroadcastNewDownload(DownloadFileOperation download, String linkedToRemotePath) {
Intent added = new Intent(getDownloadAddedMessage());
added.putExtra(ACCOUNT_NAME, download.getAccount().name);
added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+ added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
sendStickyBroadcast(added);
}
if (uploadType == UPLOAD_SINGLE_FILE) {
if (intent.hasExtra(KEY_FILE)) {
- files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
+ files = new OCFile[] { (OCFile) intent.getParcelableExtra(KEY_FILE) };
} else {
localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
*
* If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload.
*
- * @param account Owncloud account where the remote file will be stored.
- * @param file A file that could be in the queue of pending uploads
+ * @param account ownCloud account where the remote file will be stored.
+ * @param file A file that could be in the queue of pending uploads
*/
public boolean isUploading(Account account, OCFile file) {
if (account == null || file == null)
*
* @param listener Object to notify about progress of transfer.
* @param account ownCloud account holding the file of interest.
- * @param file {@link OCfile} of interest for listener.
+ * @param file {@link OCFile} of interest for listener.
*/
public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
if (account == null || file == null || listener == null) return;
*
* @param listener Object to notify about progress of transfer.
* @param account ownCloud account holding the file of interest.
- * @param file {@link OCfile} of interest for listener.
+ * @param file {@link OCFile} of interest for listener.
*/
public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
if (account == null || file == null || listener == null) return;
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.files.services;
+
+import android.accounts.Account;
+import android.util.Pair;
+
+import com.owncloud.android.datamodel.OCFile;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Helper structure to keep the trees of folders containing any file downloading or synchronizing.
+ *
+ * A map provides the indexation based in hashing.
+ *
+ * A tree is created per account.
+ *
+ * @author David A. Velasco
+ */
+public class IndexedForest<V> {
+
+ private ConcurrentMap<String, Node<V>> mMap = new ConcurrentHashMap<String, Node<V>>();
+
+ private class Node<V> {
+ String mKey = null;
+ Node<V> mParent = null;
+ Set<Node<V>> mChildren = new HashSet<Node<V>>(); // TODO be careful with hash()
+ V mPayload = null;
+
+ // payload is optional
+ public Node(String key, V payload) {
+ if (key == null) {
+ throw new IllegalArgumentException("Argument key MUST NOT be null");
+ }
+ mKey = key;
+ mPayload = payload;
+ }
+
+ public Node<V> getParent() {
+ return mParent;
+ };
+
+ public Set<Node<V>> getChildren() {
+ return mChildren;
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public V getPayload() {
+ return mPayload;
+ }
+
+ public void addChild(Node<V> child) {
+ mChildren.add(child);
+ child.setParent(this);
+ }
+
+ private void setParent(Node<V> parent) {
+ mParent = parent;
+ }
+
+ public boolean hasChildren() {
+ return mChildren.size() > 0;
+ }
+
+ public void removeChild(Node<V> removed) {
+ mChildren.remove(removed);
+ }
+
+ public void clearPayload() {
+ mPayload = null;
+ }
+ }
+
+
+ public /* synchronized */ Pair<String, String> putIfAbsent(Account account, String remotePath, V value) {
+ String targetKey = buildKey(account, remotePath);
+ Node<V> valuedNode = new Node(targetKey, value);
+ mMap.putIfAbsent(
+ targetKey,
+ valuedNode
+ );
+
+ String currentPath = remotePath, parentPath = null, parentKey = null;
+ Node<V> currentNode = valuedNode, parentNode = null;
+ boolean linked = false;
+ while (!OCFile.ROOT_PATH.equals(currentPath) && !linked) {
+ parentPath = new File(currentPath).getParent();
+ if (!parentPath.endsWith(OCFile.PATH_SEPARATOR)) {
+ parentPath += OCFile.PATH_SEPARATOR;
+ }
+ parentKey = buildKey(account, parentPath);
+ parentNode = mMap.get(parentKey);
+ if (parentNode == null) {
+ parentNode = new Node(parentKey, null);
+ parentNode.addChild(currentNode);
+ mMap.put(parentKey, parentNode);
+ } else {
+ parentNode.addChild(currentNode);
+ linked = true;
+ }
+ currentPath = parentPath;
+ currentNode = parentNode;
+ }
+
+ String linkedTo = OCFile.ROOT_PATH;
+ if (linked) {
+ linkedTo = parentNode.getKey().substring(account.name.length());
+ }
+ return new Pair<String, String>(targetKey, linkedTo);
+ };
+
+
+ public Pair<V, String> removePayload(Account account, String remotePath) {
+ String targetKey = buildKey(account, remotePath);
+ Node<V> target = mMap.get(targetKey);
+ if (target != null) {
+ target.clearPayload();
+ if (!target.hasChildren()) {
+ return remove(account, remotePath);
+ }
+ }
+ return new Pair<V, String>(null, null);
+ }
+
+
+ public /* synchronized */ Pair<V, String> remove(Account account, String remotePath) {
+ String targetKey = buildKey(account, remotePath);
+ Node<V> firstRemoved = mMap.remove(targetKey);
+ String unlinkedFrom = null;
+
+ if (firstRemoved != null) {
+ /// remove children
+ removeDescendants(firstRemoved);
+
+ /// remove ancestors if only here due to firstRemoved
+ Node<V> removed = firstRemoved;
+ Node<V> parent = removed.getParent();
+ boolean unlinked = false;
+ while (parent != null) {
+ parent.removeChild(removed);
+ if (!parent.hasChildren()) {
+ removed = mMap.remove(parent.getKey());
+ parent = removed.getParent();
+ } else {
+ break;
+ }
+ }
+
+ if (parent != null) {
+ unlinkedFrom = parent.getKey().substring(account.name.length());
+ }
+
+ return new Pair<V, String>(firstRemoved.getPayload(), unlinkedFrom);
+ }
+
+ return new Pair<V, String>(null, null);
+ }
+
+ private void removeDescendants(Node<V> removed) {
+ Iterator<Node<V>> childrenIt = removed.getChildren().iterator();
+ Node<V> child = null;
+ while (childrenIt.hasNext()) {
+ child = childrenIt.next();
+ mMap.remove(child.getKey());
+ removeDescendants(child);
+ }
+ }
+
+ public boolean contains(Account account, String remotePath) {
+ String targetKey = buildKey(account, remotePath);
+ return mMap.containsKey(targetKey);
+ }
+
+ public /* synchronized */ V get(String key) {
+ Node<V> node = mMap.get(key);
+ if (node != null) {
+ return node.getPayload();
+ } else {
+ return null;
+ }
+ }
+
+ public V get(Account account, String remotePath) {
+ String key = buildKey(account, remotePath);
+ return get(key);
+ }
+
+
+ /**
+ * Builds a key to index files
+ *
+ * @param account Account where the file to download is stored
+ * @param remotePath Path of the file in the server
+ */
+ private String buildKey(Account account, String remotePath) {
+ return account.name + remotePath;
+ }
+
+
+
+}
--- /dev/null
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.operations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.http.HttpStatus;
+import android.accounts.Account;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+//import android.support.v4.content.LocalBroadcastManager;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation;
+import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
+
+import com.owncloud.android.syncadapter.FileSyncAdapter;
+import com.owncloud.android.utils.FileStorageUtils;
+
+
+
+/**
+ * Remote operation performing the synchronization of the list of files contained
+ * in a folder identified with its remote path.
+ *
+ * Fetches the list and properties of the files contained in the given folder, including their
+ * properties, and updates the local database with them.
+ *
+ * Does NOT enter in the child folders to synchronize their contents also.
+ *
+ * @author David A. Velasco
+ */
+public class RefreshFolderOperation extends RemoteOperation {
+
+ private static final String TAG = RefreshFolderOperation.class.getSimpleName();
+
+ public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED =
+ RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
+ public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED =
+ RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
+
+ /** Time stamp for the synchronization process in progress */
+ private long mCurrentSyncTime;
+
+ /** Remote folder to synchronize */
+ private OCFile mLocalFolder;
+
+ /** Access to the local database */
+ private FileDataStorageManager mStorageManager;
+
+ /** Account where the file to synchronize belongs */
+ private Account mAccount;
+
+ /** Android context; necessary to send requests to the download service */
+ private Context mContext;
+
+ /** Files and folders contained in the synchronized folder after a successful operation */
+ private List<OCFile> mChildren;
+
+ /** Counter of conflicts found between local and remote files */
+ private int mConflictsFound;
+
+ /** Counter of failed operations in synchronization of kept-in-sync files */
+ private int mFailsInFavouritesFound;
+
+ /**
+ * Map of remote and local paths to files that where locally stored in a location
+ * out of the ownCloud folder and couldn't be copied automatically into it
+ **/
+ private Map<String, String> 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 should be refreshed also */
+ private boolean mIsShareSupported;
+
+ /** 'True' means that the remote folder changed and should be fetched */
+ private boolean mRemoteFolderChanged;
+
+ /** 'True' means that Etag will be ignored */
+ private boolean mIgnoreETag;
+
+
+ /**
+ * Creates a new instance of {@link RefreshFolderOperation}.
+ *
+ * @param folder Folder to synchronize.
+ * @param currentSyncTime Time stamp for the synchronization process in progress.
+ * @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.
+ */
+ public RefreshFolderOperation(OCFile folder,
+ long currentSyncTime,
+ boolean syncFullAccount,
+ boolean isShareSupported,
+ boolean ignoreETag,
+ FileDataStorageManager dataStorageManager,
+ Account account,
+ Context context) {
+ mLocalFolder = folder;
+ mCurrentSyncTime = currentSyncTime;
+ mSyncFullAccount = syncFullAccount;
+ mIsShareSupported = isShareSupported;
+ mStorageManager = dataStorageManager;
+ mAccount = account;
+ mContext = context;
+ mForgottenLocalFiles = new HashMap<String, String>();
+ mRemoteFolderChanged = false;
+ mIgnoreETag = ignoreETag;
+ }
+
+
+ public int getConflictsFound() {
+ return mConflictsFound;
+ }
+
+ public int getFailsInFavouritesFound() {
+ return mFailsInFavouritesFound;
+ }
+
+ public Map<String, String> getForgottenLocalFiles() {
+ return mForgottenLocalFiles;
+ }
+
+ /**
+ * 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.
+ */
+ public List<OCFile> getChildren() {
+ return mChildren;
+ }
+
+ /**
+ * Performs the synchronization.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected RemoteOperationResult run(OwnCloudClient client) {
+ RemoteOperationResult result = null;
+ mFailsInFavouritesFound = 0;
+ mConflictsFound = 0;
+ mForgottenLocalFiles.clear();
+
+ if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) {
+ updateOCVersion(client);
+ }
+
+ result = checkForChanges(client);
+
+ if (result.isSuccess()) {
+ if (mRemoteFolderChanged) {
+ result = fetchAndSyncRemoteFolder(client);
+ } else {
+ mChildren = mStorageManager.getFolderContent(mLocalFolder);
+ }
+ }
+
+ if (!mSyncFullAccount) {
+ sendLocalBroadcast(
+ EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
+ );
+ }
+
+ if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) {
+ refreshSharesForFolder(client); // share result is ignored
+ }
+
+ if (!mSyncFullAccount) {
+ sendLocalBroadcast(
+ EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
+ );
+ }
+
+ return result;
+
+ }
+
+
+ private void updateOCVersion(OwnCloudClient client) {
+ UpdateOCVersionOperation update = new UpdateOCVersionOperation(mAccount, mContext);
+ RemoteOperationResult result = update.execute(client);
+ if (result.isSuccess()) {
+ mIsShareSupported = update.getOCVersion().isSharedSupported();
+ }
+ }
+
+
+ private RemoteOperationResult checkForChanges(OwnCloudClient client) {
+ mRemoteFolderChanged = true;
+ RemoteOperationResult result = null;
+ String remotePath = null;
+
+ remotePath = mLocalFolder.getRemotePath();
+ Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath);
+
+ // remote request
+ ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath);
+ result = operation.execute(client);
+ if (result.isSuccess()){
+ OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
+
+ if (!mIgnoreETag) {
+ // check if remote and local folder are different
+ mRemoteFolderChanged =
+ !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag()));
+ }
+
+ result = new RemoteOperationResult(ResultCode.OK);
+
+ Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ (mRemoteFolderChanged ? "changed" : "not changed"));
+
+ } else {
+ // check failed
+ if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
+ removeLocalFolder();
+ }
+ if (result.isException()) {
+ Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ result.getLogMessage(), result.getException());
+ } else {
+ Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ result.getLogMessage());
+ }
+ }
+
+ return result;
+ }
+
+
+ private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
+ String remotePath = mLocalFolder.getRemotePath();
+ ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(remotePath);
+ RemoteOperationResult result = operation.execute(client);
+ Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath);
+
+ if (result.isSuccess()) {
+ synchronizeData(result.getData(), client);
+ if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
+ 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)
+ removeLocalFolder();
+ }
+
+ return result;
+ }
+
+
+ private void removeLocalFolder() {
+ if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
+ String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
+ mStorageManager.removeFolder(
+ mLocalFolder,
+ true,
+ ( mLocalFolder.isDown() &&
+ mLocalFolder.getStoragePath().startsWith(currentSavePath)
+ )
+ );
+ }
+ }
+
+
+ /**
+ * Synchronizes the data retrieved from the server about the contents of the target folder
+ * with the current data in the local database.
+ *
+ * Grants that mChildren is updated with fresh data after execution.
+ *
+ * @param folderAndFiles Remote folder and children files in Folder
+ *
+ * @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
+ */
+ private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client) {
+ // get 'fresh data' from the database
+ mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath());
+
+ // parse data from remote folder
+ OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
+ remoteFolder.setParentId(mLocalFolder.getParentId());
+ remoteFolder.setFileId(mLocalFolder.getFileId());
+
+ Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath()
+ + " changed - starting update of local data ");
+
+ List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
+ List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+
+ // get current data about local contents of the folder to synchronize
+ List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder);
+ Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
+ for (OCFile file : localFiles) {
+ localFilesMap.put(file.getRemotePath(), file);
+ }
+
+ // loop to update every child
+ OCFile remoteFile = null, localFile = null;
+ for (int i=1; i<folderAndFiles.size(); i++) {
+ /// new OCFile instance with the data from the server
+ remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
+ remoteFile.setParentId(mLocalFolder.getFileId());
+
+ /// retrieve local data for the read file
+ // localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
+ localFile = localFilesMap.remove(remoteFile.getRemotePath());
+
+ /// 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.setStoragePath(localFile.getStoragePath());
+ // 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
+ } else if (mRemoteFolderChanged && remoteFile.isImage() &&
+ remoteFile.getModificationTimestamp() != localFile.getModificationTimestamp()) {
+ remoteFile.setNeedsUpdateThumbnail(true);
+ Log.d(TAG, "Image " + remoteFile.getFileName() + " updated on the server");
+ }
+ remoteFile.setPublicLink(localFile.getPublicLink());
+ remoteFile.setShareByLink(localFile.isShareByLink());
+ } else {
+ // 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); // policy - local files are COPIED
+ // into the ownCloud local folder;
+ searchForLocalFileInDefaultPath(remoteFile); // legacy
+
+ /// prepare content synchronization for kept-in-sync files
+ if (remoteFile.keepInSync()) {
+ SynchronizeFileOperation operation = new SynchronizeFileOperation( localFile,
+ remoteFile,
+ mAccount,
+ true,
+ mContext
+ );
+
+ filesToSyncContents.add(operation);
+ }
+
+ updatedFiles.add(remoteFile);
+ }
+
+ // save updated contents in local database
+ mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+
+ // request for the synchronization of file contents AFTER saving current remote properties
+ startContentSynchronizations(filesToSyncContents, client);
+
+ mChildren = updatedFiles;
+ }
+
+ /**
+ * 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.
+ *
+ * @param filesToSyncContents Synchronization operations to execute.
+ * @param client Interface to the remote ownCloud server.
+ */
+ private void startContentSynchronizations(
+ List<SynchronizeFileOperation> filesToSyncContents, OwnCloudClient client
+ ) {
+ RemoteOperationResult contentsResult = null;
+ for (SynchronizeFileOperation op: filesToSyncContents) {
+ 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());
+ } else {
+ Log_OC.e(TAG, "Error while synchronizing favourites : "
+ + contentsResult.getLogMessage());
+ }
+ }
+ } // won't let these fails break the synchronization process
+ }
+ }
+
+
+ public boolean isMultiStatus(int status) {
+ return (status == HttpStatus.SC_MULTI_STATUS);
+ }
+
+ /**
+ * Creates and populates a new {@link OCFile} object with the data read from the server.
+ *
+ * @param remote remote file read from the server (remote file or folder).
+ * @return New OCFile instance representing the remote resource described by we.
+ */
+ private OCFile fillOCFile(RemoteFile remote) {
+ OCFile file = new OCFile(remote.getRemotePath());
+ file.setCreationTimestamp(remote.getCreationTimestamp());
+ file.setFileLength(remote.getLength());
+ file.setMimetype(remote.getMimeType());
+ file.setModificationTimestamp(remote.getModifiedTimestamp());
+ file.setEtag(remote.getEtag());
+ file.setPermissions(remote.getPermissions());
+ file.setRemoteId(remote.getRemoteId());
+ return file;
+ }
+
+
+ /**
+ * 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}
+ *)
+ * @param file File to check and fix.
+ */
+ private void checkAndFixForeignStoragePath(OCFile file) {
+ String storagePath = file.getStoragePath();
+ String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
+ if (storagePath != null && !storagePath.equals(expectedPath)) {
+ /// fix storagePaths out of the local ownCloud folder
+ File originalFile = new File(storagePath);
+ if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
+ mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+ file.setStoragePath(null);
+
+ } else {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ File expectedFile = new File(expectedPath);
+ File expectedParent = expectedFile.getParentFile();
+ expectedParent.mkdirs();
+ if (!expectedParent.isDirectory()) {
+ throw new IOException(
+ "Unexpected error: parent directory could not be created"
+ );
+ }
+ expectedFile.createNewFile();
+ if (!expectedFile.isFile()) {
+ throw new IOException("Unexpected error: target file could not be created");
+ }
+ in = new FileInputStream(originalFile);
+ out = new FileOutputStream(expectedFile);
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0){
+ out.write(buf, 0, len);
+ }
+ file.setStoragePath(expectedPath);
+
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e);
+ mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+ file.setStoragePath(null);
+
+ } finally {
+ try {
+ if (in != null) in.close();
+ } catch (Exception 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);
+ }
+ }
+ }
+ }
+ }
+
+
+ private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
+ RemoteOperationResult result = null;
+
+ // remote request
+ GetRemoteSharesForFileOperation operation =
+ new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true);
+ result = operation.execute(client);
+
+ if (result.isSuccess()) {
+ // update local database
+ ArrayList<OCShare> shares = new ArrayList<OCShare>();
+ for(Object obj: result.getData()) {
+ shares.add((OCShare) obj);
+ }
+ mStorageManager.saveSharesInFolder(shares, mLocalFolder);
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Scans the default location for saving local copies of files searching for
+ * a 'lost' file with the same full name as the {@link OCFile} received as
+ * parameter.
+ *
+ * @param file File to associate a possible 'lost' local file.
+ */
+ private void searchForLocalFileInDefaultPath(OCFile file) {
+ if (file.getStoragePath() == null && !file.isFolder()) {
+ File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
+ if (f.exists()) {
+ file.setStoragePath(f.getAbsolutePath());
+ file.setLastSyncDateForData(f.lastModified());
+ }
+ }
+ }
+
+
+ /**
+ * 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 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);
+ if (dirRemotePath != null) {
+ intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath);
+ }
+ intent.putExtra(FileSyncAdapter.EXTRA_RESULT, result);
+ mContext.sendStickyBroadcast(intent);
+ //LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+ }
+
+
+ public boolean getRemoteFolderChanged() {
+ return mRemoteFolderChanged;
+ }
+
+}
*/
private boolean isValidNewName() throws IOException {
// check tricky names
- if (mNewName == null || mNewName.length() <= 0 || mNewName.contains(File.separator) || mNewName.contains("%")) {
+ if (mNewName == null || mNewName.length() <= 0 || mNewName.contains(File.separator)) {
return false;
}
// create a test file
private boolean mTransferWasRequested = false;
+ /**
+ * When 'false', uploads to the server are not done; only downloads or conflict detection.
+ * This is a temporal field.
+ * TODO Remove when 'folder synchronization' replaces 'folder download'.
+ */
+ private boolean mAllowUploads;
+
/**
- * Constructor.
+ * Constructor for "full synchronization mode".
*
- * Uses remotePath to retrieve all the data in local cache and remote server when the operation
+ * Uses remotePath to retrieve all the data both in local cache and in the remote OC server when the operation
* is executed, instead of reusing {@link OCFile} instances.
*
+ * Useful for direct synchronization of a single file.
+ *
* @param
* @param account ownCloud account holding the file.
* @param syncFileContents When 'true', transference of data will be started by the
mAccount = account;
mSyncFileContents = syncFileContents;
mContext = context;
+ mAllowUploads = true;
}
/**
- * Constructor allowing to reuse {@link OCFile} instances just queried from cache or network.
+ * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or from remote OC server.
*
- * Useful for folder / account synchronizations.
+ * Useful to include this operation as part of the synchronization of a folder (or a full account), avoiding the
+ * repetition of fetch operations (both in local database or remote server).
*
- * @param localFile Data of file currently hold in device cache. MUSTN't be null.
- * @param serverFile Data of file just retrieved from network. If null, will be
+ * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them, use the other
+ * constructor.
+ *
+ * @param localFile Data of file (just) retrieved from local cache/database.
+ * @param serverFile Data of file (just) retrieved from a remote server. If null, will be
* retrieved from network by the operation when executed.
* @param account ownCloud account holding the file.
* @param syncFileContents When 'true', transference of data will be started by the
mLocalFile = localFile;
mServerFile = serverFile;
- mRemotePath = localFile.getRemotePath();
+ if (mLocalFile != null) {
+ mRemotePath = mLocalFile.getRemotePath();
+ if (mServerFile != null && !mServerFile.getRemotePath().equals(mRemotePath)) {
+ throw new IllegalArgumentException("serverFile and localFile do not correspond to the same OC file");
+ }
+ } else if (mServerFile != null) {
+ mRemotePath = mServerFile.getRemotePath();
+ } else {
+ throw new IllegalArgumentException("Both serverFile and localFile are NULL");
+ }
mAccount = account;
mSyncFileContents = syncFileContents;
mContext = context;
+ mAllowUploads = true;
+ }
+
+
+ /**
+ * Temporal constructor.
+ *
+ * Extends the previous one to allow constrained synchronizations where uploads are never performed - only
+ * downloads or conflict detection.
+ *
+ * Do not use unless you are involved in 'folder synchronization' or 'folder download' work in progress.
+ *
+ * TODO Remove when 'folder synchronization' replaces 'folder download'.
+ *
+ * @param localFile Data of file (just) retrieved from local cache/database. MUSTN't be null.
+ * @param serverFile Data of file (just) retrieved from a remote server. If null, will be
+ * retrieved from network by the operation when executed.
+ * @param account ownCloud account holding the file.
+ * @param syncFileContents When 'true', transference of data will be started by the
+ * operation if needed and no conflict is detected.
+ * @param allowUploads When 'false', uploads to the server are not done; only downloads or conflict
+ * detection.
+ * @param context Android context; needed to start transfers.
+ */
+ public SynchronizeFileOperation(
+ OCFile localFile,
+ OCFile serverFile,
+ Account account,
+ boolean syncFileContents,
+ boolean allowUploads,
+ Context context) {
+
+ this(localFile, serverFile, account, syncFileContents, context);
+ mAllowUploads = allowUploads;
}
boolean serverChanged = false;
/* time for eTag is coming, but not yet
if (mServerFile.getEtag() != null) {
- serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged?
+ serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag()));
} else { */
- // server without etags
- serverChanged = (mServerFile.getModificationTimestamp() != mLocalFile.getModificationTimestampAtLastSyncForData());
+ serverChanged = (
+ mServerFile.getModificationTimestamp() != mLocalFile.getModificationTimestampAtLastSyncForData()
+ );
//}
- boolean localChanged = (mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData());
- // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads
+ boolean localChanged = (
+ mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()
+ );
/// decide action to perform depending upon changes
//if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
} else if (localChanged) {
- if (mSyncFileContents) {
+ if (mSyncFileContents && mAllowUploads) {
requestForUpload(mLocalFile);
// the local update of file properties will be done by the FileUploader service when the upload finishes
} else {
}
- Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage());
+ Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": "
+ + result.getLogMessage());
return result;
}
package com.owncloud.android.operations;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Vector;
-
-import org.apache.http.HttpStatus;
import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
-//import android.support.v4.content.LocalBroadcastManager;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
-
+import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.resources.shares.OCShare;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation;
-import com.owncloud.android.lib.resources.files.FileUtils;
import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
import com.owncloud.android.lib.resources.files.RemoteFile;
-
-import com.owncloud.android.syncadapter.FileSyncAdapter;
+import com.owncloud.android.operations.common.SyncOperation;
+import com.owncloud.android.services.OperationsService;
import com.owncloud.android.utils.FileStorageUtils;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+//import android.support.v4.content.LocalBroadcastManager;
/**
*
* @author David A. Velasco
*/
-public class SynchronizeFolderOperation extends RemoteOperation {
+public class SynchronizeFolderOperation extends SyncOperation {
private static final String TAG = SynchronizeFolderOperation.class.getSimpleName();
- public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED =
- SynchronizeFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
- public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED =
- SynchronizeFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
-
/** Time stamp for the synchronization process in progress */
private long mCurrentSyncTime;
-
- /** Remote folder to synchronize */
- private OCFile mLocalFolder;
-
- /** Access to the local database */
- private FileDataStorageManager mStorageManager;
+
+ /** Remote path of the folder to synchronize */
+ private String mRemotePath;
/** Account where the file to synchronize belongs */
private Account mAccount;
-
+
/** Android context; necessary to send requests to the download service */
private Context mContext;
-
+
+ /** Locally cached information about folder to synchronize */
+ private OCFile mLocalFolder;
+
/** Files and folders contained in the synchronized folder after a successful operation */
- private List<OCFile> mChildren;
+ //private List<OCFile> mChildren;
/** Counter of conflicts found between local and remote files */
private int mConflictsFound;
/** Counter of failed operations in synchronization of kept-in-sync files */
- private int mFailsInFavouritesFound;
+ private int mFailsInFileSyncsFound;
- /**
- * Map of remote and local paths to files that where locally stored in a location
- * out of the ownCloud folder and couldn't be copied automatically into it
- **/
- private Map<String, String> 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 should be refreshed also */
- private boolean mIsShareSupported;
-
/** 'True' means that the remote folder changed and should be fetched */
private boolean mRemoteFolderChanged;
- /** 'True' means that Etag will be ignored */
- private boolean mIgnoreETag;
-
+ private List<OCFile> mFilesForDirectDownload;
+ // to avoid extra PROPFINDs when there was no change in the folder
+ private List<SyncOperation> mFilesToSyncContentsWithoutUpload;
+ // this will go out when 'folder synchronization' replaces 'folder download'; step by step
+
+ private List<SyncOperation> mFavouriteFilesToSyncContents;
+ // this will be used for every file when 'folder synchronization' replaces 'folder download'
+
+ private final AtomicBoolean mCancellationRequested;
+
/**
* Creates a new instance of {@link SynchronizeFolderOperation}.
- *
- * @param folder Folder to synchronize.
- * @param currentSyncTime Time stamp for the synchronization process in progress.
- * @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.
+ * @param remotePath Path to synchronize.
+ * @param account ownCloud account where the folder is located.
+ * @param currentSyncTime Time stamp for the synchronization process in progress.
*/
- public SynchronizeFolderOperation( OCFile folder,
- long currentSyncTime,
- boolean syncFullAccount,
- boolean isShareSupported,
- boolean ignoreETag,
- FileDataStorageManager dataStorageManager,
- Account account,
- Context context ) {
- mLocalFolder = folder;
+ public SynchronizeFolderOperation(Context context, String remotePath, Account account, long currentSyncTime){
+ mRemotePath = remotePath;
mCurrentSyncTime = currentSyncTime;
- mSyncFullAccount = syncFullAccount;
- mIsShareSupported = isShareSupported;
- mStorageManager = dataStorageManager;
mAccount = account;
mContext = context;
- mForgottenLocalFiles = new HashMap<String, String>();
mRemoteFolderChanged = false;
- mIgnoreETag = ignoreETag;
+ mFilesForDirectDownload = new Vector<OCFile>();
+ mFilesToSyncContentsWithoutUpload = new Vector<SyncOperation>();
+ mFavouriteFilesToSyncContents = new Vector<SyncOperation>();
+ mCancellationRequested = new AtomicBoolean(false);
}
-
-
+
+
public int getConflictsFound() {
return mConflictsFound;
}
-
- public int getFailsInFavouritesFound() {
- return mFailsInFavouritesFound;
- }
-
- public Map<String, String> getForgottenLocalFiles() {
- return mForgottenLocalFiles;
- }
-
- /**
- * 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.
- */
- public List<OCFile> getChildren() {
- return mChildren;
+
+ public int getFailsInFileSyncsFound() {
+ return mFailsInFileSyncsFound;
}
-
+
/**
* Performs the synchronization.
- *
+ *
* {@inheritDoc}
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result = null;
- mFailsInFavouritesFound = 0;
+ mFailsInFileSyncsFound = 0;
mConflictsFound = 0;
- mForgottenLocalFiles.clear();
-
- if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) {
- updateOCVersion(client);
- }
- result = checkForChanges(client);
-
- if (result.isSuccess()) {
- if (mRemoteFolderChanged) {
- result = fetchAndSyncRemoteFolder(client);
- } else {
- mChildren = mStorageManager.getFolderContent(mLocalFolder);
+ try {
+ // get locally cached information about folder
+ mLocalFolder = getStorageManager().getFileByPath(mRemotePath);
+
+ result = checkForChanges(client);
+
+ if (result.isSuccess()) {
+ if (mRemoteFolderChanged) {
+ result = fetchAndSyncRemoteFolder(client);
+
+ } else {
+ prepareOpsFromLocalKnowledge();
+ }
+
+ if (result.isSuccess()) {
+ syncContents(client);
+ }
+
}
+
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+
+ } catch (OperationCancelledException e) {
+ result = new RemoteOperationResult(e);
}
-
- if (!mSyncFullAccount) {
- sendLocalBroadcast(
- EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
- );
- }
-
- if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) {
- refreshSharesForFolder(client); // share result is ignored
- }
-
- if (!mSyncFullAccount) {
- sendLocalBroadcast(
- EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
- );
- }
-
- return result;
-
- }
+ return result;
- private void updateOCVersion(OwnCloudClient client) {
- UpdateOCVersionOperation update = new UpdateOCVersionOperation(mAccount, mContext);
- RemoteOperationResult result = update.execute(client);
- if (result.isSuccess()) {
- mIsShareSupported = update.getOCVersion().isSharedSupported();
- }
}
-
- private RemoteOperationResult checkForChanges(OwnCloudClient client) {
+ private RemoteOperationResult checkForChanges(OwnCloudClient client) throws OperationCancelledException {
+ Log_OC.d(TAG, "Checking changes in " + mAccount.name + mRemotePath);
+
mRemoteFolderChanged = true;
RemoteOperationResult result = null;
- String remotePath = null;
-
- remotePath = mLocalFolder.getRemotePath();
- Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath);
- // remote request
- ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath);
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+
+ // remote request
+ ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mRemotePath);
result = operation.execute(client);
if (result.isSuccess()){
OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
- if (!mIgnoreETag) {
- // check if remote and local folder are different
- mRemoteFolderChanged =
+ // check if remote and local folder are different
+ mRemoteFolderChanged =
!(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag()));
- }
result = new RemoteOperationResult(ResultCode.OK);
-
- Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " +
+
+ Log_OC.i(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
(mRemoteFolderChanged ? "changed" : "not changed"));
-
+
} else {
// check failed
if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
removeLocalFolder();
}
if (result.isException()) {
- Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ Log_OC.e(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
result.getLogMessage(), result.getException());
} else {
- Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ Log_OC.e(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
result.getLogMessage());
}
+
}
-
+
return result;
}
- private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
- String remotePath = mLocalFolder.getRemotePath();
- ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(remotePath);
- RemoteOperationResult result = operation.execute(client);
- Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath);
+ private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) throws OperationCancelledException {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(mRemotePath);
+ RemoteOperationResult result = operation.execute(client);
+ Log_OC.d(TAG, "Synchronizing " + mAccount.name + mRemotePath);
+
if (result.isSuccess()) {
synchronizeData(result.getData(), client);
- if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
- result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
+ if (mConflictsFound > 0 || mFailsInFileSyncsFound > 0) {
+ result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
// should be a different result code, but will do the job
}
} else {
removeLocalFolder();
}
+
return result;
}
-
+
private void removeLocalFolder() {
- if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
+ FileDataStorageManager storageManager = getStorageManager();
+ if (storageManager.fileExists(mLocalFolder.getFileId())) {
String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
- mStorageManager.removeFolder(
- mLocalFolder,
- true,
- ( mLocalFolder.isDown() &&
+ storageManager.removeFolder(
+ mLocalFolder,
+ true,
+ ( mLocalFolder.isDown() && // TODO: debug, I think this is always false for folders
mLocalFolder.getStoragePath().startsWith(currentSavePath)
)
);
/**
- * Synchronizes the data retrieved from the server about the contents of the target folder
+ * Synchronizes the data retrieved from the server about the contents of the target folder
* with the current data in the local database.
- *
+ *
* Grants that mChildren is updated with fresh data after execution.
- *
- * @param folderAndFiles Remote folder and children files in Folder
- *
- * @param client Client instance to the remote server where the data were
- * retrieved.
+ *
+ * @param folderAndFiles Remote folder and children files in Folder
+ *
+ * @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
*/
- private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client) {
- // get 'fresh data' from the database
- mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath());
-
- // parse data from remote folder
+ private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client)
+ throws OperationCancelledException {
+ FileDataStorageManager storageManager = getStorageManager();
+
+ // parse data from remote folder
OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
remoteFolder.setParentId(mLocalFolder.getParentId());
remoteFolder.setFileId(mLocalFolder.getFileId());
-
- Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath()
+
+ Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath()
+ " changed - starting update of local data ");
-
+
List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
- List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+ mFilesForDirectDownload.clear();
+ mFilesToSyncContentsWithoutUpload.clear();
+ mFavouriteFilesToSyncContents.clear();
+
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
// get current data about local contents of the folder to synchronize
- List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder);
+ List<OCFile> localFiles = storageManager.getFolderContent(mLocalFolder);
Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
for (OCFile file : localFiles) {
localFilesMap.put(file.getRemotePath(), file);
}
-
- // loop to update every child
+
+ // loop to synchronize every child
OCFile remoteFile = null, localFile = null;
for (int i=1; i<folderAndFiles.size(); i++) {
/// new OCFile instance with the data from the server
remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
remoteFile.setParentId(mLocalFolder.getFileId());
- /// retrieve local data for the read file
+ /// retrieve local data for the read file
// localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
localFile = localFilesMap.remove(remoteFile.getRemotePath());
-
+
/// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server)
remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
if (localFile != null) {
localFile.getModificationTimestampAtLastSyncForData()
);
remoteFile.setStoragePath(localFile.getStoragePath());
- // eTag will not be updated unless contents are synchronized
+ // eTag will not be updated unless contents are synchronized
// (Synchronize[File|Folder]Operation with remoteFile as parameter)
- remoteFile.setEtag(localFile.getEtag());
+ remoteFile.setEtag(localFile.getEtag());
if (remoteFile.isFolder()) {
- remoteFile.setFileLength(localFile.getFileLength());
+ remoteFile.setFileLength(localFile.getFileLength());
// TODO move operations about size of folders to FileContentProvider
} else if (mRemoteFolderChanged && remoteFile.isImage() &&
remoteFile.getModificationTimestamp() != localFile.getModificationTimestamp()) {
remoteFile.setPublicLink(localFile.getPublicLink());
remoteFile.setShareByLink(localFile.isShareByLink());
} else {
- // remote eTag will not be updated unless contents are synchronized
+ // remote eTag will not be updated unless contents are synchronized
// (Synchronize[File|Folder]Operation with remoteFile as parameter)
- remoteFile.setEtag("");
+ remoteFile.setEtag("");
}
/// check and fix, if needed, local storage path
- checkAndFixForeignStoragePath(remoteFile); // policy - local files are COPIED
- // into the ownCloud local folder;
- searchForLocalFileInDefaultPath(remoteFile); // legacy
-
- /// prepare content synchronization for kept-in-sync files
- if (remoteFile.keepInSync()) {
- SynchronizeFileOperation operation = new SynchronizeFileOperation( localFile,
- remoteFile,
- mAccount,
- true,
- mContext
- );
+ searchForLocalFileInDefaultPath(remoteFile);
+
+ /// classify file to sync/download contents later
+ if (remoteFile.isFolder()) {
+ /// to download children files recursively
+ synchronized(mCancellationRequested) {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ startSyncFolderOperation(remoteFile.getRemotePath());
+ }
+
+ } else if (remoteFile.keepInSync()) {
+ /// prepare content synchronization for kept-in-sync files
+ SynchronizeFileOperation operation = new SynchronizeFileOperation(
+ localFile,
+ remoteFile,
+ mAccount,
+ true,
+ mContext
+ );
+ mFavouriteFilesToSyncContents.add(operation);
- filesToSyncContents.add(operation);
+ } else {
+ /// prepare limited synchronization for regular files
+ SynchronizeFileOperation operation = new SynchronizeFileOperation(
+ localFile,
+ remoteFile,
+ mAccount,
+ true,
+ false,
+ mContext
+ );
+ mFilesToSyncContentsWithoutUpload.add(operation);
}
-
+
updatedFiles.add(remoteFile);
}
// save updated contents in local database
- mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+ storageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
- // request for the synchronization of file contents AFTER saving current remote properties
- startContentSynchronizations(filesToSyncContents, client);
+ }
+
+
+ private void prepareOpsFromLocalKnowledge() throws OperationCancelledException {
+ List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder);
+ for (OCFile child : children) {
+ /// classify file to sync/download contents later
+ if (child.isFolder()) {
+ /// to download children files recursively
+ synchronized(mCancellationRequested) {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ startSyncFolderOperation(child.getRemotePath());
+ }
- mChildren = updatedFiles;
+ } else {
+ /// prepare limited synchronization for regular files
+ if (!child.isDown()) {
+ mFilesForDirectDownload.add(child);
+ }
+ }
+ }
+ }
+
+
+ private void syncContents(OwnCloudClient client) throws OperationCancelledException {
+ startDirectDownloads();
+ startContentSynchronizations(mFilesToSyncContentsWithoutUpload, client);
+ startContentSynchronizations(mFavouriteFilesToSyncContents, client);
+ }
+
+
+ private void startDirectDownloads() throws OperationCancelledException {
+ for (OCFile file : mFilesForDirectDownload) {
+ synchronized(mCancellationRequested) {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ Intent i = new Intent(mContext, FileDownloader.class);
+ i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+ i.putExtra(FileDownloader.EXTRA_FILE, file);
+ mContext.startService(i);
+ }
+ }
}
/**
* 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
+ *
+ * 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<SynchronizeFileOperation> filesToSyncContents, OwnCloudClient client
- ) {
+ private void startContentSynchronizations(List<SyncOperation> filesToSyncContents, OwnCloudClient client)
+ throws OperationCancelledException {
+
RemoteOperationResult contentsResult = null;
- for (SynchronizeFileOperation op: filesToSyncContents) {
- contentsResult = op.execute(mStorageManager, mContext); // async
+ for (SyncOperation op: filesToSyncContents) {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ contentsResult = op.execute(getStorageManager(), mContext);
if (!contentsResult.isSuccess()) {
if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
mConflictsFound++;
} else {
- mFailsInFavouritesFound++;
+ mFailsInFileSyncsFound++;
if (contentsResult.getException() != null) {
- Log_OC.e(TAG, "Error while synchronizing favourites : "
+ Log_OC.e(TAG, "Error while synchronizing file : "
+ contentsResult.getLogMessage(), contentsResult.getException());
} else {
- Log_OC.e(TAG, "Error while synchronizing favourites : "
+ Log_OC.e(TAG, "Error while synchronizing file : "
+ contentsResult.getLogMessage());
}
}
+ // TODO - use the errors count in notifications
} // won't let these fails break the synchronization process
}
}
-
- public boolean isMultiStatus(int status) {
- return (status == HttpStatus.SC_MULTI_STATUS);
- }
-
+
/**
- * Creates and populates a new {@link OCFile} object with the data read from the server.
- *
+ * Creates and populates a new {@link com.owncloud.android.datamodel.OCFile} object with the data read from the server.
+ *
* @param remote remote file read from the server (remote file or folder).
* @return New OCFile instance representing the remote resource described by we.
*/
file.setRemoteId(remote.getRemoteId());
return file;
}
-
- /**
- * 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}
- *)
- * @param file File to check and fix.
- */
- private void checkAndFixForeignStoragePath(OCFile file) {
- String storagePath = file.getStoragePath();
- String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
- if (storagePath != null && !storagePath.equals(expectedPath)) {
- /// fix storagePaths out of the local ownCloud folder
- File originalFile = new File(storagePath);
- if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
- mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
- file.setStoragePath(null);
-
- } else {
- InputStream in = null;
- OutputStream out = null;
- try {
- File expectedFile = new File(expectedPath);
- File expectedParent = expectedFile.getParentFile();
- expectedParent.mkdirs();
- if (!expectedParent.isDirectory()) {
- throw new IOException(
- "Unexpected error: parent directory could not be created"
- );
- }
- expectedFile.createNewFile();
- if (!expectedFile.isFile()) {
- throw new IOException("Unexpected error: target file could not be created");
- }
- in = new FileInputStream(originalFile);
- out = new FileOutputStream(expectedFile);
- byte[] buf = new byte[1024];
- int len;
- while ((len = in.read(buf)) > 0){
- out.write(buf, 0, len);
- }
- file.setStoragePath(expectedPath);
-
- } catch (Exception e) {
- Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e);
- mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
- file.setStoragePath(null);
-
- } finally {
- try {
- if (in != null) in.close();
- } catch (Exception 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);
- }
- }
- }
- }
- }
-
-
- private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
- RemoteOperationResult result = null;
-
- // remote request
- GetRemoteSharesForFileOperation operation =
- new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true);
- result = operation.execute(client);
-
- if (result.isSuccess()) {
- // update local database
- ArrayList<OCShare> shares = new ArrayList<OCShare>();
- for(Object obj: result.getData()) {
- shares.add((OCShare) obj);
- }
- mStorageManager.saveSharesInFolder(shares, mLocalFolder);
- }
-
- return result;
- }
-
/**
* Scans the default location for saving local copies of files searching for
- * a 'lost' file with the same full name as the {@link OCFile} received as
+ * a 'lost' file with the same full name as the {@link com.owncloud.android.datamodel.OCFile} received as
* parameter.
*
* @param file File to associate a possible 'lost' local file.
/**
- * 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 result
+ * Cancel operation
*/
- 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);
- if (dirRemotePath != null) {
- intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath);
- }
- intent.putExtra(FileSyncAdapter.EXTRA_RESULT, result);
- mContext.sendStickyBroadcast(intent);
- //LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+ public void cancel() {
+ mCancellationRequested.set(true);
}
+ public String getFolderPath() {
+ String path = mLocalFolder.getStoragePath();
+ if (path != null && path.length() > 0) {
+ return path;
+ }
+ return FileStorageUtils.getDefaultSavePathFor(mAccount.name, mLocalFolder);
+ }
- public boolean getRemoteFolderChanged() {
- return mRemoteFolderChanged;
+ private void startSyncFolderOperation(String path){
+ Intent intent = new Intent(mContext, OperationsService.class);
+ intent.setAction(OperationsService.ACTION_SYNC_FOLDER);
+ intent.putExtra(OperationsService.EXTRA_ACCOUNT, mAccount);
+ intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, path);
+ mContext.startService(intent);
}
+ public String getRemotePath() {
+ return mRemotePath;
+ }
}
ProviderTableMeta.FILE_REMOTE_ID);
mFileProjectionMap.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL,
ProviderTableMeta.FILE_UPDATE_THUMBNAIL);
+ mFileProjectionMap.put(ProviderTableMeta.FILE_IS_DOWNLOADING,
+ ProviderTableMeta.FILE_IS_DOWNLOADING);
}
private static final int SINGLE_FILE = 1;
+ ProviderTableMeta.FILE_PUBLIC_LINK + " TEXT, "
+ ProviderTableMeta.FILE_PERMISSIONS + " TEXT null,"
+ ProviderTableMeta.FILE_REMOTE_ID + " TEXT null,"
- + ProviderTableMeta.FILE_UPDATE_THUMBNAIL + " INTEGER);" //boolean
+ + ProviderTableMeta.FILE_UPDATE_THUMBNAIL + " INTEGER," //boolean
+ + ProviderTableMeta.FILE_IS_DOWNLOADING + " INTEGER);" //boolean
);
// Create table ocshares
}
}
if (!upgraded)
- Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
+ Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
+ ", newVersion == " + newVersion);
+
+ if (oldVersion < 9 && newVersion >= 9) {
+ Log_OC.i("SQL", "Entering in the #9 ADD in onUpgrade");
+ db.beginTransaction();
+ try {
+ db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
+ " ADD COLUMN " + ProviderTableMeta.FILE_IS_DOWNLOADING + " INTEGER " +
+ " DEFAULT 0");
+
+ upgraded = true;
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+ if (!upgraded)
+ Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
", newVersion == " + newVersion);
}
}
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.RenameFileOperation;
import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
import com.owncloud.android.operations.UnshareLinkOperation;
import android.accounts.Account;
public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
public static final String EXTRA_RESULT = "RESULT";
public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH";
-
+ public static final String EXTRA_FILE = "FILE";
+
// TODO review if ALL OF THEM are necessary
public static final String EXTRA_SUCCESS_IF_ABSENT = "SUCCESS_IF_ABSENT";
public static final String EXTRA_USERNAME = "USERNAME";
public static final String ACTION_REMOVE = "REMOVE";
public static final String ACTION_CREATE_FOLDER = "CREATE_FOLDER";
public static final String ACTION_SYNC_FILE = "SYNC_FILE";
+ public static final String ACTION_SYNC_FOLDER = "SYNC_FOLDER"; // for the moment, just to download
+ //public static final String ACTION_CANCEL_SYNC_FOLDER = "CANCEL_SYNC_FOLDER"; // for the moment, just to download
public static final String ACTION_MOVE_FILE = "MOVE_FILE";
public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
public static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED";
- private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
- new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
private ConcurrentMap<Integer, Pair<RemoteOperation, RemoteOperationResult>>
mUndispatchedFinishedOperations =
}
}
- private Looper mServiceLooper;
- private ServiceHandler mServiceHandler;
- private OperationsServiceBinder mBinder;
- private OwnCloudClient mOwnCloudClient = null;
- private Target mLastTarget = null;
- private FileDataStorageManager mStorageManager;
- private RemoteOperation mCurrentOperation = null;
+ private ServiceHandler mOperationsHandler;
+ private OperationsServiceBinder mOperationsBinder;
+ private SyncFolderHandler mSyncFolderHandler;
/**
* Service initialization
@Override
public void onCreate() {
super.onCreate();
- HandlerThread thread = new HandlerThread("Operations service thread", Process.THREAD_PRIORITY_BACKGROUND);
+ /// First worker thread for most of operations
+ HandlerThread thread = new HandlerThread("Operations thread", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
- mServiceLooper = thread.getLooper();
- mServiceHandler = new ServiceHandler(mServiceLooper, this);
- mBinder = new OperationsServiceBinder();
+ mOperationsHandler = new ServiceHandler(thread.getLooper(), this);
+ mOperationsBinder = new OperationsServiceBinder(mOperationsHandler);
+
+ /// Separated worker thread for download of folders (WIP)
+ thread = new HandlerThread("Syncfolder thread", Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ mSyncFolderHandler = new SyncFolderHandler(thread.getLooper(), this);
}
*
* New operations are added calling to startService(), resulting in a call to this method.
* This ensures the service will keep on working although the caller activity goes away.
- *
- * IMPORTANT: the only operations performed here right now is {@link GetSharedFilesOperation}. The class
- * is taking advantage of it due to time constraints.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- //Log_OC.wtf(TAG, "onStartCommand init" );
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- mServiceHandler.sendMessage(msg);
- //Log_OC.wtf(TAG, "onStartCommand end" );
+ // WIP: for the moment, only SYNC_FOLDER and CANCEL_SYNC_FOLDER is expected here;
+ // the rest of the operations are requested through the Binder
+ if (ACTION_SYNC_FOLDER.equals(intent.getAction())) {
+
+ /*Log_OC.v("NOW " + TAG + ", thread " + Thread.currentThread().getName(), "Received request to sync folder");*/
+
+ if (!intent.hasExtra(EXTRA_ACCOUNT) || !intent.hasExtra(EXTRA_REMOTE_PATH)) {
+ Log_OC.e(TAG, "Not enough information provided in intent");
+ return START_NOT_STICKY;
+ }
+ Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+ String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
+
+ Pair<Account, String> itemSyncKey = new Pair<Account , String>(account, remotePath);
+
+ Pair<Target, RemoteOperation> itemToQueue = newOperation(intent);
+ if (itemToQueue != null) {
+ mSyncFolderHandler.add(account, remotePath, (SynchronizeFolderOperation)itemToQueue.second);
+ Message msg = mSyncFolderHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = itemSyncKey;
+ /*Log_OC.v(
+ "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Sync folder " + remotePath + " added to queue"
+ );*/
+ mSyncFolderHandler.sendMessage(msg);
+ }
+
+ } else {
+ Message msg = mOperationsHandler.obtainMessage();
+ msg.arg1 = startId;
+ mOperationsHandler.sendMessage(msg);
+ }
+
return START_NOT_STICKY;
}
e.printStackTrace();
}
- //Log_OC.wtf(TAG, "Clear mUndispatchedFinisiedOperations" );
+ //Log_OC.wtf(TAG, "Clear mUndispatchedFinishedOperations" );
mUndispatchedFinishedOperations.clear();
//Log_OC.wtf(TAG, "onDestroy end" );
super.onDestroy();
}
-
/**
* Provides a binder object that clients can use to perform actions on the queue of operations,
* except the addition of new operations.
@Override
public IBinder onBind(Intent intent) {
//Log_OC.wtf(TAG, "onBind" );
- return mBinder;
+ return mOperationsBinder;
}
*/
@Override
public boolean onUnbind(Intent intent) {
- ((OperationsServiceBinder)mBinder).clearListeners();
+ ((OperationsServiceBinder)mOperationsBinder).clearListeners();
return false; // not accepting rebinding (default behaviour)
}
-
+
/**
* Binder to let client components to perform actions on the queue of operations.
*
private ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners =
new ConcurrentHashMap<OnRemoteOperationListener, Handler>();
+ private ServiceHandler mServiceHandler = null;
+
+ public OperationsServiceBinder(ServiceHandler serviceHandler) {
+ mServiceHandler = serviceHandler;
+ }
+
+
/**
- * Cancels an operation
+ * Cancels a pending or current synchronization.
*
- * TODO
+ * @param account ownCloud account where the remote folder is stored.
+ * @param file A folder in the queue of pending synchronizations
*/
- public void cancel() {
- // TODO
+ public void cancel(Account account, OCFile file) {
+ /*Log_OC.v(
+ "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Received request to cancel folder " + file.getRemotePath()
+ );*/
+ mSyncFolderHandler.cancel(account, file);
}
-
-
+
+
public void clearListeners() {
mBoundListeners.clear();
* @return 'True' when an operation that enforces the user to wait for completion is in process.
*/
public boolean isPerformingBlockingOperation() {
- return (!mPendingOperations.isEmpty());
+ return (!mServiceHandler.mPendingOperations.isEmpty());
}
/**
- * Creates and adds to the queue a new operation, as described by operationIntent
+ * Creates and adds to the queue a new operation, as described by operationIntent.
+ *
+ * Calls startService to make the operation is processed by the ServiceHandler.
*
* @param operationIntent Intent describing a new operation to queue and execute.
* @return Identifier of the operation created, or null if failed.
*/
- public long newOperation(Intent operationIntent) {
- RemoteOperation operation = null;
- Target target = null;
- try {
- if (!operationIntent.hasExtra(EXTRA_ACCOUNT) &&
- !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
- Log_OC.e(TAG, "Not enough information provided in intent");
-
- } else {
- Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
- String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
- String username = operationIntent.getStringExtra(EXTRA_USERNAME);
- String password = operationIntent.getStringExtra(EXTRA_PASSWORD);
- String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN);
- String cookie = operationIntent.getStringExtra(EXTRA_COOKIE);
- target = new Target(
- account,
- (serverUrl == null) ? null : Uri.parse(serverUrl),
- username,
- password,
- authToken,
- cookie
- );
-
- String action = operationIntent.getAction();
- if (action.equals(ACTION_CREATE_SHARE)) { // Create Share
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
- if (remotePath.length() > 0) {
- operation = new CreateShareOperation(OperationsService.this, remotePath, ShareType.PUBLIC_LINK,
- "", false, "", 1, sendIntent);
- }
-
- } else if (action.equals(ACTION_UNSHARE)) { // Unshare file
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- if (remotePath.length() > 0) {
- operation = new UnshareLinkOperation(
- remotePath,
- OperationsService.this);
- }
-
- } else if (action.equals(ACTION_GET_SERVER_INFO)) {
- // check OC server and get basic information from it
- operation = new GetServerInfoOperation(serverUrl, OperationsService.this);
-
- } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) {
- /// GET ACCESS TOKEN to the OAuth server
- String oauth2QueryParameters =
- operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS);
- operation = new OAuth2GetAccessToken(
- getString(R.string.oauth2_client_id),
- getString(R.string.oauth2_redirect_uri),
- getString(R.string.oauth2_grant_type),
- oauth2QueryParameters);
-
- } else if (action.equals(ACTION_EXISTENCE_CHECK)) {
- // Existence Check
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- boolean successIfAbsent = operationIntent.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT, false);
- operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent);
-
- } else if (action.equals(ACTION_GET_USER_NAME)) {
- // Get User Name
- operation = new GetRemoteUserNameOperation();
-
- } else if (action.equals(ACTION_RENAME)) {
- // Rename file or folder
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- String newName = operationIntent.getStringExtra(EXTRA_NEWNAME);
- operation = new RenameFileOperation(remotePath, newName);
-
- } else if (action.equals(ACTION_REMOVE)) {
- // Remove file or folder
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
- operation = new RemoveFileOperation(remotePath, onlyLocalCopy);
-
- } else if (action.equals(ACTION_CREATE_FOLDER)) {
- // Create Folder
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
- operation = new CreateFolderOperation(remotePath, createFullPath);
-
- } else if (action.equals(ACTION_SYNC_FILE)) {
- // Sync file
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- boolean syncFileContents = operationIntent.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS, true);
- operation = new SynchronizeFileOperation(remotePath, account, syncFileContents, getApplicationContext());
- } else if (action.equals(ACTION_MOVE_FILE)) {
- // Move file/folder
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
- operation = new MoveFileOperation(remotePath,newParentPath,account);
- }
-
- }
-
- } catch (IllegalArgumentException e) {
- Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
- operation = null;
- }
-
- if (operation != null) {
- mPendingOperations.add(new Pair<Target , RemoteOperation>(target, operation));
+ public long queueNewOperation(Intent operationIntent) {
+ Pair<Target, RemoteOperation> itemToQueue = newOperation(operationIntent);
+ if (itemToQueue != null) {
+ mServiceHandler.mPendingOperations.add(itemToQueue);
startService(new Intent(OperationsService.this, OperationsService.class));
- //Log_OC.wtf(TAG, "New operation added, opId: " + operation.hashCode());
- // better id than hash? ; should be good enough by the time being
- return operation.hashCode();
+ return itemToQueue.second.hashCode();
} else {
- //Log_OC.wtf(TAG, "New operation failed, returned Long.MAX_VALUE");
return Long.MAX_VALUE;
}
}
-
+
+
public boolean dispatchResultIfFinished(int operationId, OnRemoteOperationListener listener) {
Pair<RemoteOperation, RemoteOperationResult> undispatched =
mUndispatchedFinishedOperations.remove(operationId);
return true;
//Log_OC.wtf(TAG, "Sending callback later");
} else {
- if (!mPendingOperations.isEmpty()) {
+ if (!mServiceHandler.mPendingOperations.isEmpty()) {
return true;
} else {
return false;
//Log_OC.wtf(TAG, "Not finished yet");
}
}
+
+
+ /**
+ * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting
+ * to download.
+ *
+ * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting
+ * to download.
+ *
+ * @param account ownCloud account where the remote file is stored.
+ * @param remotePath Path of the folder to check if something is synchronizing / downloading / uploading
+ * inside.
+ */
+ public boolean isSynchronizing(Account account, String remotePath) {
+ return mSyncFolderHandler.isSynchronizing(account, remotePath);
+ }
}
-
-
- /**
+
+
+ /**
* Operations worker. Performs the pending operations in the order they were requested.
*
* Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.
*/
private static class ServiceHandler extends Handler {
// don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
+
+
OperationsService mService;
+
+
+ private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
+ new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
+ private RemoteOperation mCurrentOperation = null;
+ private Target mLastTarget = null;
+ private OwnCloudClient mOwnCloudClient = null;
+ private FileDataStorageManager mStorageManager;
+
+
public ServiceHandler(Looper looper, OperationsService service) {
super(looper);
if (service == null) {
@Override
public void handleMessage(Message msg) {
- mService.nextOperation();
+ nextOperation();
mService.stopSelf(msg.arg1);
}
- }
-
-
- /**
- * Performs the next operation in the queue
- */
- private void nextOperation() {
- //Log_OC.wtf(TAG, "nextOperation init" );
- Pair<Target, RemoteOperation> next = null;
- synchronized(mPendingOperations) {
- next = mPendingOperations.peek();
- }
-
- if (next != null) {
+ /**
+ * Performs the next operation in the queue
+ */
+ private void nextOperation() {
- mCurrentOperation = next.second;
- RemoteOperationResult result = null;
- try {
- /// prepare client object to send the request to the ownCloud server
- if (mLastTarget == null || !mLastTarget.equals(next.first)) {
- mLastTarget = next.first;
- if (mLastTarget.mAccount != null) {
- OwnCloudAccount ocAccount = new OwnCloudAccount(mLastTarget.mAccount, this);
- mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
- getClientFor(ocAccount, this);
- mStorageManager =
- new FileDataStorageManager(
- mLastTarget.mAccount,
- getContentResolver());
- } else {
- OwnCloudCredentials credentials = null;
- if (mLastTarget.mUsername != null &&
- mLastTarget.mUsername.length() > 0) {
- credentials = OwnCloudCredentialsFactory.newBasicCredentials(
- mLastTarget.mUsername,
- mLastTarget.mPassword); // basic
-
- } else if (mLastTarget.mAuthToken != null &&
- mLastTarget.mAuthToken.length() > 0) {
- credentials = OwnCloudCredentialsFactory.newBearerCredentials(
- mLastTarget.mAuthToken); // bearer token
-
- } else if (mLastTarget.mCookie != null &&
- mLastTarget.mCookie.length() > 0) {
- credentials = OwnCloudCredentialsFactory.newSamlSsoCredentials(
- mLastTarget.mCookie); // SAML SSO
+ //Log_OC.wtf(TAG, "nextOperation init" );
+
+ Pair<Target, RemoteOperation> next = null;
+ synchronized(mPendingOperations) {
+ next = mPendingOperations.peek();
+ }
+
+ if (next != null) {
+
+ mCurrentOperation = next.second;
+ RemoteOperationResult result = null;
+ try {
+ /// prepare client object to send the request to the ownCloud server
+ if (mLastTarget == null || !mLastTarget.equals(next.first)) {
+ mLastTarget = next.first;
+ if (mLastTarget.mAccount != null) {
+ OwnCloudAccount ocAccount = new OwnCloudAccount(mLastTarget.mAccount, mService);
+ mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+ getClientFor(ocAccount, mService);
+ mStorageManager = new FileDataStorageManager(
+ mLastTarget.mAccount,
+ mService.getContentResolver()
+ );
+ } else {
+ OwnCloudCredentials credentials = null;
+ if (mLastTarget.mUsername != null &&
+ mLastTarget.mUsername.length() > 0) {
+ credentials = OwnCloudCredentialsFactory.newBasicCredentials(
+ mLastTarget.mUsername,
+ mLastTarget.mPassword); // basic
+
+ } else if (mLastTarget.mAuthToken != null &&
+ mLastTarget.mAuthToken.length() > 0) {
+ credentials = OwnCloudCredentialsFactory.newBearerCredentials(
+ mLastTarget.mAuthToken); // bearer token
+
+ } else if (mLastTarget.mCookie != null &&
+ mLastTarget.mCookie.length() > 0) {
+ credentials = OwnCloudCredentialsFactory.newSamlSsoCredentials(
+ mLastTarget.mCookie); // SAML SSO
+ }
+ OwnCloudAccount ocAccount = new OwnCloudAccount(
+ mLastTarget.mServerUrl, credentials);
+ mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+ getClientFor(ocAccount, mService);
+ mStorageManager = null;
}
- OwnCloudAccount ocAccount = new OwnCloudAccount(
- mLastTarget.mServerUrl, credentials);
- mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
- getClientFor(ocAccount, this);
- mStorageManager = null;
}
- }
- /// perform the operation
- if (mCurrentOperation instanceof SyncOperation) {
- result = ((SyncOperation)mCurrentOperation).execute(mOwnCloudClient, mStorageManager);
- } else {
- result = mCurrentOperation.execute(mOwnCloudClient);
- }
+ /// perform the operation
+ if (mCurrentOperation instanceof SyncOperation) {
+ result = ((SyncOperation)mCurrentOperation).execute(mOwnCloudClient, mStorageManager);
+ } else {
+ result = mCurrentOperation.execute(mOwnCloudClient);
+ }
+
+ } catch (AccountsException e) {
+ if (mLastTarget.mAccount == null) {
+ Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
+ } else {
+ Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+ }
+ result = new RemoteOperationResult(e);
+
+ } catch (IOException e) {
+ if (mLastTarget.mAccount == null) {
+ Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
+ } else {
+ Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+ }
+ result = new RemoteOperationResult(e);
+ } catch (Exception e) {
+ if (mLastTarget.mAccount == null) {
+ Log_OC.e(TAG, "Unexpected error for a NULL account", e);
+ } else {
+ Log_OC.e(TAG, "Unexpected error for " + mLastTarget.mAccount.name, e);
+ }
+ result = new RemoteOperationResult(e);
- } catch (AccountsException e) {
- if (mLastTarget.mAccount == null) {
- Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
- } else {
- Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+ } finally {
+ synchronized(mPendingOperations) {
+ mPendingOperations.poll();
+ }
}
- result = new RemoteOperationResult(e);
- } catch (IOException e) {
- if (mLastTarget.mAccount == null) {
- Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
- } else {
- Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
- }
- result = new RemoteOperationResult(e);
- } catch (Exception e) {
- if (mLastTarget.mAccount == null) {
- Log_OC.e(TAG, "Unexpected error for a NULL account", e);
- } else {
- Log_OC.e(TAG, "Unexpected error for " + mLastTarget.mAccount.name, e);
- }
- result = new RemoteOperationResult(e);
-
- } finally {
- synchronized(mPendingOperations) {
- mPendingOperations.poll();
- }
+ //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
+ mService.dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result);
}
-
- //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
- dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result);
}
+
+
+
}
+
+ /**
+ * Creates a new operation, as described by operationIntent.
+ *
+ * TODO - move to ServiceHandler (probably)
+ *
+ * @param operationIntent Intent describing a new operation to queue and execute.
+ * @return Pair with the new operation object and the information about its target server.
+ */
+ private Pair<Target , RemoteOperation> newOperation(Intent operationIntent) {
+ RemoteOperation operation = null;
+ Target target = null;
+ try {
+ if (!operationIntent.hasExtra(EXTRA_ACCOUNT) &&
+ !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
+ Log_OC.e(TAG, "Not enough information provided in intent");
+
+ } else {
+ Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
+ String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
+ String username = operationIntent.getStringExtra(EXTRA_USERNAME);
+ String password = operationIntent.getStringExtra(EXTRA_PASSWORD);
+ String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN);
+ String cookie = operationIntent.getStringExtra(EXTRA_COOKIE);
+ target = new Target(
+ account,
+ (serverUrl == null) ? null : Uri.parse(serverUrl),
+ username,
+ password,
+ authToken,
+ cookie
+ );
+
+ String action = operationIntent.getAction();
+ if (action.equals(ACTION_CREATE_SHARE)) { // Create Share
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
+ if (remotePath.length() > 0) {
+ operation = new CreateShareOperation(OperationsService.this, remotePath, ShareType.PUBLIC_LINK,
+ "", false, "", 1, sendIntent);
+ }
+
+ } else if (action.equals(ACTION_UNSHARE)) { // Unshare file
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ if (remotePath.length() > 0) {
+ operation = new UnshareLinkOperation(
+ remotePath,
+ OperationsService.this);
+ }
+
+ } else if (action.equals(ACTION_GET_SERVER_INFO)) {
+ // check OC server and get basic information from it
+ operation = new GetServerInfoOperation(serverUrl, OperationsService.this);
+
+ } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) {
+ /// GET ACCESS TOKEN to the OAuth server
+ String oauth2QueryParameters =
+ operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS);
+ operation = new OAuth2GetAccessToken(
+ getString(R.string.oauth2_client_id),
+ getString(R.string.oauth2_redirect_uri),
+ getString(R.string.oauth2_grant_type),
+ oauth2QueryParameters);
+
+ } else if (action.equals(ACTION_EXISTENCE_CHECK)) {
+ // Existence Check
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean successIfAbsent = operationIntent.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT, false);
+ operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent);
+
+ } else if (action.equals(ACTION_GET_USER_NAME)) {
+ // Get User Name
+ operation = new GetRemoteUserNameOperation();
+
+ } else if (action.equals(ACTION_RENAME)) {
+ // Rename file or folder
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ String newName = operationIntent.getStringExtra(EXTRA_NEWNAME);
+ operation = new RenameFileOperation(remotePath, newName);
+
+ } else if (action.equals(ACTION_REMOVE)) {
+ // Remove file or folder
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
+ operation = new RemoveFileOperation(remotePath, onlyLocalCopy);
+
+ } else if (action.equals(ACTION_CREATE_FOLDER)) {
+ // Create Folder
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
+ operation = new CreateFolderOperation(remotePath, createFullPath);
+
+ } else if (action.equals(ACTION_SYNC_FILE)) {
+ // Sync file
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean syncFileContents = operationIntent.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS, true);
+ operation = new SynchronizeFileOperation(
+ remotePath, account, syncFileContents, getApplicationContext()
+ );
+
+ } else if (action.equals(ACTION_SYNC_FOLDER)) {
+ // Sync file
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ operation = new SynchronizeFolderOperation(
+ this, // TODO remove this dependency from construction time
+ remotePath,
+ account,
+ System.currentTimeMillis() // TODO remove this dependency from construction time
+ );
+
+ } else if (action.equals(ACTION_MOVE_FILE)) {
+ // Move file/folder
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
+ operation = new MoveFileOperation(remotePath,newParentPath,account);
+ }
+
+ }
+
+ } catch (IllegalArgumentException e) {
+ Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
+ operation = null;
+ }
+
+ if (operation != null) {
+ return new Pair<Target , RemoteOperation>(target, operation);
+ } else {
+ return null;
+ }
+ }
+
/**
* Sends a broadcast when a new operation is added to the queue.
/**
* Notifies the currently subscribed listeners about the end of an operation.
- *
+ *
* @param target Account or URL pointing to an OC server.
* @param operation Finished operation.
* @param result Result of the operation.
*/
- private void dispatchResultToOperationListeners(
+ protected void dispatchResultToOperationListeners(
Target target, final RemoteOperation operation, final RemoteOperationResult result) {
int count = 0;
- Iterator<OnRemoteOperationListener> listeners = mBinder.mBoundListeners.keySet().iterator();
+ Iterator<OnRemoteOperationListener> listeners = mOperationsBinder.mBoundListeners.keySet().iterator();
while (listeners.hasNext()) {
final OnRemoteOperationListener listener = listeners.next();
- final Handler handler = mBinder.mBoundListeners.get(listener);
+ final Handler handler = mOperationsBinder.mBoundListeners.get(listener);
if (handler != null) {
handler.post(new Runnable() {
@Override
}
Log_OC.d(TAG, "Called " + count + " listeners");
}
-
-
}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.services;
+
+import android.accounts.Account;
+import android.accounts.AccountsException;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Pair;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader;
+import com.owncloud.android.files.services.IndexedForest;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.utils.FileStorageUtils;
+
+import java.io.IOException;
+
+/**
+ * SyncFolder worker. Performs the pending operations in the order they were requested.
+ *
+ * Created with the Looper of a new thread, started in
+ * {@link com.owncloud.android.services.OperationsService#onCreate()}.
+ */
+class SyncFolderHandler extends Handler {
+
+ private static final String TAG = SyncFolderHandler.class.getSimpleName();
+
+
+ OperationsService mService;
+
+ private IndexedForest<SynchronizeFolderOperation> mPendingOperations =
+ new IndexedForest<SynchronizeFolderOperation>();
+
+ private OwnCloudClient mOwnCloudClient = null;
+ private Account mCurrentAccount = null;
+ private FileDataStorageManager mStorageManager;
+ private SynchronizeFolderOperation mCurrentSyncOperation;
+
+
+ public SyncFolderHandler(Looper looper, OperationsService service) {
+ super(looper);
+ if (service == null) {
+ throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
+ }
+ mService = service;
+ }
+
+
+ /**
+ * Returns True when the folder located in 'remotePath' in the ownCloud account 'account', or any of its
+ * descendants, is being synchronized (or waiting for it).
+ *
+ * @param account ownCloud account where the remote folder is stored.
+ * @param remotePath The path to a folder that could be in the queue of synchronizations.
+ */
+ public boolean isSynchronizing(Account account, String remotePath) {
+ if (account == null || remotePath == null) return false;
+ return (mPendingOperations.contains(account, remotePath));
+ }
+
+
+ @Override
+ public void handleMessage(Message msg) {
+ Pair<Account, String> itemSyncKey = (Pair<Account, String>) msg.obj;
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Handling sync folder " + itemSyncKey.second);*/
+ doOperation(itemSyncKey.first, itemSyncKey.second);
+ mService.stopSelf(msg.arg1);
+ }
+
+
+ /**
+ * Performs the next operation in the queue
+ */
+ private void doOperation(Account account, String remotePath) {
+
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Getting sync folder " + remotePath);*/
+ mCurrentSyncOperation = mPendingOperations.get(account, remotePath);
+
+ if (mCurrentSyncOperation != null) {
+ RemoteOperationResult result = null;
+
+ try {
+
+ if (mCurrentAccount == null || !mCurrentAccount.equals(account)) {
+ mCurrentAccount = account;
+ mStorageManager = new FileDataStorageManager(
+ account,
+ mService.getContentResolver()
+ );
+ } // else, reuse storage manager from previous operation
+
+ // always get client from client manager, to get fresh credentials in case of update
+ OwnCloudAccount ocAccount = new OwnCloudAccount(account, mService);
+ mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+ getClientFor(ocAccount, mService);
+
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Executing sync folder " + remotePath);*/
+ result = mCurrentSyncOperation.execute(mOwnCloudClient, mStorageManager);
+
+ } catch (AccountsException e) {
+ Log_OC.e(TAG, "Error while trying to get authorization", e);
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Error while trying to get authorization", e);
+ } finally {
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Removing payload " + remotePath);*/
+
+ mPendingOperations.removePayload(account, remotePath);
+
+ mService.dispatchResultToOperationListeners(null, mCurrentSyncOperation, result);
+
+ sendBroadcastFinishedSyncFolder(account, remotePath, result.isSuccess());
+ }
+ }
+ }
+
+ public void add(Account account, String remotePath, SynchronizeFolderOperation syncFolderOperation){
+ mPendingOperations.putIfAbsent(account, remotePath, syncFolderOperation);
+ sendBroadcastNewSyncFolder(account, remotePath); // TODO upgrade!
+ }
+
+
+ /**
+ * Cancels a pending or current sync' operation.
+ *
+ * @param account ownCloud account where the remote file is stored.
+ * @param file A file in the queue of pending synchronizations
+ */
+ public void cancel(Account account, OCFile file){
+ if (account == null || file == null) {
+ Log_OC.e(TAG, "Cannot cancel with NULL parameters");
+ return;
+ }
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Removing sync folder " + file.getRemotePath());*/
+ Pair<SynchronizeFolderOperation, String> removeResult =
+ mPendingOperations.remove(account, file.getRemotePath());
+ SynchronizeFolderOperation synchronization = removeResult.first;
+ if (synchronization != null) {
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Canceling returned sync of " + file.getRemotePath());*/
+ synchronization.cancel();
+ } else {
+ // TODO synchronize?
+ if (mCurrentSyncOperation != null && mCurrentAccount != null &&
+ mCurrentSyncOperation.getRemotePath().startsWith(file.getRemotePath()) &&
+ account.name.equals(mCurrentAccount.name)) {
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Canceling current sync as descendant: " + mCurrentSyncOperation.getRemotePath());*/
+ mCurrentSyncOperation.cancel();
+ } else {
+ /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+ "Nothing else in cancelation of " + file.getRemotePath());*/
+ }
+ }
+
+ //sendBroadcastFinishedSyncFolder(account, file.getRemotePath());
+ }
+
+ /**
+ * TODO review this method when "folder synchronization" replaces "folder download"; this is a fast and ugly
+ * patch.
+ */
+ private void sendBroadcastNewSyncFolder(Account account, String remotePath) {
+ Intent added = new Intent(FileDownloader.getDownloadAddedMessage());
+ added.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
+ added.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
+ added.putExtra(FileDownloader.EXTRA_FILE_PATH, FileStorageUtils.getSavePath(account.name) + remotePath);
+ mService.sendStickyBroadcast(added);
+ }
+
+ /**
+ * TODO review this method when "folder synchronization" replaces "folder download"; this is a fast and ugly
+ * patch.
+ */
+ private void sendBroadcastFinishedSyncFolder(Account account, String remotePath, boolean success) {
+ Intent finished = new Intent(FileDownloader.getDownloadFinishMessage());
+ finished.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
+ finished.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
+ finished.putExtra(FileDownloader.EXTRA_FILE_PATH, FileStorageUtils.getSavePath(account.name) + remotePath);
+ finished.putExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, success);
+ mService.sendStickyBroadcast(finished);
+ }
+
+
+}
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.UpdateOCVersionOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
}
*/
// folder synchronization
- SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( folder,
+ RefreshFolderOperation synchFolderOp = new RefreshFolderOperation( folder,
mCurrentSyncTime,
true,
mIsShareSupported,
import com.owncloud.android.files.FileOperationsHelper;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
public interface ComponentsGetter {
/**
- * Callback method invoked when the parent activity is fully created to get a reference to the FileDownloader service API.
- *
- * @return Directory to list firstly. Can be NULL.
+ * To be invoked when the parent activity is fully created to get a reference to the FileDownloader service API.
*/
public FileDownloaderBinder getFileDownloaderBinder();
/**
- * Callback method invoked when the parent activity is fully created to get a reference to the FileUploader service API.
- *
- * @return Directory to list firstly. Can be NULL.
+ * To be invoked when the parent activity is fully created to get a reference to the FileUploader service API.
*/
public FileUploaderBinder getFileUploaderBinder();
+ /**
+ * To be invoked when the parent activity is fully created to get a reference to the OperationsSerivce service API.
+ */
+ public OperationsServiceBinder getOperationsServiceBinder();
+
public FileDataStorageManager getStorageManager();
public FileOperationsHelper getFileOperationsHelper();
+
}
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.CreateShareOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
import com.owncloud.android.operations.UnshareLinkOperation;
import com.owncloud.android.services.OperationsService;
} else if (operation instanceof UnshareLinkOperation) {
onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result);
- }
+ } else if (operation instanceof SynchronizeFolderOperation) {
+ onSynchronizeFolderOperationFinish((SynchronizeFolderOperation)operation, result);
+
+ }
}
protected void requestCredentialsUpdate() {
t.show();
}
}
-
+
+ private void onSynchronizeFolderOperationFinish(SynchronizeFolderOperation operation, RemoteOperationResult result) {
+ if (!result.isSuccess() && result.getCode() != ResultCode.CANCELLED){
+ Toast t = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
+ Toast.LENGTH_LONG);
+ t.show();
+ }
+ }
protected void updateFileFromDB(){
OCFile file = getFile();
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.RenameFileOperation;
import com.owncloud.android.operations.SynchronizeFileOperation;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.UnshareLinkOperation;
import com.owncloud.android.services.observer.FileObserverService;
import com.owncloud.android.syncadapter.FileSyncAdapter;
import com.owncloud.android.ui.preview.PreviewVideoActivity;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.ErrorMessageAdapter;
+import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.UriUtils;
// Read sorting order, default to sort by name ascending
Integer sortOrder = appPreferences
- .getInt("sortOrder", FileListListAdapter.SORT_NAME);
+ .getInt("sortOrder", FileStorageUtils.SORT_NAME);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.actionbar_sort_title)
IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
- syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
- syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+ syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+ syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
mSyncBroadcastReceiver = new SyncBroadcastReceiver();
registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
//LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
setFile(currentFile);
}
- mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
+ mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
- if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
+ if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
equals(event) &&
/// TODO refactor and make common
synchResult != null && !synchResult.isSuccess() &&
/**
- * Class waiting for broadcast events from the {@link FielDownloader} service.
+ * Class waiting for broadcast events from the {@link FileDownloader} service.
*
* Updates the UI when a download is started or finished, provided that it is relevant for the
* current folder.
*/
private class DownloadFinishReceiver extends BroadcastReceiver {
+
+ //int refreshCounter = 0;
@Override
public void onReceive(Context context, Intent intent) {
try {
boolean sameAccount = isSameAccount(context, intent);
String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
boolean isDescendant = isDescendant(downloadedRemotePath);
-
+
if (sameAccount && isDescendant) {
- refreshListOfFilesFragment();
- refreshSecondFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false));
+ String linkedToRemotePath = intent.getStringExtra(FileDownloader.EXTRA_LINKED_TO_PATH);
+ if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) {
+ //Log_OC.v(TAG, "refresh #" + ++refreshCounter);
+ refreshListOfFilesFragment();
+ }
+ refreshSecondFragment(
+ intent.getAction(),
+ downloadedRemotePath,
+ intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)
+ );
}
if (mWaitingToSend != null) {
- mWaitingToSend = getStorageManager().getFileByPath(mWaitingToSend.getRemotePath()); // Update the file to send
+ mWaitingToSend = getStorageManager().getFileByPath(mWaitingToSend.getRemotePath());
if (mWaitingToSend.isDown()) {
sendDownloadedFile();
}
private boolean isDescendant(String downloadedRemotePath) {
OCFile currentDir = getCurrentDir();
- return (currentDir != null && downloadedRemotePath != null && downloadedRemotePath.startsWith(currentDir.getRemotePath()));
+ return (
+ currentDir != null &&
+ downloadedRemotePath != null &&
+ downloadedRemotePath.startsWith(currentDir.getRemotePath())
+ );
+ }
+
+ private boolean isAscendant(String linkedToRemotePath) {
+ OCFile currentDir = getCurrentDir();
+ return (
+ currentDir != null &&
+ currentDir.getRemotePath().startsWith(linkedToRemotePath)
+ );
}
private boolean isSameAccount(Context context, Intent intent) {
private void requestForDownload() {
Account account = getAccount();
+ //if (!mWaitingToPreview.isDownloading()) {
if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
mSyncInProgress = true;
// perform folder synchronization
- RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,
+ RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
currentSyncTime,
false,
getFileOperationsHelper().isSharedSupported(),
private void requestForDownload(OCFile file) {
Account account = getAccount();
- if (!mDownloaderBinder.isDownloading(account, file)) {
+ if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
i.putExtra(FileDownloader.EXTRA_FILE, file);
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.CreateFolderOperation;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.syncadapter.FileSyncAdapter;
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
import com.owncloud.android.ui.fragment.FileFragment;
mSyncInProgress = true;
// perform folder synchronization
- RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,
+ RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
currentSyncTime,
false,
getFileOperationsHelper().isSharedSupported(),
IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
- syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
- syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+ syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+ syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
mSyncBroadcastReceiver = new SyncBroadcastReceiver();
registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
}
mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
- !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
+ !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
- if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
+ if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
equals(event) &&
/// TODO refactor and make common
synchResult != null && !synchResult.isSuccess() &&
private String mAccountName;
private boolean mShowContextMenu = false;
private String mUploadPath;
+ private PreferenceCategory mPrefInstantUploadCategory;
+ private Preference mPrefInstantUpload;
private Preference mPrefInstantUploadPath;
+ private Preference mPrefInstantUploadPathWiFi;
+ private Preference mPrefInstantVideoUpload;
private Preference mPrefInstantVideoUploadPath;
+ private Preference mPrefInstantVideoUploadPathWiFi;
private String mUploadVideoPath;
}
});
}
-
+
+ mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category");
+
+ mPrefInstantUploadPathWiFi = findPreference("instant_upload_on_wifi");
+ mPrefInstantUpload = findPreference("instant_uploading");
+
+ toggleInstantPictureOptions(((CheckBoxPreference) mPrefInstantUpload).isChecked());
+
+ mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ toggleInstantPictureOptions((Boolean) newValue);
+ return true;
+ }
+ });
+
mPrefInstantVideoUploadPath = findPreference("instant_video_upload_path");
if (mPrefInstantVideoUploadPath != null){
}
});
}
+
+ mPrefInstantVideoUploadPathWiFi = findPreference("instant_video_upload_on_wifi");
+ mPrefInstantVideoUpload = findPreference("instant_video_uploading");
+ toggleInstantVideoOptions(((CheckBoxPreference) mPrefInstantVideoUpload).isChecked());
+
+ mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ toggleInstantVideoOptions((Boolean) newValue);
+ return true;
+ }
+ });
/* About App */
pAboutApp = (Preference) findPreference("about_app");
loadInstantUploadVideoPath();
}
+
+ private void toggleInstantPictureOptions(Boolean value){
+ if (value){
+ mPrefInstantUploadCategory.addPreference(mPrefInstantUploadPathWiFi);
+ mPrefInstantUploadCategory.addPreference(mPrefInstantUploadPath);
+ } else {
+ mPrefInstantUploadCategory.removePreference(mPrefInstantUploadPathWiFi);
+ mPrefInstantUploadCategory.removePreference(mPrefInstantUploadPath);
+ }
+ }
+
+ private void toggleInstantVideoOptions(Boolean value){
+ if (value){
+ mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPathWiFi);
+ mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPath);
+ } else {
+ mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadPathWiFi);
+ mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadPath);
+ }
+ }
@Override
protected void onPause() {
*/\r
package com.owncloud.android.ui.adapter;\r
\r
-
+\r
import java.io.File;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
import java.util.Vector;\r
\r
-import third_parties.daveKoeller.AlphanumComparator;\r
import android.accounts.Account;\r
import android.content.Context;\r
import android.content.SharedPreferences;\r
import com.owncloud.android.datamodel.FileDataStorageManager;\r
import com.owncloud.android.datamodel.OCFile;\r
import com.owncloud.android.datamodel.ThumbnailsCacheManager;\r
-import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncDrawable;\r
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;\r
import com.owncloud.android.ui.activity.ComponentsGetter;\r
import com.owncloud.android.utils.DisplayUtils;\r
import com.owncloud.android.utils.FileStorageUtils;\r
-
+\r
\r
/**\r
* This Adapter populates a ListView with all files and folders in an ownCloud\r
* @author Tobias Kaminsky\r
* @author David A. Velasco\r
*/\r
-public class FileListListAdapter extends BaseAdapter implements ListAdapter {
+public class FileListListAdapter extends BaseAdapter implements ListAdapter {\r
private final static String PERMISSION_SHARED_WITH_ME = "S";\r
\r
private Context mContext;\r
private Vector<OCFile> mFiles = null;\r
private boolean mJustFolders;\r
\r
- private FileDataStorageManager mStorageManager;
- private Account mAccount;
+ private FileDataStorageManager mStorageManager;\r
+ private Account mAccount;\r
private ComponentsGetter mTransferServiceGetter;\r
- private Integer mSortOrder;\r
- public static final Integer SORT_NAME = 0;\r
- public static final Integer SORT_DATE = 1;\r
- public static final Integer SORT_SIZE = 2;\r
- private Boolean mSortAscending;\r
+\r
private SharedPreferences mAppPreferences;\r
\r
public FileListListAdapter(\r
boolean justFolders, \r
- Context context, \r
+ Context context,\r
ComponentsGetter transferServiceGetter\r
) {\r
\r
mJustFolders = justFolders;\r
mContext = context;\r
mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);\r
-
- mTransferServiceGetter = transferServiceGetter;
- \r
+\r
+ mTransferServiceGetter = transferServiceGetter;\r
+\r
mAppPreferences = PreferenceManager\r
.getDefaultSharedPreferences(mContext);\r
\r
// Read sorting order, default to sort by name ascending\r
- mSortOrder = mAppPreferences\r
- .getInt("sortOrder", 0);\r
- mSortAscending = mAppPreferences.getBoolean("sortAscending", true);
+ FileStorageUtils.mSortOrder = mAppPreferences.getInt("sortOrder", 0);\r
+ FileStorageUtils.mSortAscending = mAppPreferences.getBoolean("sortAscending", true);\r
+\r
\r
// initialise thumbnails cache on background thread\r
new ThumbnailsCacheManager.InitDiskCacheTask().execute();\r
\r
}\r
-
+ \r
@Override\r
public boolean areAllItemsEnabled() {\r
return true;\r
\r
ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2);\r
localStateView.bringToFront();\r
- FileDownloaderBinder downloaderBinder = \r
- mTransferServiceGetter.getFileDownloaderBinder();\r
+ FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();\r
FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();\r
- if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {\r
+ boolean downloading = (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file));\r
+ OperationsServiceBinder opsBinder = mTransferServiceGetter.getOperationsServiceBinder();\r
+ downloading |= (opsBinder != null && opsBinder.isSynchronizing(mAccount, file.getRemotePath()));\r
+ if (downloading) {\r
localStateView.setImageResource(R.drawable.downloading_file_indicator);\r
localStateView.setVisibility(View.VISIBLE);\r
} else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {\r
if (thumbnail != null && !file.needsUpdateThumbnail()){\r
fileIcon.setImageBitmap(thumbnail);\r
} else {\r
+\r
// generate new Thumbnail\r
if (ThumbnailsCacheManager.cancelPotentialWork(file, fileIcon)) {\r
- final ThumbnailsCacheManager.ThumbnailGenerationTask task = \r
+ final ThumbnailsCacheManager.ThumbnailGenerationTask task =\r
new ThumbnailsCacheManager.ThumbnailGenerationTask(\r
fileIcon, mStorageManager, mAccount\r
);\r
if (thumbnail == null) {\r
thumbnail = ThumbnailsCacheManager.mDefaultImg;\r
}\r
- final AsyncDrawable asyncDrawable = new AsyncDrawable(\r
+ final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =\r
+ new ThumbnailsCacheManager.AsyncDrawable(\r
mContext.getResources(), \r
thumbnail, \r
task\r
}\r
}\r
} else {\r
- fileIcon.setImageResource(\r
- DisplayUtils.getResourceId(file.getMimetype(), file.getFileName())\r
- );\r
+ fileIcon.setImageResource(DisplayUtils.getFileTypeIconId(file.getMimetype(), file.getFileName()));\r
}\r
-
+\r
if (checkIfFileIsSharedWithMe(file)) {\r
sharedWithMeIconV.setVisibility(View.VISIBLE);\r
}\r
// } else {\r
fileSizeV.setVisibility(View.INVISIBLE);\r
// }\r
-
+\r
lastModV.setVisibility(View.VISIBLE);\r
lastModV.setText(showRelativeTimestamp(file));\r
checkBoxV.setVisibility(View.GONE);\r
sharedWithMeIconV.setVisibility(View.VISIBLE);\r
} else {\r
fileIcon.setImageResource(\r
- DisplayUtils.getResourceId(file.getMimetype(), file.getFileName())\r
+ DisplayUtils.getFileTypeIconId(file.getMimetype(), file.getFileName())\r
);\r
}\r
\r
\r
return view;\r
}\r
-
+\r
/**\r
* Local Folder size in human readable format\r
* \r
File dir = new File(path);\r
\r
if (dir.exists()) {\r
- long bytes = getFolderSize(dir);\r
+ long bytes = FileStorageUtils.getFolderSize(dir);\r
return DisplayUtils.bytesToHumanReadable(bytes);\r
}\r
\r
return result;\r
}\r
return 0;\r
- }
-
+ } \r
+\r
@Override\r
public int getViewTypeCount() {\r
return 1;\r
mFiles = null;\r
}\r
\r
- sortDirectory();\r
- }\r
- \r
- /**\r
- * Sorts all filenames, regarding last user decision \r
- */\r
- private void sortDirectory(){\r
- switch (mSortOrder){\r
- case 0:\r
- sortByName(mSortAscending);\r
- break;\r
- case 1:\r
- sortByDate(mSortAscending);\r
- break;\r
- case 2: \r
- sortBySize(mSortAscending);\r
- break;\r
- }\r
- \r
+ mFiles = FileStorageUtils.sortFolder(mFiles);\r
notifyDataSetChanged();\r
}\r
\r
- \r
+\r
/**\r
* Filter for getting only the folders\r
* @param files\r
&& file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));\r
}\r
\r
- /**\r
- * Sorts list by Date\r
- * @param sortAscending true: ascending, false: descending\r
- */\r
- private void sortByDate(boolean sortAscending){\r
- final Integer val;\r
- if (sortAscending){\r
- val = 1;\r
- } else {\r
- val = -1;\r
- }\r
- \r
- Collections.sort(mFiles, new Comparator<OCFile>() {\r
- public int compare(OCFile o1, OCFile o2) {\r
- if (o1.isFolder() && o2.isFolder()) {\r
- Long obj1 = o1.getModificationTimestamp();\r
- return val * obj1.compareTo(o2.getModificationTimestamp());\r
- }\r
- else if (o1.isFolder()) {\r
- return -1;\r
- } else if (o2.isFolder()) {\r
- return 1;\r
- } else if (o1.getModificationTimestamp() == 0 || o2.getModificationTimestamp() == 0){\r
- return 0;\r
- } else {\r
- Long obj1 = o1.getModificationTimestamp();\r
- return val * obj1.compareTo(o2.getModificationTimestamp());\r
- }\r
- }\r
- });\r
- }\r
-\r
- /**\r
- * Sorts list by Size\r
- * @param sortAscending true: ascending, false: descending\r
- */\r
- private void sortBySize(boolean sortAscending){\r
- final Integer val;\r
- if (sortAscending){\r
- val = 1;\r
- } else {\r
- val = -1;\r
- }\r
- \r
- Collections.sort(mFiles, new Comparator<OCFile>() {\r
- public int compare(OCFile o1, OCFile o2) {\r
- if (o1.isFolder() && o2.isFolder()) {\r
- Long obj1 = getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o1)));\r
- return val * obj1.compareTo(getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o2))));\r
- }\r
- else if (o1.isFolder()) {\r
- return -1;\r
- } else if (o2.isFolder()) {\r
- return 1;\r
- } else if (o1.getFileLength() == 0 || o2.getFileLength() == 0){\r
- return 0;\r
- } else {\r
- Long obj1 = o1.getFileLength();\r
- return val * obj1.compareTo(o2.getFileLength());\r
- }\r
- }\r
- });\r
- }\r
-\r
- /**\r
- * Sorts list by Name\r
- * @param sortAscending true: ascending, false: descending\r
- */\r
- private void sortByName(boolean sortAscending){\r
- final Integer val;\r
- if (sortAscending){\r
- val = 1;\r
- } else {\r
- val = -1;\r
- }\r
-\r
- Collections.sort(mFiles, new Comparator<OCFile>() {\r
- public int compare(OCFile o1, OCFile o2) {\r
- if (o1.isFolder() && o2.isFolder()) {\r
- return val * o1.getRemotePath().toLowerCase().compareTo(o2.getRemotePath().toLowerCase());\r
- } else if (o1.isFolder()) {\r
- return -1;\r
- } else if (o2.isFolder()) {\r
- return 1;\r
- }\r
- return val * new AlphanumComparator().compare(o1, o2);\r
- }\r
- });\r
- }\r
-\r
public void setSortOrder(Integer order, boolean ascending) {\r
SharedPreferences.Editor editor = mAppPreferences.edit();\r
editor.putInt("sortOrder", order);\r
editor.putBoolean("sortAscending", ascending);\r
editor.commit();\r
\r
- mSortOrder = order;\r
- mSortAscending = ascending;\r
+ FileStorageUtils.mSortOrder = order;\r
+ FileStorageUtils.mSortAscending = ascending;\r
\r
- sortDirectory();\r
+\r
+ mFiles = FileStorageUtils.sortFolder(mFiles);\r
+ notifyDataSetChanged();\r
+\r
} \r
\r
private CharSequence showRelativeTimestamp(OCFile file){\r
return DisplayUtils.getRelativeDateTimeString(mContext, file.getModificationTimestamp(),\r
DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0);\r
- }
+ }\r
}\r
import java.util.Comparator;
import android.content.Context;
+import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.owncloud.android.R;
+import com.owncloud.android.datamodel.ThumbnailsCacheManager;
+import com.owncloud.android.utils.BitmapUtils;
import com.owncloud.android.utils.DisplayUtils;
/**
private Context mContext;
private File mDirectory;
private File[] mFiles = null;
-
+
public LocalFileListAdapter(File directory, Context context) {
mContext = context;
swapDirectory(directory);
} else {
fileIcon.setImageResource(R.drawable.ic_menu_archive);
}
+ fileIcon.setTag(file.hashCode());
TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);
TextView lastModV = (TextView) view.findViewById(R.id.last_mod);
}
checkBoxV.setVisibility(View.VISIBLE);
}
+
+ // get Thumbnail if file is image
+ if (BitmapUtils.isImage(file)){
+ // Thumbnail in Cache?
+ Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
+ String.valueOf(file.hashCode())
+ );
+ if (thumbnail != null){
+ fileIcon.setImageBitmap(thumbnail);
+ } else {
+
+ // generate new Thumbnail
+ if (ThumbnailsCacheManager.cancelPotentialWork(file, fileIcon)) {
+ final ThumbnailsCacheManager.ThumbnailGenerationTask task =
+ new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon);
+ if (thumbnail == null) {
+ thumbnail = ThumbnailsCacheManager.mDefaultImg;
+ }
+ final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =
+ new ThumbnailsCacheManager.AsyncDrawable(
+ mContext.getResources(),
+ thumbnail,
+ task
+ );
+ fileIcon.setImageDrawable(asyncDrawable);
+ task.execute(file);
+ }
+ }
+ } else {
+ fileIcon.setImageResource(DisplayUtils.getFileTypeIconId(null, file.getName()));
+ }
} else {
fileSizeV.setVisibility(View.GONE);
checkBoxV.setVisibility(View.GONE);
}
- view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE); // not GONE; the alignment changes; ugly way to keep it
+ view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE); // not GONE; the alignment would change
view.findViewById(R.id.imageView3).setVisibility(View.GONE);
view.findViewById(R.id.sharedIcon).setVisibility(View.GONE);
// configure UI for depending upon local state of the file
FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
- if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file))) {
+ if (transferring ||
+ (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) ||
+ (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file))
+ ) {
setButtonsForTransferring();
} else if (file.isDown()) {
}
ImageView iv = (ImageView) getView().findViewById(R.id.fdIcon);
if (iv != null) {
- iv.setImageResource(DisplayUtils.getResourceId(mimetype, filename));
+ iv.setImageResource(DisplayUtils.getFileTypeIconId(mimetype, filename));
}
}
progressText.setVisibility(View.VISIBLE);
FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
+ //if (getFile().isDownloading()) {
if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) {
progressText.setText(R.string.downloader_download_in_progress_ticker);
} else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile())) {
import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
import com.owncloud.android.ui.preview.PreviewImageFragment;
import com.owncloud.android.ui.preview.PreviewMediaFragment;
+import com.owncloud.android.utils.FileStorageUtils;
/**
* A Fragment that lists all files and folders in a given path.
boolean justFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false);
mAdapter = new FileListListAdapter(
justFolders,
- getSherlockActivity(),
+ getSherlockActivity(),
mContainerActivity
);
setListAdapter(mAdapter);
}
public void sortByName(boolean descending) {
- mAdapter.setSortOrder(FileListListAdapter.SORT_NAME, descending);
+ mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
}
public void sortByDate(boolean descending) {
- mAdapter.setSortOrder(FileListListAdapter.SORT_DATE, descending);
+ mAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending);
}
public void sortBySize(boolean descending) {
- mAdapter.setSortOrder(FileListListAdapter.SORT_SIZE, descending);
+ mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
}
}
* @param transferring When true, the view must be updated assuming that the holded file is
* downloading, no matter what the downloaderBinder says.
*/
+ /*
public void updateView(boolean transferring) {
// configure UI for depending upon local state of the file
- FileDownloaderBinder downloaderBinder = (mContainerActivity == null) ? null : mContainerActivity.getFileDownloaderBinder();
- if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile()))) {
+ // TODO remove
+ if (transferring || getFile().isDownloading()) {
setButtonsForTransferring();
} else if (getFile().isDown()) {
getView().invalidate();
}
-
+ */
/**
* Enables or disables buttons for a file being downloaded
/**
- * Class waiting for broadcast events from the {@link FielDownloader} service.
+ * Class waiting for broadcast events from the {@link FileDownloader} service.
*
* Updates the UI when a download is started or finished, provided that it is relevant for the
* folder displayed in the gallery.
*/
package com.owncloud.android.ui.preview;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.adapter.FileListListAdapter;
import com.owncloud.android.ui.fragment.FileFragment;
+import com.owncloud.android.utils.FileStorageUtils;
/**
* Adapter class that provides Fragment instances
mAccount = account;
mStorageManager = storageManager;
mImageFiles = mStorageManager.getFolderImages(parentFolder);
+
+ mImageFiles = FileStorageUtils.sortFolder(mImageFiles);
+
mObsoleteFragments = new HashSet<Object>();
mObsoletePositions = new HashSet<Integer>();
mDownloadErrors = new HashSet<Integer>();
//mFragmentManager = fragmentManager;
mCachedFragments = new HashMap<Integer, FileFragment>();
}
-
/**
* Returns the image files handled by the adapter.
import android.graphics.Matrix;
import android.graphics.BitmapFactory.Options;
import android.media.ExifInterface;
+import android.net.Uri;
+import android.webkit.MimeTypeMap;
+
+import java.io.File;
/**
* Utility class with methods for decoding Bitmaps.
}
return resultBitmap;
}
-
+
+ /**
+ * Checks if file passed is an image
+ * @param file
+ * @return true/false
+ */
+ public static boolean isImage(File file) {
+ Uri selectedUri = Uri.fromFile(file);
+ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(selectedUri.toString().toLowerCase());
+ String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
+
+ return (mimeType != null && mimeType.startsWith("image/"));
+ }
}
package com.owncloud.android.utils;\r
\r
import java.net.IDN;\r
+import java.text.DateFormat;\r
import java.util.Arrays;\r
import java.util.Calendar;\r
import java.util.Date;\r
import android.content.Context;\r
import android.os.Build;\r
import android.text.format.DateUtils;\r
+import android.webkit.MimeTypeMap;\r
\r
import com.owncloud.android.MainApp;\r
import com.owncloud.android.R;\r
private static final String TYPE_VIDEO = "video";\r
\r
private static final String SUBTYPE_PDF = "pdf";\r
- private static final String[] SUBTYPES_DOCUMENT = { "msword",\r
- "vnd.openxmlformats-officedocument.wordprocessingml.document",\r
- "vnd.oasis.opendocument.text",\r
- "rtf"\r
- };\r
+ private static final String SUBTYPE_XML = "xml";\r
+ private static final String[] SUBTYPES_DOCUMENT = { \r
+ "msword",\r
+ "vnd.openxmlformats-officedocument.wordprocessingml.document",\r
+ "vnd.oasis.opendocument.text",\r
+ "rtf",\r
+ "javascript"\r
+ };\r
private static Set<String> SUBTYPES_DOCUMENT_SET = new HashSet<String>(Arrays.asList(SUBTYPES_DOCUMENT));\r
- private static final String[] SUBTYPES_SPREADSHEET = { "msexcel",\r
- "vnd.openxmlformats-officedocument.spreadsheetml.sheet",\r
- "vnd.oasis.opendocument.spreadsheet"\r
- };\r
+ private static final String[] SUBTYPES_SPREADSHEET = {\r
+ "msexcel",\r
+ "vnd.ms-excel",\r
+ "vnd.openxmlformats-officedocument.spreadsheetml.sheet",\r
+ "vnd.oasis.opendocument.spreadsheet"\r
+ };\r
private static Set<String> SUBTYPES_SPREADSHEET_SET = new HashSet<String>(Arrays.asList(SUBTYPES_SPREADSHEET));\r
- private static final String[] SUBTYPES_PRESENTATION = { "mspowerpoint",\r
- "vnd.openxmlformats-officedocument.presentationml.presentation",\r
- "vnd.oasis.opendocument.presentation"\r
- };\r
+ private static final String[] SUBTYPES_PRESENTATION = { \r
+ "mspowerpoint",\r
+ "vnd.ms-powerpoint",\r
+ "vnd.openxmlformats-officedocument.presentationml.presentation",\r
+ "vnd.oasis.opendocument.presentation"\r
+ };\r
private static Set<String> SUBTYPES_PRESENTATION_SET = new HashSet<String>(Arrays.asList(SUBTYPES_PRESENTATION));\r
private static final String[] SUBTYPES_COMPRESSED = {"x-tar", "x-gzip", "zip"};\r
private static final Set<String> SUBTYPES_COMPRESSED_SET = new HashSet<String>(Arrays.asList(SUBTYPES_COMPRESSED));\r
private static final String EXTENSION_RAR = "rar";\r
private static final String EXTENSION_RTF = "rtf";\r
private static final String EXTENSION_3GP = "3gp";\r
+ private static final String EXTENSION_PY = "py";\r
+ private static final String EXTENSION_JS = "js";\r
\r
/**\r
* Converts the file size in bytes to human readable output.\r
}\r
\r
/**\r
- * Removes special HTML entities from a string\r
- * \r
- * @param s Input string\r
- * @return A cleaned version of the string\r
- */\r
- public static String HtmlDecode(String s) {\r
- /*\r
- * TODO: Perhaps we should use something more proven like:\r
- * http://commons.apache.org/lang/api-2.6/org/apache/commons/lang/StringEscapeUtils.html#unescapeHtml%28java.lang.String%29\r
- */\r
-\r
- String ret = "";\r
- for (int i = 0; i < s.length(); ++i) {\r
- if (s.charAt(i) == '%') {\r
- ret += (char) Integer.parseInt(s.substring(i + 1, i + 3), 16);\r
- i += 2;\r
- } else {\r
- ret += s.charAt(i);\r
- }\r
- }\r
- return ret;\r
- }\r
-\r
- /**\r
* Converts MIME types like "image/jpg" to more end user friendly output\r
* like "JPG image".\r
* \r
\r
\r
/**\r
- * Returns the resource identifier of an image resource to use as icon associated to a \r
- * known MIME type.\r
+ * Returns the resource identifier of an image to use as icon associated to a known MIME type.\r
* \r
- * @param mimetype MIME type string.\r
- * @param filename name, with extension\r
- * @return Resource identifier of an image resource.\r
+ * @param mimetype MIME type string; if NULL, the method tries to guess it from the extension in filename\r
+ * @param filename Name, with extension.\r
+ * @return Identifier of an image resource.\r
*/\r
- public static int getResourceId(String mimetype, String filename) {\r
+ public static int getFileTypeIconId(String mimetype, String filename) {\r
\r
- if (mimetype == null || "DIR".equals(mimetype)) {\r
- return R.drawable.ic_menu_archive;\r
+ if (mimetype == null) {\r
+ String fileExtension = getExtension(filename);\r
+ mimetype = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);\r
+ if (mimetype == null) {\r
+ mimetype = TYPE_APPLICATION + "/" + SUBTYPE_OCTET_STREAM;\r
+ }\r
+ } \r
\r
+ if ("DIR".equals(mimetype)) {\r
+ return R.drawable.ic_menu_archive;\r
+\r
} else {\r
String [] parts = mimetype.split("/");\r
String type = parts[0];\r
if (SUBTYPE_PDF.equals(subtype)) {\r
return R.drawable.file_pdf;\r
\r
+ } else if (SUBTYPE_XML.equals(subtype)) {\r
+ return R.drawable.file_doc;\r
+\r
} else if (SUBTYPES_DOCUMENT_SET.contains(subtype)) {\r
return R.drawable.file_doc;\r
\r
\r
} else if (SUBTYPES_COMPRESSED_SET.contains(subtype)) {\r
return R.drawable.file_zip;\r
- \r
+\r
} else if (SUBTYPE_OCTET_STREAM.equals(subtype) ) {\r
if (getExtension(filename).equalsIgnoreCase(EXTENSION_RAR)) {\r
return R.drawable.file_zip;\r
\r
} else if (getExtension(filename).equalsIgnoreCase(EXTENSION_3GP)) {\r
return R.drawable.file_movie;\r
- \r
+ \r
+ } else if ( getExtension(filename).equalsIgnoreCase(EXTENSION_PY) ||\r
+ getExtension(filename).equalsIgnoreCase(EXTENSION_JS)) {\r
+ return R.drawable.file_doc;\r
} \r
} \r
}\r
\r
\r
private static String getExtension(String filename) {\r
- String extension = filename.substring(filename.lastIndexOf(".") + 1);\r
- \r
+ String extension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();\r
return extension;\r
}\r
\r
*/\r
public static String unixTimeToHumanReadable(long milliseconds) {\r
Date date = new Date(milliseconds);\r
- return date.toLocaleString();\r
+ DateFormat df = DateFormat.getDateTimeInstance();\r
+ return df.format(date);\r
}\r
\r
\r
return fileExtension;\r
}\r
\r
- public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution, long transitionResolution, int flags){\r
+ @SuppressWarnings("deprecation")\r
+ public static CharSequence getRelativeDateTimeString (\r
+ Context c, long time, long minResolution, long transitionResolution, int flags\r
+ ){\r
+ \r
CharSequence dateString = "";\r
\r
// in Future\r
return c.getString(R.string.file_list_seconds_ago);\r
} else {\r
// Workaround 2.x bug (see https://github.com/owncloud/android/issues/716)\r
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB && (System.currentTimeMillis() - time) > 24 * 60 * 60 * 1000){\r
+ if ( Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB && \r
+ (System.currentTimeMillis() - time) > 24 * 60 * 60 * 1000 ) {\r
Date date = new Date(time);\r
date.setHours(0);\r
date.setMinutes(0);\r
date.setSeconds(0);\r
- dateString = DateUtils.getRelativeDateTimeString(c, date.getTime(), minResolution, transitionResolution, flags);\r
+ dateString = DateUtils.getRelativeDateTimeString(\r
+ c, date.getTime(), minResolution, transitionResolution, flags\r
+ );\r
} else {\r
dateString = DateUtils.getRelativeDateTimeString(c, time, minResolution, transitionResolution, flags);\r
}\r
}\r
\r
- return dateString.toString().split(",")[0];
+ return dateString.toString().split(",")[0];\r
}\r
\r
/**\r
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.RenameFileOperation;
import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
import com.owncloud.android.operations.UnshareLinkOperation;
import com.owncloud.android.operations.UploadFileOperation;
// Show a Message, operation finished without success
message = res.getString(R.string.move_file_error);
}
+ } else if (operation instanceof SynchronizeFolderOperation) {
+
+ if (!result.isSuccess()) {
+ String folderPathName = new File(
+ ((SynchronizeFolderOperation) operation).getFolderPath()).getName();
+ if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
+ message = String.format(res.getString(R.string.sync_current_folder_was_removed),
+ folderPathName);
+
+ } else { // Generic error
+ // Show a Message, operation finished without success
+ message = String.format(res.getString(R.string.download_folder_failed_content),
+ folderPathName);
+ }
+ }
}
return message;
package com.owncloud.android.utils;
import java.io.File;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Vector;
+
+import third_parties.daveKoeller.AlphanumComparator;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
* @author David A. Velasco
*/
public class FileStorageUtils {
+ public static Integer mSortOrder;
+ public static Boolean mSortAscending;
+ public static final Integer SORT_NAME = 0;
+ public static final Integer SORT_DATE = 1;
+ public static final Integer SORT_SIZE = 2;
+
+
//private static final String LOG_TAG = "FileStorageUtils";
public static final String getSavePath(String accountName) {
file.setRemoteId(ocFile.getRemoteId());
return file;
}
+
+ /**
+ * Sorts all filenames, regarding last user decision
+ */
+ public static Vector<OCFile> sortFolder(Vector<OCFile> files){
+ switch (mSortOrder){
+ case 0:
+ files = FileStorageUtils.sortByName(files);
+ break;
+ case 1:
+ files = FileStorageUtils.sortByDate(files);
+ break;
+ case 2:
+ // mFiles = FileStorageUtils.sortBySize(mSortAscending);
+ break;
+ }
+
+ return files;
+ }
+
+ /**
+ * Sorts list by Date
+ * @param sortAscending true: ascending, false: descending
+ */
+ public static Vector<OCFile> sortByDate(Vector<OCFile> files){
+ final Integer val;
+ if (mSortAscending){
+ val = 1;
+ } else {
+ val = -1;
+ }
+
+ Collections.sort(files, new Comparator<OCFile>() {
+ 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());
+ }
+ }
+ });
+
+ return files;
+ }
+
+// /**
+// * Sorts list by Size
+// * @param sortAscending true: ascending, false: descending
+// */
+// public static Vector<OCFile> sortBySize(Vector<OCFile> files){
+// final Integer val;
+// if (mSortAscending){
+// val = 1;
+// } else {
+// val = -1;
+// }
+//
+// Collections.sort(files, new Comparator<OCFile>() {
+// 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());
+// }
+// }
+// });
+//
+// return files;
+// }
+
+ /**
+ * Sorts list by Name
+ * @param sortAscending true: ascending, false: descending
+ */
+ public static Vector<OCFile> sortByName(Vector<OCFile> files){
+ final Integer val;
+ if (mSortAscending){
+ val = 1;
+ } else {
+ val = -1;
+ }
+
+ Collections.sort(files, new Comparator<OCFile>() {
+ 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);
+ }
+ });
+
+ return files;
+ }
+
+ /**
+ * Local Folder size
+ * @param dir File
+ * @return Size in bytes
+ */
+ public static 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;
+ }
}
--- /dev/null
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " pdf to make PDF files"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html: html-org
+
+html-all: html-release html-org html-com
+
+html-release:
+ $(SPHINXBUILD) -b html -D html_theme='owncloud_release' $(ALLSPHINXOPTS) $(BUILDDIR)/html/release
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/release."
+
+html-org:
+ $(SPHINXBUILD) -b html -D html_theme='owncloud_org' $(ALLSPHINXOPTS) $(BUILDDIR)/html/org
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/org."
+
+html-com:
+ $(SPHINXBUILD) -b html -D html_theme='owncloud_com' $(ALLSPHINXOPTS) $(BUILDDIR)/html/com
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/com."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OwncloudDocumentation.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OwncloudDocumentation.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/OwncloudDocumentation"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OwncloudDocumentation"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+pdf:
+ $(SPHINXBUILD) -b pdf $(ALLSPHINXOPTS) $(BUILDDIR)/pdf
+ @echo
+ @echo "build finished. the text files are in $(BUILDDIR)/pdf."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "build finished. the text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
--- /dev/null
+==============================
+Using the ownCloud Android App
+==============================
+
+Accessing your files on your ownCloud server via the Web interface is easy and
+convenient, as you can use any Web browser on any operating system without
+installing special client software. However, the ownCloud Android app offers
+some advantages over the Web interface:
+
+* A simplified interface that fits nicely on a tablet or smartphone
+* Automatic synchronization of your files
+* Instant uploads of photos or videos recorded on your Android device
+* Easily add files from your device to ownCloud
+* Two-factor authentication
+
+Getting the ownCloud Android App
+--------------------------------
+
+One way to get your ownCloud Android app is to log into your ownCloud server
+from your Android device using a Web browser such as Chrome, Firefox, or
+Dolphin. The first time you log in to a new ownCloud account you'll see a screen
+with a download link to the ownCloud app in the `Google Play store
+<https://play.google.com/store/apps/details?id=com.owncloud.android>`_.
+
+.. figure:: images/android-first-screen.jpg
+
+You will also find these links on your Personal page in the Web interface,
+
+You can also get it from the `Amazon App store
+<http://www.amazon.com/ownCloud-Inc/dp/B00944PQMK/>`_, and get source code and
+more information from the `ownCloud download page
+<http://owncloud.org/install/#mobile>`_.
+
+Connecting to Your ownCloud Server
+----------------------------------
+
+The first time you run your ownCloud Android app it opens to a configuration
+screen. Enter your server URL, login name, password, and click the Connect
+button. (Click the eyeball to the right of your password to expose your
+password.)
+
+.. figure:: images/android-new-account.png
+
+For best security your ownCloud server should be SSL-enabled, so that you can
+connect via ``https``. The ownCloud app will test your connection as soon as
+you enter it and tell you if you entered it correctly. If your server has a
+self-signed SSL certificate you'll get a scary warning how it is not to be
+trusted. Click the OK button to accept the certificate and complete your account
+setup.
+
+.. figure:: images/android-ssl-cert.png
+
+Managing Files
+--------------
+
+Now you should see the Files page of your ownCloud account. Click the overflow
+button at the top right (that's the one with three vertical dots, and that is
+really what it is called) to open a user menu. ``Refresh account`` refreshes the
+page view. ``Settings`` take you to your settings menu. ``Sort`` gives you the
+option to sort your files by date, or alphabetically.
+
+.. figure:: images/android-files-page.png
+
+The little file folder icon to the left of the overflow button opens a dialog to
+create a new folder. The arrow button opens a file upload dialog, and you can
+either upload content from other Android apps such as Google Drive, the Gallery,
+your music player, or from your Android filesystem. When you add a new file
+you will see a confirmation on the top left when it has uploaded successfully,
+and it is immediately synchronized with the server.
+
+.. figure:: images/android-upload.png
+
+All files (that you have permission to access) on your ownCloud server are
+displayed in your Android app, but are not downloaded until you download them.
+Downloaded files are marked with a green arrow.
+
+.. figure:: images/android-file-list.png
+
+Download and preview a file with a short press on the filename. When the file
+is in preview mode, a short press on the overflow button opens a menu with
+options for sharing, opening with an app, removing, sending, and displaying file
+details.
+
+.. figure:: images/android-file.png
+
+
+A long press on the filename does not download it, but opens a dialog with
+options for sharing, downloading, renaming, moving, removing, sending, and
+viewing file details.
+
+
+.. figure:: images/android-file-options.png
+
+
+Settings
+--------
+
+The Settings screen offers a number of useful options. In the Accounts
+section you can configure multiple ownCloud accounts.
+
+The Security section sets up strong two-factor authentication by allowing you
+to add a PIN (personal identification number) to access your account.
+
+The Instant Uploads section creates a directory, :file:`/InstantUpload`, and
+any photos or videos created with your Android device's camera are instantly
+uploaded to this directory. You also have the option to choose any other
+existing directory. Another nice option is Upload Pictures/Video via WiFi Only,
+to conserve your Internet data usage.
+
+.. figure:: images/android-settings.png
+
+The bottom section of the Settings screen has links to help and the
+app's version number.
+
+.. figure:: images/android-help.png
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# ownCloud Documentation documentation build configuration file, created by
+# sphinx-quickstart on Mon Oct 22 23:16:40 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os, inspect
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+#path to this script
+scriptpath = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.todo']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = [scriptpath+'/ocdoc/_shared_assets/templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'ownCloud Android App Manual'
+copyright = u'2013-2015, The ownCloud developers'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.6.2'
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build','scripts/*', 'ocdoc/*']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+2
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = [scriptpath+'/ocdoc/_shared_assets/themes']
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#html_theme = 'bootstrap'
+html_theme = 'default'
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+html_short_title = "Android App Manual"
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = [scriptpath+'/ocdoc/_shared_assets/static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+html_show_sphinx = False
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ownCloudAndroidAppManual'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'ownCloudAndroidAppManual.tex', u'ownCloud Android App Manual',
+ u'The ownCloud developers', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('owncloud.1', 'owncloud', u'Android synchronisation and file management utility.',
+ [u'The ownCloud developers'], 1),
+ ('owncloudcmd.1', 'owncloudcmd', u'ownCloud Android app.',
+ [u'The ownCloud developers'], 1),
+]
+
+# If true, show URL addresses after external links.
+man_show_urls = True
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'ownCloudClientManual', u'ownCloud Android App Manual',
+ u'The ownCloud developers', 'ownCloud', 'The ownCloud Android App Manual.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+
+# -- Options for Epub output ---------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = u'ownCloud Android App Manual'
+epub_author = u'The ownCloud developers'
+epub_publisher = u'The ownCloud developers'
+epub_copyright = u'2013-2015, The ownCloud developers'
+
+# The language of the text. It defaults to the language option
+# or en if the language is not set.
+#epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+#epub_scheme = ''
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#epub_identifier = ''
+
+# A unique identification for the text.
+#epub_uid = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+#epub_cover = ()
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+#epub_exclude_files = []
+
+# The depth of the table of contents in toc.ncx.
+#epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+#epub_tocdup = True
+
+# Include todos?
+todo_include_todos = True
--- /dev/null
+.. _contents:
+
+ownCloud Android App Manual
+==============================
+
+.. toctree::
+ :maxdepth: 2
+
+ android_app
--- /dev/null
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pdf to make a PDF file with rst2pdf
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pdf" (
+ %SPHINXBUILD% -b pdf %ALLSPHINXOPTS% %BUILDDIR%/pdf
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The PDF file is in %BUILDDIR%/pdf.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OwncloudDocumentation.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OwncloudDocumentation.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
--- /dev/null
+Subproject commit 343496c792616459e8204b6614fd42a1b16a6d68