Эх сурвалжийг харах

Merge branch 'develop' into imageGrid

David A. Velasco 10 жил өмнө
parent
commit
4af15f7b4e
72 өөрчлөгдсөн 3150 нэмэгдсэн , 967 устгасан
  1. 4 0
      .gitmodules
  2. 6 0
      res/values-ca/strings.xml
  3. 1 0
      res/values-cs-rCZ/strings.xml
  4. 1 0
      res/values-da/strings.xml
  5. 1 0
      res/values-de-rDE/strings.xml
  6. 1 0
      res/values-de/strings.xml
  7. 1 0
      res/values-el/strings.xml
  8. 1 0
      res/values-en-rGB/strings.xml
  9. 3 2
      res/values-es/strings.xml
  10. 4 0
      res/values-eu/strings.xml
  11. 3 0
      res/values-fi-rFI/strings.xml
  12. 5 4
      res/values-fr/strings.xml
  13. 1 0
      res/values-gl/strings.xml
  14. 19 0
      res/values-hr/strings.xml
  15. 1 0
      res/values-it/strings.xml
  16. 2 1
      res/values-ja-rJP/strings.xml
  17. 14 0
      res/values-km/strings.xml
  18. 1 0
      res/values-ko/strings.xml
  19. 1 0
      res/values-nl/strings.xml
  20. 1 0
      res/values-pt-rBR/strings.xml
  21. 15 14
      res/values-ru/strings.xml
  22. 7 0
      res/values-sk-rSK/strings.xml
  23. 1 0
      res/values-tr/strings.xml
  24. 1 0
      res/values-zh-rCN/strings.xml
  25. 1 0
      res/values/strings.xml
  26. 10 12
      res/xml/preferences.xml
  27. 4 4
      src/com/owncloud/android/authentication/AuthenticatorActivity.java
  28. 59 8
      src/com/owncloud/android/datamodel/FileDataStorageManager.java
  29. 19 12
      src/com/owncloud/android/datamodel/OCFile.java
  30. 2 1
      src/com/owncloud/android/db/ProviderMeta.java
  31. 10 6
      src/com/owncloud/android/files/FileMenuFilter.java
  32. 43 24
      src/com/owncloud/android/files/FileOperationsHelper.java
  33. 181 127
      src/com/owncloud/android/files/services/FileDownloader.java
  34. 5 5
      src/com/owncloud/android/files/services/FileUploader.java
  35. 225 0
      src/com/owncloud/android/files/services/IndexedForest.java
  36. 610 0
      src/com/owncloud/android/operations/RefreshFolderOperation.java
  37. 3 4
      src/com/owncloud/android/operations/RenameFileOperation.java
  38. 74 14
      src/com/owncloud/android/operations/SynchronizeFileOperation.java
  39. 263 341
      src/com/owncloud/android/operations/SynchronizeFolderOperation.java
  40. 23 2
      src/com/owncloud/android/providers/FileContentProvider.java
  41. 341 240
      src/com/owncloud/android/services/OperationsService.java
  42. 213 0
      src/com/owncloud/android/services/SyncFolderHandler.java
  43. 2 2
      src/com/owncloud/android/syncadapter/FileSyncAdapter.java
  44. 9 6
      src/com/owncloud/android/ui/activity/ComponentsGetter.java
  45. 13 2
      src/com/owncloud/android/ui/activity/FileActivity.java
  46. 36 13
      src/com/owncloud/android/ui/activity/FileDisplayActivity.java
  47. 6 6
      src/com/owncloud/android/ui/activity/FolderPickerActivity.java
  48. 55 1
      src/com/owncloud/android/ui/activity/Preferences.java
  49. 11 106
      src/com/owncloud/android/ui/adapter/FileListListAdapter.java
  50. 5 1
      src/com/owncloud/android/ui/fragment/FileDetailFragment.java
  51. 1 1
      src/com/owncloud/android/ui/fragment/OCFileListFragment.java
  52. 4 3
      src/com/owncloud/android/ui/preview/FileDownloadFragment.java
  53. 1 1
      src/com/owncloud/android/ui/preview/PreviewImageActivity.java
  54. 16 0
      src/com/owncloud/android/utils/ErrorMessageAdapter.java
  55. 20 4
      src/com/owncloud/android/utils/FileStorageUtils.java
  56. 173 0
      user_manual/Makefile
  57. 115 0
      user_manual/android_app.rst
  58. 293 0
      user_manual/conf.py
  59. BIN
      user_manual/images/android-downloads.png
  60. BIN
      user_manual/images/android-file-list.png
  61. BIN
      user_manual/images/android-file-options.png
  62. BIN
      user_manual/images/android-file.png
  63. BIN
      user_manual/images/android-files-page.png
  64. BIN
      user_manual/images/android-first-screen.jpg
  65. BIN
      user_manual/images/android-help.png
  66. BIN
      user_manual/images/android-new-account.png
  67. BIN
      user_manual/images/android-settings.png
  68. BIN
      user_manual/images/android-ssl-cert.png
  69. BIN
      user_manual/images/android-upload.png
  70. 9 0
      user_manual/index.rst
  71. 199 0
      user_manual/make.bat
  72. 1 0
      user_manual/ocdoc

+ 4 - 0
.gitmodules

@@ -3,3 +3,7 @@
 	path = owncloud-android-library
 	path = owncloud-android-library
 	url = git://github.com/owncloud/android-library.git
 	url = git://github.com/owncloud/android-library.git
 	branch = develop
 	branch = develop
+[submodule "ocdoc"]
+	path = user_manual/ocdoc
+	url = https://github.com/owncloud/documentation
+	branch = master

+ 6 - 0
res/values-ca/strings.xml

@@ -11,6 +11,12 @@
   <string name="actionbar_settings">Configuració</string>
   <string name="actionbar_settings">Configuració</string>
   <string name="actionbar_see_details">Detalls</string>
   <string name="actionbar_see_details">Detalls</string>
   <string name="actionbar_send_file">Envia</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   
   <!--TODO re-enable when server-side folder size calculation is available   
     	<item>Biggest - Smallest</item>-->
     	<item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">General</string>
   <string name="prefs_category_general">General</string>

+ 1 - 0
res/values-cs-rCZ/strings.xml

@@ -297,6 +297,7 @@ správce systému.</string>
   <string name="prefs_category_instant_uploading">Okamžitá odesílání</string>
   <string name="prefs_category_instant_uploading">Okamžitá odesílání</string>
   <string name="prefs_category_security">Zabezpečení</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="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="shared_subject_header">sdílené</string>
   <string name="with_you_subject_header">s vámi</string>
   <string name="with_you_subject_header">s vámi</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-da/strings.xml

@@ -296,6 +296,7 @@
   <string name="prefs_category_instant_uploading">Øjeblikkelige uploads</string>
   <string name="prefs_category_instant_uploading">Øjeblikkelige uploads</string>
   <string name="prefs_category_security">Sikkerhed</string>
   <string name="prefs_category_security">Sikkerhed</string>
   <string name="prefs_instant_video_upload_path_title">Sti til videoupload</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="shared_subject_header">delt</string>
   <string name="with_you_subject_header">med dig</string>
   <string name="with_you_subject_header">med dig</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-de-rDE/strings.xml

@@ -298,6 +298,7 @@
   <string name="prefs_category_instant_uploading">Sofortiges Hochladen</string>
   <string name="prefs_category_instant_uploading">Sofortiges Hochladen</string>
   <string name="prefs_category_security">Sicherheit</string>
   <string name="prefs_category_security">Sicherheit</string>
   <string name="prefs_instant_video_upload_path_title">Verzeichnis zum Hochladen der Videos</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="shared_subject_header">geteilt</string>
   <string name="with_you_subject_header">Mit Ihnen</string>
   <string name="with_you_subject_header">Mit Ihnen</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-de/strings.xml

@@ -298,6 +298,7 @@
   <string name="prefs_category_instant_uploading">Sofortiges Hochladen</string>
   <string name="prefs_category_instant_uploading">Sofortiges Hochladen</string>
   <string name="prefs_category_security">Sicherheit</string>
   <string name="prefs_category_security">Sicherheit</string>
   <string name="prefs_instant_video_upload_path_title">Verzeichnis zum Hochladen der Videos</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="shared_subject_header">geteilt</string>
   <string name="with_you_subject_header">Mit Dir</string>
   <string name="with_you_subject_header">Mit Dir</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-el/strings.xml

@@ -298,6 +298,7 @@
   <string name="prefs_category_instant_uploading">Στιγμιαίες Μεταφορτώσεις</string>
   <string name="prefs_category_instant_uploading">Στιγμιαίες Μεταφορτώσεις</string>
   <string name="prefs_category_security">Ασφάλεια</string>
   <string name="prefs_category_security">Ασφάλεια</string>
   <string name="prefs_instant_video_upload_path_title">Διαδρομή Μεταφόρτωσης Βίντεο</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="shared_subject_header">μοιρασμένο </string>
   <string name="with_you_subject_header">με εσένα</string>
   <string name="with_you_subject_header">με εσένα</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-en-rGB/strings.xml

@@ -298,6 +298,7 @@
   <string name="prefs_category_instant_uploading">Instant Uploads</string>
   <string name="prefs_category_instant_uploading">Instant Uploads</string>
   <string name="prefs_category_security">Security</string>
   <string name="prefs_category_security">Security</string>
   <string name="prefs_instant_video_upload_path_title">Upload Video Path</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="shared_subject_header">shared</string>
   <string name="with_you_subject_header">with you</string>
   <string name="with_you_subject_header">with you</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 3 - 2
res/values-es/strings.xml

@@ -46,7 +46,7 @@
   <string name="auth_host_url">Dirección del servidor https://…</string>
   <string name="auth_host_url">Dirección del servidor https://…</string>
   <string name="auth_username">Nombre de usuario</string>
   <string name="auth_username">Nombre de usuario</string>
   <string name="auth_password">Contraseña</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="sync_string_files">Archivos</string>
   <string name="setup_btn_connect">Conectar</string>
   <string name="setup_btn_connect">Conectar</string>
   <string name="uploader_btn_upload_text">Subir</string>
   <string name="uploader_btn_upload_text">Subir</string>
@@ -297,7 +297,8 @@
   <string name="forbidden_permissions_move">para mover este archivo</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_instant_uploading">Subidas instantáneas</string>
   <string name="prefs_category_security">Seguridad</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="shared_subject_header">compartido</string>
   <string name="with_you_subject_header">contigo</string>
   <string name="with_you_subject_header">contigo</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 4 - 0
res/values-eu/strings.xml

@@ -271,6 +271,8 @@ Mesedez, baimendu berriz</string>
   <string name="downloader_download_file_not_found">Fitxategia jadanik ez dago eskuragarri zerbitzarian</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_category_accounts">Kontuak</string>
   <string name="prefs_add_account">Gehitu kontua</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_required_text">Autentikazioa beharrezkoa</string>
   <string name="saml_authentication_wrong_pass">Pasahitz okerra</string>
   <string name="saml_authentication_wrong_pass">Pasahitz okerra</string>
   <string name="actionbar_move">Mugitu</string>
   <string name="actionbar_move">Mugitu</string>
@@ -278,4 +280,6 @@ Mesedez, baimendu berriz</string>
   <string name="prefs_category_instant_uploading">Berehalako Igoerak</string>
   <string name="prefs_category_instant_uploading">Berehalako Igoerak</string>
   <string name="prefs_category_security">Segurtasuna</string>
   <string name="prefs_category_security">Segurtasuna</string>
   <string name="shared_subject_header">konpartitua</string>
   <string name="shared_subject_header">konpartitua</string>
+  <string name="with_you_subject_header">zurekin</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
 </resources>

+ 3 - 0
res/values-fi-rFI/strings.xml

@@ -131,12 +131,15 @@
   <string name="pincode_wrong">Väärä sovelluksen PIN</string>
   <string name="pincode_wrong">Väärä sovelluksen PIN</string>
   <string name="pincode_removed">Sovelluksen PIN poistettu</string>
   <string name="pincode_removed">Sovelluksen PIN poistettu</string>
   <string name="pincode_stored">Sovelluksen PIN-koodi tallennettu</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_playing">%1$s (toistetaan)</string>
   <string name="media_state_loading">%1$s (ladataan)</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_nothing_to_play">Mediatiedostoa ei löytynyt</string>
   <string name="media_err_no_account">Tiliä ei määritetty</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_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_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_timeout">Aikakatkaisu toistoa yrittäessä</string>
   <string name="media_err_invalid_progressive_playback">Mediatiedostoa ei voi suoratoistaa</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="media_err_security_ex">Turvallisuusvirhe yrittäessä toistaa kohdetta %1$s</string>

+ 5 - 4
res/values-fr/strings.xml

@@ -112,10 +112,10 @@ Téléchargez-le ici : %2$s</string>
   <string name="common_choose_account">Choisissez un compte</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">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_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_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_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_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="sync_foreign_files_forgotten_ticker">Certains fichiers locaux ont été oubliés</string>
@@ -151,7 +151,7 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq
   <string name="media_err_unsupported">Le codec de ce média n\'est pas pris en charge </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_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_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_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_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="media_err_security_ex">Erreur de sécurité à la lecture de %1$s</string>
@@ -302,6 +302,7 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq
   <string name="prefs_category_instant_uploading">Envoi instantané</string>
   <string name="prefs_category_instant_uploading">Envoi instantané</string>
   <string name="prefs_category_security">Sécurité</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="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="shared_subject_header">partagé(e)</string>
   <string name="with_you_subject_header">avec vous</string>
   <string name="with_you_subject_header">avec vous</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-gl/strings.xml

@@ -299,6 +299,7 @@ Descárgueo de aquí: %2$s</string>
   <string name="prefs_category_instant_uploading">Envío instantáneo</string>
   <string name="prefs_category_instant_uploading">Envío instantáneo</string>
   <string name="prefs_category_security">Seguranza</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="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="shared_subject_header">compartido</string>
   <string name="with_you_subject_header">con vostede</string>
   <string name="with_you_subject_header">con vostede</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 19 - 0
res/values-hr/strings.xml

@@ -1,16 +1,34 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
 <resources>
+  <string name="about_android">%1$s Android aplikacija</string>
   <string name="about_version">verzija %1$s</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">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_upload_files">Datoteke</string>
+  <string name="actionbar_open_with">Otvori sa</string>
   <string name="actionbar_mkdir">Nova mapa</string>
   <string name="actionbar_mkdir">Nova mapa</string>
   <string name="actionbar_settings">Postavke</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_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   
   <!--TODO re-enable when server-side folder size calculation is available   
     	<item>Biggest - Smallest</item>-->
     	<item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">Općenito</string>
   <string name="prefs_category_general">Općenito</string>
   <string name="prefs_category_more">više</string>
   <string name="prefs_category_more">više</string>
   <string name="prefs_accounts">Korisnićki računi</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="prefs_help">Pomoć</string>
   <string name="auth_username">Korisničko ime</string>
   <string name="auth_username">Korisničko ime</string>
   <string name="auth_password">Lozinka</string>
   <string name="auth_password">Lozinka</string>
@@ -38,6 +56,7 @@
   <string name="auth_trying_to_login">Trying to login…</string>
   <string name="auth_trying_to_login">Trying to login…</string>
   <string name="common_rename">Promjeni ime</string>
   <string name="common_rename">Promjeni ime</string>
   <string name="common_remove">Makni</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="activity_chooser_send_file_title">Pošaljite</string>
   <string name="empty"></string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Korisnićki računi</string>
   <string name="prefs_category_accounts">Korisnićki računi</string>

+ 1 - 0
res/values-it/strings.xml

@@ -298,6 +298,7 @@
   <string name="prefs_category_instant_uploading">Caricamenti istantanei</string>
   <string name="prefs_category_instant_uploading">Caricamenti istantanei</string>
   <string name="prefs_category_security">Protezione</string>
   <string name="prefs_category_security">Protezione</string>
   <string name="prefs_instant_video_upload_path_title">Percorso di caricamento video</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="shared_subject_header">condiviso</string>
   <string name="with_you_subject_header">con te</string>
   <string name="with_you_subject_header">con te</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 2 - 1
res/values-ja-rJP/strings.xml

@@ -283,7 +283,7 @@
   <string name="auth_redirect_non_secure_connection_title">暗号化接続は非暗号化接続にリダイレクトされました。</string>
   <string name="auth_redirect_non_secure_connection_title">暗号化接続は非暗号化接続にリダイレクトされました。</string>
   <string name="actionbar_logger">ログ</string>
   <string name="actionbar_logger">ログ</string>
   <string name="log_send_history_button">ログを送信</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_send_mail_subject">%1$s アンドロイドアプリログ</string>
   <string name="log_progress_dialog_text">読込中 ...</string>
   <string name="log_progress_dialog_text">読込中 ...</string>
   <string name="saml_authentication_required_text">認証を必要とする</string>
   <string name="saml_authentication_required_text">認証を必要とする</string>
@@ -299,6 +299,7 @@
   <string name="prefs_category_instant_uploading">自動アップロード</string>
   <string name="prefs_category_instant_uploading">自動アップロード</string>
   <string name="prefs_category_security">セキュリティ</string>
   <string name="prefs_category_security">セキュリティ</string>
   <string name="prefs_instant_video_upload_path_title">動画のアップロードパス</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="shared_subject_header">共有中</string>
   <string name="with_you_subject_header">あなたと</string>
   <string name="with_you_subject_header">あなたと</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 14 - 0
res/values-km/strings.xml

@@ -1,17 +1,27 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
 <resources>
+  <string name="about_android">%1$s កម្មវិធីអានដ្រយ</string>
+  <string name="about_version">ជំនាន់ %1$s</string>
   <string name="actionbar_upload">ផ្ទុក​ឡើង</string>
   <string name="actionbar_upload">ផ្ទុក​ឡើង</string>
   <string name="actionbar_upload_files">ឯកសារ</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_settings">ការកំណត់</string>
   <string name="actionbar_see_details">ព័ត៌មាន​លម្អិត</string>
   <string name="actionbar_see_details">ព័ត៌មាន​លម្អិត</string>
   <string name="actionbar_send_file">ផ្ញើ</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   
   <!--TODO re-enable when server-side folder size calculation is available   
     	<item>Biggest - Smallest</item>-->
     	<item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">ទូទៅ</string>
   <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_accounts">គណនី</string>
   <string name="prefs_manage_accounts">គ្រប់គ្រង​គណនី</string>
   <string name="prefs_manage_accounts">គ្រប់គ្រង​គណនី</string>
+  <string name="prefs_pincode">ភីន​កូដ កម្មវិធី</string>
   <string name="prefs_help">ជំនួយ</string>
   <string name="prefs_help">ជំនួយ</string>
   <string name="auth_username">ឈ្មោះ​អ្នកប្រើ</string>
   <string name="auth_username">ឈ្មោះ​អ្នកប្រើ</string>
   <string name="auth_password">ពាក្យសម្ងាត់</string>
   <string name="auth_password">ពាក្យសម្ងាត់</string>
@@ -41,6 +51,7 @@
   <string name="common_error">កំហុស</string>
   <string name="common_error">កំហុស</string>
   <string name="common_loading">កំពុងដំណើរការ</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="change_password">ប្តូរ​ពាក្យសម្ងាត់</string>
   <string name="delete_account">លប់គណនី</string>
   <string name="delete_account">លប់គណនី</string>
   <string name="create_account">បង្កើតគណនី</string>
   <string name="create_account">បង្កើតគណនី</string>
@@ -69,6 +80,9 @@
   <string name="fd_keep_in_sync">រក្សាឯកសាររហូតដល់កាលបរិច្ឆេទ</string>
   <string name="fd_keep_in_sync">រក្សាឯកសាររហូតដល់កាលបរិច្ឆេទ</string>
   <string name="common_rename">ប្ដូរ​ឈ្មោះ</string>
   <string name="common_rename">ប្ដូរ​ឈ្មោះ</string>
   <string name="common_remove">ដកចេញ</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_success_msg">ការដកយកចេញបានជោគជ័យ</string>
   <string name="remove_fail_msg">ការដកយកចេញបានបរាជ័យ</string>
   <string name="remove_fail_msg">ការដកយកចេញបានបរាជ័យ</string>
   <string name="rename_dialog_title">បញ្ចូលឈ្មោះថ្មី</string>
   <string name="rename_dialog_title">បញ្ចូលឈ្មោះថ្មី</string>

+ 1 - 0
res/values-ko/strings.xml

@@ -297,6 +297,7 @@
   <string name="prefs_category_instant_uploading">즉시 업로드</string>
   <string name="prefs_category_instant_uploading">즉시 업로드</string>
   <string name="prefs_category_security">보안</string>
   <string name="prefs_category_security">보안</string>
   <string name="prefs_instant_video_upload_path_title">동영상 업로드 경로</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="shared_subject_header">공유됨</string>
   <string name="with_you_subject_header">나와</string>
   <string name="with_you_subject_header">나와</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-nl/strings.xml

@@ -301,6 +301,7 @@ Hieronder staan de lokale bestanden en de externe bestanden in %5$s waar ze naar
   <string name="prefs_category_instant_uploading">Directe uploads</string>
   <string name="prefs_category_instant_uploading">Directe uploads</string>
   <string name="prefs_category_security">Beveiliging</string>
   <string name="prefs_category_security">Beveiliging</string>
   <string name="prefs_instant_video_upload_path_title">Upload Video Pad</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="shared_subject_header">gedeeld</string>
   <string name="with_you_subject_header">met u</string>
   <string name="with_you_subject_header">met u</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-pt-rBR/strings.xml

@@ -298,6 +298,7 @@
   <string name="prefs_category_instant_uploading">Envios Instantâneos</string>
   <string name="prefs_category_instant_uploading">Envios Instantâneos</string>
   <string name="prefs_category_security">Segurança</string>
   <string name="prefs_category_security">Segurança</string>
   <string name="prefs_instant_video_upload_path_title">Enviar o Caminho do Vídeo</string>
   <string name="prefs_instant_video_upload_path_title">Enviar o Caminho do Vídeo</string>
+  <string name="download_folder_failed_content">Baixar %1$s da pasta não pode ser completado</string>
   <string name="shared_subject_header">compartilhado</string>
   <string name="shared_subject_header">compartilhado</string>
   <string name="with_you_subject_header">com você</string>
   <string name="with_you_subject_header">com você</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 15 - 14
res/values-ru/strings.xml

@@ -77,7 +77,7 @@
   <string name="filedetails_sync_file">Обновить файл</string>
   <string name="filedetails_sync_file">Обновить файл</string>
   <string name="filedetails_renamed_in_upload_msg">Файл был переименован в %1$s во время загрузки</string>
   <string name="filedetails_renamed_in_upload_msg">Файл был переименован в %1$s во время загрузки</string>
   <string name="action_share_file">Поделиться ссылкой</string>
   <string name="action_share_file">Поделиться ссылкой</string>
-  <string name="action_unshare_file">Удалить ссылку</string>
+  <string name="action_unshare_file">Убрать ссылку</string>
   <string name="common_yes">Да</string>
   <string name="common_yes">Да</string>
   <string name="common_no">Нет</string>
   <string name="common_no">Нет</string>
   <string name="common_ok">ОК</string>
   <string name="common_ok">ОК</string>
@@ -115,11 +115,11 @@
   <string name="sync_fail_content">Синхронизация %1$s не может быть завершена</string>
   <string name="sync_fail_content">Синхронизация %1$s не может быть завершена</string>
   <string name="sync_fail_content_unauthorized">Неверный пароль для %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_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_ticker">Не удалось синхронизировать файлы</string>
   <string name="sync_fail_in_favourites_content">Содержимое %1$d файлов не может быть синхронизировано (конфликтов: %2$d)</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_ticker">Некоторые загруженные файлы не были перенесены в локальную папку </string>
+  <string name="sync_foreign_files_forgotten_content"> Невозможно скопировать %1$d файлов из папки %2$s</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_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="sync_current_folder_was_removed">Каталог %1$s больше не существует</string>
   <string name="foreign_files_move">Переместить всё</string>
   <string name="foreign_files_move">Переместить всё</string>
@@ -141,8 +141,8 @@
   <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_state_loading">%1$s (загружается)</string>
   <string name="media_event_done">%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_not_in_owncloud">Файл в неверной учётной записи</string>
   <string name="media_err_unsupported">Неподдерживаемый кодек</string>
   <string name="media_err_unsupported">Неподдерживаемый кодек</string>
   <string name="media_err_io">Медиафайл не может быть прочитан</string>
   <string name="media_err_io">Медиафайл не может быть прочитан</string>
@@ -171,7 +171,7 @@
   <string name="auth_timeout_title">Сервер слишком долго не отвечает</string>
   <string name="auth_timeout_title">Сервер слишком долго не отвечает</string>
   <string name="auth_incorrect_address_title">Неверный URL</string>
   <string name="auth_incorrect_address_title">Неверный URL</string>
   <string name="auth_ssl_general_error_title">Ошибка инициализации SSL</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_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_secure_connection">Защищённое соединение установлено</string>
@@ -180,12 +180,12 @@
   <string name="auth_oauth_error_access_denied">Сервер авторизации отказал в доступе</string>
   <string name="auth_oauth_error_access_denied">Сервер авторизации отказал в доступе</string>
   <string name="auth_wtf_reenter_URL">Неожиданный ответ; введите адрес сервера ещё раз</string>
   <string name="auth_wtf_reenter_URL">Неожиданный ответ; введите адрес сервера ещё раз</string>
   <string name="auth_expired_oauth_token_toast">Время авторизации истекло. Пожалуйста, авторизуйтесь снова</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_expired_saml_sso_token_toast">Время сессии истекло. Пожалуйста, подключитесь снова</string>
   <string name="auth_connecting_auth_server">Подключение к серверу аутентификации...</string>
   <string name="auth_connecting_auth_server">Подключение к серверу аутентификации...</string>
   <string name="auth_unsupported_auth_method">Сервер не поддерживает выбранный метод аутентификации</string>
   <string name="auth_unsupported_auth_method">Сервер не поддерживает выбранный метод аутентификации</string>
-  <string name="auth_unsupported_multiaccount">%1$s не поддерживает сразу несколько учётных записей</string>
-  <string name="auth_fail_get_user_name">Cервер не возвращает корректный пользовательский идентификатор. Пожалуйста, свяжитесь с вашим администратором
+  <string name="auth_unsupported_multiaccount">%1$s не поддерживает несколько учётных записей</string>
+  <string name="auth_fail_get_user_name">Сервер вернул некорректный пользовательский идентификатор. Пожалуйста, свяжитесь с вашим администратором
 ⇥</string>
 ⇥</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="fd_keep_in_sync">Обновлять файл</string>
@@ -204,7 +204,7 @@
   <string name="rename_server_fail_msg">Переименование не может быть завершено</string>
   <string name="rename_server_fail_msg">Переименование не может быть завершено</string>
   <string name="sync_file_fail_msg">Удаленный файл не может быть проверен</string>
   <string name="sync_file_fail_msg">Удаленный файл не может быть проверен</string>
   <string name="sync_file_nothing_to_do_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">Недопустимые символы: / \\ &lt; &gt; : \" | ? *</string>
   <string name="filename_forbidden_characters">Недопустимые символы: / \\ &lt; &gt; : \" | ? *</string>
   <string name="filename_empty">Имя файла не может быть пустым</string>
   <string name="filename_empty">Имя файла не может быть пустым</string>
   <string name="wait_a_moment">Подождите немного</string>
   <string name="wait_a_moment">Подождите немного</string>
@@ -236,20 +236,20 @@
   <string name="ssl_validator_label_signature">Подпись:</string>
   <string name="ssl_validator_label_signature">Подпись:</string>
   <string name="ssl_validator_label_signature_algorithm">Алгоритм:</string>
   <string name="ssl_validator_label_signature_algorithm">Алгоритм:</string>
   <string name="ssl_validator_null_cert">Сертификат не может быть показан.</string>
   <string name="ssl_validator_null_cert">Сертификат не может быть показан.</string>
-  <string name="ssl_validator_no_info_about_error">- Информации об ошибке нет</string>
+  <string name="ssl_validator_no_info_about_error">- Нет информации об ошибке</string>
   <string name="placeholder_sentence">Это заполнитель</string>
   <string name="placeholder_sentence">Это заполнитель</string>
   <string name="placeholder_filename">placeholder.txt</string>
   <string name="placeholder_filename">placeholder.txt</string>
   <string name="placeholder_filetype">Изображение PNG</string>
   <string name="placeholder_filetype">Изображение PNG</string>
   <string name="placeholder_filesize">389 КБ</string>
   <string name="placeholder_filesize">389 КБ</string>
   <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
   <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
   <string name="placeholder_media_time">12:23:45</string>
   <string name="placeholder_media_time">12:23:45</string>
-  <string name="instant_upload_on_wifi">Загружать изображения только через Wi-Fi</string>
+  <string name="instant_upload_on_wifi">Загрузка изображений только через Wi-Fi</string>
   <string name="instant_video_upload_on_wifi">Загрузка видео только через WiFi</string>
   <string name="instant_video_upload_on_wifi">Загрузка видео только через WiFi</string>
   <string name="instant_upload_path">/InstantUpload</string>
   <string name="instant_upload_path">/InstantUpload</string>
   <string name="conflict_title">Конфликт обновления</string>
   <string name="conflict_title">Конфликт обновления</string>
   <string name="conflict_message">Удаленный файл %s не синхронизирован с локальным. Продолжение приведет к замене содержимого файла на сервере.</string>
   <string name="conflict_message">Удаленный файл %s не синхронизирован с локальным. Продолжение приведет к замене содержимого файла на сервере.</string>
   <string name="conflict_keep_both">Сохранить оба</string>
   <string name="conflict_keep_both">Сохранить оба</string>
-  <string name="conflict_overwrite">Заменить</string>
+  <string name="conflict_overwrite">Перезаписать</string>
   <string name="conflict_dont_upload">Не загружать</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="preview_image_error_unknown_format">Это изображение не может быть отображено</string>
@@ -299,6 +299,7 @@
   <string name="prefs_category_instant_uploading">Мгновенные загрузки</string>
   <string name="prefs_category_instant_uploading">Мгновенные загрузки</string>
   <string name="prefs_category_security">Безопасность</string>
   <string name="prefs_category_security">Безопасность</string>
   <string name="prefs_instant_video_upload_path_title">Путь для загрузки Видео</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="shared_subject_header">общие</string>
   <string name="with_you_subject_header">с вами</string>
   <string name="with_you_subject_header">с вами</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 7 - 0
res/values-sk-rSK/strings.xml

@@ -282,6 +282,9 @@
   <string name="auth_redirect_non_secure_connection_title">Zabezpečené pripojenie je presmerované na nezabezpečenú trasu.</string>
   <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="actionbar_logger">Logy</string>
   <string name="log_send_history_button">Odoslať históriu</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_required_text">Vyžaduje sa overenie</string>
   <string name="saml_authentication_wrong_pass">Nesprávne heslo</string>
   <string name="saml_authentication_wrong_pass">Nesprávne heslo</string>
   <string name="actionbar_move">Presunúť</string>
   <string name="actionbar_move">Presunúť</string>
@@ -294,5 +297,9 @@
   <string name="forbidden_permissions_move">pre presun tohoto súboru</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_instant_uploading">Okamžité nahratie</string>
   <string name="prefs_category_security">Zabezpečenie</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="shared_subject_header">zdieľané</string>
+  <string name="with_you_subject_header">s vami</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
 </resources>

+ 1 - 0
res/values-tr/strings.xml

@@ -298,6 +298,7 @@
   <string name="prefs_category_instant_uploading">Anında Yüklemeler</string>
   <string name="prefs_category_instant_uploading">Anında Yüklemeler</string>
   <string name="prefs_category_security">Güvenlik</string>
   <string name="prefs_category_security">Güvenlik</string>
   <string name="prefs_instant_video_upload_path_title">Video Yükleme Yolu</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="shared_subject_header">paylaşılan</string>
   <string name="with_you_subject_header">sizinle</string>
   <string name="with_you_subject_header">sizinle</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
   <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>

+ 1 - 0
res/values-zh-rCN/strings.xml

@@ -297,5 +297,6 @@
   <string name="prefs_category_instant_uploading">即时上传</string>
   <string name="prefs_category_instant_uploading">即时上传</string>
   <string name="prefs_category_security">安全</string>
   <string name="prefs_category_security">安全</string>
   <string name="prefs_instant_video_upload_path_title">视频上传路径</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="shared_subject_header">分享</string>
 </resources>
 </resources>

+ 1 - 0
res/values/strings.xml

@@ -324,6 +324,7 @@
 	<string name="prefs_category_security">Security</string>
 	<string name="prefs_category_security">Security</string>
 
 
 	<string name="prefs_instant_video_upload_path_title">Upload Video Path</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="shared_subject_header">shared</string>
 	<string name="with_you_subject_header">with you</string>
 	<string name="with_you_subject_header">with you</string>

+ 10 - 12
res/xml/preferences.xml

@@ -31,25 +31,23 @@
                         android:summary="@string/prefs_pincode_summary"/>
                         android:summary="@string/prefs_pincode_summary"/>
 	</PreferenceCategory>
 	</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:title="@string/prefs_instant_upload"
 	                        android:summary="@string/prefs_instant_upload_summary"/>
 	                        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:title="@string/instant_upload_on_wifi"
 	        				android:key="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
 	    <com.owncloud.android.ui.PreferenceWithLongSummary
 							android:title="@string/prefs_instant_video_upload_path_title"
 							android:title="@string/prefs_instant_video_upload_path_title"
 							android:key="instant_video_upload_path" />
 							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:title="@string/instant_video_upload_on_wifi"
 	        				android:key="instant_video_upload_on_wifi"/>
 	        				android:key="instant_video_upload_on_wifi"/>
 	    <!-- DISABLED FOR RELEASE UNTIL FIXED
 	    <!-- DISABLED FOR RELEASE UNTIL FIXED

+ 4 - 4
src/com/owncloud/android/authentication/AuthenticatorActivity.java

@@ -683,7 +683,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         
         
         if (mOperationsServiceBinder != null) {
         if (mOperationsServiceBinder != null) {
             //Log_OC.wtf(TAG, "getting access token..." );
             //Log_OC.wtf(TAG, "getting access token..." );
-            mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent);
+            mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
         }
         }
     }
     }
 
 
@@ -752,7 +752,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
                 normalizeUrlSuffix(uri)
                 normalizeUrlSuffix(uri)
             );
             );
             if (mOperationsServiceBinder != null) {
             if (mOperationsServiceBinder != null) {
-                mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent);
+                mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
             } else {
             } else {
               Log_OC.wtf(TAG, "Server check tried with OperationService unbound!" );
               Log_OC.wtf(TAG, "Server check tried with OperationService unbound!" );
             }
             }
@@ -888,7 +888,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         
         
         if (mOperationsServiceBinder != null) {
         if (mOperationsServiceBinder != null) {
             //Log_OC.wtf(TAG, "starting existenceCheckRemoteOperation..." );
             //Log_OC.wtf(TAG, "starting existenceCheckRemoteOperation..." );
-            mWaitingForOpId = mOperationsServiceBinder.newOperation(existenceCheckIntent);
+            mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(existenceCheckIntent);
         }
         }
     }
     }
 
 
@@ -1704,7 +1704,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         
         
         if (mOperationsServiceBinder != null) {
         if (mOperationsServiceBinder != null) {
             //Log_OC.wtf(TAG, "starting getRemoteUserNameOperation..." );
             //Log_OC.wtf(TAG, "starting getRemoteUserNameOperation..." );
-            mWaitingForOpId = mOperationsServiceBinder.newOperation(getUserNameIntent);
+            mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getUserNameIntent);
         }
         }
     }
     }
 
 

+ 59 - 8
src/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -46,6 +46,7 @@ import android.content.OperationApplicationException;
 import android.database.Cursor;
 import android.database.Cursor;
 import android.net.Uri;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.RemoteException;
+import android.provider.MediaStore;
 
 
 public class FileDataStorageManager {
 public class FileDataStorageManager {
 
 
@@ -193,6 +194,7 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
         cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
         cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
         cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
         cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
         cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
+        cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading());
         
         
         boolean sameRemotePath = fileExists(file.getRemotePath());
         boolean sameRemotePath = fileExists(file.getRemotePath());
         if (sameRemotePath ||
         if (sameRemotePath ||
@@ -261,8 +263,8 @@ public class FileDataStorageManager {
      * HERE ONLY DATA CONSISTENCY SHOULD BE GRANTED
      * HERE ONLY DATA CONSISTENCY SHOULD BE GRANTED
      *  
      *  
      * @param folder
      * @param folder
-     * @param files
-     * @param removeNotUpdated
+     * @param updatedFiles
+     * @param filesToRemove
      */
      */
     public void saveFolder(
     public void saveFolder(
             OCFile folder, Collection<OCFile> updatedFiles, Collection<OCFile> filesToRemove
             OCFile folder, Collection<OCFile> updatedFiles, Collection<OCFile> filesToRemove
@@ -302,6 +304,7 @@ public class FileDataStorageManager {
             cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
             cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
             cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
             cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
             cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
             cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
+            cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading());
 
 
             boolean existsByPath = fileExists(file.getRemotePath());
             boolean existsByPath = fileExists(file.getRemotePath());
             if (existsByPath || fileExists(file.getFileId())) {
             if (existsByPath || fileExists(file.getFileId())) {
@@ -491,7 +494,7 @@ public class FileDataStorageManager {
                 if (removeLocalCopy && file.isDown() && localPath != null && success) {
                 if (removeLocalCopy && file.isDown() && localPath != null && success) {
                     success = new File(localPath).delete();
                     success = new File(localPath).delete();
                     if (success) {
                     if (success) {
-                        triggerMediaScan(localPath);
+                        deleteFileInMediaScan(localPath);
                     }
                     }
                     if (!removeDBData && success) {
                     if (!removeDBData && success) {
                         // maybe unnecessary, but should be checked TODO remove if unnecessary
                         // maybe unnecessary, but should be checked TODO remove if unnecessary
@@ -539,7 +542,8 @@ public class FileDataStorageManager {
 
 
     private boolean removeLocalFolder(OCFile folder) {
     private boolean removeLocalFolder(OCFile folder) {
         boolean success = true;
         boolean success = true;
-        File localFolder = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder));
+        String localFolderPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder);
+        File localFolder = new File(localFolderPath);
         if (localFolder.exists()) {
         if (localFolder.exists()) {
             // stage 1: remove the local files already registered in the files database
             // stage 1: remove the local files already registered in the files database
             Vector<OCFile> files = getFolderContent(folder.getFileId());
             Vector<OCFile> files = getFolderContent(folder.getFileId());
@@ -549,13 +553,13 @@ public class FileDataStorageManager {
                         success &= removeLocalFolder(file);
                         success &= removeLocalFolder(file);
                     } else {
                     } else {
                         if (file.isDown()) {
                         if (file.isDown()) {
-                            String path = file.getStoragePath();
                             File localFile = new File(file.getStoragePath());
                             File localFile = new File(file.getStoragePath());
                             success &= localFile.delete();
                             success &= localFile.delete();
                             if (success) {
                             if (success) {
+                                // notify MediaScanner about removed file
+                                deleteFileInMediaScan(file.getStoragePath());
                                 file.setStoragePath(null);
                                 file.setStoragePath(null);
                                 saveFile(file);
                                 saveFile(file);
-                                triggerMediaScan(path); // notify MediaScanner about removed file
                             }
                             }
                         }
                         }
                     }
                     }
@@ -579,7 +583,6 @@ public class FileDataStorageManager {
                 } else {
                 } else {
                     String path = localFile.getAbsolutePath();
                     String path = localFile.getAbsolutePath();
                     success &= localFile.delete();
                     success &= localFile.delete();
-                    triggerMediaScan(path); // notify MediaScanner about removed file
                 }
                 }
             }
             }
         }
         }
@@ -714,7 +717,7 @@ public class FileDataStorageManager {
                 Iterator<String> it = originalPathsToTriggerMediaScan.iterator();
                 Iterator<String> it = originalPathsToTriggerMediaScan.iterator();
                 while (it.hasNext()) {
                 while (it.hasNext()) {
                     // Notify MediaScanner about removed file
                     // Notify MediaScanner about removed file
-                    triggerMediaScan(it.next());
+                    deleteFileInMediaScan(it.next());
                 }
                 }
                 it = newPathsToTriggerMediaScan.iterator();
                 it = newPathsToTriggerMediaScan.iterator();
                 while (it.hasNext()) {
                 while (it.hasNext()) {
@@ -877,6 +880,8 @@ public class FileDataStorageManager {
             file.setRemoteId(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID)));
             file.setRemoteId(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID)));
             file.setNeedsUpdateThumbnail(c.getInt(
             file.setNeedsUpdateThumbnail(c.getInt(
                     c.getColumnIndex(ProviderTableMeta.FILE_UPDATE_THUMBNAIL)) == 1 ? true : false);
                     c.getColumnIndex(ProviderTableMeta.FILE_UPDATE_THUMBNAIL)) == 1 ? true : false);
+            file.setDownloading(c.getInt(
+                    c.getColumnIndex(ProviderTableMeta.FILE_IS_DOWNLOADING)) == 1 ? true : false);
                     
                     
         }
         }
         return file;
         return file;
@@ -1259,6 +1264,10 @@ public class FileDataStorageManager {
                     ProviderTableMeta.FILE_UPDATE_THUMBNAIL, 
                     ProviderTableMeta.FILE_UPDATE_THUMBNAIL, 
                     file.needsUpdateThumbnail() ? 1 : 0
                     file.needsUpdateThumbnail() ? 1 : 0
                 );
                 );
+                cv.put(
+                        ProviderTableMeta.FILE_IS_DOWNLOADING,
+                        file.isDownloading() ? 1 : 0
+                );
 
 
                 boolean existsByPath = fileExists(file.getRemotePath());
                 boolean existsByPath = fileExists(file.getRemotePath());
                 if (existsByPath || fileExists(file.getFileId())) {
                 if (existsByPath || fileExists(file.getFileId())) {
@@ -1490,4 +1499,46 @@ public class FileDataStorageManager {
         MainApp.getAppContext().sendBroadcast(intent);
         MainApp.getAppContext().sendBroadcast(intent);
     }
     }
 
 
+    public void deleteFileInMediaScan(String path) {
+
+        String mimetypeString = FileStorageUtils.getMimeTypeFromName(path);
+        ContentResolver contentResolver = getContentResolver();
+
+        if (contentResolver != null) {
+            if (mimetypeString.startsWith("image/")) {
+                // Images
+                contentResolver.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                        MediaStore.Images.Media.DATA + "=?", new String[]{path});
+            } else if (mimetypeString.startsWith("audio/")) {
+                // Audio
+                contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        MediaStore.Audio.Media.DATA + "=?", new String[]{path});
+            } else if (mimetypeString.startsWith("video/")) {
+                // Video
+                contentResolver.delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                        MediaStore.Video.Media.DATA + "=?", new String[]{path});
+            }
+        } else {
+            ContentProviderClient contentProviderClient = getContentProviderClient();
+            try {
+                if (mimetypeString.startsWith("image/")) {
+                    // Images
+                    contentProviderClient.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                            MediaStore.Images.Media.DATA + "=?", new String[]{path});
+                } else if (mimetypeString.startsWith("audio/")) {
+                    // Audio
+                    contentProviderClient.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                            MediaStore.Audio.Media.DATA + "=?", new String[]{path});
+                } else if (mimetypeString.startsWith("video/")) {
+                    // Video
+                    contentProviderClient.delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                            MediaStore.Video.Media.DATA + "=?", new String[]{path});
+                }
+            } catch (RemoteException e) {
+                Log_OC.e(TAG, "Exception deleting media file in MediaStore " + e.getMessage());
+            }
+        }
+
+    }
+
 }
 }

+ 19 - 12
src/com/owncloud/android/datamodel/OCFile.java

@@ -20,9 +20,9 @@ package com.owncloud.android.datamodel;
 
 
 import android.os.Parcel;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Parcelable;
-import android.webkit.MimeTypeMap;
 
 
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.utils.FileStorageUtils;
 
 
 import java.io.File;
 import java.io.File;
 
 
@@ -70,6 +70,8 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
 
     private boolean mNeedsUpdateThumbnail;
     private boolean mNeedsUpdateThumbnail;
 
 
+    private boolean mIsDownloading;
+
 
 
     /**
     /**
      * Create new {@link OCFile} with given path.
      * Create new {@link OCFile} with given path.
@@ -112,6 +114,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         mPermissions = source.readString();
         mPermissions = source.readString();
         mRemoteId = source.readString();
         mRemoteId = source.readString();
         mNeedsUpdateThumbnail = source.readInt() == 0;
         mNeedsUpdateThumbnail = source.readInt() == 0;
+        mIsDownloading = source.readInt() == 0;
 
 
     }
     }
 
 
@@ -136,6 +139,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         dest.writeString(mPermissions);
         dest.writeString(mPermissions);
         dest.writeString(mRemoteId);
         dest.writeString(mRemoteId);
         dest.writeInt(mNeedsUpdateThumbnail ? 1 : 0);
         dest.writeInt(mNeedsUpdateThumbnail ? 1 : 0);
+        dest.writeInt(mIsDownloading ? 1 : 0);
     }
     }
 
 
     /**
     /**
@@ -348,6 +352,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         mPermissions = null;
         mPermissions = null;
         mRemoteId = null;
         mRemoteId = null;
         mNeedsUpdateThumbnail = false;
         mNeedsUpdateThumbnail = false;
+        mIsDownloading = false;
     }
     }
 
 
     /**
     /**
@@ -533,17 +538,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
      */
      */
     public boolean isImage() {
     public boolean isImage() {
         return ((mMimeType != null && mMimeType.startsWith("image/")) ||
         return ((mMimeType != null && mMimeType.startsWith("image/")) ||
-                getMimeTypeFromName().startsWith("image/"));
-    }
-
-    public String getMimeTypeFromName() {
-        String extension = "";
-        int pos = mRemotePath.lastIndexOf('.');
-        if (pos >= 0) {
-            extension = mRemotePath.substring(pos + 1);
-        }
-        String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
-        return (result != null) ? result : "";
+                FileStorageUtils.getMimeTypeFromName(mRemotePath).startsWith("image/"));
     }
     }
 
 
     public String getPermissions() {
     public String getPermissions() {
@@ -562,4 +557,16 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         this.mRemoteId = remoteId;
         this.mRemoteId = remoteId;
     }
     }
 
 
+    public boolean isDownloading() {
+        return mIsDownloading;
+    }
+
+    public void setDownloading(boolean isDownloading) {
+        this.mIsDownloading = isDownloading;
+    }
+
+    public boolean isSynchronizing() {
+        // TODO real implementation
+        return false;
+    }
 }
 }

+ 2 - 1
src/com/owncloud/android/db/ProviderMeta.java

@@ -31,7 +31,7 @@ import com.owncloud.android.MainApp;
 public class ProviderMeta {
 public class ProviderMeta {
 
 
     public static final String DB_NAME = "filelist";
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 8;
+    public static final int DB_VERSION = 9;
 
 
     private ProviderMeta() {
     private ProviderMeta() {
     }
     }
@@ -71,6 +71,7 @@ public class ProviderMeta {
         public static final String FILE_PERMISSIONS = "permissions";
         public static final String FILE_PERMISSIONS = "permissions";
         public static final String FILE_REMOTE_ID = "remote_id";
         public static final String FILE_REMOTE_ID = "remote_id";
         public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail";
         public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail";
+        public static final String FILE_IS_DOWNLOADING= "is_downloading";
 
 
         public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME
         public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME
                 + " collate nocase asc";
                 + " collate nocase asc";

+ 10 - 6
src/com/owncloud/android/files/FileMenuFilter.java

@@ -31,6 +31,8 @@ import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 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;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 
 
 /**
 /**
@@ -51,8 +53,8 @@ public class FileMenuFilter {
      * 
      * 
      * @param targetFile        {@link OCFile} target of the action to filter in the {@link Menu}.
      * @param targetFile        {@link OCFile} target of the action to filter in the {@link Menu}.
      * @param account           ownCloud {@link Account} holding targetFile.
      * @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.
      * @param context           Android {@link Context}, needed to access build setup resources.
      */
      */
     public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, Context context) {
     public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, Context context) {
@@ -140,15 +142,17 @@ public class FileMenuFilter {
         boolean uploading = false;
         boolean uploading = false;
         if (mComponentsGetter != null && mFile != null && mAccount != null) {
         if (mComponentsGetter != null && mFile != null && mAccount != null) {
             FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder();
             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();
             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
         /// decision is taken for each possible action on a file in the menu
         
         
         // DOWNLOAD 
         // DOWNLOAD 
-        if (mFile == null || mFile.isFolder() || mFile.isDown() || downloading || uploading) {
+        if (mFile == null || mFile.isDown() || downloading || uploading) {
             toHide.add(R.id.action_download_file);
             toHide.add(R.id.action_download_file);
             
             
         } else {
         } else {
@@ -189,7 +193,7 @@ public class FileMenuFilter {
         
         
         
         
         // CANCEL DOWNLOAD
         // CANCEL DOWNLOAD
-        if (mFile == null || !downloading || mFile.isFolder()) {
+        if (mFile == null || !downloading) {
             toHide.add(R.id.action_cancel_download);
             toHide.add(R.id.action_cancel_download);
         } else {
         } else {
             toShow.add(R.id.action_cancel_download);
             toShow.add(R.id.action_cancel_download);

+ 43 - 24
src/com/owncloud/android/files/FileOperationsHelper.java

@@ -1,5 +1,5 @@
 /* ownCloud Android client application
 /* 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
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
  *   it under the terms of the GNU General Public License version 2,
@@ -127,7 +127,7 @@ public class FileOperationsHelper {
             service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
             service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
             service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
             service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
             service.putExtra(OperationsService.EXTRA_SEND_INTENT, sendIntent);
             service.putExtra(OperationsService.EXTRA_SEND_INTENT, sendIntent);
-            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
             
             
         } else {
         } else {
             Log_OC.wtf(TAG, "Trying to open a NULL OCFile");
             Log_OC.wtf(TAG, "Trying to open a NULL OCFile");
@@ -165,7 +165,7 @@ public class FileOperationsHelper {
             service.setAction(OperationsService.ACTION_UNSHARE);
             service.setAction(OperationsService.ACTION_UNSHARE);
             service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
             service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
             service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
             service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
-            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
             
             
             mFileActivity.showLoadingDialog();
             mFileActivity.showLoadingDialog();
             
             
@@ -197,18 +197,25 @@ public class FileOperationsHelper {
     
     
     
     
     public void syncFile(OCFile file) {
     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) {
     public void renameFile(OCFile file, String newFilename) {
         // RenameFile
         // RenameFile
         Intent service = new Intent(mFileActivity, OperationsService.class);
         Intent service = new Intent(mFileActivity, OperationsService.class);
@@ -216,7 +223,7 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
         service.putExtra(OperationsService.EXTRA_NEWNAME, newFilename);
         service.putExtra(OperationsService.EXTRA_NEWNAME, newFilename);
-        mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+        mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
         
         
         mFileActivity.showLoadingDialog();
         mFileActivity.showLoadingDialog();
     }
     }
@@ -229,7 +236,7 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
         service.putExtra(OperationsService.EXTRA_REMOVE_ONLY_LOCAL, onlyLocalCopy);
         service.putExtra(OperationsService.EXTRA_REMOVE_ONLY_LOCAL, onlyLocalCopy);
-        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().newOperation(service);
+        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
         
         
         mFileActivity.showLoadingDialog();
         mFileActivity.showLoadingDialog();
     }
     }
@@ -242,26 +249,38 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, remotePath);
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, remotePath);
         service.putExtra(OperationsService.EXTRA_CREATE_FULL_PATH, createFullPath);
         service.putExtra(OperationsService.EXTRA_CREATE_FULL_PATH, createFullPath);
-        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().newOperation(service);
+        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
         
         
         mFileActivity.showLoadingDialog();
         mFileActivity.showLoadingDialog();
     }
     }
 
 
-    
+    /**
+     * Cancel the transference in downloads (files/folders) and file uploads
+     * @param file OCFile
+     */
     public void cancelTransference(OCFile file) {
     public void cancelTransference(OCFile file) {
         Account account = mFileActivity.getAccount();
         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();
         FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
-        FileUploaderBinder uploaderBinder =  mFileActivity.getFileUploaderBinder();
+        FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
         if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
         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
             // Remove etag for parent, if file is a keep_in_sync
             if (file.keepInSync()) {
             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)) {
         } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
             uploaderBinder.cancel(account, file);
             uploaderBinder.cancel(account, file);
         }
         }
@@ -279,7 +298,7 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_NEW_PARENT_PATH, newfile.getRemotePath());
         service.putExtra(OperationsService.EXTRA_NEW_PARENT_PATH, newfile.getRemotePath());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, currentFile.getRemotePath());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, currentFile.getRemotePath());
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
-        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().newOperation(service);
+        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
 
 
         mFileActivity.showLoadingDialog();
         mFileActivity.showLoadingDialog();
     }
     }

+ 181 - 127
src/com/owncloud/android/files/services/FileDownloader.java

@@ -1,6 +1,6 @@
 /* ownCloud Android client application
 /* ownCloud Android client application
  *   Copyright (C) 2012 Bartek Przybylski
  *   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
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
  *   it under the terms of the GNU General Public License version 2,
@@ -25,8 +25,6 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Map;
 import java.util.Vector;
 import java.util.Vector;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 
 
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.authentication.AuthenticatorActivity;
@@ -64,17 +62,19 @@ import android.os.Looper;
 import android.os.Message;
 import android.os.Message;
 import android.os.Process;
 import android.os.Process;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.NotificationCompat;
+import android.util.Pair;
 
 
 public class FileDownloader extends Service implements OnDatatransferProgressListener {
 public class FileDownloader extends Service implements OnDatatransferProgressListener {
     
     
     public static final String EXTRA_ACCOUNT = "ACCOUNT";
     public static final String EXTRA_ACCOUNT = "ACCOUNT";
     public static final String EXTRA_FILE = "FILE";
     public static final String EXTRA_FILE = "FILE";
-    
+
     private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
     private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
     private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
     private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
     public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";    
     public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";    
     public static final String EXTRA_FILE_PATH = "FILE_PATH";
     public static final String EXTRA_FILE_PATH = "FILE_PATH";
     public static final String EXTRA_REMOTE_PATH = "REMOTE_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";
     public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
     
     
     private static final String TAG = "FileDownloader";
     private static final String TAG = "FileDownloader";
@@ -83,35 +83,25 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     private ServiceHandler mServiceHandler;
     private ServiceHandler mServiceHandler;
     private IBinder mBinder;
     private IBinder mBinder;
     private OwnCloudClient mDownloadClient = null;
     private OwnCloudClient mDownloadClient = null;
-    private Account mLastAccount = null;
+    private Account mCurrentAccount = null;
     private FileDataStorageManager mStorageManager;
     private FileDataStorageManager mStorageManager;
     
     
-    private ConcurrentMap<String, DownloadFileOperation> mPendingDownloads = new ConcurrentHashMap<String, DownloadFileOperation>();
+    private IndexedForest<DownloadFileOperation> mPendingDownloads = new IndexedForest<DownloadFileOperation>();
+
     private DownloadFileOperation mCurrentDownload = null;
     private DownloadFileOperation mCurrentDownload = null;
     
     
     private NotificationManager mNotificationManager;
     private NotificationManager mNotificationManager;
     private NotificationCompat.Builder mNotificationBuilder;
     private NotificationCompat.Builder mNotificationBuilder;
     private int mLastPercent;
     private int mLastPercent;
-    
+
     
     
     public static String getDownloadAddedMessage() {
     public static String getDownloadAddedMessage() {
-        return FileDownloader.class.getName().toString() + DOWNLOAD_ADDED_MESSAGE;
+        return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
     }
     }
     
     
     public static String getDownloadFinishMessage() {
     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
      * Service initialization
@@ -130,52 +120,73 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
 
 
     /**
     /**
      * Entry point to add one or several files to the queue of downloads.
      * 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
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
     public int onStartCommand(Intent intent, int flags, int startId) {
         if (    !intent.hasExtra(EXTRA_ACCOUNT) ||
         if (    !intent.hasExtra(EXTRA_ACCOUNT) ||
                 !intent.hasExtra(EXTRA_FILE)
                 !intent.hasExtra(EXTRA_FILE)
-                /*!intent.hasExtra(EXTRA_FILE_PATH) ||
-                !intent.hasExtra(EXTRA_REMOTE_PATH)*/
            ) {
            ) {
             Log_OC.e(TAG, "Not enough information provided in intent");
             Log_OC.e(TAG, "Not enough information provided in intent");
             return START_NOT_STICKY;
             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;
         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.
      * Implemented to perform cancellation, pause and resume of existing downloads.
      */
      */
     @Override
     @Override
@@ -193,33 +204,49 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
         return false;   // not accepting rebinding (default behaviour)
         return false;   // not accepting rebinding (default behaviour)
     }
     }
 
 
-    
+
     /**
     /**
      *  Binder to let client components to perform operations on the queue of downloads.
      *  Binder to let client components to perform operations on the queue of downloads.
-     * 
+     *
      *  It provides by itself the available operations.
      *  It provides by itself the available operations.
      */
      */
     public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener {
     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.
          * 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
          * @param file          A file in the queue of pending downloads
          */
          */
         public void cancel(Account account, OCFile file) {
         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) {
             if (download != null) {
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Canceling returned download of " + file.getRemotePath());*/
                 download.cancel();
                 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();
+                }
             }
             }
         }
         }
         
         
@@ -230,29 +257,18 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
 
 
 
 
         /**
         /**
-         * 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.
          * @param file          A file that could be in the queue of downloads.
          */
          */
         public boolean isDownloading(Account account, OCFile file) {
         public boolean isDownloading(Account account, OCFile file) {
             if (account == null || file == null) return false;
             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()));
         }
         }
 
 
         
         
@@ -261,12 +277,14 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
          * 
          * 
          * @param listener      Object to notify about progress of transfer.    
          * @param listener      Object to notify about progress of transfer.    
          * @param account       ownCloud account holding the file of interest.
          * @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;
             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);
         }
         }
         
         
         
         
@@ -275,21 +293,24 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
          * 
          * 
          * @param listener      Object to notify about progress of transfer.    
          * @param listener      Object to notify about progress of transfer.    
          * @param account       ownCloud account holding the file of interest.
          * @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;
             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
         @Override
         public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
         public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
                 String fileName) {
                 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) {
             if (boundListener != null) {
                 boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
                 boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
             }
             }
@@ -320,7 +341,10 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             if (msg.obj != null) {
             if (msg.obj != null) {
                 Iterator<String> it = requestedDownloads.iterator();
                 Iterator<String> it = requestedDownloads.iterator();
                 while (it.hasNext()) {
                 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);
             mService.stopSelf(msg.arg1);
@@ -334,11 +358,11 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
      * @param downloadKey   Key to access the download to perform, contained in mPendingDownloads 
      * @param downloadKey   Key to access the download to perform, contained in mPendingDownloads 
      */
      */
     private void downloadFile(String downloadKey) {
     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) {
         if (mCurrentDownload != null) {
             
             
             notifyDownloadStart(mCurrentDownload);
             notifyDownloadStart(mCurrentDownload);
@@ -346,39 +370,48 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             RemoteOperationResult downloadResult = null;
             RemoteOperationResult downloadResult = null;
             try {
             try {
                 /// prepare client object to send the request to the ownCloud server
                 /// 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
                 /// perform the download
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Executing download of " + mCurrentDownload.getRemotePath());*/
                 downloadResult = mCurrentDownload.execute(mDownloadClient);
                 downloadResult = mCurrentDownload.execute(mDownloadClient);
                 if (downloadResult.isSuccess()) {
                 if (downloadResult.isSuccess()) {
                     saveDownloadedFile();
                     saveDownloadedFile();
                 }
                 }
             
             
             } catch (AccountsException e) {
             } 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);
                 downloadResult = new RemoteOperationResult(e);
             } catch (IOException 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);
                 downloadResult = new RemoteOperationResult(e);
                 
                 
             } finally {
             } 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);
         }
         }
     }
     }
 
 
@@ -403,6 +436,15 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
         mStorageManager.triggerMediaScan(file.getStoragePath());
         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
      * Creates a status notification to show the download progress
@@ -448,7 +490,8 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
      * Callback method to update the progress bar in the status notification.
      * Callback method to update the progress bar in the status notification.
      */
      */
     @Override
     @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));
         int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
         if (percent != mLastPercent) {
         if (percent != mLastPercent) {
             mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
             mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
@@ -492,7 +535,9 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                 // let the user update credentials with one click
                 // let the user update credentials with one click
                 Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
                 Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
                 updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, download.getAccount());
                 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_NEW_TASK);
                 updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                 updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                 updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
                 updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -500,8 +545,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                     .setContentIntent(PendingIntent.getActivity(
                     .setContentIntent(PendingIntent.getActivity(
                         this, (int) System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT));
                         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 {
             } else {
                 // TODO put something smart in showDetailsIntent
                 // TODO put something smart in showDetailsIntent
                 Intent   showDetailsIntent = new Intent();
                 Intent   showDetailsIntent = new Intent();
@@ -510,7 +553,9 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                         this, (int) System.currentTimeMillis(), showDetailsIntent, 0));
                         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());
             mNotificationManager.notify(tickerId, mNotificationBuilder.build());
             
             
             // Remove success notification
             // Remove success notification
@@ -529,15 +574,22 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     /**
     /**
      * Sends a broadcast when a download finishes in order to the interested activities can update their view
      * 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());
         Intent end = new Intent(getDownloadFinishMessage());
         end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
         end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
         end.putExtra(ACCOUNT_NAME, download.getAccount().name);
         end.putExtra(ACCOUNT_NAME, download.getAccount().name);
         end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
         end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
         end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
         end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+        if (unlinkedFromRemotePath != null) {
+            end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
+        }
         sendStickyBroadcast(end);
         sendStickyBroadcast(end);
     }
     }
     
     
@@ -545,13 +597,15 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     /**
     /**
      * Sends a broadcast when a new download is added to the queue.
      * 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());
         Intent added = new Intent(getDownloadAddedMessage());
         added.putExtra(ACCOUNT_NAME, download.getAccount().name);
         added.putExtra(ACCOUNT_NAME, download.getAccount().name);
         added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
         added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
         added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
         added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+        added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
         sendStickyBroadcast(added);
         sendStickyBroadcast(added);
     }
     }
 
 

+ 5 - 5
src/com/owncloud/android/files/services/FileUploader.java

@@ -199,7 +199,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         if (uploadType == UPLOAD_SINGLE_FILE) {
         if (uploadType == UPLOAD_SINGLE_FILE) {
 
 
             if (intent.hasExtra(KEY_FILE)) {
             if (intent.hasExtra(KEY_FILE)) {
-                files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
+                files = new OCFile[] { (OCFile) intent.getParcelableExtra(KEY_FILE) };
 
 
             } else {
             } else {
                 localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
                 localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
@@ -372,8 +372,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
          * 
          * 
          * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload. 
          * 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) {
         public boolean isUploading(Account account, OCFile file) {
             if (account == null || file == null)
             if (account == null || file == null)
@@ -400,7 +400,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
          * 
          * 
          * @param listener      Object to notify about progress of transfer.    
          * @param listener      Object to notify about progress of transfer.    
          * @param account       ownCloud account holding the file of interest.
          * @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;
             if (account == null || file == null || listener == null) return;
@@ -415,7 +415,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
          * 
          * 
          * @param listener      Object to notify about progress of transfer.    
          * @param listener      Object to notify about progress of transfer.    
          * @param account       ownCloud account holding the file of interest.
          * @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;
             if (account == null || file == null || listener == null) return;

+ 225 - 0
src/com/owncloud/android/files/services/IndexedForest.java

@@ -0,0 +1,225 @@
+/* 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;
+    }
+
+
+
+}

+ 610 - 0
src/com/owncloud/android/operations/RefreshFolderOperation.java

@@ -0,0 +1,610 @@
+/* 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;
+    }
+
+}

+ 3 - 4
src/com/owncloud/android/operations/RenameFileOperation.java

@@ -51,7 +51,6 @@ public class RenameFileOperation extends SyncOperation {
      * Constructor
      * Constructor
      * 
      * 
      * @param remotePath            RemotePath of the OCFile instance describing the remote file or folder to rename
      * @param remotePath            RemotePath of the OCFile instance describing the remote file or folder to rename
-     * @param account               OwnCloud account containing the remote file 
      * @param newName               New name to set as the name of file.
      * @param newName               New name to set as the name of file.
      */
      */
     public RenameFileOperation(String remotePath, String newName) {
     public RenameFileOperation(String remotePath, String newName) {
@@ -117,7 +116,7 @@ public class RenameFileOperation extends SyncOperation {
 
 
     private void saveLocalFile() {
     private void saveLocalFile() {
         mFile.setFileName(mNewName);
         mFile.setFileName(mNewName);
-        
+
         // try to rename the local copy of the file
         // try to rename the local copy of the file
         if (mFile.isDown()) {
         if (mFile.isDown()) {
             String oldPath = mFile.getStoragePath();
             String oldPath = mFile.getStoragePath();
@@ -129,8 +128,8 @@ public class RenameFileOperation extends SyncOperation {
                 String newPath = parentStoragePath + mNewName;
                 String newPath = parentStoragePath + mNewName;
                 mFile.setStoragePath(newPath);
                 mFile.setStoragePath(newPath);
 
 
-                // notify MediaScanner about removed file - TODO really works?
-                getStorageManager().triggerMediaScan(oldPath);
+                // notify MediaScanner about removed file
+                getStorageManager().deleteFileInMediaScan(oldPath);
                 // notify to scan about new file
                 // notify to scan about new file
                 getStorageManager().triggerMediaScan(newPath);
                 getStorageManager().triggerMediaScan(newPath);
             }
             }

+ 74 - 14
src/com/owncloud/android/operations/SynchronizeFileOperation.java

@@ -54,13 +54,22 @@ public class SynchronizeFileOperation extends SyncOperation {
     
     
     private boolean mTransferWasRequested = false;
     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.
      * is executed, instead of reusing {@link OCFile} instances.
      * 
      * 
+     * Useful for direct synchronization of a single file.
+     * 
      * @param 
      * @param 
      * @param account               ownCloud account holding the file.
      * @param account               ownCloud account holding the file.
      * @param syncFileContents      When 'true', transference of data will be started by the 
      * @param syncFileContents      When 'true', transference of data will be started by the 
@@ -79,16 +88,21 @@ public class SynchronizeFileOperation extends SyncOperation {
         mAccount = account;
         mAccount = account;
         mSyncFileContents = syncFileContents;
         mSyncFileContents = syncFileContents;
         mContext = context;
         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.
      *                              retrieved from network by the operation when executed.
      * @param account               ownCloud account holding the file.
      * @param account               ownCloud account holding the file.
      * @param syncFileContents      When 'true', transference of data will be started by the 
      * @param syncFileContents      When 'true', transference of data will be started by the 
@@ -104,10 +118,53 @@ public class SynchronizeFileOperation extends SyncOperation {
         
         
         mLocalFile = localFile;
         mLocalFile = localFile;
         mServerFile = serverFile;
         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;
         mAccount = account;
         mSyncFileContents = syncFileContents;
         mSyncFileContents = syncFileContents;
         mContext = context;
         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;
     }
     }
     
     
 
 
@@ -145,13 +202,15 @@ public class SynchronizeFileOperation extends SyncOperation {
                 boolean serverChanged = false;
                 boolean serverChanged = false;
                 /* time for eTag is coming, but not yet
                 /* time for eTag is coming, but not yet
                     if (mServerFile.getEtag() != null) {
                     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 { */
                     } 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
                 /// decide action to perform depending upon changes
                 //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
                 //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
@@ -159,7 +218,7 @@ public class SynchronizeFileOperation extends SyncOperation {
                     result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
                     result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
 
 
                 } else if (localChanged) {
                 } else if (localChanged) {
-                    if (mSyncFileContents) {
+                    if (mSyncFileContents && mAllowUploads) {
                         requestForUpload(mLocalFile);
                         requestForUpload(mLocalFile);
                         // the local update of file properties will be done by the FileUploader service when the upload finishes
                         // the local update of file properties will be done by the FileUploader service when the upload finishes
                     } else {
                     } else {
@@ -195,7 +254,8 @@ public class SynchronizeFileOperation extends SyncOperation {
 
 
         }
         }
 
 
-        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;
         return result;
     }
     }

+ 263 - 341
src/com/owncloud/android/operations/SynchronizeFolderOperation.java

@@ -17,43 +17,35 @@
 
 
 package com.owncloud.android.operations;
 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.accounts.Account;
 import android.content.Context;
 import android.content.Context;
 import android.content.Intent;
 import android.content.Intent;
 import android.util.Log;
 import android.util.Log;
-//import android.support.v4.content.LocalBroadcastManager;
 
 
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 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.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;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
 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.ReadRemoteFileOperation;
 import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
 import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
 import com.owncloud.android.lib.resources.files.RemoteFile;
 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 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;
 
 
 
 
 /**
 /**
@@ -67,225 +59,177 @@ import com.owncloud.android.utils.FileStorageUtils;
  * 
  * 
  *  @author David A. Velasco
  *  @author David A. Velasco
  */
  */
-public class SynchronizeFolderOperation extends RemoteOperation {
+public class SynchronizeFolderOperation extends SyncOperation {
 
 
     private static final String TAG = SynchronizeFolderOperation.class.getSimpleName();
     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 */
     /** Time stamp for the synchronization process in progress */
     private long mCurrentSyncTime;
     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 */
     /** Account where the file to synchronize belongs */
     private Account mAccount;
     private Account mAccount;
-    
+
     /** Android context; necessary to send requests to the download service */
     /** Android context; necessary to send requests to the download service */
     private Context mContext;
     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 */
     /** 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 */
     /** Counter of conflicts found between local and remote files */
     private int mConflictsFound;
     private int mConflictsFound;
 
 
     /** Counter of failed operations in synchronization of kept-in-sync files */
     /** 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 */
     /** 'True' means that the remote folder changed and should be fetched */
     private boolean mRemoteFolderChanged;
     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}.
      * 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   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;
         mCurrentSyncTime = currentSyncTime;
-        mSyncFullAccount = syncFullAccount;
-        mIsShareSupported = isShareSupported;
-        mStorageManager = dataStorageManager;
         mAccount = account;
         mAccount = account;
         mContext = context;
         mContext = context;
-        mForgottenLocalFiles = new HashMap<String, String>();
         mRemoteFolderChanged = false;
         mRemoteFolderChanged = false;
-        mIgnoreETag = ignoreETag;
+        mFilesForDirectDownload = new Vector<OCFile>();
+        mFilesToSyncContentsWithoutUpload = new Vector<SyncOperation>();
+        mFavouriteFilesToSyncContents = new Vector<SyncOperation>();
+        mCancellationRequested = new AtomicBoolean(false);
     }
     }
-    
-    
+
+
     public int getConflictsFound() {
     public int getConflictsFound() {
         return mConflictsFound;
         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.
      * Performs the synchronization.
-     * 
+     *
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */
     @Override
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
     protected RemoteOperationResult run(OwnCloudClient client) {
         RemoteOperationResult result = null;
         RemoteOperationResult result = null;
-        mFailsInFavouritesFound = 0;
+        mFailsInFileSyncsFound = 0;
         mConflictsFound = 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;
         mRemoteFolderChanged = true;
         RemoteOperationResult result = null;
         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);
         result = operation.execute(client);
         if (result.isSuccess()){
         if (result.isSuccess()){
             OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
             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()));
                         !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag()));
-            }
 
 
             result = new RemoteOperationResult(ResultCode.OK);
             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"));
                     (mRemoteFolderChanged ? "changed" : "not changed"));
-            
+
         } else {
         } else {
             // check failed
             // check failed
             if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
             if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
                 removeLocalFolder();
                 removeLocalFolder();
             }
             }
             if (result.isException()) {
             if (result.isException()) {
-                Log_OC.e(TAG, "Checked " + mAccount.name + remotePath  + " : " + 
+                Log_OC.e(TAG, "Checked " + mAccount.name + mRemotePath  + " : " +
                         result.getLogMessage(), result.getException());
                         result.getLogMessage(), result.getException());
             } else {
             } else {
-                Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + 
+                Log_OC.e(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
                         result.getLogMessage());
                         result.getLogMessage());
             }
             }
+
         }
         }
-        
+
         return result;
         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()) {
         if (result.isSuccess()) {
             synchronizeData(result.getData(), client);
             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
                     // should be a different result code, but will do the job
             }
             }
         } else {
         } else {
@@ -293,17 +237,19 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                 removeLocalFolder();
                 removeLocalFolder();
         }
         }
         
         
+
         return result;
         return result;
     }
     }
 
 
-    
+
     private void removeLocalFolder() {
     private void removeLocalFolder() {
-        if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
+        FileDataStorageManager storageManager = getStorageManager();
+        if (storageManager.fileExists(mLocalFolder.getFileId())) {
             String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
             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)
                             mLocalFolder.getStoragePath().startsWith(currentSavePath)
                     )
                     )
             );
             );
@@ -312,50 +258,56 @@ public class SynchronizeFolderOperation extends RemoteOperation {
 
 
 
 
     /**
     /**
-     *  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.
      *  with the current data in the local database.
-     *  
+     *
      *  Grants that mChildren is updated with fresh data after execution.
      *  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
      *  @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));
         OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
         remoteFolder.setParentId(mLocalFolder.getParentId());
         remoteFolder.setParentId(mLocalFolder.getParentId());
         remoteFolder.setFileId(mLocalFolder.getFileId());
         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 ");
                 + " changed - starting update of local data ");
-        
+
         List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
         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
         // 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());
         Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
         for (OCFile file : localFiles) {
         for (OCFile file : localFiles) {
             localFilesMap.put(file.getRemotePath(), file);
             localFilesMap.put(file.getRemotePath(), file);
         }
         }
-        
-        // loop to update every child
+
+        // loop to synchronize every child
         OCFile remoteFile = null, localFile = null;
         OCFile remoteFile = null, localFile = null;
         for (int i=1; i<folderAndFiles.size(); i++) {
         for (int i=1; i<folderAndFiles.size(); i++) {
             /// new OCFile instance with the data from the server
             /// new OCFile instance with the data from the server
             remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
             remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
             remoteFile.setParentId(mLocalFolder.getFileId());
             remoteFile.setParentId(mLocalFolder.getFileId());
 
 
-            /// retrieve local data for the read file 
+            /// retrieve local data for the read file
             //  localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
             //  localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
             localFile = localFilesMap.remove(remoteFile.getRemotePath());
             localFile = localFilesMap.remove(remoteFile.getRemotePath());
-            
+
             /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server)
             /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server)
             remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
             remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
             if (localFile != null) {
             if (localFile != null) {
@@ -367,11 +319,11 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                         localFile.getModificationTimestampAtLastSyncForData()
                         localFile.getModificationTimestampAtLastSyncForData()
                 );
                 );
                 remoteFile.setStoragePath(localFile.getStoragePath());
                 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)
                 //  (Synchronize[File|Folder]Operation with remoteFile as parameter)
-                remoteFile.setEtag(localFile.getEtag());    
+                remoteFile.setEtag(localFile.getEtag());
                 if (remoteFile.isFolder()) {
                 if (remoteFile.isFolder()) {
-                    remoteFile.setFileLength(localFile.getFileLength()); 
+                    remoteFile.setFileLength(localFile.getFileLength());
                         // TODO move operations about size of folders to FileContentProvider
                         // TODO move operations about size of folders to FileContentProvider
                 } else if (mRemoteFolderChanged && remoteFile.isImage() &&
                 } else if (mRemoteFolderChanged && remoteFile.isImage() &&
                         remoteFile.getModificationTimestamp() != localFile.getModificationTimestamp()) {
                         remoteFile.getModificationTimestamp() != localFile.getModificationTimestamp()) {
@@ -381,81 +333,142 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                 remoteFile.setPublicLink(localFile.getPublicLink());
                 remoteFile.setPublicLink(localFile.getPublicLink());
                 remoteFile.setShareByLink(localFile.isShareByLink());
                 remoteFile.setShareByLink(localFile.isShareByLink());
             } else {
             } 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)
                 //  (Synchronize[File|Folder]Operation with remoteFile as parameter)
-                remoteFile.setEtag(""); 
+                remoteFile.setEtag("");
             }
             }
 
 
             /// check and fix, if needed, local storage path
             /// 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);
             updatedFiles.add(remoteFile);
         }
         }
 
 
         // save updated contents in local database
         // 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
      * 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.
      * 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.
      * on.
-     * 
+     *
      * @param filesToSyncContents       Synchronization operations to execute.
      * @param filesToSyncContents       Synchronization operations to execute.
      * @param client                    Interface to the remote ownCloud server.
      * @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;
         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.isSuccess()) {
                 if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
                 if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
                     mConflictsFound++;
                     mConflictsFound++;
                 } else {
                 } else {
-                    mFailsInFavouritesFound++;
+                    mFailsInFileSyncsFound++;
                     if (contentsResult.getException() != null) {
                     if (contentsResult.getException() != null) {
-                        Log_OC.e(TAG, "Error while synchronizing favourites : " 
+                        Log_OC.e(TAG, "Error while synchronizing file : "
                                 +  contentsResult.getLogMessage(), contentsResult.getException());
                                 +  contentsResult.getLogMessage(), contentsResult.getException());
                     } else {
                     } else {
-                        Log_OC.e(TAG, "Error while synchronizing favourites : " 
+                        Log_OC.e(TAG, "Error while synchronizing file : "
                                 + contentsResult.getLogMessage());
                                 + contentsResult.getLogMessage());
                     }
                     }
                 }
                 }
+                // TODO - use the errors count in notifications
             }   // won't let these fails break the synchronization process
             }   // 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).
      * @param remote    remote file read from the server (remote file or folder).
      * @return          New OCFile instance representing the remote resource described by we.
      * @return          New OCFile instance representing the remote resource described by we.
      */
      */
@@ -470,100 +483,11 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         file.setRemoteId(remote.getRemoteId());
         file.setRemoteId(remote.getRemoteId());
         return file;
         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
      * 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.
      * parameter.
      *  
      *  
      * @param file      File to associate a possible 'lost' local file.
      * @param file      File to associate a possible 'lost' local file.
@@ -580,31 +504,29 @@ public class SynchronizeFolderOperation extends RemoteOperation {
 
 
     
     
     /**
     /**
-     * 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;
+    }
 }
 }

+ 23 - 2
src/com/owncloud/android/providers/FileContentProvider.java

@@ -97,6 +97,8 @@ public class FileContentProvider extends ContentProvider {
                 ProviderTableMeta.FILE_REMOTE_ID);
                 ProviderTableMeta.FILE_REMOTE_ID);
         mFileProjectionMap.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL,
         mFileProjectionMap.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL,
                 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;
     private static final int SINGLE_FILE = 1;
@@ -624,7 +626,8 @@ public class FileContentProvider extends ContentProvider {
                     + ProviderTableMeta.FILE_PUBLIC_LINK  + " TEXT, "
                     + ProviderTableMeta.FILE_PUBLIC_LINK  + " TEXT, "
                     + ProviderTableMeta.FILE_PERMISSIONS  + " TEXT null,"
                     + ProviderTableMeta.FILE_PERMISSIONS  + " TEXT null,"
                     + ProviderTableMeta.FILE_REMOTE_ID  + " 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
             // Create table ocshares
@@ -795,7 +798,25 @@ public class FileContentProvider extends ContentProvider {
                 }
                 }
             }
             }
             if (!upgraded)
             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);
                         ", newVersion == " + newVersion);
         }
         }
     }
     }

+ 341 - 240
src/com/owncloud/android/services/OperationsService.java

@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentMap;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 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.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
@@ -48,6 +49,7 @@ import com.owncloud.android.operations.OAuth2GetAccessToken;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 
 
 import android.accounts.Account;
 import android.accounts.Account;
@@ -81,7 +83,8 @@ public class OperationsService extends Service {
     public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
     public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
     public static final String EXTRA_RESULT = "RESULT";
     public static final String EXTRA_RESULT = "RESULT";
     public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH";
     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
     // TODO review if ALL OF THEM are necessary
     public static final String EXTRA_SUCCESS_IF_ABSENT = "SUCCESS_IF_ABSENT";
     public static final String EXTRA_SUCCESS_IF_ABSENT = "SUCCESS_IF_ABSENT";
     public static final String EXTRA_USERNAME = "USERNAME";
     public static final String EXTRA_USERNAME = "USERNAME";
@@ -99,13 +102,13 @@ public class OperationsService extends Service {
     public static final String ACTION_REMOVE = "REMOVE";
     public static final String ACTION_REMOVE = "REMOVE";
     public static final String ACTION_CREATE_FOLDER = "CREATE_FOLDER";
     public static final String ACTION_CREATE_FOLDER = "CREATE_FOLDER";
     public static final String ACTION_SYNC_FILE = "SYNC_FILE";
     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_MOVE_FILE = "MOVE_FILE";
     
     
     public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
     public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
     public static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED";
     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>> 
     private ConcurrentMap<Integer, Pair<RemoteOperation, RemoteOperationResult>> 
         mUndispatchedFinishedOperations =
         mUndispatchedFinishedOperations =
@@ -130,14 +133,10 @@ public class OperationsService extends Service {
         }
         }
     }
     }
 
 
-    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
      * Service initialization
@@ -145,11 +144,16 @@ public class OperationsService extends Service {
     @Override
     @Override
     public void onCreate() {
     public void onCreate() {
         super.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();
         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);
     }
     }
 
 
     
     
@@ -158,17 +162,43 @@ public class OperationsService extends Service {
      * 
      * 
      * New operations are added calling to startService(), resulting in a call to this method. 
      * 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.
      * 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
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
     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;
         return START_NOT_STICKY;
     }
     }
 
 
@@ -191,14 +221,13 @@ public class OperationsService extends Service {
             e.printStackTrace();
             e.printStackTrace();
         }
         }
         
         
-        //Log_OC.wtf(TAG, "Clear mUndispatchedFinisiedOperations" );
+        //Log_OC.wtf(TAG, "Clear mUndispatchedFinishedOperations" );
         mUndispatchedFinishedOperations.clear();
         mUndispatchedFinishedOperations.clear();
         
         
         //Log_OC.wtf(TAG, "onDestroy end" );
         //Log_OC.wtf(TAG, "onDestroy end" );
         super.onDestroy();
         super.onDestroy();
     }
     }
 
 
-
     /**
     /**
      * Provides a binder object that clients can use to perform actions on the queue of operations, 
      * Provides a binder object that clients can use to perform actions on the queue of operations, 
      * except the addition of new operations. 
      * except the addition of new operations. 
@@ -206,7 +235,7 @@ public class OperationsService extends Service {
     @Override
     @Override
     public IBinder onBind(Intent intent) {
     public IBinder onBind(Intent intent) {
         //Log_OC.wtf(TAG, "onBind" );
         //Log_OC.wtf(TAG, "onBind" );
-        return mBinder;
+        return mOperationsBinder;
     }
     }
 
 
     
     
@@ -215,11 +244,11 @@ public class OperationsService extends Service {
      */
      */
     @Override
     @Override
     public boolean onUnbind(Intent intent) {
     public boolean onUnbind(Intent intent) {
-        ((OperationsServiceBinder)mBinder).clearListeners();
+        ((OperationsServiceBinder)mOperationsBinder).clearListeners();
         return false;   // not accepting rebinding (default behaviour)
         return false;   // not accepting rebinding (default behaviour)
     }
     }
 
 
-    
+
     /**
     /**
      *  Binder to let client components to perform actions on the queue of operations.
      *  Binder to let client components to perform actions on the queue of operations.
      * 
      * 
@@ -233,16 +262,28 @@ public class OperationsService extends Service {
         private ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners = 
         private ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners = 
                 new ConcurrentHashMap<OnRemoteOperationListener, Handler>();
                 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() {
         public void clearListeners() {
             
             
             mBoundListeners.clear();
             mBoundListeners.clear();
@@ -280,131 +321,31 @@ public class OperationsService extends Service {
          * @return  'True' when an operation that enforces the user to wait for completion is in process.
          * @return  'True' when an operation that enforces the user to wait for completion is in process.
          */
          */
         public boolean isPerformingBlockingOperation() {
         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.
          * @param operationIntent       Intent describing a new operation to queue and execute.
          * @return                      Identifier of the operation created, or null if failed.
          * @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));
                 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 {
             } else {
-                //Log_OC.wtf(TAG, "New operation failed, returned Long.MAX_VALUE");
                 return Long.MAX_VALUE;
                 return Long.MAX_VALUE;
             }
             }
         }
         }
-
+        
+        
         public boolean dispatchResultIfFinished(int operationId, OnRemoteOperationListener listener) {
         public boolean dispatchResultIfFinished(int operationId, OnRemoteOperationListener listener) {
             Pair<RemoteOperation, RemoteOperationResult> undispatched = 
             Pair<RemoteOperation, RemoteOperationResult> undispatched = 
                     mUndispatchedFinishedOperations.remove(operationId);
                     mUndispatchedFinishedOperations.remove(operationId);
@@ -413,7 +354,7 @@ public class OperationsService extends Service {
                 return true;
                 return true;
                 //Log_OC.wtf(TAG, "Sending callback later");
                 //Log_OC.wtf(TAG, "Sending callback later");
             } else {
             } else {
-                if (!mPendingOperations.isEmpty()) {
+                if (!mServiceHandler.mPendingOperations.isEmpty()) {
                     return true;
                     return true;
                 } else {
                 } else {
                     return false;
                     return false;
@@ -421,18 +362,46 @@ public class OperationsService extends Service {
                 //Log_OC.wtf(TAG, "Not finished yet");
                 //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. 
      * 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()}. 
      * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}. 
      */
      */
     private static class ServiceHandler extends Handler {
     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
         // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
+        
+        
         OperationsService mService;
         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) {
         public ServiceHandler(Looper looper, OperationsService service) {
             super(looper);
             super(looper);
             if (service == null) {
             if (service == null) {
@@ -443,107 +412,241 @@ public class OperationsService extends Service {
 
 
         @Override
         @Override
         public void handleMessage(Message msg) {
         public void handleMessage(Message msg) {
-            mService.nextOperation();
+            nextOperation();
             mService.stopSelf(msg.arg1);
             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.
      * Sends a broadcast when a new operation is added to the queue.
@@ -593,18 +696,18 @@ public class OperationsService extends Service {
     
     
     /**
     /**
      * Notifies the currently subscribed listeners about the end of an operation.
      * Notifies the currently subscribed listeners about the end of an operation.
-     * 
+     *
      * @param target            Account or URL pointing to an OC server.
      * @param target            Account or URL pointing to an OC server.
      * @param operation         Finished operation.
      * @param operation         Finished operation.
      * @param result            Result of the operation.
      * @param result            Result of the operation.
      */
      */
-    private void dispatchResultToOperationListeners(
+    protected void dispatchResultToOperationListeners(
             Target target, final RemoteOperation operation, final RemoteOperationResult result) {
             Target target, final RemoteOperation operation, final RemoteOperationResult result) {
         int count = 0;
         int count = 0;
-        Iterator<OnRemoteOperationListener> listeners = mBinder.mBoundListeners.keySet().iterator();
+        Iterator<OnRemoteOperationListener> listeners = mOperationsBinder.mBoundListeners.keySet().iterator();
         while (listeners.hasNext()) {
         while (listeners.hasNext()) {
             final OnRemoteOperationListener listener = listeners.next();
             final OnRemoteOperationListener listener = listeners.next();
-            final Handler handler = mBinder.mBoundListeners.get(listener);
+            final Handler handler = mOperationsBinder.mBoundListeners.get(listener);
             if (handler != null) { 
             if (handler != null) { 
                 handler.post(new Runnable() {
                 handler.post(new Runnable() {
                     @Override
                     @Override
@@ -623,6 +726,4 @@ public class OperationsService extends Service {
         }
         }
         Log_OC.d(TAG, "Called " + count + " listeners");
         Log_OC.d(TAG, "Called " + count + " listeners");
     }
     }
-    
-
 }
 }

+ 213 - 0
src/com/owncloud/android/services/SyncFolderHandler.java

@@ -0,0 +1,213 @@
+/* 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);
+    }
+
+
+}

+ 2 - 2
src/com/owncloud/android/syncadapter/FileSyncAdapter.java

@@ -31,7 +31,7 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 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.operations.UpdateOCVersionOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -260,7 +260,7 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
         }
         }
         */
         */
         // folder synchronization
         // folder synchronization
-        SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation(  folder, 
+        RefreshFolderOperation synchFolderOp = new RefreshFolderOperation(  folder,
                                                                                     mCurrentSyncTime, 
                                                                                     mCurrentSyncTime, 
                                                                                     true,
                                                                                     true,
                                                                                     mIsShareSupported,
                                                                                     mIsShareSupported,

+ 9 - 6
src/com/owncloud/android/ui/activity/ComponentsGetter.java

@@ -22,28 +22,31 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.files.FileOperationsHelper;
 import com.owncloud.android.files.FileOperationsHelper;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 
 
 public interface ComponentsGetter {
 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();
     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();
     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 FileDataStorageManager getStorageManager();
     
     
     public FileOperationsHelper getFileOperationsHelper();
     public FileOperationsHelper getFileOperationsHelper();
 
 
+
 }
 }

+ 13 - 2
src/com/owncloud/android/ui/activity/FileActivity.java

@@ -54,6 +54,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CreateShareOperation;
 import com.owncloud.android.operations.CreateShareOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 
 
 import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.services.OperationsService;
@@ -464,7 +465,10 @@ implements OnRemoteOperationListener, ComponentsGetter {
         } else if (operation instanceof UnshareLinkOperation) {
         } else if (operation instanceof UnshareLinkOperation) {
             onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result);
             onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result);
         
         
-        } 
+        } else if (operation instanceof SynchronizeFolderOperation) {
+            onSynchronizeFolderOperationFinish((SynchronizeFolderOperation)operation, result);
+
+        }
     }
     }
 
 
     protected void requestCredentialsUpdate() {
     protected void requestCredentialsUpdate() {
@@ -506,7 +510,14 @@ implements OnRemoteOperationListener, ComponentsGetter {
             t.show();
             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(){
     protected void updateFileFromDB(){
         OCFile file = getFile();
         OCFile file = getFile();

+ 36 - 13
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -92,7 +92,7 @@ import com.owncloud.android.operations.MoveFileOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 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.operations.UnshareLinkOperation;
 import com.owncloud.android.services.observer.FileObserverService;
 import com.owncloud.android.services.observer.FileObserverService;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
@@ -810,8 +810,8 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
         IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
         IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
         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();
         mSyncBroadcastReceiver = new SyncBroadcastReceiver();
         registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
         registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
         //LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
         //LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
@@ -1099,9 +1099,9 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
                             setFile(currentFile);
                             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) &&
                                     equals(event) &&
                                 /// TODO refactor and make common
                                 /// TODO refactor and make common
                                 synchResult != null && !synchResult.isSuccess() &&  
                                 synchResult != null && !synchResult.isSuccess() &&  
@@ -1255,26 +1255,36 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
 
 
 
 
     /**
     /**
-     * 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
      * Updates the UI when a download is started or finished, provided that it is relevant for the
      * current folder.
      * current folder.
      */
      */
     private class DownloadFinishReceiver extends BroadcastReceiver {
     private class DownloadFinishReceiver extends BroadcastReceiver {
+
+        //int refreshCounter = 0;
         @Override
         @Override
         public void onReceive(Context context, Intent intent) {
         public void onReceive(Context context, Intent intent) {
             try {
             try {
                 boolean sameAccount = isSameAccount(context, intent);
                 boolean sameAccount = isSameAccount(context, intent);
                 String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
                 String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
                 boolean isDescendant = isDescendant(downloadedRemotePath);
                 boolean isDescendant = isDescendant(downloadedRemotePath);
-    
+
                 if (sameAccount && isDescendant) {
                 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) {
                 if (mWaitingToSend != null) {
-                    mWaitingToSend = getStorageManager().getFileByPath(mWaitingToSend.getRemotePath()); // Update the file to send
+                    mWaitingToSend = getStorageManager().getFileByPath(mWaitingToSend.getRemotePath());
                     if (mWaitingToSend.isDown()) { 
                     if (mWaitingToSend.isDown()) { 
                         sendDownloadedFile();
                         sendDownloadedFile();
                     }
                     }
@@ -1289,7 +1299,19 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
 
 
         private boolean isDescendant(String downloadedRemotePath) {
         private boolean isDescendant(String downloadedRemotePath) {
             OCFile currentDir = getCurrentDir();
             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 boolean isSameAccount(Context context, Intent intent) {
@@ -1725,6 +1747,7 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
 
 
     private void requestForDownload() {
     private void requestForDownload() {
         Account account = getAccount();
         Account account = getAccount();
+        //if (!mWaitingToPreview.isDownloading()) {
         if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
         if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
             Intent i = new Intent(this, FileDownloader.class);
             Intent i = new Intent(this, FileDownloader.class);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
@@ -1753,7 +1776,7 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
         mSyncInProgress = true;
         mSyncInProgress = true;
                 
                 
         // perform folder synchronization
         // perform folder synchronization
-        RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,  
+        RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
                                                                         currentSyncTime, 
                                                                         currentSyncTime, 
                                                                         false,
                                                                         false,
                                                                         getFileOperationsHelper().isSharedSupported(),
                                                                         getFileOperationsHelper().isSharedSupported(),
@@ -1782,7 +1805,7 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
     
     
     private void requestForDownload(OCFile file) {
     private void requestForDownload(OCFile file) {
         Account account = getAccount();
         Account account = getAccount();
-        if (!mDownloaderBinder.isDownloading(account, file)) {
+        if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
             Intent i = new Intent(this, FileDownloader.class);
             Intent i = new Intent(this, FileDownloader.class);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
             i.putExtra(FileDownloader.EXTRA_FILE, file);
             i.putExtra(FileDownloader.EXTRA_FILE, file);

+ 6 - 6
src/com/owncloud/android/ui/activity/FolderPickerActivity.java

@@ -55,7 +55,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CreateFolderOperation;
 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.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
@@ -208,7 +208,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         mSyncInProgress = true;
         mSyncInProgress = true;
                 
                 
         // perform folder synchronization
         // perform folder synchronization
-        RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,  
+        RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
                                                                         currentSyncTime, 
                                                                         currentSyncTime, 
                                                                         false,
                                                                         false,
                                                                         getFileOperationsHelper().isSharedSupported(),
                                                                         getFileOperationsHelper().isSharedSupported(),
@@ -236,8 +236,8 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
         IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
         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();
         mSyncBroadcastReceiver = new SyncBroadcastReceiver();
         registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
         registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
         
         
@@ -478,9 +478,9 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
                         }
                         }
                         
                         
                         mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && 
                         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) &&
                                     equals(event) &&
                                 /// TODO refactor and make common
                                 /// TODO refactor and make common
                                 synchResult != null && !synchResult.isSuccess() &&  
                                 synchResult != null && !synchResult.isSuccess() &&  

+ 55 - 1
src/com/owncloud/android/ui/activity/Preferences.java

@@ -79,8 +79,13 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
     private String mAccountName;
     private String mAccountName;
     private boolean mShowContextMenu = false;
     private boolean mShowContextMenu = false;
     private String mUploadPath;
     private String mUploadPath;
+    private PreferenceCategory mPrefInstantUploadCategory;
+    private Preference mPrefInstantUpload;
     private Preference mPrefInstantUploadPath;
     private Preference mPrefInstantUploadPath;
+    private Preference mPrefInstantUploadPathWiFi;
+    private Preference mPrefInstantVideoUpload;
     private Preference mPrefInstantVideoUploadPath;
     private Preference mPrefInstantVideoUploadPath;
+    private Preference mPrefInstantVideoUploadPathWiFi;
     private String mUploadVideoPath;
     private String mUploadVideoPath;
 
 
 
 
@@ -275,7 +280,23 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
                     }
                     }
                 });
                 });
         }
         }
-
+        
+        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");
         mPrefInstantVideoUploadPath =  findPreference("instant_video_upload_path");
         if (mPrefInstantVideoUploadPath != null){
         if (mPrefInstantVideoUploadPath != null){
 
 
@@ -292,6 +313,19 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
                     }
                     }
                 });
                 });
         }
         }
+        
+        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 */
         /* About App */
        pAboutApp = (Preference) findPreference("about_app");
        pAboutApp = (Preference) findPreference("about_app");
@@ -304,6 +338,26 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
        loadInstantUploadVideoPath();
        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
     @Override
     protected void onPause() {
     protected void onPause() {

+ 11 - 106
src/com/owncloud/android/ui/adapter/FileListListAdapter.java

@@ -1,6 +1,6 @@
 /* ownCloud Android client application
 /* ownCloud Android client application
  *   Copyright (C) 2011  Bartek Przybylski
  *   Copyright (C) 2011  Bartek Przybylski
- *   Copyright (C) 2012-2014 ownCloud Inc.
+ *   Copyright (C) 2012-2015 ownCloud Inc.
  *
  *
  *   This program is free software: you can redistribute it and/or modify
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
  *   it under the terms of the GNU General Public License version 2,
@@ -19,25 +19,18 @@ package com.owncloud.android.ui.adapter;
 
 
 
 
 import java.io.File;
 import java.io.File;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.Vector;
 import java.util.Vector;
 
 
-import third_parties.daveKoeller.AlphanumComparator;
 import android.accounts.Account;
 import android.accounts.Account;
 import android.content.Context;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.ThumbnailUtils;
 import android.preference.PreferenceManager;
 import android.preference.PreferenceManager;
 import android.text.format.DateUtils;
 import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.BaseAdapter;
-import android.widget.Filter;
-import android.widget.Filterable;
 import android.widget.GridView;
 import android.widget.GridView;
 import android.widget.ImageView;
 import android.widget.ImageView;
 import android.widget.ListAdapter;
 import android.widget.ListAdapter;
@@ -50,6 +43,7 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.FileStorageUtils;
@@ -75,17 +69,14 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
     private FileDataStorageManager mStorageManager;
     private FileDataStorageManager mStorageManager;
     private Account mAccount;
     private Account mAccount;
     private ComponentsGetter mTransferServiceGetter;
     private ComponentsGetter mTransferServiceGetter;
+
     private enum ViewType {LIST_ITEM, GRID_IMAGE, GRID_ITEM };
     private enum ViewType {LIST_ITEM, GRID_IMAGE, GRID_ITEM };
-    private Integer mSortOrder;
-    public static final Integer SORT_NAME = 0;
-    public static final Integer SORT_DATE = 1;
-    public static final Integer SORT_SIZE = 2;
-    private Boolean mSortAscending;
+
     private SharedPreferences mAppPreferences;
     private SharedPreferences mAppPreferences;
     
     
     public FileListListAdapter(
     public FileListListAdapter(
             boolean justFolders, 
             boolean justFolders, 
-            Context context, 
+            Context context,
             ComponentsGetter transferServiceGetter
             ComponentsGetter transferServiceGetter
             ) {
             ) {
         
         
@@ -93,7 +84,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
         mContext = context;
         mContext = context;
         mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
         mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
         mTransferServiceGetter = transferServiceGetter;
         mTransferServiceGetter = transferServiceGetter;
-        
+
         mAppPreferences = PreferenceManager
         mAppPreferences = PreferenceManager
                 .getDefaultSharedPreferences(mContext);
                 .getDefaultSharedPreferences(mContext);
         
         
@@ -182,6 +173,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
         if (file != null){
         if (file != null){
 
 
             ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);
             ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);
+            fileIcon.setTag(file.getFileId());
             TextView fileName;
             TextView fileName;
             String name;
             String name;
 
 
@@ -237,7 +229,10 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                     localStateView.bringToFront();
                     localStateView.bringToFront();
                     FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();
                     FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();
                     FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();
                     FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();
-                    if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {
+                    boolean downloading = (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file));
+                    OperationsServiceBinder opsBinder = mTransferServiceGetter.getOperationsServiceBinder();
+                    downloading |= (opsBinder != null && opsBinder.isSynchronizing(mAccount, file.getRemotePath()));
+                    if (downloading) {
                         localStateView.setImageResource(R.drawable.downloading_file_indicator);
                         localStateView.setImageResource(R.drawable.downloading_file_indicator);
                         localStateView.setVisibility(View.VISIBLE);
                         localStateView.setVisibility(View.VISIBLE);
                     } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {
                     } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {
@@ -442,96 +437,6 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                 && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));
                 && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));
     }
     }
 
 
-    /**
-     * Sorts list by Date
-     * @param sortAscending true: ascending, false: descending
-     */
-    private void sortByDate(boolean sortAscending){
-        final Integer val;
-        if (sortAscending){
-            val = 1;
-        } else {
-            val = -1;
-        }
-        
-        Collections.sort(mFiles, new Comparator<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());
-                }
-            }
-        });
-    }
-
-    /**
-     * Sorts list by Size
-     * @param sortAscending true: ascending, false: descending
-     */
-    private void sortBySize(boolean sortAscending){
-        final Integer val;
-        if (sortAscending){
-            val = 1;
-        } else {
-            val = -1;
-        }
-        
-        Collections.sort(mFiles, new Comparator<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());
-                }
-            }
-        });
-    }
-
-    /**
-     * Sorts list by Name
-     * @param sortAscending true: ascending, false: descending
-     */
-    private void sortByName(boolean sortAscending){
-        final Integer val;
-        if (sortAscending){
-            val = 1;
-        } else {
-            val = -1;
-        }
-
-        Collections.sort(mFiles, new Comparator<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);
-            }
-        });
-    }
-
     public void setSortOrder(Integer order, boolean ascending) {
     public void setSortOrder(Integer order, boolean ascending) {
         SharedPreferences.Editor editor = mAppPreferences.edit();
         SharedPreferences.Editor editor = mAppPreferences.edit();
         editor.putInt("sortOrder", order);
         editor.putInt("sortOrder", order);

+ 5 - 1
src/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -348,7 +348,10 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
             // configure UI for depending upon local state of the file
             // configure UI for depending upon local state of the file
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
             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();
                 setButtonsForTransferring();
                 
                 
             } else if (file.isDown()) {
             } else if (file.isDown()) {
@@ -449,6 +452,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
             progressText.setVisibility(View.VISIBLE);
             progressText.setVisibility(View.VISIBLE);
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
             FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
+            //if (getFile().isDownloading()) {
             if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) {
             if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) {
                 progressText.setText(R.string.downloader_download_in_progress_ticker);
                 progressText.setText(R.string.downloader_download_in_progress_ticker);
             } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile())) {
             } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile())) {

+ 1 - 1
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -133,7 +133,7 @@ public class OCFileListFragment extends ExtendedListFragment {
         boolean justFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false); 
         boolean justFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false); 
         mAdapter = new FileListListAdapter(
         mAdapter = new FileListListAdapter(
                 justFolders,
                 justFolders,
-                getSherlockActivity(), 
+                getSherlockActivity(),
                 mContainerActivity
                 mContainerActivity
                 );
                 );
         setListAdapter(mAdapter);
         setListAdapter(mAdapter);

+ 4 - 3
src/com/owncloud/android/ui/preview/FileDownloadFragment.java

@@ -211,10 +211,11 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene
      * @param   transferring    When true, the view must be updated assuming that the holded file is 
      * @param   transferring    When true, the view must be updated assuming that the holded file is 
      *                          downloading, no matter what the downloaderBinder says.
      *                          downloading, no matter what the downloaderBinder says.
      */
      */
+    /*
     public void updateView(boolean transferring) {
     public void updateView(boolean transferring) {
         // configure UI for depending upon local state of the file
         // 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();
             setButtonsForTransferring();
             
             
         } else if (getFile().isDown()) {
         } else if (getFile().isDown()) {
@@ -227,7 +228,7 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene
         getView().invalidate();
         getView().invalidate();
         
         
     }
     }
-
+    */
 
 
     /**
     /**
      * Enables or disables buttons for a file being downloaded
      * Enables or disables buttons for a file being downloaded

+ 1 - 1
src/com/owncloud/android/ui/preview/PreviewImageActivity.java

@@ -426,7 +426,7 @@ ViewPager.OnPageChangeListener, OnRemoteOperationListener {
     
     
 
 
     /**
     /**
-     * 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
      * Updates the UI when a download is started or finished, provided that it is relevant for the
      * folder displayed in the gallery.
      * folder displayed in the gallery.

+ 16 - 0
src/com/owncloud/android/utils/ErrorMessageAdapter.java

@@ -36,6 +36,7 @@ import com.owncloud.android.operations.MoveFileOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.operations.UploadFileOperation;
 
 
@@ -206,6 +207,21 @@ public class ErrorMessageAdapter {
                 // Show a Message, operation finished without success
                 // Show a Message, operation finished without success
                 message = res.getString(R.string.move_file_error);
                 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;
         return message;

+ 20 - 4
src/com/owncloud/android/utils/FileStorageUtils.java

@@ -36,6 +36,7 @@ import android.preference.PreferenceManager;
 import android.net.Uri;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.Environment;
 import android.os.StatFs;
 import android.os.StatFs;
+import android.webkit.MimeTypeMap;
 
 
 
 
 /**
 /**
@@ -135,7 +136,7 @@ public class FileStorageUtils {
     /**
     /**
      * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}.
      * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}.
      * 
      * 
-     * @param oCFile    OCFile 
+     * @param ocFile    OCFile
      * @return          New RemoteFile instance representing the resource described by ocFile.
      * @return          New RemoteFile instance representing the resource described by ocFile.
      */
      */
     public static RemoteFile fillRemoteFile(OCFile ocFile){
     public static RemoteFile fillRemoteFile(OCFile ocFile){
@@ -171,7 +172,7 @@ public class FileStorageUtils {
     
     
     /**
     /**
      * Sorts list by Date
      * Sorts list by Date
-     * @param sortAscending true: ascending, false: descending
+     * @param files
      */
      */
     public static Vector<OCFile> sortByDate(Vector<OCFile> files){
     public static Vector<OCFile> sortByDate(Vector<OCFile> files){
         final Integer val;
         final Integer val;
@@ -239,7 +240,7 @@ public class FileStorageUtils {
 
 
     /**
     /**
      * Sorts list by Name
      * Sorts list by Name
-     * @param sortAscending true: ascending, false: descending
+     * @param files     files to sort
      */
      */
     public static Vector<OCFile> sortByName(Vector<OCFile> files){
     public static Vector<OCFile> sortByName(Vector<OCFile> files){
         final Integer val;
         final Integer val;
@@ -284,6 +285,21 @@ public class FileStorageUtils {
             return result;
             return result;
         }
         }
         return 0;
         return 0;
-    } 
+    }
+
+    /**
+     * Mimetype String of a file
+     * @param path
+     * @return
+     */
+    public static String getMimeTypeFromName(String path) {
+        String extension = "";
+        int pos = path.lastIndexOf('.');
+        if (pos >= 0) {
+            extension = path.substring(pos + 1);
+        }
+        String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
+        return (result != null) ? result : "";
+    }
   
   
 }
 }

+ 173 - 0
user_manual/Makefile

@@ -0,0 +1,173 @@
+# 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."

+ 115 - 0
user_manual/android_app.rst

@@ -0,0 +1,115 @@
+==============================
+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

+ 293 - 0
user_manual/conf.py

@@ -0,0 +1,293 @@
+# -*- 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

BIN
user_manual/images/android-downloads.png


BIN
user_manual/images/android-file-list.png


BIN
user_manual/images/android-file-options.png


BIN
user_manual/images/android-file.png


BIN
user_manual/images/android-files-page.png


BIN
user_manual/images/android-first-screen.jpg


BIN
user_manual/images/android-help.png


BIN
user_manual/images/android-new-account.png


BIN
user_manual/images/android-settings.png


BIN
user_manual/images/android-ssl-cert.png


BIN
user_manual/images/android-upload.png


+ 9 - 0
user_manual/index.rst

@@ -0,0 +1,9 @@
+.. _contents:
+
+ownCloud Android App Manual
+==============================
+
+.. toctree::
+   :maxdepth: 2
+ 
+   android_app

+ 199 - 0
user_manual/make.bat

@@ -0,0 +1,199 @@
+@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

+ 1 - 0
user_manual/ocdoc

@@ -0,0 +1 @@
+Subproject commit 343496c792616459e8204b6614fd42a1b16a6d68