Browse Source

Merge master into oauth_login

David A. Velasco 12 năm trước cách đây
mục cha
commit
c38a3b2ef1
55 tập tin đã thay đổi với 3348 bổ sung897 xóa
  1. 5 3
      AndroidManifest.xml
  2. 4 6
      res/layout-land/account_setup.xml
  3. 68 0
      res/layout-v14/generic_explanation.xml
  4. 3 7
      res/layout/account_setup.xml
  5. 3 22
      res/layout/edit_box_dialog.xml
  6. 67 0
      res/layout/generic_explanation.xml
  7. 33 0
      res/menu/file_context_menu.xml
  8. 76 50
      res/values-de-rDE/strings.xml
  9. 74 50
      res/values-de/strings.xml
  10. 72 35
      res/values-es/strings.xml
  11. 4 0
      res/values-large/bools.xml
  12. 4 0
      res/values/bools.xml
  13. 84 50
      res/values/strings.xml
  14. 4 0
      res/values/urls.xml
  15. 5 4
      src/com/owncloud/android/Uploader.java
  16. 4 3
      src/com/owncloud/android/authenticator/AuthenticationRunnable.java
  17. 4 0
      src/com/owncloud/android/datamodel/DataStorageManager.java
  18. 147 23
      src/com/owncloud/android/datamodel/FileDataStorageManager.java
  19. 93 13
      src/com/owncloud/android/datamodel/OCFile.java
  20. 5 5
      src/com/owncloud/android/db/DbHandler.java
  21. 4 2
      src/com/owncloud/android/db/ProviderMeta.java
  22. 1 1
      src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java
  23. 46 80
      src/com/owncloud/android/files/OwnCloudFileObserver.java
  24. 59 36
      src/com/owncloud/android/files/services/FileDownloader.java
  25. 139 139
      src/com/owncloud/android/files/services/FileObserverService.java
  26. 172 56
      src/com/owncloud/android/files/services/FileUploader.java
  27. 3 2
      src/com/owncloud/android/location/LocationUpdateService.java
  28. 3 2
      src/com/owncloud/android/operations/ChunkedUploadFileOperation.java
  29. 21 5
      src/com/owncloud/android/operations/DownloadFileOperation.java
  30. 11 6
      src/com/owncloud/android/operations/RemoteOperationResult.java
  31. 18 3
      src/com/owncloud/android/operations/RemoveFileOperation.java
  32. 70 51
      src/com/owncloud/android/operations/RenameFileOperation.java
  33. 151 30
      src/com/owncloud/android/operations/SynchronizeFileOperation.java
  34. 156 37
      src/com/owncloud/android/operations/SynchronizeFolderOperation.java
  35. 1 1
      src/com/owncloud/android/operations/UpdateOCVersionOperation.java
  36. 172 12
      src/com/owncloud/android/operations/UploadFileOperation.java
  37. 51 3
      src/com/owncloud/android/providers/FileContentProvider.java
  38. 97 39
      src/com/owncloud/android/syncadapter/FileSyncAdapter.java
  39. 1 1
      src/com/owncloud/android/ui/activity/AccountSelectActivity.java
  40. 20 6
      src/com/owncloud/android/ui/activity/AuthenticatorActivity.java
  41. 18 9
      src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java
  42. 258 0
      src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java
  43. 5 2
      src/com/owncloud/android/ui/activity/FileDetailActivity.java
  44. 205 23
      src/com/owncloud/android/ui/activity/FileDisplayActivity.java
  45. 95 0
      src/com/owncloud/android/ui/activity/GenericExplanationActivity.java
  46. 131 7
      src/com/owncloud/android/ui/activity/UploadFilesActivity.java
  47. 0 1
      src/com/owncloud/android/ui/adapter/FileListListAdapter.java
  48. 5 0
      src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java
  49. 81 26
      src/com/owncloud/android/ui/dialog/EditNameDialog.java
  50. 74 0
      src/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.java
  51. 10 0
      src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java
  52. 130 42
      src/com/owncloud/android/ui/fragment/FileDetailFragment.java
  53. 320 3
      src/com/owncloud/android/ui/fragment/OCFileListFragment.java
  54. 60 0
      src/com/owncloud/android/utils/FileStorageUtils.java
  55. 1 1
      src/eu/alefzero/webdav/WebdavEntry.java

+ 5 - 3
AndroidManifest.xml

@@ -17,8 +17,8 @@
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  -->
 <manifest package="com.owncloud.android"
-    android:versionCode="103014"
-    android:versionName="1.3.14" xmlns:android="http://schemas.android.com/apk/res/android">
+    android:versionCode="103017"
+    android:versionName="1.3.17" xmlns:android="http://schemas.android.com/apk/res/android">
 
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />
@@ -141,7 +141,9 @@
         <activity android:name=".extensions.ExtensionsListActivity"></activity>
         <activity android:name=".ui.activity.AccountSelectActivity" android:uiOptions="none" android:label="@string/prefs_accounts"></activity>
         <activity android:name=".ui.activity.ConflictsResolveActivity"/>
-            
+        <activity android:name=".ui.activity.GenericExplanationActivity"/>
+        <activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"/>
+        
         <service android:name=".files.services.FileUploader" >
         </service>
         <service android:name=".files.services.InstantUploadService" />

+ 4 - 6
res/layout-land/account_setup.xml

@@ -64,7 +64,6 @@
                         android:layout_weight="1"
                         android:ems="10"
                         android:hint="@string/auth_host_url"
-                        android:singleLine="true"
                         android:inputType="textNoSuggestions">
                         <requestFocus />
                     </EditText>
@@ -159,7 +158,6 @@
                     android:layout_weight="1"
                     android:ems="10"
                     android:hint="@string/auth_username"
-                    android:singleLine="true"
                     android:inputType="textNoSuggestions" />
 
                 <FrameLayout
@@ -174,8 +172,7 @@
                         android:layout_weight="1"
                         android:ems="10"
                         android:hint="@string/auth_password"
-                        android:inputType="textPassword"
-                        android:singleLine="true" />
+                        android:inputType="textPassword"/>
 
                     <ImageView
                         android:id="@+id/viewPassword"
@@ -217,6 +214,7 @@
                 android:onClick="onOkClick"
                 android:text="@string/setup_btn_connect"
                 android:textColor="@android:color/black" />
+            
         </LinearLayout>
         
 		<Button
@@ -229,8 +227,8 @@
 			android:paddingTop="10dp"
 			android:paddingBottom="10dp"
 			android:textColor="#0000FF"
-			android:background="@android:color/transparent"
-			android:text="@string/auth_register" />
+			android:background="@android:color/transparent" />
+			<!-- android:text="@string/app_name @string/auth_register" /-->
         
     </RelativeLayout>
 

+ 68 - 0
res/layout-v14/generic_explanation.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+  ownCloud Android client application
+
+  Copyright (C) 2012  Bartek Przybylski
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  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/>.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/owncloud_white" 
+    android:id="@+id/explanation"
+    android:orientation="vertical">
+
+	<TextView
+		android:id="@+id/message"
+		android:layout_width="match_parent"
+		android:layout_height="0dp"
+		android:layout_weight="2"
+	    android:padding="10dip"
+	    android:scrollbarAlwaysDrawVerticalTrack="true"
+		android:text="@string/text_placeholder" 
+		/>
+    
+	<ListView 
+	    android:id="@+id/list"
+	    android:layout_width="match_parent"
+	    android:layout_height="0dp"
+		android:layout_weight="3"
+	    android:padding="10dip"
+	    />
+	    
+    <LinearLayout
+        android:id="@+id/buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal" >
+
+        <!-- 'OK' / 'CANCEL' BUTTONS CHANGE THEIR ORDER FROM ANDROID 4.0 ; THANKS, GOOGLE -->
+        <Button
+            android:id="@+id/cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/common_cancel" />
+
+		<Button
+		    android:id="@+id/ok"
+		    android:layout_width="wrap_content"
+		    android:layout_height="wrap_content"
+		    android:layout_weight="1"
+		    android:text="@string/common_ok" />
+		
+	</LinearLayout>
+	
+</LinearLayout>

+ 3 - 7
res/layout/account_setup.xml

@@ -59,7 +59,6 @@
                     android:layout_weight="1"
                     android:ems="10"
                     android:hint="@string/auth_host_url"
-                    android:singleLine="true"
                     android:inputType="textNoSuggestions" >
                     <requestFocus />
                 </EditText>
@@ -153,7 +152,6 @@
                 android:layout_height="0dp"
                 android:layout_weight="1"
                 android:ems="10"
-                android:singleLine="true"
                 android:hint="@string/auth_username"
                 android:inputType="textNoSuggestions" />
 
@@ -169,8 +167,7 @@
                     android:layout_weight="1"
                     android:ems="10"
                     android:hint="@string/auth_password"
-                    android:inputType="textPassword"
-                    android:singleLine="true" />
+                    android:inputType="textPassword"/>
 
                 <ImageView
                     android:id="@+id/viewPassword"
@@ -226,9 +223,8 @@
 			android:paddingTop="10dp"
 			android:paddingBottom="10dp"
 			android:textColor="#0000FF"
-			android:background="@android:color/transparent"
-			android:text="@string/auth_register" />
-            
+			android:background="@android:color/transparent" />
+			<!-- android:text="@string/app_name @string/auth_register" /-->
         
     </RelativeLayout>
 

+ 3 - 22
res/layout/edit_box_dialog.xml

@@ -26,29 +26,10 @@
         android:id="@+id/user_input"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:ems="10" >
+        android:ems="10" 
+		android:inputType="textNoSuggestions"
+		>
 
-        <requestFocus />
     </EditText>
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:gravity="center_horizontal" >
-
-        <Button
-            android:id="@+id/cancel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/common_cancel" />
-
-        <Button
-            android:id="@+id/ok"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/common_ok" />
-
-    </LinearLayout>
-
 </LinearLayout>

+ 67 - 0
res/layout/generic_explanation.xml

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+  ownCloud Android client application
+
+  Copyright (C) 2012  Bartek Przybylski
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  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/>.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/owncloud_white" 
+    android:id="@+id/explanation"
+    android:orientation="vertical">
+
+	<TextView
+		android:id="@+id/message"
+		android:layout_width="match_parent"
+		android:layout_height="0dp"
+		android:layout_weight="2"
+	    android:padding="10dip"
+	    android:scrollbarAlwaysDrawVerticalTrack="true"
+		android:text="@string/text_placeholder" 
+		/>
+    
+	<ListView 
+	    android:id="@+id/list"
+	    android:layout_width="match_parent"
+	    android:layout_height="0dp"
+		android:layout_weight="3"
+	    android:padding="10dip"
+	    />
+	    
+    <LinearLayout
+        android:id="@+id/buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal" >
+
+		<Button
+		    android:id="@+id/ok"
+		    android:layout_width="wrap_content"
+		    android:layout_height="wrap_content"
+		    android:layout_weight="1"
+		    android:text="@string/common_ok" />
+		
+        <Button
+            android:id="@+id/cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/common_cancel" />
+
+	</LinearLayout>
+	
+</LinearLayout>

+ 33 - 0
res/menu/file_context_menu.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu 	xmlns:android="http://schemas.android.com/apk/res/android">
+    
+	<item 	android:id="@+id/open_file_item" 
+	    	android:title="@string/filedetails_open" 
+	    	android:icon="@android:drawable/ic_menu_edit"
+	/>
+	
+	<item 	android:id="@+id/download_file_item" 
+	    	android:title="@string/filedetails_download" 
+	/>
+	
+	<item 	android:id="@+id/cancel_download_item" 
+	    	android:title="@string/common_cancel_download" 
+	    	android:icon="@android:drawable/ic_menu_close_clear_cancel"
+	/>
+	
+	<item 	android:id="@+id/cancel_upload_item" 
+	    	android:title="@string/common_cancel_upload" 
+	    	android:icon="@android:drawable/ic_menu_close_clear_cancel"
+	/>
+	
+	<item 	android:id="@+id/rename_file_item" 
+	    	android:title="@string/common_rename" 
+	    	android:icon="@android:drawable/ic_menu_set_as"
+	/>
+	
+    <item 	android:id="@+id/remove_file_item" 
+        	android:title="@string/common_remove" 
+        	android:icon="@android:drawable/ic_menu_delete"
+	/>
+    
+</menu>

+ 76 - 50
res/values-de-rDE/strings.xml

@@ -1,11 +1,10 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
-  <string name="hello">Hallo Welt, ownCloud-Start-Bildschirm!</string>
   <string name="app_name">ownCloud</string>
   <string name="main_password">Passwort:</string>
   <string name="main_login">Benutzername:</string>
   <string name="main_button_login">Anmelden</string>
-  <string name="main_welcome">Herzlich willkommen bei Deiner ownCloud!</string>
+  <string name="main_welcome">Willkommen</string>
   <string name="main_files">Dateien</string>
   <string name="main_music">Musik</string>
   <string name="main_contacts">Kontakte</string>
@@ -13,75 +12,79 @@
   <string name="main_bookmarks">Lesezeichen</string>
   <string name="main_settings">Einstellungen</string>
   <string name="main_tit_accsetup">Konto einrichten</string>
-  <string name="main_wrn_accsetup">Auf Deinem Gerät sind keine ownCloud Konten eingerichtet. Bitte erstelle ein Konto, um diese App zu nutzen.</string>
+  <string name="main_wrn_accsetup">Auf Ihrem Gerät sind keine Konten eingerichtet. Bitte erstellen Sie ein Konto, um diese App zu nutzen.</string>
+  <string name="about_message">%1$s Android App\n\nVersion: %2$s</string>
   <string name="actionbar_sync">Aktualisieren</string>
   <string name="actionbar_upload">Datei hochladen</string>
   <string name="actionbar_upload_from_apps">Inhalt von anderen Apps</string>
   <string name="actionbar_upload_files">Dateien</string>
-  <string name="actionbar_mkdir">Verzeichnis erstellen</string>
+  <string name="actionbar_mkdir">Ordner anlegen</string>
   <string name="actionbar_search">Suche</string>
   <string name="actionbar_settings">Einstellungen</string>
   <string name="prefs_category_general">Allgemein</string>
   <string name="prefs_category_trackmydevice">Gerät verfolgen</string>
   <string name="prefs_add_session">Neue Sitzung hinzufügen</string>
   <string name="prefs_create_img_thumbnails">Bildvorschau erstellen</string>
-  <string name="prefs_select_oc_account">Konto auswählen</string>
-  <string name="prefs_summary_select_oc_account">Bitte wähle, welches Konto von der App verwendet werden soll.</string>
+  <string name="prefs_select_oc_account">Account auswählen</string>
+  <string name="prefs_summary_select_oc_account">Bitte wählen Sie, welches Konto von der App verwendet werden soll.</string>
   <string name="prefs_trackmydevice">Gerät verfolgen</string>
-  <string name="prefs_trackmydevice_summary_off">Geräteverfolgung in ownCloud aktivieren</string>
-  <string name="prefs_trackmydevice_summary_on">Deine ownCloud verfolgt dieses Gerät</string>
+  <string name="prefs_trackmydevice_summary_off">Geräteverfolgung in aktivieren</string>
+  <string name="prefs_trackmydevice_summary_on">Ihre App verfolgt dieses Gerät</string>
   <string name="prefs_trackmydevice_interval">Aktualisierungsintervall</string>
   <string name="prefs_trackmydevice_interval_summary">Alle %1$s Minuten aktualisieren</string>
   <string name="prefs_accounts">Konten</string>
   <string name="prefs_manage_accounts">Konten verwalten</string>
-  <string name="prefs_pincode">ownCloud-App-PIN</string>
-  <string name="prefs_pincode_summary">Schütze Deinen OwnCloud-Client.</string>
-  <string name="prefs_instant_upload">Aktiviere sofortigen Upload.</string>
-  <string name="prefs_instant_upload_summary">Lade Fotos von der Kamera sofort hoch.</string>
-  <string name="auth_host_url">ownCloud-URL</string>
+  <string name="prefs_pincode">App-PIN</string>
+  <string name="prefs_pincode_summary">Schützen Sie Ihren Client</string>
+  <string name="prefs_instant_upload">Aktiviere Sie den sofortigen Upload</string>
+  <string name="prefs_instant_upload_summary">Laden Sie Fotos von der Kamera sofort hoch</string>
+  <string name="auth_host_url">URL</string>
   <string name="auth_username">Benutzername</string>
   <string name="auth_password">Passwort</string>
-  <string name="auth_register">Ich bin neu bei ownCloud</string>
+  <string name="auth_register">Ich bin neu bei %1$s</string>
   <string name="new_session_uri_error">Falsche URL angegeben</string>
   <string name="new_session_session_name_error">Falscher Sitzungsname</string>
   <string name="sync_string_files">Dateien</string>
-  <string name="uploader_no_file_selected">Du hast keine Datei zum Hochladen ausgewählt</string>
+  <string name="uploader_no_file_selected">Sie haben keine Datei zum Hochladen ausgewählt</string>
   <string name="setup_hint_username">Benutzername</string>
   <string name="setup_hint_password">Passwort</string>
   <string name="setup_hint_address">Internetadresse</string>
   <string name="setup_hint_show_password">Passwort anzeigen?</string>
-  <string name="setup_title">Mit Deiner ownCloud verbinden</string>
+  <string name="setup_title">Mit Ihrer %1$s verbinden</string>
   <string name="setup_btn_connect">Verbinden</string>
   <string name="uploader_btn_upload_text">Hochladen</string>
   <string name="uploader_wrn_no_account_title">Kein Konto gefunden</string>
-  <string name="uploader_wrn_no_account_text">Es sind keine ownCloud-Konten auf Deinem Gerät eingerichtet. Bitte richte zuerst ein Konto ein.</string>
+  <string name="uploader_wrn_no_account_text">Es sind keine %1$s-Konten auf Ihrem Gerät eingerichtet. Bitte richten Sie zuerst ein Konto ein.</string>
   <string name="uploader_wrn_no_account_setup_btn_text">Einrichten</string>
   <string name="uploader_wrn_no_account_quit_btn_text">Beenden</string>
   <string name="uploader_wrn_no_content_title">Keine Inhalte zum Hochladen vorhanden</string>
   <string name="uploader_wrn_no_content_text">Es wurden keine Inhalte empfangen. Es gibt nichts zum Hochladen.</string>
-  <string name="uploader_error_forbidden_content">ownCloud darf den geteilten Inhalt nicht nutzen.</string>
+  <string name="uploader_error_forbidden_content">%1$s darf den freigegebenen Inhalt nicht nutzen.</string>
   <string name="uploader_info_uploading">Lade hoch</string>
   <string name="uploader_btn_create_dir_text">Verzeichnis für hochzuladene Dateien erstellen</string>
+  <string name="file_list_empty">Es sind keine Dateien im Verzeichnis vorhanden.\nNeue Dateien  können mit der Menüfunktion \"Hochladen\" hinzugefügt werden.</string>
   <string name="filedetails_select_file">Klicken Sie auf eine Datei für weitere Informationen.</string>
   <string name="filedetails_size">Größe:</string>
   <string name="filedetails_type">Art:</string>
   <string name="filedetails_created">Erstellt:</string>
   <string name="filedetails_modified">Geändert:</string>
   <string name="filedetails_download">Herunterladen</string>
+  <string name="filedetails_sync_file">Aktualisieren</string>
   <string name="filedetails_redownload">Neu laden</string>
   <string name="filedetails_open">Öffnen</string>
   <string name="common_yes">Ja</string>
   <string name="common_no">Nein</string>
   <string name="common_ok">OK</string>
+  <string name="common_cancel_upload">Upload abbrechen</string>
   <string name="common_cancel">Abbrechen</string>
-  <string name="common_save_exit">Speichern &amp; schließen</string>
-  <string name="common_exit">Verlasse ownCloud</string>
+  <string name="common_save_exit">Speichern &amp; Schließen</string>
+  <string name="common_exit">%1$s verlasse</string>
   <string name="common_error">Fehler</string>
   <string name="about_title">Über</string>
-  <string name="delete_account">Konto löschen</string>
-  <string name="create_account">Konto erstellen</string>
+  <string name="delete_account">Account löschen</string>
+  <string name="create_account">Account erstellen</string>
   <string name="upload_chooser_title">Dateien hochladen von...</string>
-  <string name="uploader_info_dirname">Verzeichnisname</string>
+  <string name="uploader_info_dirname">Ordnername</string>
   <string name="uploader_upload_in_progress_ticker">Hochladen...</string>
   <string name="uploader_upload_in_progress_content">%1$d%% Hochladen %2$s</string>
   <string name="uploader_upload_succeeded_ticker">Hochladen erfolgreich</string>
@@ -101,16 +104,17 @@
   <string name="sync_fail_ticker">Synchronisation fehlgeschlagen</string>
   <string name="sync_fail_content">Bei der Synchronisation konnte %1$s nicht übertragen werden</string>
   <string name="use_ssl">Sichere Verbindung benutzen</string>
-  <string name="location_no_provider">ownCloud kann Dein Gerät nicht verfolgen. Bitte überprüfe Deine Standorteinstellungen</string>
-  <string name="pincode_enter_pin_code">Bitte gib Deine App-PIN ein</string>
-  <string name="pincode_enter_new_pin_code">Bitte gib eine neue App-PIN ein</string>
-  <string name="pincode_configure_your_pin">Bitte gib Deine ownCloud-App-PIN ein</string>
-  <string name="pincode_reenter_your_pincode">Bitte gib Deine ownCloud-App-PIN erneut ein.</string>
-  <string name="pincode_remove_your_pincode">ownCloud-App-PIN entfernen</string>
-  <string name="pincode_mismatch">Die ownCloud-App-PINs stimmen nicht überein</string>
-  <string name="pincode_wrong">Falsche ownCloud-App-PIN</string>
-  <string name="pincode_removed">Die ownCloud-App-PIN wurde entfernt</string>
-  <string name="pincode_stored">Die ownCloud-App-PIN wurde gespeichert</string>
+  <string name="location_no_provider">%1$s kann Ihr Gerät nicht verfolgen. Bitte überprüfen Sie Ihre Standorteinstellungen.</string>
+  <string name="pincode_enter_pin_code">Bitte geben Sie Ihren App-PIN ein</string>
+  <string name="pincode_enter_new_pin_code">Bitte geben Sie Ihren neue App-PIN ein</string>
+  <string name="pincode_configure_your_pin">Bitte geben Sie Ihren App-PIN ein</string>
+  <string name="pincode_configure_your_pin_explanation">PIN-Abfrage erfolgt nach Starten der App.</string>
+  <string name="pincode_reenter_your_pincode">Bitte geben Sie Ihren App-PIN erneut ein.</string>
+  <string name="pincode_remove_your_pincode">App-PIN entfernen</string>
+  <string name="pincode_mismatch">Die App-PINs stimmen nicht überein</string>
+  <string name="pincode_wrong">Falsche App-PIN</string>
+  <string name="pincode_removed">Die App-PIN wurde entfernt</string>
+  <string name="pincode_stored">Die App-PIN wurde gespeichert</string>
   <string-array name="prefs_trackmydevice_intervall_keys">
     <item>15 Minuten</item>
     <item>30 Minuten</item>
@@ -123,38 +127,38 @@
   </string-array>
   <string name="auth_trying_to_login">Anmeldungsversuch...</string>
   <string name="auth_no_net_conn_title">Keine Netzwerkverbindung</string>
-  <string name="auth_no_net_conn_message">Es konnte keine Netzwerkverbindung gefunden werden, bitte überprüfe Deine Internetverbindung.</string>
+  <string name="auth_no_net_conn_message">Es konnte keine Netzwerkverbindung gefunden werden, bitte überprüfen Sie Ihre Internetverbindung.</string>
   <string name="auth_connect_anyway">Trotzdem verbinden</string>
   <string name="auth_nossl_plain_ok_title">Sichere Verbindung nicht verfügbar.</string>
-  <string name="auth_nossl_plain_ok_message">Die App konnte keine sichere Verbindung zum Server herstellen. Eine nicht sichere Verbindung ist nichtsdestotrotz verfügbar. Möchtest Du fortfahren oder abbrechen?</string>
+  <string name="auth_nossl_plain_ok_message">Die App konnte keine sichere Verbindung zum Server herstellen. Eine unsichere Verbindung ist verfügbar. Möchten Sie fortfahren oder abbrechen?</string>
   <string name="auth_connection_established">Verbindung hergestellt</string>
   <string name="auth_testing_connection">Verbindung testen...</string>
-  <string name="auth_not_configured_title">Falsch konfigurierte ownCloud</string>
-  <string name="auth_not_configured_message">Es scheint, als wäre Deine ownCloud-Installation nicht richtig konfiguriert. Bitte kontaktiere Deinen Administrator, um weitere Details zu erhalten.</string>
+  <string name="auth_not_configured_title">Fehlerhafte Server Konfiguration</string>
+  <string name="auth_not_configured_message">Es scheint, als wäre Ihre Server-Installation nicht richtig konfiguriert. Bitte kontaktieren Sie Ihren Administrator, um weitere Details zu erhalten.</string>
   <string name="auth_unknown_error_title">Ein unbekannter Fehler ist aufgetreten!</string>
-  <string name="auth_unknown_error_message">Ein unbekannter Fehler ist aufgetreten. Bitte kontaktiere Deinen Administrator unter Zu­hil­fe­nah­me der Log-Dateien Deines Gerätes.</string>
+  <string name="auth_unknown_error_message">Ein unbekannter Fehler ist aufgetreten. Bitte kontaktieren Sie Ihren Administrator unter Zu­hil­fe­nah­me der Log-Dateien Ihres Gerätes.</string>
   <string name="auth_unknown_host_title">Konnte den Host nicht finden.</string>
-  <string name="auth_unknown_host_message">Konnte den eingetragenen Host nicht finden. Bitte prüfe den Hostnamen und die Verfügbarkeit des Servers und versuche es erneut.</string>
-  <string name="auth_incorrect_path_title">ownCloud-Installation nicht gefunden</string>
-  <string name="auth_incorrect_path_message">Die App konnte die ownCloud unter dem angegebenen Pfad nicht finden. Bitte überprüfe den Pfad und versuche es erneut.</string>
-  <string name="auth_timeout_title">Der Server brauchte zu lange für eine Antwort.</string>
+  <string name="auth_unknown_host_message">Konnte den eingetragenen Host nicht finden. Bitte prüfen Sie den Hostnamen und die Verfügbarkeit des Servers und versuchen es erneut.</string>
+  <string name="auth_incorrect_path_title">Server-Installation nicht gefunden</string>
+  <string name="auth_incorrect_path_message">Die App konnte die server unter dem angegebenen Pfad nicht finden. Bitte überprüfen Sie den Pfad und versuchen es erneut.</string>
+  <string name="auth_timeout_title">Der Server braucht zu lange für eine Antwort.</string>
   <string name="auth_incorrect_address_title">Fehlerhafte URL</string>
   <string name="auth_ssl_general_error_title">SSL-Initialisierung fehlgeschlagen.</string>
   <string name="auth_ssl_unverified_server_title">Nichtüberprüfte SSL-Server-Identität</string>
-  <string name="auth_bad_oc_version_title">Unbekannte ownCloud-Server-Version</string>
+  <string name="auth_bad_oc_version_title">Unbekannte Server-Version</string>
   <string name="auth_wrong_connection_title">Konnte keine Verbindung aufbauen.</string>
   <string name="auth_secure_connection">Sichere Verbindung hergestellt</string>
   <string name="auth_login_details">Anmeldedetails</string>
-  <string name="crashlog_message">Die Andwendung ist abgestürzt. Möchtest Du einen Bericht senden?</string>
+  <string name="crashlog_message">Die Anwendung ist abgestürzt. Möchten Sie einen Bericht senden?</string>
   <string name="crashlog_send_report">Bericht senden</string>
   <string name="crashlog_dont_send_report">Keinen Bericht senden</string>
   <string name="extensions_avail_title">Erweiterung verfügbar!</string>
-  <string name="extensions_avail_message">Scheinbar unterstützt Deine ownCloud weitere Erweiterungen. Möchtest Du die verfügbaren Erweiterungen für Android sehen?</string>
+  <string name="extensions_avail_message">Scheinbar unterstützt Ihre server weitere Erweiterungen. Möchten Sie die verfügbaren Erweiterungen für Android sehen?</string>
   <string name="fd_keep_in_sync">Datei aktuell halten</string>
-  <string name="common_share">Teilen</string>
+  <string name="common_share">Freigeben</string>
   <string name="common_rename">Umbenennen</string>
   <string name="common_remove">Löschen</string>
-  <string name="confirmation_remove_alert">Möchtest Du %1$s wirklich löschen?</string>
+  <string name="confirmation_remove_alert">Möchten Sie %1$s wirklich löschen?</string>
   <string name="confirmation_remove_local">Nur lokal</string>
   <string name="confirmation_remove_remote">Vom Server entfernen</string>
   <string name="confirmation_remove_remote_and_local">Lokal und auf dem Server</string>
@@ -162,19 +166,41 @@
   <string name="remove_fail_msg">Der Löschvorgang konnte nicht beendet werden</string>
   <string name="rename_local_fail_msg">Die lokale Kopie konnte nicht umbenannt werden. Versuchen Sie es mit einem anderen neuen Namen.</string>
   <string name="rename_server_fail_msg">Die Umbenennung konnte nicht abgeschlossen werden.</string>
+  <string name="sync_file_nothing_to_do_msg">Dateiinhalte bereits synchronisiert</string>
   <string name="create_dir_fail_msg">Das Verzeichnis konnte nicht erstellt werden.</string>
   <string name="wait_a_moment">Bitte warten Sie einen Moment.</string>
-  <string name="filedisplay_unexpected_bad_get_content">Ein unerwartetes Problem ist aufgetreten. Bitte versuche, die Datei in einer anderen App zu öffnen</string>
+  <string name="filedisplay_unexpected_bad_get_content">Ein unerwartetes Problem ist aufgetreten. Bitte versuchen Sie, die Datei in einer anderen App zu öffnen.</string>
   <string name="filedisplay_no_file_selected">Es wurde keine Datei ausgewählt.</string>
   <string name="ssl_validator_title">Warnung</string>
   <string name="ssl_validator_header">Die Identität der Website konnte nicht überprüft werden</string>
   <string name="ssl_validator_reason_cert_not_trusted">- Das Zertifikat des Servers ist nicht vertrauenswürdig</string>
   <string name="ssl_validator_reason_cert_expired">- Das Zertifikat des Servers ist abgelaufen</string>
   <string name="ssl_validator_reason_cert_not_yet_valid">- Das Zertifikat des Servers ist zu neu</string>
-  <string name="ssl_validator_reason_hostname_not_verified">- Die Adresse stimmt nicht mit dem im Zertifikat angegebenen Hostnamen  überein</string>
+  <string name="ssl_validator_reason_hostname_not_verified">- Die Adresse stimmt nicht mit dem im Zertifikat angegebenen Hostnamen überein</string>
   <string name="ssl_validator_certificate_not_available">Das Zertifikat des Servers konnte nicht abgerufen werden</string>
-  <string name="ssl_validator_question">Möchtest Du diesem Zertifikat trotzdem vertrauen?</string>
+  <string name="ssl_validator_question">Möchten Sie diesem Zertifikat trotzdem vertrauen?</string>
   <string name="ssl_validator_not_saved">Das Zertifikat konnte nicht gespeichert werden</string>
+  <string name="ssl_validator_btn_details_see">Details</string>
+  <string name="ssl_validator_btn_details_hide">Ausblenden</string>
+  <string name="ssl_validator_label_subject">Ausgestellt für:</string>
+  <string name="ssl_validator_label_issuer">Ausgestellt von:</string>
+  <string name="ssl_validator_label_CN">Üblicher Name:</string>
+  <string name="ssl_validator_label_O">Organisation:</string>
+  <string name="ssl_validator_label_OU">Organisationseinheit:</string>
+  <string name="ssl_validator_label_C">Land:</string>
+  <string name="ssl_validator_label_ST">Bundesland:</string>
+  <string name="ssl_validator_label_L">Ort:</string>
+  <string name="ssl_validator_label_validity">Gültigkeit:</string>
+  <string name="ssl_validator_label_validity_from">Von:</string>
+  <string name="ssl_validator_label_validity_to">An:</string>
+  <string name="ssl_validator_label_signature">Signatur:</string>
+  <string name="ssl_validator_label_signature_algorithm">Algorithmus:</string>
   <string name="text_placeholder">Dies ist ein Platzhalter</string>
   <string name="instant_upload_on_wifi">Fotos nur über WiFi hochladen</string>
+  <string name="instant_upload_path">/SofortUpload</string>
+  <string name="conflict_title">Konflikt beim Update</string>
+  <string name="conflict_message">Serverdatei %s ist nicht synchronisiert mit der lokalen Datei. Weitermachen bedeutet, dass der Inhalt der Datei auf dem Server ersetzt wird.</string>
+  <string name="conflict_keep_both">Beide behalten</string>
+  <string name="conflict_overwrite">Überschreiben</string>
+  <string name="conflict_dont_upload">Nicht hochladen</string>
 </resources>

+ 74 - 50
res/values-de/strings.xml

@@ -1,47 +1,47 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
-  <string name="hello">Hallo Welt, ownCloud-Start-Bildschirm!</string>
   <string name="app_name">ownCloud</string>
   <string name="main_password">Passwort:</string>
   <string name="main_login">Benutzername:</string>
   <string name="main_button_login">Anmelden</string>
-  <string name="main_welcome">Herzlich willkommen bei Deiner ownCloud!</string>
+  <string name="main_welcome">Willkommen</string>
   <string name="main_files">Dateien</string>
   <string name="main_music">Musik</string>
   <string name="main_contacts">Kontakte</string>
   <string name="main_calendar">Kalender</string>
   <string name="main_bookmarks">Lesezeichen</string>
   <string name="main_settings">Einstellungen</string>
-  <string name="main_tit_accsetup">Konto einrichten</string>
-  <string name="main_wrn_accsetup">Auf Deinem Gerät sind keine ownCloud Konten eingerichtet. Bitte erstelle ein Konto, um diese App zu nutzen.</string>
+  <string name="main_tit_accsetup">Account einrichten</string>
+  <string name="main_wrn_accsetup">Auf Deinem Gerät sind keine Konten eingerichtet. Bitte erstelle ein Konto, um diese App zu nutzen.</string>
+  <string name="about_message">%1$s Android App\n\nVersion: %2$s</string>
   <string name="actionbar_sync">Aktualisieren</string>
-  <string name="actionbar_upload">Hochladen</string>
+  <string name="actionbar_upload">Datei hochladen</string>
   <string name="actionbar_upload_from_apps">Inhalt von anderen Apps</string>
   <string name="actionbar_upload_files">Dateien</string>
-  <string name="actionbar_mkdir">Verzeichnis erstellen</string>
+  <string name="actionbar_mkdir">Ordner anlegen</string>
   <string name="actionbar_search">Suche</string>
   <string name="actionbar_settings">Einstellungen</string>
   <string name="prefs_category_general">Allgemein</string>
   <string name="prefs_category_trackmydevice">Gerät verfolgen</string>
   <string name="prefs_add_session">Neue Sitzung hinzufügen</string>
   <string name="prefs_create_img_thumbnails">Bildvorschau erstellen</string>
-  <string name="prefs_select_oc_account">Konto auswählen</string>
-  <string name="prefs_summary_select_oc_account">Bitte wähle, welches Konto von der App verwendet werden soll.</string>
+  <string name="prefs_select_oc_account">Account auswählen</string>
+  <string name="prefs_summary_select_oc_account">Bitte wähle, welches Accounts von der App verwendet werden soll.</string>
   <string name="prefs_trackmydevice">Gerät verfolgen</string>
-  <string name="prefs_trackmydevice_summary_off">Geräteverfolgung in ownCloud aktivieren</string>
-  <string name="prefs_trackmydevice_summary_on">Deine ownCloud verfolgt dieses Gerät</string>
+  <string name="prefs_trackmydevice_summary_off">Geräteverfolgung in App aktivieren</string>
+  <string name="prefs_trackmydevice_summary_on">Deine App verfolgt dieses Gerät</string>
   <string name="prefs_trackmydevice_interval">Aktualisierungsintervall</string>
   <string name="prefs_trackmydevice_interval_summary">Alle %1$s Minuten aktualisieren</string>
   <string name="prefs_accounts">Konten</string>
   <string name="prefs_manage_accounts">Konten verwalten</string>
-  <string name="prefs_pincode">ownCloud-App-PIN</string>
-  <string name="prefs_pincode_summary">Schütze Deinen OwnCloud-Client.</string>
-  <string name="prefs_instant_upload">Aktiviere sofortigen Upload.</string>
-  <string name="prefs_instant_upload_summary">Lade Fotos von der Kamera sofort hoch.</string>
-  <string name="auth_host_url">ownCloud-URL</string>
+  <string name="prefs_pincode">App-PIN</string>
+  <string name="prefs_pincode_summary">Schütze Deinen Client</string>
+  <string name="prefs_instant_upload">Aktiviere Sie den sofortigen Upload</string>
+  <string name="prefs_instant_upload_summary">Laden Sie Fotos von der Kamera sofort hoch</string>
+  <string name="auth_host_url">URL</string>
   <string name="auth_username">Benutzername</string>
   <string name="auth_password">Passwort</string>
-  <string name="auth_register">Ich bin neu bei ownCloud</string>
+  <string name="auth_register">Ich bin neu bei %1$s</string>
   <string name="new_session_uri_error">Falsche URL angegeben</string>
   <string name="new_session_session_name_error">Falscher Sitzungsname</string>
   <string name="sync_string_files">Dateien</string>
@@ -50,40 +50,42 @@
   <string name="setup_hint_password">Passwort</string>
   <string name="setup_hint_address">Internetadresse</string>
   <string name="setup_hint_show_password">Passwort anzeigen?</string>
-  <string name="setup_title">Mit Deiner ownCloud verbinden</string>
+  <string name="setup_title">Mit Deiner %1$s verbinden</string>
   <string name="setup_btn_connect">Verbinden</string>
   <string name="uploader_btn_upload_text">Hochladen</string>
-  <string name="uploader_wrn_no_account_title">Kein Konto gefunden</string>
-  <string name="uploader_wrn_no_account_text">Es sind keine ownCloud-Konten auf Deinem Gerät eingerichtet. Bitte richte zuerst ein Konto ein.</string>
+  <string name="uploader_wrn_no_account_title">Kein Account gefunden</string>
+  <string name="uploader_wrn_no_account_text">Es sind keine %1$s-Accounts auf Deinem Gerät eingerichtet. Bitte richte zuerst ein Konto ein.</string>
   <string name="uploader_wrn_no_account_setup_btn_text">Einrichten</string>
   <string name="uploader_wrn_no_account_quit_btn_text">Beenden</string>
   <string name="uploader_wrn_no_content_title">Keine Inhalte zum Hochladen vorhanden</string>
   <string name="uploader_wrn_no_content_text">Es wurden keine Inhalte empfangen. Es gibt nichts zum Hochladen.</string>
-  <string name="uploader_error_forbidden_content">ownCloud darf den geteilten Inhalt nicht nutzen.</string>
+  <string name="uploader_error_forbidden_content">%1$s darf den geteilten Inhalt nicht nutzen.</string>
   <string name="uploader_info_uploading">Lade hoch</string>
-  <string name="uploader_btn_create_dir_text">Verzeichnis für hochzuladene Dateien erstellen</string>
-  <string name="file_list_empty">Im ausgewählten Ordner befinden sich noch keine Dateien.\nÜber die Schaltfläche „Hochladen“ können neue Dateien hinzugefügt werden.</string>
+  <string name="uploader_btn_create_dir_text">Ordner für hochzuladene Dateien erstellen</string>
+  <string name="file_list_empty">In diesem Ordner befinden sich keine Dateien.\nNeue Dateien können mit der \"Hochladen\" Menüfunktion hinzugefügt werden.</string>
   <string name="filedetails_select_file">Klicken Sie auf eine Datei für weitere Informationen.</string>
   <string name="filedetails_size">Größe:</string>
   <string name="filedetails_type">Art:</string>
   <string name="filedetails_created">Erstellt:</string>
   <string name="filedetails_modified">Geändert:</string>
   <string name="filedetails_download">Herunterladen</string>
+  <string name="filedetails_sync_file">Neu laden</string>
   <string name="filedetails_redownload">Neu laden</string>
   <string name="filedetails_open">Öffnen</string>
   <string name="common_yes">Ja</string>
   <string name="common_no">Nein</string>
   <string name="common_ok">OK</string>
+  <string name="common_cancel_upload">Upload abbrechen</string>
   <string name="common_cancel">Abbrechen</string>
   <string name="common_save_exit">Speichern &amp; schließen</string>
-  <string name="common_exit">Verlasse ownCloud</string>
+  <string name="common_exit">%1$s verlasse</string>
   <string name="common_error">Fehler</string>
   <string name="about_title">Über</string>
-  <string name="delete_account">Konto löschen</string>
-  <string name="create_account">Konto erstellen</string>
-  <string name="upload_chooser_title">Dateien hochladen von&#8230;</string>
-  <string name="uploader_info_dirname">Verzeichnisname</string>
-  <string name="uploader_upload_in_progress_ticker">Hochladen&#8230;</string>
+  <string name="delete_account">Account löschen</string>
+  <string name="create_account">Account erstellen</string>
+  <string name="upload_chooser_title">Dateien hochladen von...</string>
+  <string name="uploader_info_dirname">Ordnername</string>
+  <string name="uploader_upload_in_progress_ticker">Hochladen...</string>
   <string name="uploader_upload_in_progress_content">%1$d%% Hochladen %2$s</string>
   <string name="uploader_upload_succeeded_ticker">Hochladen erfolgreich</string>
   <string name="uploader_upload_succeeded_content_single">%1$s wurde(n) erfolgreich hochgeladen</string>
@@ -91,28 +93,28 @@
   <string name="uploader_upload_failed_ticker">Hochladen fehlgeschlagen</string>
   <string name="uploader_upload_failed_content_single">Hochladen von  %1$s konnte nicht abgeschlossen werden</string>
   <string name="uploader_upload_failed_content_multiple">Hochladen fehlgeschlagen: %1$d/%2$d Dateien wurden hochgeladen</string>
-  <string name="downloader_download_in_progress_ticker">Herunterladen&#8230;</string>
+  <string name="downloader_download_in_progress_ticker">Herunterladen...</string>
   <string name="downloader_download_in_progress_content">%1$d%% Herunterladen %2$s</string>
   <string name="downloader_download_succeeded_ticker">Herunterladen erfolgreich</string>
   <string name="downloader_download_succeeded_content">%1$s wurde erfolgreich heruntergeladen</string>
   <string name="downloader_download_failed_ticker">Herunterladen fehlgeschlagen</string>
   <string name="downloader_download_failed_content">Herunterladen von %1$s konnte nicht abgeschlossen werden</string>
-  <string name="common_choose_account">Konto auswählen</string>
+  <string name="common_choose_account">Account auswählen</string>
   <string name="sync_string_contacts">Kontakte</string>
   <string name="sync_fail_ticker">Synchronisation fehlgeschlagen</string>
   <string name="sync_fail_content">Bei der Synchronisation konnte %1$s nicht übertragen werden</string>
   <string name="use_ssl">Sichere Verbindung benutzen</string>
-  <string name="location_no_provider">ownCloud kann Dein Gerät nicht verfolgen. Bitte überprüfe Deine Standorteinstellungen</string>
+  <string name="location_no_provider">%1$s kann Dein Gerät nicht verfolgen. Bitte überprüfe Deine Standorteinstellungen</string>
   <string name="pincode_enter_pin_code">Bitte gib Deine App-PIN ein</string>
   <string name="pincode_enter_new_pin_code">Bitte gib eine neue App-PIN ein</string>
-  <string name="pincode_configure_your_pin">Bitte gib Deine ownCloud-App-PIN ein</string>
-  <string name="pincode_configure_your_pin_explanation">Der App PIN wird bei jedem Start abgefragt</string>
-  <string name="pincode_reenter_your_pincode">Bitte gib Deine ownCloud-App-PIN erneut ein.</string>
-  <string name="pincode_remove_your_pincode">ownCloud-App-PIN entfernen</string>
-  <string name="pincode_mismatch">Die ownCloud-App-PINs stimmen nicht überein</string>
-  <string name="pincode_wrong">Falsche ownCloud-App-PIN</string>
-  <string name="pincode_removed">Die ownCloud-App-PIN wurde entfernt</string>
-  <string name="pincode_stored">Die ownCloud-App-PIN wurde gespeichert</string>
+  <string name="pincode_configure_your_pin">Bitte gib Deine App-PIN ein</string>
+  <string name="pincode_configure_your_pin_explanation">PIN-Abfrage erfolgt nach Starten der App.</string>
+  <string name="pincode_reenter_your_pincode">Bitte gib Deine App-PIN erneut ein.</string>
+  <string name="pincode_remove_your_pincode">App-PIN entfernen</string>
+  <string name="pincode_mismatch">Die App-PINs stimmen nicht überein</string>
+  <string name="pincode_wrong">Falsche App-PIN</string>
+  <string name="pincode_removed">Die App-PIN wurde entfernt</string>
+  <string name="pincode_stored">Die App-PIN wurde gespeichert</string>
   <string-array name="prefs_trackmydevice_intervall_keys">
     <item>15 Minuten</item>
     <item>30 Minuten</item>
@@ -123,27 +125,27 @@
     <item>30</item>
     <item>60</item>
   </string-array>
-  <string name="auth_trying_to_login">Anmeldungsversuch&#8230;</string>
+  <string name="auth_trying_to_login">Anmeldungsversuch...</string>
   <string name="auth_no_net_conn_title">Keine Netzwerkverbindung</string>
   <string name="auth_no_net_conn_message">Es konnte keine Netzwerkverbindung gefunden werden, bitte überprüfe Deine Internetverbindung.</string>
   <string name="auth_connect_anyway">Trotzdem verbinden</string>
   <string name="auth_nossl_plain_ok_title">Sichere Verbindung nicht verfügbar.</string>
   <string name="auth_nossl_plain_ok_message">Die App konnte keine sichere Verbindung zum Server herstellen. Eine nicht sichere Verbindung ist nichtsdestotrotz verfügbar. Möchtest Du fortfahren oder abbrechen?</string>
   <string name="auth_connection_established">Verbindung hergestellt</string>
-  <string name="auth_testing_connection">Verbindung testen&#8230;</string>
-  <string name="auth_not_configured_title">Falsch konfigurierte ownCloud</string>
-  <string name="auth_not_configured_message">Es scheint, als wäre Deine ownCloud Installation nicht richtig konfiguriert. Bitte kontaktiere Deinen Administrator, um weitere Details zu erhalten.</string>
+  <string name="auth_testing_connection">Verbindung testen...</string>
+  <string name="auth_not_configured_title">Falsch konfigurierte Server</string>
+  <string name="auth_not_configured_message">Es scheint, als wäre Deine server-Installation nicht richtig konfiguriert. Bitte kontaktiere Deinen Administrator, um weitere Details zu erhalten.</string>
   <string name="auth_unknown_error_title">Ein unbekannter Fehler ist aufgetreten!</string>
   <string name="auth_unknown_error_message">Ein unbekannter Fehler ist aufgetreten. Bitte kontaktiere Deinen Administrator unter Zu­hil­fe­nah­me der Log-Dateien Deines Gerätes.</string>
   <string name="auth_unknown_host_title">Konnte den Host nicht finden.</string>
   <string name="auth_unknown_host_message">Konnte den eingetragenen Host nicht finden. Bitte prüfe den Hostnamen und die Verfügbarkeit des Servers und versuche es erneut.</string>
-  <string name="auth_incorrect_path_title">ownCloud Installation nicht gefunden</string>
-  <string name="auth_incorrect_path_message">Die App konnte die ownCloud unter dem angegebenen Pfad nicht finden. Bitte überprüfe den Pfad und versuche es erneut.</string>
-  <string name="auth_timeout_title">Der Server brauchte zu lange für eine Antwort.</string>
+  <string name="auth_incorrect_path_title">Server-Installation nicht gefunden</string>
+  <string name="auth_incorrect_path_message">Die App konnte die server unter dem angegebenen Pfad nicht finden. Bitte überprüfe den Pfad und versuche es erneut.</string>
+  <string name="auth_timeout_title">Der Server braucht zu lange für eine Antwort.</string>
   <string name="auth_incorrect_address_title">Fehlerhafte URL</string>
   <string name="auth_ssl_general_error_title">SSL-Initialisierung fehlgeschlagen.</string>
   <string name="auth_ssl_unverified_server_title">Nichtüberprüfte SSL-Server-Identität</string>
-  <string name="auth_bad_oc_version_title">Unbekannte ownCloud-Server-Version</string>
+  <string name="auth_bad_oc_version_title">Unbekannte Server-Version</string>
   <string name="auth_wrong_connection_title">Konnte keine Verbindung aufbauen.</string>
   <string name="auth_secure_connection">Sichere Verbindung hergestellt</string>
   <string name="auth_login_details">Anmeldedetails</string>
@@ -151,7 +153,7 @@
   <string name="crashlog_send_report">Bericht senden</string>
   <string name="crashlog_dont_send_report">Keinen Bericht senden</string>
   <string name="extensions_avail_title">Erweiterung verfügbar!</string>
-  <string name="extensions_avail_message">Scheinbar unterstützt Deine ownCloud weitere Erweiterungen. Möchtest Du die verfügbaren Erweiterungen für Android sehen?</string>
+  <string name="extensions_avail_message">Scheinbar unterstützt Deine server weitere Erweiterungen. Möchtest Du die verfügbaren Erweiterungen für Android sehen?</string>
   <string name="fd_keep_in_sync">Datei aktuell halten</string>
   <string name="common_share">Teilen</string>
   <string name="common_rename">Umbenennen</string>
@@ -159,12 +161,13 @@
   <string name="confirmation_remove_alert">Möchtest Du %1$s wirklich löschen?</string>
   <string name="confirmation_remove_local">Nur lokal</string>
   <string name="confirmation_remove_remote">Vom Server entfernen</string>
-  <string name="confirmation_remove_remote_and_local">Lokal und Server</string>
+  <string name="confirmation_remove_remote_and_local">Lokal und auf dem Server</string>
   <string name="remove_success_msg">Erfolgreich gelöscht</string>
   <string name="remove_fail_msg">Der Löschvorgang konnte nicht beendet werden</string>
   <string name="rename_local_fail_msg">Die lokale Kopie konnte nicht umbenannt werden. Versuchen Sie es mit einem anderen neuen Namen.</string>
   <string name="rename_server_fail_msg">Die Umbenennung konnte nicht abgeschlossen werden.</string>
-  <string name="create_dir_fail_msg">Das Verzeichnis konnte nicht erstellt werden.</string>
+  <string name="sync_file_nothing_to_do_msg">Dateiinhalte bereits synchronisiert</string>
+  <string name="create_dir_fail_msg">Das Ordner konnte nicht erstellt werden.</string>
   <string name="wait_a_moment">Bitte warten Sie einen Moment.</string>
   <string name="filedisplay_unexpected_bad_get_content">Ein unerwartetes Problem ist aufgetreten. Bitte versuche, die Datei in einer anderen App zu öffnen</string>
   <string name="filedisplay_no_file_selected">Es wurde keine Datei ausgewählt.</string>
@@ -177,6 +180,27 @@
   <string name="ssl_validator_certificate_not_available">Das Zertifikat des Servers konnte nicht abgerufen werden</string>
   <string name="ssl_validator_question">Möchtest Du diesem Zertifikat trotzdem vertrauen?</string>
   <string name="ssl_validator_not_saved">Das Zertifikat konnte nicht gespeichert werden</string>
+  <string name="ssl_validator_btn_details_see">Details</string>
+  <string name="ssl_validator_btn_details_hide">Ausblenden</string>
+  <string name="ssl_validator_label_subject">Ausgestellt für:</string>
+  <string name="ssl_validator_label_issuer">Ausgestellt von:</string>
+  <string name="ssl_validator_label_CN">Üblicher Name:</string>
+  <string name="ssl_validator_label_O">Organisation:</string>
+  <string name="ssl_validator_label_OU">Organisationseinheit:</string>
+  <string name="ssl_validator_label_C">Land:</string>
+  <string name="ssl_validator_label_ST">Bundesland:</string>
+  <string name="ssl_validator_label_L">Ort:</string>
+  <string name="ssl_validator_label_validity">Gültigkeit:</string>
+  <string name="ssl_validator_label_validity_from">Von:</string>
+  <string name="ssl_validator_label_validity_to">Bis:</string>
+  <string name="ssl_validator_label_signature">Signatur:</string>
+  <string name="ssl_validator_label_signature_algorithm">Algorithmus:</string>
   <string name="text_placeholder">Dies ist ein Platzhalter</string>
   <string name="instant_upload_on_wifi">Fotos nur über WiFi hochladen</string>
+  <string name="instant_upload_path">/SofortUpload</string>
+  <string name="conflict_title">Konflikt beim Update</string>
+  <string name="conflict_message">Serverdatei %s ist nicht synchronisiert mit der lokalen Datei. Weitermachen bedeutet, dass der Inhalt der Datei auf dem Server ersetzt wird.</string>
+  <string name="conflict_keep_both">Beide behalten</string>
+  <string name="conflict_overwrite">Überschreiben</string>
+  <string name="conflict_dont_upload">Nicht hochladen</string>
 </resources>

+ 72 - 35
res/values-es/strings.xml

@@ -1,11 +1,10 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
-  <string name="hello">Hola Mundo, ¡PantallaPrincipalOwnCloud!</string>
   <string name="app_name">ownCloud</string>
   <string name="main_password">Contraseña:</string>
   <string name="main_login">Nombre de usuario:</string>
   <string name="main_button_login">Inicio de sesión</string>
-  <string name="main_welcome">Bienvenido a tu ownCloud</string>
+  <string name="main_welcome">Bienvenido</string>
   <string name="main_files">Archivos</string>
   <string name="main_music">Música</string>
   <string name="main_contacts">Contactos</string>
@@ -13,9 +12,10 @@
   <string name="main_bookmarks">Marcadores</string>
   <string name="main_settings">Ajustes</string>
   <string name="main_tit_accsetup">Configuración de cuenta</string>
-  <string name="main_wrn_accsetup">No hay cuentas de ownCloud en tu dispositivo. Para usar esta aplicación, necesitas crear una.</string>
+  <string name="main_wrn_accsetup">No hay cuentas en tu dispositivo. Para usar esta aplicación, necesitas crear una.</string>
+  <string name="about_message">%1$s para Android\n\nversión: %2$s</string>
   <string name="actionbar_sync">Sincronización de cuenta</string>
-  <string name="actionbar_upload">Subir</string>
+  <string name="actionbar_upload">Subir archivo</string>
   <string name="actionbar_upload_from_apps">Contenido de otras aplicaciones</string>
   <string name="actionbar_upload_files">Archivos</string>
   <string name="actionbar_mkdir">Crear directorio</string>
@@ -28,20 +28,20 @@
   <string name="prefs_select_oc_account">Seleccionar una cuenta</string>
   <string name="prefs_summary_select_oc_account">Elige, cuál de tus cuentas la aplicación debería usar.</string>
   <string name="prefs_trackmydevice">Rastreo de dispositivo</string>
-  <string name="prefs_trackmydevice_summary_off">Habilitar ownCloud para rastrear la localización de tu dispositivo</string>
-  <string name="prefs_trackmydevice_summary_on">Tu ownCloud realiza un seguimiento de este dispositivo</string>
+  <string name="prefs_trackmydevice_summary_off">Permitir la localización de tu dispositivo</string>
+  <string name="prefs_trackmydevice_summary_on">La app realiza un seguimiento de este dispositivo</string>
   <string name="prefs_trackmydevice_interval">Intervalo de actualización</string>
   <string name="prefs_trackmydevice_interval_summary">Actualizar cada %1$s minutos</string>
   <string name="prefs_accounts">Cuentas</string>
   <string name="prefs_manage_accounts">Gestionar cuentas</string>
-  <string name="prefs_pincode">PIN ownCloud App </string>
-  <string name="prefs_pincode_summary">Proteja su cliente ownCloud</string>
+  <string name="prefs_pincode">PIN de aplicación </string>
+  <string name="prefs_pincode_summary">Proteja su cliente</string>
   <string name="prefs_instant_upload">Habilita la subida instantánea</string>
   <string name="prefs_instant_upload_summary">Subir instantáneamente las fotos tomadas por la cámara</string>
-  <string name="auth_host_url">URL de ownCloud</string>
+  <string name="auth_host_url">URL</string>
   <string name="auth_username">Nombre de usuario</string>
   <string name="auth_password">Contraseña</string>
-  <string name="auth_register">Soy nuevo en ownCloud</string>
+  <string name="auth_register">Soy nuevo en %1$s</string>
   <string name="new_session_uri_error">La URL dada es incorrecta</string>
   <string name="new_session_session_name_error">Nombre de sesión incorrecto</string>
   <string name="sync_string_files">Archivos</string>
@@ -50,40 +50,44 @@
   <string name="setup_hint_password">Contraseña</string>
   <string name="setup_hint_address">Dirección web</string>
   <string name="setup_hint_show_password">¿Mostrar contraseña?</string>
-  <string name="setup_title">Conectar a tu ownCloud</string>
+  <string name="setup_title">Conectar a tu %1$s</string>
   <string name="setup_btn_connect">Conectar</string>
   <string name="uploader_btn_upload_text">Subir</string>
   <string name="uploader_wrn_no_account_title">No se encontraron cuentas</string>
-  <string name="uploader_wrn_no_account_text">No hay cuentas de ownCloud en tu dispositivo. Por favor configura una cuenta primero.</string>
+  <string name="uploader_wrn_no_account_text">No hay cuentas de %1$s en tu dispositivo. Por favor configura una cuenta primero.</string>
   <string name="uploader_wrn_no_account_setup_btn_text">Configuración</string>
   <string name="uploader_wrn_no_account_quit_btn_text">Salir</string>
   <string name="uploader_wrn_no_content_title">No hay contenido para subir</string>
   <string name="uploader_wrn_no_content_text">Ningún contenido ha sido recibido. No hay nada que subir.</string>
-  <string name="uploader_error_forbidden_content">ownCloud no está autorizado para acceder al contenido compartido</string>
+  <string name="uploader_error_forbidden_content">%1$s no está autorizado para acceder al contenido compartido</string>
   <string name="uploader_info_uploading">Enviando</string>
   <string name="uploader_btn_create_dir_text">Crear directorio para envío</string>
-  <string name="file_list_empty">La carpeta seleccionada no contiene ningún archivo.\nPuede añadir ficheros con la opción de menú \"Subir\".</string>
+  <string name="file_list_empty">No hay archivos en esta carpeta.\nPuedes añadir nuevos archivos con la opción \"Subir\" del menú.</string>
   <string name="filedetails_select_file">Pulsa sobre un archivo para mostrar información adicional.</string>
   <string name="filedetails_size">Tamaño:</string>
   <string name="filedetails_type">Tipo:</string>
   <string name="filedetails_created">Creado:</string>
   <string name="filedetails_modified">Modificado:</string>
   <string name="filedetails_download">Descargar</string>
+  <string name="filedetails_sync_file">Actualizar</string>
   <string name="filedetails_redownload">Volver a descargar</string>
   <string name="filedetails_open">Abrir</string>
+  <string name="filedetails_renamed_in_upload_msg">El fichero fue renombrado como %1$s durante la subida</string>
   <string name="common_yes">Sí</string>
   <string name="common_no">No</string>
   <string name="common_ok">Aceptar</string>
+  <string name="common_cancel_download">Cancelar descarga</string>
+  <string name="common_cancel_upload">Cancelar subida</string>
   <string name="common_cancel">Cancelar</string>
   <string name="common_save_exit">Guardar &amp; Salir</string>
-  <string name="common_exit">Salir de ownCloud</string>
+  <string name="common_exit">Salir de %1$s</string>
   <string name="common_error">Error</string>
   <string name="about_title">Acerca de</string>
   <string name="delete_account">Eliminar cuenta</string>
   <string name="create_account">Crear cuenta</string>
   <string name="upload_chooser_title">Subir</string>
   <string name="uploader_info_dirname">Nombre de directorio</string>
-  <string name="uploader_upload_in_progress_ticker">Subiendo&#8230;</string>
+  <string name="uploader_upload_in_progress_ticker">Subiendo...</string>
   <string name="uploader_upload_in_progress_content">%1$d%% Subiendo %2$s</string>
   <string name="uploader_upload_succeeded_ticker">Subido con éxito</string>
   <string name="uploader_upload_succeeded_content_single">%1$s se ha subido con éxito</string>
@@ -91,7 +95,7 @@
   <string name="uploader_upload_failed_ticker">Error en la subida</string>
   <string name="uploader_upload_failed_content_single">La subida de %1$s no se pudo completar</string>
   <string name="uploader_upload_failed_content_multiple">Error de subida: %1$d/%2$d archivos fueron cargados</string>
-  <string name="downloader_download_in_progress_ticker">Descargando &#8230;</string>
+  <string name="downloader_download_in_progress_ticker">Descargando ...</string>
   <string name="downloader_download_in_progress_content">%1$s Descargada de %2$s</string>
   <string name="downloader_download_succeeded_ticker">Descarga completa</string>
   <string name="downloader_download_succeeded_content">%1$s se ha descargado con éxito</string>
@@ -101,18 +105,22 @@
   <string name="sync_string_contacts">Contactos</string>
   <string name="sync_fail_ticker">Fallór la sincronización</string>
   <string name="sync_fail_content">La sincronización de %1$s s no se pudo completar</string>
+  <string name="sync_conflicts_in_favourites_ticker">Se encontraron conflictos</string>
+  <string name="sync_conflicts_in_favourites_content">Falló la sincronización de contenidos de %1$d ficheros</string>
+  <string name="sync_fail_in_favourites_ticker">Fallos en la sincronización de contenidos</string>
+  <string name="sync_fail_in_favourites_content">Los contenidos de %1$d ficheros no fueron sincronizados (%2$d conflictos)</string>
   <string name="use_ssl">Usar conexión segura</string>
-  <string name="location_no_provider">ownCloud no puede rastear tu dispositivo. Por favor chequea tu configuración de localización</string>
+  <string name="location_no_provider">%1$s no puede rastear tu dispositivo. Por favor chequea tu configuración de localización</string>
   <string name="pincode_enter_pin_code">Por favor, inserta tu PIN de aplicación</string>
   <string name="pincode_enter_new_pin_code">Por favor, inserta tu nuevo PIN de aplicación</string>
-  <string name="pincode_configure_your_pin">Ingrese PIN de aplicación ownCloud</string>
-  <string name="pincode_configure_your_pin_explanation">El PIN se solicitará cada vez que se inicie la aplicación</string> 
-  <string name="pincode_reenter_your_pincode">Reingrese PIN de aplicación ownCloud, por favor</string>
-  <string name="pincode_remove_your_pincode">Borrar tu PIN de aplicación ownCloud</string>
-  <string name="pincode_mismatch">Los PIN de aplicación ownCloud no son iguales</string>
-  <string name="pincode_wrong">PIN de aplicación ownCloud incorrecto</string>
-  <string name="pincode_removed">PIN de aplicación ownCloud borrado</string>
-  <string name="pincode_stored">PIN de aplicación ownCloud almacenado</string>
+  <string name="pincode_configure_your_pin">Introduzca un PIN para la aplicación</string>
+  <string name="pincode_configure_your_pin_explanation">Se solicitará el PIN cada vez que se inicie la aplicación</string>
+  <string name="pincode_reenter_your_pincode">Repita el PIN para la aplicación, por favor</string>
+  <string name="pincode_remove_your_pincode">Borre su PIN de aplicación</string>
+  <string name="pincode_mismatch">Los PIN introducidos no son iguales</string>
+  <string name="pincode_wrong">PIN de aplicación incorrecto</string>
+  <string name="pincode_removed">PIN de aplicación borrado</string>
+  <string name="pincode_stored">PIN de aplicación guardado</string>
   <string-array name="prefs_trackmydevice_intervall_keys">
     <item>15 minutos</item>
     <item>30 minutos</item>
@@ -123,47 +131,55 @@
     <item>30</item>
     <item>60</item>
   </string-array>
-  <string name="auth_trying_to_login">Intentado iniciar sesión&#8230;</string>
+  <string name="auth_trying_to_login">Intentado iniciar sesión...</string>
   <string name="auth_no_net_conn_title">Sin conexión de red</string>
   <string name="auth_no_net_conn_message">No se ha detectado una conexión de red, chequea tu conexión a internet e intenta nuevamente.</string>
   <string name="auth_connect_anyway">Conectar de todos modos</string>
   <string name="auth_nossl_plain_ok_title">Conexión segura no disponible.</string>
   <string name="auth_nossl_plain_ok_message">La aplicación no pudo establecer una conexión segura al servidor. Aunque no haya una conexión segura disponible, puedes continuar o cancelar.</string>
   <string name="auth_connection_established">Conexión establecida</string>
-  <string name="auth_testing_connection">Probando conexión&#8230;</string>
-  <string name="auth_not_configured_title">Configuración de ownCloud en formato incorrecto</string>
-  <string name="auth_not_configured_message">Parece que tu instancia de ownCloud no está correctamente configurada. Contacta a tu administrador para más detalles.</string>
+  <string name="auth_testing_connection">Probando conexión...</string>
+  <string name="auth_not_configured_title">Configuración de servidor en formato incorrecto</string>
+  <string name="auth_not_configured_message">Parece que tu servidor no está correctamente configurado. Contacta a tu administrador para más detalles.</string>
   <string name="auth_unknown_error_title">Ocurrió un error desconocido</string>
   <string name="auth_unknown_error_message">Ocurrió un error desconocido. Por favor, contacta a los autores e incluye los registros de tu dispositivo.</string>
   <string name="auth_unknown_host_title">No se pudo encontrar la dirección</string>
   <string name="auth_unknown_host_message">No se pudo encontrar el dirección introducida. Consulte la disponibilidad del servidor y la dirección e intenta nuevamete.</string>
-  <string name="auth_incorrect_path_title">Instancia de ownCloud no encontrada</string>
-  <string name="auth_incorrect_path_message">La aplicación no pudo encontrar la instancia de ownCloud en la ruta de acceso dada. Por favor chequea tu ruta de acceso e intenta nuevamente.</string>
+  <string name="auth_incorrect_path_title">Instancia de servidor no encontrada</string>
+  <string name="auth_incorrect_path_message">La aplicación no pudo encontrar la instancia del servidor en la ruta de acceso dada. Por favor, compruebe la ruta de acceso e inténtelo de nuevo.</string>
   <string name="auth_timeout_title">El servidor ha tardado demasiado en responder</string>
   <string name="auth_incorrect_address_title">URL no válida</string>
   <string name="auth_ssl_general_error_title">Falló la inicialización SSL</string>
   <string name="auth_ssl_unverified_server_title">Identidad del Servidor SSL no verificada</string>
-  <string name="auth_bad_oc_version_title">No se reconoce  la versión del servidor ownCloud </string>
+  <string name="auth_bad_oc_version_title">No se reconoce  la versión del servidor </string>
   <string name="auth_wrong_connection_title">No se ha podido establecer la conexión</string>
   <string name="auth_secure_connection">Conexión segura establecida</string>
   <string name="auth_login_details">Detalles de inicio de sesión</string>
+  <string name="auth_unauthorized">Nombre / contraseña incorrectos</string>
+  <string name="auth_not_found">Ruta errónea</string>
+  <string name="auth_internal">Error interno en el servidor, código %1$d</string>
   <string name="crashlog_message">La aplicación finalizó inesperadamente. ¿Desea enviar un reporte de error?</string>
   <string name="crashlog_send_report">Enviar reporte</string>
   <string name="crashlog_dont_send_report">No enviar reporte</string>
   <string name="extensions_avail_title">¡Extensiones disponibles!</string>
-  <string name="extensions_avail_message">Parece que su instancia de ownCloud soporta extensiones avanzadas. ¿Desea ver las extensiones disponibles para android?</string>
+  <string name="extensions_avail_message">Parece que su servidor soporta extensiones avanzadas. ¿Desea ver las extensiones disponibles para Android?</string>
   <string name="fd_keep_in_sync">Mantener el archivo actualizado</string>
   <string name="common_share">Compartir</string>
   <string name="common_rename">Renombrar</string>
   <string name="common_remove">Borrar</string>
   <string name="confirmation_remove_alert">¿Está seguro que desea borrar %1$s ?</string>
+  <string name="confirmation_remove_folder_alert">¿Desea elimiar %1$s y sus descendientes?</string>
   <string name="confirmation_remove_local">Sólo local</string>
+  <string name="confirmation_remove_folder_local">Sólo ficheros locales</string>
   <string name="confirmation_remove_remote">Eliminar del servidor</string>
-  <string name="confirmation_remove_remote_and_local">Remoto y local</string>
+  <string name="confirmation_remove_remote_and_local">Tanto remoto como local</string>
   <string name="remove_success_msg">Borrado correctamente</string>
   <string name="remove_fail_msg">El borrado no pudo ser completado</string>
+  <string name="rename_dialog_title">Introduzca un nombre nuevo</string>
   <string name="rename_local_fail_msg">No se pudo cambiar el nombre de la copia local, trata con un nombre differente</string>
   <string name="rename_server_fail_msg">No se pudo cambiar el nombre</string>
+  <string name="sync_file_fail_msg">No pudo comprobarse el fichero remoto</string>
+  <string name="sync_file_nothing_to_do_msg">Ya está sincronizado</string>
   <string name="create_dir_fail_msg">El directorio no pudo ser creado</string>
   <string name="wait_a_moment">Espere un momento</string>
   <string name="filedisplay_unexpected_bad_get_content">Problema inesperado; por favor, prueba otra app para seleccionar el archivo</string>
@@ -177,6 +193,27 @@
   <string name="ssl_validator_certificate_not_available">No se pudo obtener el certificado del servidor</string>
   <string name="ssl_validator_question">¿Confías de todas formas en este certificado?</string>
   <string name="ssl_validator_not_saved">El certificado no pudo ser guardado</string>
+  <string name="ssl_validator_btn_details_see">Detalles</string>
+  <string name="ssl_validator_btn_details_hide">Ocultar</string>
+  <string name="ssl_validator_label_subject">Emitido para:</string>
+  <string name="ssl_validator_label_issuer">Emitido por:</string>
+  <string name="ssl_validator_label_CN">Nombre común:</string>
+  <string name="ssl_validator_label_O">Organización:</string>
+  <string name="ssl_validator_label_OU">Unidad organizativa</string>
+  <string name="ssl_validator_label_C">Pais:</string>
+  <string name="ssl_validator_label_ST">Estado:</string>
+  <string name="ssl_validator_label_L">Ubicación:</string>
+  <string name="ssl_validator_label_validity">Validez:</string>
+  <string name="ssl_validator_label_validity_from">De:</string>
+  <string name="ssl_validator_label_validity_to">A:</string>
+  <string name="ssl_validator_label_signature">Firma:</string>
+  <string name="ssl_validator_label_signature_algorithm">Algoritmo:</string>
   <string name="text_placeholder">Es un marcador de posición</string>
   <string name="instant_upload_on_wifi">Subir imágenes sólo via WiFi</string>
+  <string name="instant_upload_path">/SubidasInstantáneas</string>
+  <string name="conflict_title">Conflicto en la actualización</string>
+  <string name="conflict_message">El archivo remoto %s no está sincronizado con el archivo local. Si continúa, se reemplazará el contenido del archivo en el servidor.</string>
+  <string name="conflict_keep_both">Mantener ambas</string>
+  <string name="conflict_overwrite">Sobrescribir</string>
+  <string name="conflict_dont_upload">No subir</string>
 </resources>

+ 4 - 0
res/values-large/bools.xml

@@ -0,0 +1,4 @@
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>

+ 4 - 0
res/values/bools.xml

@@ -0,0 +1,4 @@
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>

+ 84 - 50
res/values/strings.xml

@@ -1,11 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="hello">Hello World, OwnCloudMainScreen!</string>
     <string name="app_name">ownCloud</string>
     <string name="main_password">Password:</string>
     <string name="main_login">Username:</string>
     <string name="main_button_login">Login</string>
-    <string name="main_welcome">Welcome to your ownCloud</string>
+    <string name="main_welcome">Welcome</string>
     <string name="main_files">Files</string>
     <string name="main_music">Music</string>
     <string name="main_contacts">Contacts</string>
@@ -13,9 +12,9 @@
     <string name="main_bookmarks">Bookmarks</string>
     <string name="main_settings">Settings</string>
     <string name="main_tit_accsetup">Setup Account</string>
-    <string name="main_wrn_accsetup">There are no ownCloud accounts on your device. In order to use this App, you need to create one.</string>
+    <string name="main_wrn_accsetup">There is no account set up on your device. In order to use this App, you need to create one.</string>
     
-	<string name="about_message">ownCloud Android client\n\nversion: %1$s</string>
+    <string name="about_message">%1$s Android App\n\nversion: %2$s</string>
     
     <string name="actionbar_sync">Refresh</string>
     <string name="actionbar_upload">Upload</string>
@@ -30,41 +29,41 @@
     <string name="prefs_add_session">Add new session</string>
     <string name="prefs_create_img_thumbnails">Create image thumbnails</string>
     <string name="prefs_select_oc_account">Select an account</string>
-    <string name="prefs_summary_select_oc_account">Choose, which of your accounts the app should use.</string>
-    <string name="prefs_trackmydevice">Device tracking</string>
-    <string name="prefs_trackmydevice_summary_off">Enable ownCloud to track your device location</string>
-    <string name="prefs_trackmydevice_summary_on">Your ownCloud keeps track of this device</string>
-    <string name="prefs_trackmydevice_interval">Update interval</string>
+    <string name="prefs_summary_select_oc_account">Choose which of your accounts the app should use.</string>
+    <string name="prefs_trackmydevice">Device Tracking</string>
+    <string name="prefs_trackmydevice_summary_off">Enable this App to track your device location</string>
+    <string name="prefs_trackmydevice_summary_on">This App keeps track of this device</string>
+    <string name="prefs_trackmydevice_interval">Update Interval</string>
     <string name="prefs_trackmydevice_interval_summary">Update every %1$s minutes</string>
     <string name="prefs_accounts">Accounts</string>
-    <string name="prefs_manage_accounts">Manage accounts</string>
-    <string name="prefs_pincode">ownCloud App PIN</string>
-    <string name="prefs_pincode_summary">Protect your ownCloud client</string>
-    <string name="prefs_instant_upload">Enable instant uploading</string>
+    <string name="prefs_manage_accounts">Manage Accounts</string>
+    <string name="prefs_pincode">App PIN</string>
+    <string name="prefs_pincode_summary">Protect your client</string>
+    <string name="prefs_instant_upload">Enable instant uploads</string>
     <string name="prefs_instant_upload_summary">Instantly upload photos taken by camera</string>
     
-    <string name="auth_host_url">ownCloud URL</string>
+    <string name="auth_host_url">URL</string>
     <string name="auth_username">Username</string>
     <string name="auth_password">Password</string>
-    <string name="auth_register">I am new to ownCloud</string>
-    <string name="new_session_uri_error">Wrong URL given</string>
-    <string name="new_session_session_name_error">Wrong session name</string>
+    <string name="auth_register">I am new to %1$s</string>
+    <string name="new_session_uri_error">Incorrect address given</string>
+    <string name="new_session_session_name_error">Incorrect session name</string>
     <string name="sync_string_files">Files</string>
     <string name="uploader_no_file_selected">No file selected for upload</string>
     <string name="setup_hint_username">Username</string>
     <string name="setup_hint_password">Password</string>
     <string name="setup_hint_address">Web address</string>
     <string name="setup_hint_show_password">Show password?</string>
-    <string name="setup_title">Connect to your ownCloud</string>
+    <string name="setup_title">Connect to your %1$s</string>
     <string name="setup_btn_connect">Connect</string>
     <string name="uploader_btn_upload_text">Upload</string>
     <string name="uploader_wrn_no_account_title">No account found</string>
-    <string name="uploader_wrn_no_account_text">There are no ownCloud accounts on your device. Please setup an account first.</string>
+    <string name="uploader_wrn_no_account_text">There are no %1$s accounts on your device. Please setup an account first.</string>
     <string name="uploader_wrn_no_account_setup_btn_text">Setup</string>
     <string name="uploader_wrn_no_account_quit_btn_text">Quit</string>
 	<string name="uploader_wrn_no_content_title">No content to upload</string>
 	<string name="uploader_wrn_no_content_text">No content was received. Nothing to upload.</string>
-    <string name="uploader_error_forbidden_content">ownCloud is not allowed to access the shared content</string>
+    <string name="uploader_error_forbidden_content">%1$s is not allowed to access the shared content</string>
     <string name="uploader_info_uploading">Uploading</string>
     <string name="uploader_btn_create_dir_text">Create directory for upload</string>
 	<string name="file_list_empty">There are no files in this folder.\nNew files can be added with the \"Upload\" menu option.</string>
@@ -74,14 +73,18 @@
     <string name="filedetails_created">Created:</string>
     <string name="filedetails_modified">Modified:</string>
     <string name="filedetails_download">Download</string>
-	<string name="filedetails_redownload">Refresh</string>
+    <string name="filedetails_sync_file">Refresh</string>
+	<string name="filedetails_redownload">Redownload</string>
     <string name="filedetails_open">Open</string>
+    <string name="filedetails_renamed_in_upload_msg">File was renamed to %1$s during upload</string>
     <string name="common_yes">Yes</string>
     <string name="common_no">No</string>
     <string name="common_ok">OK</string>
-    <string name="common_cancel">Cancel</string>
+    <string name="common_cancel_download">Cancel download</string>
+    <string name="common_cancel_upload">Cancel upload</string>
+	<string name="common_cancel">Cancel</string>
     <string name="common_save_exit">Save &amp; Exit</string>
-    <string name="common_exit">Leave ownCloud</string>
+    <string name="common_exit">Leave %1$s</string>
     <string name="common_error">Error</string>
     <string name="about_title">About</string>
     
@@ -93,34 +96,50 @@
 	<string name="uploader_upload_in_progress_ticker">Uploading &#8230;</string>    
 	<string name="uploader_upload_in_progress_content">%1$d%% Uploading %2$s</string>    
 	<string name="uploader_upload_succeeded_ticker">Upload succeeded</string>
-    <string name="uploader_upload_succeeded_content_single">%1$s was successfully upload</string>
-    <string name="uploader_upload_succeeded_content_multiple">%1$d files were successfully upload</string>
+    <string name="uploader_upload_succeeded_content_single">%1$s was successfully uploaded</string>
+    <string name="uploader_upload_succeeded_content_multiple">%1$d files were successfully uploaded</string>
     <string name="uploader_upload_failed_ticker">Upload failed</string>
     <string name="uploader_upload_failed_content_single">Upload of %1$s could not be completed</string>
     <string name="uploader_upload_failed_content_multiple">Upload failed: %1$d/%2$d files were upload</string>
     <string name="downloader_download_in_progress_ticker">Downloading &#8230;</string>
     <string name="downloader_download_in_progress_content">%1$d%% Downloading %2$s</string>
-    <string name="downloader_download_succeeded_ticker">Download suceeded</string>
-    <string name="downloader_download_succeeded_content">%1$s was successfully download</string>
+    <string name="downloader_download_succeeded_ticker">Download succeeded</string>
+    <string name="downloader_download_succeeded_content">%1$s was successfully downloaded</string>
     <string name="downloader_download_failed_ticker">Download failed</string>
     <string name="downloader_download_failed_content">Download of %1$s could not be completed</string>
     <string name="common_choose_account">Choose account</string>
     <string name="sync_string_contacts">Contacts</string>
 	<string name="sync_fail_ticker">Synchronization failed</string>
     <string name="sync_fail_content">Synchronization of %1$s could not be completed</string>
+	<string name="sync_conflicts_in_favourites_ticker">Conflicts found</string>
+	<string name="sync_conflicts_in_favourites_content">%1$d kept-in-sync files could not be sync\'ed</string>
+    <string name="sync_fail_in_favourites_ticker">Kept-in-sync files failed</string>
+	<string name="sync_fail_in_favourites_content">Contents of %1$d files could not be sync\'ed (%2$d conflicts)</string>
+	<string name="sync_foreign_files_forgotten_ticker">Some local files were forgotten</string>
+	<string name="sync_foreign_files_forgotten_content">%1$d files out of the ownCloud directory could not be copied into</string>
+	<string name="sync_foreign_files_forgotten_explanation">"As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded in previous versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronization. You may either leave the file(s) as is and remove the link to %3$s, or move the file(s) into the %1$s directory and retain the link to %4$s.\n\nListed below are the local file(s), and the the remote file(s) in %5$s they were linked to.</string>
+    
+    <string name="foreign_files_move">"Move all"</string>
+    <string name="foreign_files_success">"All files were moved"</string>
+    <string name="foreign_files_fail">"Some files could not be moved"</string>
+    <string name="foreign_files_local_text">"Local: %1$s"</string>
+	<string name="foreign_files_remote_text">"Remote: %1$s"</string>
+
+	<string name="upload_query_move_foreign_files">There is not space enough to copy the selected files into the %1$s folder. Would like to move them into instead? </string>	
+    	
 	<string name="use_ssl">Use Secure Connection</string>
-    <string name="location_no_provider">ownCloud cannot track your device. Please check your location settings</string>
+    <string name="location_no_provider">%1$s cannot track your device. Please check your location settings</string>
     
     <string name="pincode_enter_pin_code">Please, insert your App PIN</string>
     <string name="pincode_enter_new_pin_code">Please, insert your new App PIN</string>
-    <string name="pincode_configure_your_pin">Enter ownCloud App PIN</string>
+    <string name="pincode_configure_your_pin">Enter your App PIN</string>
     <string name="pincode_configure_your_pin_explanation">The PIN will be requested every time the app is started</string> 
-    <string name="pincode_reenter_your_pincode">Reenter ownCloud App PIN, please</string>
-    <string name="pincode_remove_your_pincode">Remove your ownCloud App PIN</string>
-    <string name="pincode_mismatch">Both ownCloud App PIN are not the same</string> 
-    <string name="pincode_wrong">Incorrect ownCloud App PIN</string>
-    <string name="pincode_removed">ownCloud App PIN removed</string>
-    <string name="pincode_stored">ownCloud App PIN stored</string>
+    <string name="pincode_reenter_your_pincode">Please, reenter your App PIN</string>
+    <string name="pincode_remove_your_pincode">Remove your App PIN</string>
+    <string name="pincode_mismatch">The App PINs are not the same</string> 
+    <string name="pincode_wrong">Incorrect App PIN</string>
+    <string name="pincode_removed">App PIN removed</string>
+    <string name="pincode_stored">App PIN stored</string>
     
     <string-array name="prefs_trackmydevice_intervall_keys">
     	<item>15 Minutes</item>
@@ -135,56 +154,65 @@
 	</string-array>
     <string name="auth_trying_to_login">Trying to login…</string>
     <string name="auth_no_net_conn_title">No network connection</string>
-    <string name="auth_no_net_conn_message">No network connection have been detected, check your Internet connection and try again.</string>
+    <string name="auth_no_net_conn_message">No network connection has been detected, check your Internet connection and try again.</string>
     <string name="auth_connect_anyway">Connect anyway</string>
     <string name="auth_nossl_plain_ok_title">Secure connection unavailable.</string>
-    <string name="auth_nossl_plain_ok_message">Application couldn\'t establish a secure connection to server. Although non secure connection is available. You may continue or cancel.</string>
+    <string name="auth_nossl_plain_ok_message">The Application cannot establish a secure connection to the server. A non secure connection is available. You may continue or cancel.</string>
     <string name="auth_connection_established">Connection established</string>
     <string name="auth_testing_connection">Testing connection…</string>
-    <string name="auth_not_configured_title">Malformed ownCloud configuration</string>
-    <string name="auth_not_configured_message">It seems that your ownCloud instance is not correctly configured. Contact your administrator for more details.</string>
+    <string name="auth_not_configured_title">Malformed server configuration</string>
+    <string name="auth_not_configured_message">It seems that your server instance is not correctly configured. Contact your administrator for more details.</string>
     <string name="auth_unknown_error_title">Unknown error occurred!</string>
-    <string name="auth_unknown_error_message">Unknown error occurred. Please contact authors and include logs from your device.</string>
+    <string name="auth_unknown_error_message">An unknown error occurred. Please contact support and include logs from your device.</string>
     <string name="auth_unknown_host_title">Couldn\'t find host</string>
     <string name="auth_unknown_host_message">Couldn\'t find the entered host. Please check hostname and server availability and try again.</string>
-    <string name="auth_incorrect_path_title">ownCloud instance not found</string>
-    <string name="auth_incorrect_path_message">Application couldn\'t find ownClound instance at given path. Please check your path and try again.</string>
+    <string name="auth_incorrect_path_title">Server instance not found</string>
+    <string name="auth_incorrect_path_message">Application couldn\'t find a server instance at the given path. Please check your path and try again.</string>
     <string name="auth_timeout_title">The server took too long to respond</string>
     <string name="auth_incorrect_address_title">Malformed URL</string>
 	<string name="auth_ssl_general_error_title">SSL initialization failed</string>
 	<string name="auth_ssl_unverified_server_title">Unverified SSL server\'s identity</string>
-	<string name="auth_bad_oc_version_title">Unrecognized ownCloud server version</string>
+	<string name="auth_bad_oc_version_title">Unrecognized server version</string>
 	<string name="auth_wrong_connection_title">Couldn\'t establish connection</string>
 	<string name="auth_secure_connection">Secure connection established</string>
     <string name="auth_login_details">Login details</string>
+    <string name="auth_unauthorized">Invalid login / password</string>
+    <string name="auth_not_found">Wrong path given</string>
+    <string name="auth_internal">Internal server error, code %1$d</string>
     
-    <string name="crashlog_message">Application terminated unexpectedly. Would you like to submit crash report?</string>
+    <string name="crashlog_message">Application terminated unexpectedly. Would you like to submit a crash report?</string>
     <string name="crashlog_send_report">Send report</string>
     <string name="crashlog_dont_send_report">Don\'t send report</string>
     
     <string name="extensions_avail_title">Extensions available!</string>
-    <string name="extensions_avail_message">Looks like your ownCloud instance is supporting advanced extensions. Would you like to see extensions available for android ?</string>
+    <string name="extensions_avail_message">Looks like your server instance is supporting advanced extensions. Would you like to see extensions available for android ?</string>
     <string name="fd_keep_in_sync">Keep file up to date</string>
     <string name="common_share">Share</string>
     <string name="common_rename">Rename</string>
     <string name="common_remove">Remove</string>
     
 	  <string name="confirmation_remove_alert">"Do you really want to remove %1$s ?"</string>
+	  <string name="confirmation_remove_folder_alert">"Do you really want to remove %1$s and its contents ?"</string>
 	  <string name="confirmation_remove_local">Local only</string>
+	  <string name="confirmation_remove_folder_local">Local contents only</string>
 	  <string name="confirmation_remove_remote">Remove from server</string>
 	  <string name="confirmation_remove_remote_and_local">Remote and local</string>
 
-    <string name="remove_success_msg">"Successful removal"</string>
-    <string name="remove_fail_msg">"Removal could not be completed"</string>
+    <string name="remove_success_msg">"Removal succeeded"</string>
+    <string name="remove_fail_msg">"Removal failed"</string>
     
-    <string name="rename_local_fail_msg">"Local copy could not be renamed; try a differente new name"</string>
+    <string name="rename_dialog_title">Enter a new name</string>
+    <string name="rename_local_fail_msg">"Local copy could not be renamed; try a different name"</string>
     <string name="rename_server_fail_msg">"Rename could not be completed"</string>
         
+    <string name="sync_file_fail_msg">Remote file could not be checked</string> 
+    <string name="sync_file_nothing_to_do_msg">File contents already synchronized</string> 
+    
     <string name="create_dir_fail_msg">Directory could not be created</string>
     
     <string name="wait_a_moment">Wait a moment</string>
 	
-    <string name="filedisplay_unexpected_bad_get_content">"Unexpected problem ; please, try other app to select the file"</string>
+    <string name="filedisplay_unexpected_bad_get_content">"Unexpected problem ; please select the file from a different app"</string>
     <string name="filedisplay_no_file_selected">No file was selected</string>
     
     <string name="oauth_host_url">oAuth2 URL</string> 
@@ -197,7 +225,7 @@
     <string name="ssl_validator_header">The identity of the site could not be verified</string>
     <string name="ssl_validator_reason_cert_not_trusted">- The server certificate is not trusted</string>
     <string name="ssl_validator_reason_cert_expired">- The server certificate expired</string>
-    <string name="ssl_validator_reason_cert_not_yet_valid">- The server certificate is too young</string>
+    <string name="ssl_validator_reason_cert_not_yet_valid">- The server certificate valid dates are in the future</string>
     <string name="ssl_validator_reason_hostname_not_verified">- The URL does not match the hostname in the certificate</string>
     <string name="ssl_validator_certificate_not_available">The server certificate could not be obtained</string>
     <string name="ssl_validator_question">Do you want to trust this certificate anyway?</string>
@@ -221,9 +249,15 @@
     <string name="text_placeholder">This is a placeholder</string>
     
     <string name="instant_upload_on_wifi">Upload pictures via WiFi only</string>
+	<string name="instant_upload_path">/InstantUpload</string>
+    
     <string name="conflict_title">Update conflict</string>
     <string name="conflict_message">Remote file %s is not synchronized with local file. Continuing will replace content of file on server.</string>
     <string name="conflict_keep_both">Keep both</string>
     <string name="conflict_overwrite">Overwrite</string>
     <string name="conflict_dont_upload">Don\'t upload</string>
+    
+    <!-- we need to improve the communication of errors to the user -->
+    <string name="error__upload__local_file_not_copied">%1$s could not be copied to %2$s local directory</string>
+    
 </resources>

+ 4 - 0
res/values/urls.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="url_account_register">"https://owncloud.com/mobile/new"</string>
+</resources>

+ 5 - 4
src/com/owncloud/android/Uploader.java

@@ -129,7 +129,7 @@ public class Uploader extends ListActivity implements OnItemClickListener, andro
         case DIALOG_NO_ACCOUNT:
             builder.setIcon(android.R.drawable.ic_dialog_alert);
             builder.setTitle(R.string.uploader_wrn_no_account_title);
-            builder.setMessage(R.string.uploader_wrn_no_account_text);
+            builder.setMessage(String.format(getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name)));
             builder.setCancelable(false);
             builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {
                 @Override
@@ -257,7 +257,7 @@ public class Uploader extends ListActivity implements OnItemClickListener, andro
         // click on folder in the list
         Log.d(TAG, "on item click");
         Vector<OCFile> tmpfiles = mStorageManager.getDirectoryContent(mFile);
-        if (tmpfiles == null) return;
+        if (tmpfiles.size() <= 0) return;
         // filter on dirtype
         Vector<OCFile> files = new Vector<OCFile>();
         for (OCFile f : tmpfiles)
@@ -325,7 +325,7 @@ public class Uploader extends ListActivity implements OnItemClickListener, andro
         mFile = mStorageManager.getFileByPath(full_path);
         if (mFile != null) {
             Vector<OCFile> files = mStorageManager.getDirectoryContent(mFile);
-            if (files != null) {
+            if (files.size() > 0) {
                 List<HashMap<String, Object>> data = new LinkedList<HashMap<String,Object>>();
                 for (OCFile f : files) {
                     HashMap<String, Object> h = new HashMap<String, Object>();
@@ -432,7 +432,8 @@ public class Uploader extends ListActivity implements OnItemClickListener, andro
             finish();
             
         } catch (SecurityException e) {
-            Toast.makeText(this, getString(R.string.uploader_error_forbidden_content), Toast.LENGTH_LONG).show();
+            String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name));
+            Toast.makeText(this, message, Toast.LENGTH_LONG).show();            
         }
     }
 

+ 4 - 3
src/com/owncloud/android/authenticator/AuthenticationRunnable.java

@@ -22,6 +22,7 @@ import java.net.URL;
 
 import org.apache.commons.httpclient.HttpStatus;
 
+import com.owncloud.android.R;
 import com.owncloud.android.network.OwnCloudClientUtils;
 
 import eu.alefzero.webdav.WebdavClient;
@@ -64,13 +65,13 @@ public class AuthenticationRunnable implements Runnable {
             postResult(true, uri.toString());
             break;
         case HttpStatus.SC_UNAUTHORIZED:
-            postResult(false, "Invalid login or/and password");
+            postResult(false, mContext.getString(R.string.auth_unauthorized));
             break;
         case HttpStatus.SC_NOT_FOUND:
-            postResult(false, "Wrong path given");
+            postResult(false, mContext.getString(R.string.auth_not_found));
             break;
         default:
-            postResult(false, "Internal server error, code: " + login_result);
+            postResult(false, String.format(mContext.getString(R.string.auth_internal), login_result));
         }
     }
 

+ 4 - 0
src/com/owncloud/android/datamodel/DataStorageManager.java

@@ -40,4 +40,8 @@ public interface DataStorageManager {
     public Vector<OCFile> getDirectoryContent(OCFile f);
     
     public void removeFile(OCFile file, boolean removeLocalCopy);
+    
+    public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent);
+
+    public void moveDirectory(OCFile dir, String newPath);
 }

+ 147 - 23
src/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -27,7 +27,7 @@ import java.util.Vector;
 
 import com.owncloud.android.db.ProviderMeta;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
-import com.owncloud.android.files.services.FileDownloader;
+import com.owncloud.android.utils.FileStorageUtils;
 
 import android.accounts.Account;
 import android.content.ContentProviderClient;
@@ -108,6 +108,7 @@ public class FileDataStorageManager implements DataStorageManager {
         boolean overriden = false;
         ContentValues cv = new ContentValues();
         cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
+        cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData());
         cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp());
         cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
         cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
@@ -118,16 +119,18 @@ public class FileDataStorageManager implements DataStorageManager {
         if (!file.isDirectory())
             cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
         cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
-        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDate());
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
         cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0);
 
-        if (fileExists(file.getRemotePath())) {
-            OCFile oldFile = getFileByPath(file.getRemotePath());
-            if (file.getStoragePath() == null && oldFile.getStoragePath() != null)
-                file.setStoragePath(oldFile.getStoragePath());
-            if (!file.isDirectory());
-                cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
-            file.setFileId(oldFile.getFileId());
+        boolean sameRemotePath = fileExists(file.getRemotePath());
+        if (sameRemotePath ||
+            fileExists(file.getFileId())        ) {           // for renamed files; no more delete and create
+            
+            if (sameRemotePath) {
+                OCFile oldFile = getFileByPath(file.getRemotePath());
+                file.setFileId(oldFile.getFileId());
+            }
 
             overriden = true;
             if (getContentResolver() != null) {
@@ -187,6 +190,7 @@ public class FileDataStorageManager implements DataStorageManager {
             file = filesIt.next();
             ContentValues cv = new ContentValues();
             cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
+            cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData());
             cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp());
             cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
             cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
@@ -197,22 +201,32 @@ public class FileDataStorageManager implements DataStorageManager {
             if (!file.isDirectory())
                 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
             cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
-            cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDate());
+            cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
+            cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
             cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0);
 
             if (fileExists(file.getRemotePath())) {
-                OCFile tmpfile = getFileByPath(file.getRemotePath());
-                file.setStoragePath(tmpfile.getStoragePath());
-                if (!file.isDirectory());
-                    cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
-                file.setFileId(tmpfile.getFileId());
-
+                OCFile oldFile = getFileByPath(file.getRemotePath());
+                file.setFileId(oldFile.getFileId());
                 operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
                         withValues(cv).
                         withSelection(  ProviderTableMeta._ID + "=?", 
                                         new String[] { String.valueOf(file.getFileId()) })
                         .build());
                 
+            } else if (fileExists(file.getFileId())) {
+                    OCFile oldFile = getFileById(file.getFileId());
+                    if (file.getStoragePath() == null && oldFile.getStoragePath() != null)
+                        file.setStoragePath(oldFile.getStoragePath());
+                    if (!file.isDirectory());
+                        cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
+
+                    operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
+                            withValues(cv).
+                            withSelection(  ProviderTableMeta._ID + "=?", 
+                                            new String[] { String.valueOf(file.getFileId()) })
+                            .build());
+                    
             } else {
                 operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build());
             }
@@ -280,8 +294,8 @@ public class FileDataStorageManager implements DataStorageManager {
 
     @Override
     public Vector<OCFile> getDirectoryContent(OCFile f) {
+        Vector<OCFile> ret = new Vector<OCFile>();
         if (f != null && f.isDirectory() && f.getFileId() != -1) {
-            Vector<OCFile> ret = new Vector<OCFile>();
 
             Uri req_uri = Uri.withAppendedPath(
                     ProviderTableMeta.CONTENT_URI_DIR,
@@ -314,9 +328,8 @@ public class FileDataStorageManager implements DataStorageManager {
             
             Collections.sort(ret);
             
-            return ret;
         }
-        return null;
+        return ret;
     }
 
     private boolean fileExists(String cmp_key, String value) {
@@ -389,10 +402,12 @@ public class FileDataStorageManager implements DataStorageManager {
                 file.setStoragePath(c.getString(c
                         .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)));
                 if (file.getStoragePath() == null) {
-                    // try to find existing file and bind it with current account
-                    File f = new File(FileDownloader.getSavePath(mAccount.name) + file.getRemotePath());
-                    if (f.exists())
+                    // try to find existing file and bind it with current account; - with the current update of SynchronizeFolderOperation, this won't be necessary anymore after a full synchronization of the account
+                    File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
+                    if (f.exists()) {
                         file.setStoragePath(f.getAbsolutePath());
+                        file.setLastSyncDateForData(f.lastModified());
+                    }
                 }
             }
             file.setFileLength(c.getLong(c
@@ -401,8 +416,12 @@ public class FileDataStorageManager implements DataStorageManager {
                     .getColumnIndex(ProviderTableMeta.FILE_CREATION)));
             file.setModificationTimestamp(c.getLong(c
                     .getColumnIndex(ProviderTableMeta.FILE_MODIFIED)));
-            file.setLastSyncDate(c.getLong(c
+            file.setModificationTimestampAtLastSyncForData(c.getLong(c
+                    .getColumnIndex(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA)));
+            file.setLastSyncDateForProperties(c.getLong(c
                     .getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE)));
+            file.setLastSyncDateForData(c.getLong(c.
+                    getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA)));
             file.setKeepInSync(c.getInt(
                                 c.getColumnIndex(ProviderTableMeta.FILE_KEEP_IN_SYNC)) == 1 ? true : false);
         }
@@ -428,6 +447,111 @@ public class FileDataStorageManager implements DataStorageManager {
         if (file.isDown() && removeLocalCopy) {
             new File(file.getStoragePath()).delete();
         }
+        if (file.isDirectory() && removeLocalCopy) {
+            File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
+            if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) {
+                f.delete();
+            }
+        }
+    }
+
+    @Override
+    public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent) {
+        // TODO consider possible failures
+        if (dir != null && dir.isDirectory() && dir.getFileId() != -1) {
+            Vector<OCFile> children = getDirectoryContent(dir);
+            if (children.size() > 0) {
+                OCFile child = null;
+                for (int i=0; i<children.size(); i++) {
+                    child = children.get(i);
+                    if (child.isDirectory()) {
+                        removeDirectory(child, removeDBData, removeLocalContent);
+                    } else {
+                        if (removeDBData) {
+                            removeFile(child, removeLocalContent);
+                        } else if (removeLocalContent) {
+                            if (child.isDown()) {
+                                new File(child.getStoragePath()).delete();
+                            }
+                        }
+                    }
+                }
+                if (removeDBData) {
+                    removeFile(dir, true);
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Updates database for a folder that was moved to a different location.
+     * 
+     * TODO explore better (faster) implementations
+     * TODO throw exceptions up !
+     */
+    @Override
+    public void moveDirectory(OCFile dir, String newPath) {
+        // TODO check newPath
+        
+        if (dir != null && dir.isDirectory() && dir.fileExists() && !dir.getFileName().equals(OCFile.PATH_SEPARATOR)) {
+            /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir')
+            Cursor c = null;
+            if (getContentProvider() != null) {
+                try {
+                    c = getContentProvider().query(ProviderTableMeta.CONTENT_URI, 
+                                                    null,
+                                                    ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ?",
+                                                    new String[] { mAccount.name, dir.getRemotePath() + "%" }, null);
+                } catch (RemoteException e) {
+                    Log.e(TAG, e.getMessage());
+                }
+            } else {
+                c = getContentResolver().query(ProviderTableMeta.CONTENT_URI, 
+                                                null,
+                                                ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ?",
+                                                new String[] { mAccount.name, dir.getRemotePath() + "%" }, null);
+            }
+
+            /// 2. prepare a batch of update operations to change all the descendants
+            ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(c.getCount());
+            int lengthOfOldPath = dir.getRemotePath().length();
+            String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name);
+            int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath;
+            if (c.moveToFirst()) {
+                do {
+                    ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object
+                    OCFile child = createFileInstance(c);
+                    cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath));
+                    if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) {
+                        cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath));
+                    }
+                    operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
+                                                                        withValues(cv).
+                                                                        withSelection(  ProviderTableMeta._ID + "=?", 
+                                                                                new String[] { String.valueOf(child.getFileId()) })
+                                                                                .build());
+                } while (c.moveToNext());
+            }
+            c.close();
+            
+            /// 3. apply updates in batch
+            try {
+                if (getContentResolver() != null) {
+                    getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations);
+                
+                } else {
+                    getContentProvider().applyBatch(operations);
+                }
+                
+            } catch (OperationApplicationException e) {
+                Log.e(TAG, "Fail to update descendants of " + dir.getFileId() + " in database", e);
+                
+            } catch (RemoteException e) {
+                Log.e(TAG, "Fail to update desendants of " + dir.getFileId() + " in database", e);
+            }
+            
+        }
     }
 
 }

+ 93 - 13
src/com/owncloud/android/datamodel/OCFile.java

@@ -22,6 +22,7 @@ import java.io.File;
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 public class OCFile implements Parcelable, Comparable<OCFile> {
 
@@ -38,19 +39,25 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     };
 
     public static final String PATH_SEPARATOR = "/";
+
+    private static final String TAG = OCFile.class.getSimpleName();
     
     private long mId;
     private long mParentId;
     private long mLength;
     private long mCreationTimestamp;
     private long mModifiedTimestamp;
+    private long mModifiedTimestampAtLastSyncForData;
     private String mRemotePath;
     private String mLocalPath;
     private String mMimeType;
     private boolean mNeedsUpdating;
-    private long mLastSyncDate;
+    private long mLastSyncDateForProperties;
+    private long mLastSyncDateForData;
     private boolean mKeepInSync;
 
+    private String mEtag;
+
     /**
      * Create new {@link OCFile} with given path.
      * 
@@ -78,12 +85,14 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         mLength = source.readLong();
         mCreationTimestamp = source.readLong();
         mModifiedTimestamp = source.readLong();
+        mModifiedTimestampAtLastSyncForData = source.readLong();
         mRemotePath = source.readString();
         mLocalPath = source.readString();
         mMimeType = source.readString();
         mNeedsUpdating = source.readInt() == 0;
         mKeepInSync = source.readInt() == 1;
-        mLastSyncDate = source.readLong();
+        mLastSyncDateForProperties = source.readLong();
+        mLastSyncDateForData = source.readLong();
     }
 
     @Override
@@ -93,12 +102,14 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         dest.writeLong(mLength);
         dest.writeLong(mCreationTimestamp);
         dest.writeLong(mModifiedTimestamp);
+        dest.writeLong(mModifiedTimestampAtLastSyncForData);
         dest.writeString(mRemotePath);
         dest.writeString(mLocalPath);
         dest.writeString(mMimeType);
         dest.writeInt(mNeedsUpdating ? 1 : 0);
         dest.writeInt(mKeepInSync ? 1 : 0);
-        dest.writeLong(mLastSyncDate);
+        dest.writeLong(mLastSyncDateForProperties);
+        dest.writeLong(mLastSyncDateForData);
     }
     
     /**
@@ -188,9 +199,10 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     }
 
     /**
-     * Get a UNIX timestamp of the file modification time
-     * 
-     * @return A UNIX timestamp of the modification time
+     * Get a UNIX timestamp of the file modification time.
+     *
+     * @return  A UNIX timestamp of the modification time, corresponding to the value returned by the server
+     *          in the last synchronization of the properties of this file. 
      */
     public long getModificationTimestamp() {
         return mModifiedTimestamp;
@@ -199,12 +211,40 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     /**
      * Set a UNIX timestamp of the time the time the file was modified.
      * 
+     * To update with the value returned by the server in every synchronization of the properties 
+     * of this file.
+     * 
      * @param modification_timestamp to set
      */
     public void setModificationTimestamp(long modification_timestamp) {
         mModifiedTimestamp = modification_timestamp;
     }
 
+    
+    /**
+     * Get a UNIX timestamp of the file modification time.
+     *
+     * @return  A UNIX timestamp of the modification time, corresponding to the value returned by the server
+     *          in the last synchronization of THE CONTENTS of this file. 
+     */
+    public long getModificationTimestampAtLastSyncForData() {
+        return mModifiedTimestampAtLastSyncForData;
+    }
+
+    /**
+     * Set a UNIX timestamp of the time the time the file was modified.
+     * 
+     * To update with the value returned by the server in every synchronization of THE CONTENTS 
+     * of this file.
+     * 
+     * @param modification_timestamp to set
+     */
+    public void setModificationTimestampAtLastSyncForData(long modificationTimestamp) {
+        mModifiedTimestampAtLastSyncForData = modificationTimestamp;
+    }
+
+    
+    
     /**
      * Returns the filename and "/" for the root directory
      * 
@@ -212,7 +252,25 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
      */
     public String getFileName() {
         File f = new File(getRemotePath());
-        return f.getName().length() == 0 ? "/" : f.getName();
+        return f.getName().length() == 0 ? PATH_SEPARATOR : f.getName();
+    }
+    
+    /**
+     * Sets the name of the file
+     * 
+     * Does nothing if the new name is null, empty or includes "/" ; or if the file is the root directory 
+     */
+    public void setFileName(String name) {
+        Log.d(TAG, "OCFile name changin from " + mRemotePath);
+        if (name != null && name.length() > 0 && !name.contains(PATH_SEPARATOR) && !mRemotePath.equals(PATH_SEPARATOR)) {
+            String parent = (new File(getRemotePath())).getParent();
+            parent = (parent.endsWith(PATH_SEPARATOR)) ? parent : parent + PATH_SEPARATOR;
+            mRemotePath =  parent + name;
+            if (isDirectory()) {
+                mRemotePath += PATH_SEPARATOR;
+            }
+            Log.d(TAG, "OCFile name changed to " + mRemotePath);
+        }
     }
 
     /**
@@ -254,7 +312,9 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         mLength = 0;
         mCreationTimestamp = 0;
         mModifiedTimestamp = 0;
-        mLastSyncDate = 0;
+        mModifiedTimestampAtLastSyncForData = 0;
+        mLastSyncDateForProperties = 0;
+        mLastSyncDateForData = 0;
         mKeepInSync = false;
         mNeedsUpdating = false;
     }
@@ -322,12 +382,20 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         return mNeedsUpdating;
     }
     
-    public long getLastSyncDate() {
-        return mLastSyncDate;
+    public long getLastSyncDateForProperties() {
+        return mLastSyncDateForProperties;
+    }
+    
+    public void setLastSyncDateForProperties(long lastSyncDate) {
+        mLastSyncDateForProperties = lastSyncDate;
     }
     
-    public void setLastSyncDate(long lastSyncDate) {
-        mLastSyncDate = lastSyncDate;
+    public long getLastSyncDateForData() {
+        return mLastSyncDateForData;
+    }
+
+    public void setLastSyncDateForData(long lastSyncDate) {
+        mLastSyncDateForData = lastSyncDate;
     }
 
     public void setKeepInSync(boolean keepInSync) {
@@ -370,8 +438,20 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     @Override
     public String toString() {
         String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSinc=%s]";
-        asString = String.format(asString, new Long(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, new Long(mParentId), new Boolean(mKeepInSync));
+        asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync));
         return asString;
     }
 
+    public String getEtag() {
+        return mEtag;
+    }
+
+    public long getLocalModificationTimestamp() {
+        if (mLocalPath != null && mLocalPath.length() > 0) {
+            File f = new File(mLocalPath);
+            return f.lastModified();
+        }
+        return 0;
+    }
+
 }

+ 5 - 5
src/com/owncloud/android/db/DbHandler.java

@@ -31,14 +31,14 @@ import android.database.sqlite.SQLiteOpenHelper;
  */
 public class DbHandler {
     private SQLiteDatabase mDB;
-    private OpenerHepler mHelper;
+    private OpenerHelper mHelper;
     private final String mDatabaseName = "ownCloud";
     private final int mDatabaseVersion = 1;
     
     private final String TABLE_INSTANT_UPLOAD = "instant_upload";
 
     public DbHandler(Context context) {
-        mHelper = new OpenerHepler(context);
+        mHelper = new OpenerHelper(context);
         mDB = mHelper.getWritableDatabase();
     }
 
@@ -74,15 +74,15 @@ public class DbHandler {
         
     }
     
-    private class OpenerHepler extends SQLiteOpenHelper {
-        public OpenerHepler(Context context) {
+    private class OpenerHelper extends SQLiteOpenHelper {
+        public OpenerHelper(Context context) {
             super(context, mDatabaseName, null, mDatabaseVersion);
         }
 
         @Override
         public void onCreate(SQLiteDatabase db) {
             db.execSQL("CREATE TABLE " + TABLE_INSTANT_UPLOAD + " ("
-            		+ " _id INTEGET PRIMARY KEY, "
+            		+ " _id INTEGER PRIMARY KEY, "
             		+ " path TEXT,"
             		+ " account TEXT);");
         }

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

@@ -31,7 +31,7 @@ public class ProviderMeta {
     public static final String AUTHORITY_FILES = "org.owncloud";
     public static final String DB_FILE = "owncloud.db";
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 2;
+    public static final int DB_VERSION = 4;
 
     private ProviderMeta() {
     }
@@ -52,12 +52,14 @@ public class ProviderMeta {
         public static final String FILE_NAME = "filename";
         public static final String FILE_CREATION = "created";
         public static final String FILE_MODIFIED = "modified";
+        public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data";
         public static final String FILE_CONTENT_LENGTH = "content_length";
         public static final String FILE_CONTENT_TYPE = "content_type";
         public static final String FILE_STORAGE_PATH = "media_path";
         public static final String FILE_PATH = "path";
         public static final String FILE_ACCOUNT_OWNER = "file_owner";
-        public static final String FILE_LAST_SYNC_DATE = "last_sync_date";
+        public static final String FILE_LAST_SYNC_DATE = "last_sync_date";  // _for_properties, but let's keep it as it is
+        public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";
         public static final String FILE_KEEP_IN_SYNC = "keep_in_sync";
 
         public static final String DEFAULT_SORT_ORDER = FILE_NAME

+ 1 - 1
src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java

@@ -66,7 +66,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
         // remove successfull uploading, ignore rest for reupload on reconnect
         if (intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false)) {
             DbHandler db = new DbHandler(context);
-            String localPath = intent.getStringExtra(FileUploader.EXTRA_FILE_PATH);
+            String localPath = intent.getStringExtra(FileUploader.EXTRA_OLD_FILE_PATH);
             if (!db.removeIUPendingFile(localPath,
                                         intent.getStringExtra(FileUploader.ACCOUNT_NAME))) {
                 Log.w(TAG, "Tried to remove non existing instant upload file " + localPath);

+ 46 - 80
src/com/owncloud/android/files/OwnCloudFileObserver.java

@@ -18,16 +18,15 @@
 
 package com.owncloud.android.files;
 
-import java.util.LinkedList;
-import java.util.List;
+import java.io.File;
 
-import com.owncloud.android.datamodel.DataStorageManager;
+import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.OwnCloudFileObserver.FileObserverStatusListener.Status;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.network.OwnCloudClientUtils;
 import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.ui.activity.ConflictsResolveActivity;
 
 import eu.alefzero.webdav.WebdavClient;
 
@@ -41,99 +40,66 @@ public class OwnCloudFileObserver extends FileObserver {
 
     public static int CHANGES_ONLY = CLOSE_WRITE;
     
-    private static String TAG = "OwnCloudFileObserver";
+    private static String TAG = OwnCloudFileObserver.class.getSimpleName();
+    
     private String mPath;
     private int mMask;
-    DataStorageManager mStorage;
-    Account mOCAccount;
-    OCFile mFile;
-    static Context mContext;
-    List<FileObserverStatusListener> mListeners;
-    
-    public OwnCloudFileObserver(String path) {
-        this(path, ALL_EVENTS);
-    }
+    private Account mOCAccount;
+    //private OCFile mFile;
+    private Context mContext;
+
     
-    public OwnCloudFileObserver(String path, int mask) {
+    public OwnCloudFileObserver(String path, Account account, Context context, int mask) {
         super(path, mask);
+        if (path == null)
+            throw new IllegalArgumentException("NULL path argument received"); 
+        /*if (file == null)
+            throw new IllegalArgumentException("NULL file argument received");*/ 
+        if (account == null)
+            throw new IllegalArgumentException("NULL account argument received"); 
+        if (context == null)
+            throw new IllegalArgumentException("NULL context argument received");
+        /*if (!path.equals(file.getStoragePath()) && !path.equals(FileStorageUtils.getDefaultSavePathFor(account.name, file)))
+            throw new IllegalArgumentException("File argument is not linked to the local file set in path argument"); */
         mPath = path;
-        mMask = mask;
-        mListeners = new LinkedList<FileObserverStatusListener>();
-    }
-    
-    public void setAccount(Account account) {
+        //mFile = file;
         mOCAccount = account;
-    }
-    
-    public void setStorageManager(DataStorageManager manager) {
-        mStorage = manager;
-    }
-    
-    public void setOCFile(OCFile file) {
-        mFile = file;
-    }
-    
-    public void setContext(Context context) {
-        mContext = context;
-    }
-
-    public String getPath() {
-        return mPath;
-    }
-    
-    public String getRemotePath() {
-        return mFile.getRemotePath();
-    }
-    
-    public void addObserverStatusListener(FileObserverStatusListener listener) {
-        mListeners.add(listener);
+        mContext = context; 
+        mMask = mask;
     }
     
     @Override
     public void onEvent(int event, String path) {
-        Log.d(TAG, "Got file modified with event " + event + " and path " + path);
+        Log.d(TAG, "Got file modified with event " + event + " and path " + mPath + ((path != null) ? File.separator + path : ""));
         if ((event & mMask) == 0) {
-            Log.wtf(TAG, "Incorrect event " + event + " sent for file " + path +
+            Log.wtf(TAG, "Incorrect event " + event + " sent for file " + mPath + ((path != null) ? File.separator + path : "") +
                          " with registered for " + mMask + " and original path " +
                          mPath);
-            for (FileObserverStatusListener l : mListeners)
-                l.OnObservedFileStatusUpdate(mPath, getRemotePath(), mOCAccount, Status.INCORRECT_MASK);
             return;
         }
         WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mOCAccount, mContext);
-        SynchronizeFileOperation sfo = new SynchronizeFileOperation(mFile.getRemotePath(), mStorage, mOCAccount, mContext);
+        FileDataStorageManager storageManager = new FileDataStorageManager(mOCAccount, mContext.getContentResolver());
+        OCFile file = storageManager.getFileByLocalPath(mPath);     // a fresh object is needed; many things could have occurred to the file since it was registered to observe
+                                                                    // again, assuming that local files are linked to a remote file AT MOST, SOMETHING TO BE DONE; 
+        SynchronizeFileOperation sfo = new SynchronizeFileOperation(file, 
+                                                                    null, 
+                                                                    storageManager, 
+                                                                    mOCAccount, 
+                                                                    true, 
+                                                                    true, 
+                                                                    mContext);
         RemoteOperationResult result = sfo.execute(wc);
-        
-        if (result.getExtraData() == Boolean.TRUE) {
-            // inform user about conflict and let him decide what to do
-            for (FileObserverStatusListener l : mListeners)
-                l.OnObservedFileStatusUpdate(mPath, getRemotePath(), mOCAccount, Status.CONFLICT);
-            return;
-        }
-
-        for (FileObserverStatusListener l : mListeners)
-            l.OnObservedFileStatusUpdate(mPath, getRemotePath(), mOCAccount, Status.SENDING_TO_UPLOADER);
-        
-        Intent i = new Intent(mContext, FileUploader.class);
-        i.putExtra(FileUploader.KEY_ACCOUNT, mOCAccount);
-        i.putExtra(FileUploader.KEY_REMOTE_FILE, mFile.getRemotePath());
-        i.putExtra(FileUploader.KEY_LOCAL_FILE, mPath);
-        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
-        i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
-        mContext.startService(i);
-    }
-    
-    public interface FileObserverStatusListener {
-        public enum Status {
-            SENDING_TO_UPLOADER,
-            CONFLICT,
-            INCORRECT_MASK
+        if (result.getCode() == ResultCode.SYNC_CONFLICT) {
+            // ISSUE 5: if the user is not running the app (this is a service!), this can be very intrusive; a notification should be preferred
+            Intent i = new Intent(mContext, ConflictsResolveActivity.class);
+            i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+            i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file);
+            i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mOCAccount);
+            mContext.startActivity(i);
         }
-        
-        public void OnObservedFileStatusUpdate(String localPath,
-                                               String remotePath,
-                                               Account account,
-                                               FileObserverStatusListener.Status status);
+        // TODO save other errors in some point where the user can inspect them later;
+        //      or maybe just toast them;
+        //      or nothing, very strange fails
     }
     
 }

+ 59 - 36
src/com/owncloud/android/files/services/FileDownloader.java

@@ -25,8 +25,8 @@ import java.util.Vector;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import eu.alefzero.webdav.OnDatatransferProgressListener;
 
 import com.owncloud.android.network.OwnCloudClientUtils;
@@ -40,11 +40,8 @@ import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
-import android.content.ContentValues;
 import android.content.Intent;
-import android.net.Uri;
 import android.os.Binder;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -62,6 +59,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     public static final String EXTRA_ACCOUNT = "ACCOUNT";
     public static final String EXTRA_FILE = "FILE";
     
+    public static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
     public static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
     public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";    
     public static final String EXTRA_FILE_PATH = "FILE_PATH";
@@ -75,6 +73,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     private IBinder mBinder;
     private WebdavClient mDownloadClient = null;
     private Account mLastAccount = null;
+    private FileDataStorageManager mStorageManager;
     
     private ConcurrentMap<String, DownloadFileOperation> mPendingDownloads = new ConcurrentHashMap<String, DownloadFileOperation>();
     private DownloadFileOperation mCurrentDownload = null;
@@ -93,18 +92,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     private String buildRemoteName(Account account, OCFile file) {
         return account.name + file.getRemotePath();
     }
-    
-    public static final String getSavePath(String accountName) {
-        File sdCard = Environment.getExternalStorageDirectory();
-        return sdCard.getAbsolutePath() + "/owncloud/" + Uri.encode(accountName, "@");   
-            // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
-    }
-    
-    public static final String getTemporalPath(String accountName) {
-        File sdCard = Environment.getExternalStorageDirectory();
-        return sdCard.getAbsolutePath() + "/owncloud/tmp/" + Uri.encode(accountName, "@");
-            // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
-    }
 
     
     /**
@@ -149,6 +136,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             mPendingDownloads.putIfAbsent(downloadKey, newDownload);
             newDownload.addDatatransferProgressListener(this);
             requestedDownloads.add(downloadKey);
+            sendBroadcastNewDownload(newDownload);
             
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Not enough information provided in intent: " + e.getMessage());
@@ -202,14 +190,27 @@ 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. 
          * 
          * @param account       Owncloud account where the remote file is stored.
          * @param file          A file that could be in the queue of downloads.
          */
         public boolean isDownloading(Account account, OCFile file) {
+            String targetKey = buildRemoteName(account, file);
             synchronized (mPendingDownloads) {
-                return (mPendingDownloads.containsKey(buildRemoteName(account, file)));
+                if (file.isDirectory()) {
+                    // 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));
+                }
             }
         }
     }
@@ -264,6 +265,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             /// 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());
                 mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext());
             }
 
@@ -272,16 +274,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             try {
                 downloadResult = mCurrentDownload.execute(mDownloadClient);
                 if (downloadResult.isSuccess()) {
-                    ContentValues cv = new ContentValues();
-                    cv.put(ProviderTableMeta.FILE_STORAGE_PATH, mCurrentDownload.getSavePath());
-                    getContentResolver().update(
-                            ProviderTableMeta.CONTENT_URI,
-                            cv,
-                            ProviderTableMeta.FILE_NAME + "=? AND "
-                                    + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",
-                                    new String[] {
-                                    mCurrentDownload.getSavePath().substring(mCurrentDownload.getSavePath().lastIndexOf('/') + 1),
-                                    mLastAccount.name });
+                    saveDownloadedFile();
                 }
             
             } finally {
@@ -294,11 +287,29 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             /// notify result
             notifyDownloadResult(mCurrentDownload, downloadResult);
             
-            sendFinalBroadcast(mCurrentDownload, downloadResult);
+            sendBroadcastDownloadFinished(mCurrentDownload, downloadResult);
         }
     }
 
-    
+
+    /**
+     * Updates the OC File after a successful download.
+     */
+    private void saveDownloadedFile() {
+        OCFile file = mCurrentDownload.getFile();
+        long syncDate = System.currentTimeMillis();
+        file.setLastSyncDateForProperties(syncDate);
+        file.setLastSyncDateForData(syncDate);
+        file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());
+        file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());
+        // file.setEtag(mCurrentDownload.getEtag());    // TODO Etag, where available
+        file.setMimetype(mCurrentDownload.getMimeType());
+        file.setStoragePath(mCurrentDownload.getSavePath());
+        file.setFileLength((new File(mCurrentDownload.getSavePath()).length()));
+        mStorageManager.saveFile(file);
+    }
+
+
     /**
      * Creates a status notification to show the download progress
      * 
@@ -372,20 +383,32 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     
     
     /**
-     * Sends a broadcast 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
      */
-    private void sendFinalBroadcast(DownloadFileOperation download, RemoteOperationResult downloadResult) {
+    private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) {
         Intent end = new Intent(DOWNLOAD_FINISH_MESSAGE);
         end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
         end.putExtra(ACCOUNT_NAME, download.getAccount().name);
         end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
-        if (downloadResult.isSuccess()) {
-            end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
-        }
-        sendBroadcast(end);
+        end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+        sendStickyBroadcast(end);
+    }
+    
+    
+    /**
+     * Sends a broadcast when a new download is added to the queue.
+     * 
+     * @param download          Added download operation
+     */
+    private void sendBroadcastNewDownload(DownloadFileOperation download) {
+        Intent added = new Intent(DOWNLOAD_ADDED_MESSAGE);
+        /*added.putExtra(ACCOUNT_NAME, download.getAccount().name);
+        added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());*/
+        added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+        sendStickyBroadcast(added);
     }
 
 }

+ 139 - 139
src/com/owncloud/android/files/services/FileObserverService.java

@@ -18,15 +18,16 @@
 
 package com.owncloud.android.files.services;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
 
-import com.owncloud.android.AccountUtils;
 import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.files.OwnCloudFileObserver;
-import com.owncloud.android.files.OwnCloudFileObserver.FileObserverStatusListener;
-import com.owncloud.android.ui.activity.ConflictsResolveActivity;
+import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.utils.FileStorageUtils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -40,20 +41,20 @@ import android.os.Binder;
 import android.os.IBinder;
 import android.util.Log;
 
-public class FileObserverService extends Service implements FileObserverStatusListener {
-
-    public final static String KEY_FILE_CMD = "KEY_FILE_CMD";
-    public final static String KEY_CMD_ARG = "KEY_CMD_ARG";
+public class FileObserverService extends Service {
 
     public final static int CMD_INIT_OBSERVED_LIST = 1;
     public final static int CMD_ADD_OBSERVED_FILE = 2;
     public final static int CMD_DEL_OBSERVED_FILE = 3;
-    public final static int CMD_ADD_DOWNLOADING_FILE = 4;
 
-    private static String TAG = "FileObserverService";
-    private static List<OwnCloudFileObserver> mObservers;
-    private static List<DownloadCompletedReceiver> mDownloadReceivers;
-    private static Object mReceiverListLock = new Object();
+    public final static String KEY_FILE_CMD = "KEY_FILE_CMD";
+    public final static String KEY_CMD_ARG_FILE = "KEY_CMD_ARG_FILE";
+    public final static String KEY_CMD_ARG_ACCOUNT = "KEY_CMD_ARG_ACCOUNT";
+
+    private static String TAG = FileObserverService.class.getSimpleName();
+
+    private static Map<String, OwnCloudFileObserver> mObserversMap;
+    private static DownloadCompletedReceiverBis mDownloadReceiver;
     private IBinder mBinder = new LocalBinder();
 
     public class LocalBinder extends Binder {
@@ -62,6 +63,29 @@ public class FileObserverService extends Service implements FileObserverStatusLi
         }
     }
     
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mDownloadReceiver = new DownloadCompletedReceiverBis();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(FileDownloader.DOWNLOAD_ADDED_MESSAGE);
+        filter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE);        
+        registerReceiver(mDownloadReceiver, filter);
+        
+        mObserversMap = new HashMap<String, OwnCloudFileObserver>();
+        //initializeObservedList();
+    }
+    
+    
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mDownloadReceiver);
+        mObserversMap = null;   // TODO study carefully the life cycle of Services to grant the best possible observance
+        Log.d(TAG, "Bye, bye");
+    }
+    
+    
     @Override
     public IBinder onBind(Intent intent) {
         return mBinder;
@@ -86,13 +110,12 @@ public class FileObserverService extends Service implements FileObserverStatusLi
                 initializeObservedList();
                 break;
             case CMD_ADD_OBSERVED_FILE:
-                addObservedFile(intent.getStringExtra(KEY_CMD_ARG));
+                addObservedFile( (OCFile)intent.getParcelableExtra(KEY_CMD_ARG_FILE), 
+                                 (Account)intent.getParcelableExtra(KEY_CMD_ARG_ACCOUNT));
                 break;
             case CMD_DEL_OBSERVED_FILE:
-                removeObservedFile(intent.getStringExtra(KEY_CMD_ARG));
-                break;
-            case CMD_ADD_DOWNLOADING_FILE:
-                addDownloadingFile(intent.getStringExtra(KEY_CMD_ARG));
+                removeObservedFile( (OCFile)intent.getParcelableExtra(KEY_CMD_ARG_FILE), 
+                                    (Account)intent.getParcelableExtra(KEY_CMD_ARG_ACCOUNT));
                 break;
             default:
                 Log.wtf(TAG, "Incorrect key given");
@@ -101,10 +124,13 @@ public class FileObserverService extends Service implements FileObserverStatusLi
         return Service.START_STICKY;
     }
 
+    
+    /**
+     * Read from the local database the list of files that must to be kept synchronized and 
+     * starts file observers to monitor local changes on them
+     */
     private void initializeObservedList() {
-        if (mObservers != null) return; // nothing to do here
-        mObservers = new ArrayList<OwnCloudFileObserver>();
-        mDownloadReceivers = new ArrayList<DownloadCompletedReceiver>();
+        mObserversMap.clear();
         Cursor c = getContentResolver().query(
                 ProviderTableMeta.CONTENT_URI,
                 null,
@@ -129,148 +155,122 @@ public class FileObserverService extends Service implements FileObserverStatusLi
                 continue;
 
             String path = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH));
+            if (path == null || path.length() <= 0)
+                continue;
             OwnCloudFileObserver observer =
-                    new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY);
-            observer.setContext(getApplicationContext());
-            observer.setAccount(account);
-            observer.setStorageManager(storage);
-            observer.setOCFile(storage.getFileByPath(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH))));
-            observer.addObserverStatusListener(this);
-            observer.startWatching();
-            mObservers.add(observer);
-            Log.d(TAG, "Started watching file " + path);
+                    new OwnCloudFileObserver(   path, 
+                                                account, 
+                                                getApplicationContext(), 
+                                                OwnCloudFileObserver.CHANGES_ONLY);
+            mObserversMap.put(path, observer);
+            if (new File(path).exists()) {
+                observer.startWatching();
+                Log.d(TAG, "Started watching file " + path);
+            }
             
         } while (c.moveToNext());
         c.close();
     }
     
-    private void addObservedFile(String path) {
-        if (path == null) return;
-        if (mObservers == null) {
-            // this is very rare case when service was killed by system
-            // and observers list was deleted in that procedure
-            initializeObservedList();
-        }
-        boolean duplicate = false;
-        OwnCloudFileObserver observer = null;
-        for (int i = 0; i < mObservers.size(); ++i) {
-            observer = mObservers.get(i);
-            if (observer.getPath().equals(path))
-                duplicate = true;
-            observer.setContext(getBaseContext());
-        }
-        if (duplicate) return;
-        observer = new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY);
-        observer.setContext(getBaseContext());
-        Account account = AccountUtils.getCurrentOwnCloudAccount(getBaseContext());
-        observer.setAccount(account);
-        FileDataStorageManager storage =
-                new FileDataStorageManager(account, getContentResolver());
-        observer.setStorageManager(storage);
-        observer.setOCFile(storage.getFileByLocalPath(path));
-        observer.addObserverStatusListener(this);
-
-        DownloadCompletedReceiver receiver = new DownloadCompletedReceiver(path, observer);
-        registerReceiver(receiver, new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE));
-
-        mObservers.add(observer);
-        Log.d(TAG, "Observer added for path " + path);
-    }
     
-    private void removeObservedFile(String path) {
-        if (path == null) return;
-        if (mObservers == null) {
-            initializeObservedList();
+    /**
+     * Registers the local copy of a remote file to be observed for local changes,
+     * an automatically updated in the ownCloud server.
+     * 
+     * This method does NOT perform a {@link SynchronizeFileOperation} over the file. 
+     *
+     * TODO We are ignoring that, currently, a local file can be linked to different files
+     * in ownCloud if it's uploaded several times. That's something pending to update: we 
+     * will avoid that the same local file is linked to different remote files.
+     * 
+     * @param file      Object representing a remote file which local copy must be observed.
+     * @param account   OwnCloud account containing file.
+     */
+    private void addObservedFile(OCFile file, Account account) {
+        if (file == null) {
+            Log.e(TAG, "Trying to add a NULL file to observer");
             return;
         }
-        for (int i = 0; i < mObservers.size(); ++i) {
-            OwnCloudFileObserver observer = mObservers.get(i);
-            if (observer.getPath().equals(path)) {
-                observer.stopWatching();
-                mObservers.remove(i);
-                break;
-            }
-        }
-        Log.d(TAG, "Stopped watching " + path);
-    }
-        
-    private void addDownloadingFile(String remotePath) {
-        OwnCloudFileObserver observer = null;
-        for (OwnCloudFileObserver o : mObservers) {
-            if (o.getRemotePath().equals(remotePath)) {
-                observer = o;
-                break;
-            }
+        String localPath = file.getStoragePath();
+        if (localPath == null || localPath.length() <= 0) { // file downloading / to be download for the first time
+            localPath = FileStorageUtils.getDefaultSavePathFor(account.name, file);
         }
+        OwnCloudFileObserver observer = mObserversMap.get(localPath);
         if (observer == null) {
-            Log.e(TAG, "Couldn't find observer for remote file " + remotePath);
-            return;
+            /// the local file was never registered to observe before
+            observer = new OwnCloudFileObserver(    localPath, 
+                                                    account, 
+                                                    getApplicationContext(), 
+                                                    OwnCloudFileObserver.CHANGES_ONLY);
+            mObserversMap.put(localPath, observer);
+            Log.d(TAG, "Observer added for path " + localPath);
+        
+            if (file.isDown()) {
+                observer.startWatching();
+                Log.d(TAG, "Started watching " + localPath);
+            }   // else - the observance can't be started on a file not already down; mDownloadReceiver will get noticed when the download of the file finishes
         }
-        observer.stopWatching();
-        DownloadCompletedReceiver dcr = new DownloadCompletedReceiver(observer.getPath(), observer);
-        registerReceiver(dcr, new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE));
+        
     }
 
     
-    private static void addReceiverToList(DownloadCompletedReceiver r) {
-        synchronized(mReceiverListLock) {
-            mDownloadReceivers.add(r);
+    /**
+     * Unregisters the local copy of a remote file to be observed for local changes.
+     *
+     * Starts to watch it, if the file has a local copy to watch.
+     * 
+     * TODO We are ignoring that, currently, a local file can be linked to different files
+     * in ownCloud if it's uploaded several times. That's something pending to update: we 
+     * will avoid that the same local file is linked to different remote files.
+     *
+     * @param file      Object representing a remote file which local copy must be not observed longer.
+     * @param account   OwnCloud account containing file.
+     */
+    private void removeObservedFile(OCFile file, Account account) {
+        if (file == null) {
+            Log.e(TAG, "Trying to remove a NULL file");
+            return;
         }
-    }
-    
-    private static void removeReceiverFromList(DownloadCompletedReceiver r) {
-        synchronized(mReceiverListLock) {
-            mDownloadReceivers.remove(r);
+        String localPath = file.getStoragePath();
+        if (localPath == null || localPath.length() <= 0) {
+            localPath = FileStorageUtils.getDefaultSavePathFor(account.name, file);
         }
-    }
-
-    @Override
-    public void OnObservedFileStatusUpdate(String localPath, String remotePath, Account account, Status status) {
-        switch (status) {
-            case CONFLICT:
-            {
-                Intent i = new Intent(getApplicationContext(), ConflictsResolveActivity.class);
-                i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
-                i.putExtra("remotepath", remotePath);
-                i.putExtra("localpath", localPath);
-                i.putExtra("account", account);
-                startActivity(i);
-                break;
-            }
-            case SENDING_TO_UPLOADER:
-            case INCORRECT_MASK:
-                break;
-            default:
-                Log.wtf(TAG, "Unhandled status " + status);
+        
+        OwnCloudFileObserver observer = mObserversMap.get(localPath);
+        if (observer != null) {
+            observer.stopWatching();
+            mObserversMap.remove(observer);
+            Log.d(TAG, "Stopped watching " + localPath);
         }
+        
     }
 
-    private class DownloadCompletedReceiver extends BroadcastReceiver {
-        String mPath;
-        OwnCloudFileObserver mObserver;
-        
-        public DownloadCompletedReceiver(String path, OwnCloudFileObserver observer) {
-            mPath = path;
-            mObserver = observer;
-            addReceiverToList(this);
-        }
+
+    /**
+     *  Private receiver listening to events broadcast by the FileDownloader service.
+     * 
+     *  Starts and stops the observance on registered files when they are being download,
+     *  in order to avoid to start unnecessary synchronizations. 
+     */
+    private class DownloadCompletedReceiverBis extends BroadcastReceiver {
         
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (mPath.equals(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH))) {
-                context.unregisterReceiver(this);
-                removeReceiverFromList(this);
-                mObserver.startWatching();
-                Log.d(TAG, "Started watching " + mPath);
-                return;
+            String downloadPath = intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH);
+            OwnCloudFileObserver observer = mObserversMap.get(downloadPath);
+            if (observer != null) {
+                if (intent.getAction().equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE) &&
+                        new File(downloadPath).exists()) {  // the download could be successful, or not; in both cases, the file could be down, due to a former download or upload   
+                    observer.startWatching();
+                    Log.d(TAG, "Watching again " + downloadPath);
+                
+                } else if (intent.getAction().equals(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) {
+                    observer.stopWatching();
+                    Log.d(TAG, "Disabling observance of " + downloadPath);
+                } 
             }
         }
         
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof DownloadCompletedReceiver)
-                return mPath.equals(((DownloadCompletedReceiver)o).mPath);
-            return super.equals(o);
-        }
     }
+    
 }

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

@@ -25,6 +25,10 @@ import java.util.Vector;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import org.apache.http.HttpStatus;
+import org.apache.jackrabbit.webdav.MultiStatus;
+import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
+
 import com.owncloud.android.authenticator.AccountAuthenticator;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
@@ -32,11 +36,14 @@ import com.owncloud.android.files.InstantUploadBroadcastReceiver;
 import com.owncloud.android.operations.ChunkedUploadFileOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.ui.activity.FileDetailActivity;
 import com.owncloud.android.ui.fragment.FileDetailFragment;
 import com.owncloud.android.utils.OwnCloudVersion;
 
 import eu.alefzero.webdav.OnDatatransferProgressListener;
+import eu.alefzero.webdav.WebdavEntry;
+import eu.alefzero.webdav.WebdavUtils;
 
 import com.owncloud.android.network.OwnCloudClientUtils;
 
@@ -64,19 +71,27 @@ import eu.alefzero.webdav.WebdavClient;
 public class FileUploader extends Service implements OnDatatransferProgressListener {
 
     public static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
-    public static final String EXTRA_PARENT_DIR_ID = "PARENT_DIR_ID";
     public static final String EXTRA_UPLOAD_RESULT = "RESULT";
     public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
-    public static final String EXTRA_FILE_PATH = "FILE_PATH";
+    public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
+    public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
+    public static final String ACCOUNT_NAME = "ACCOUNT_NAME";    
     
+    public static final String KEY_FILE = "FILE";
     public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
     public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
+    public static final String KEY_MIME_TYPE = "MIME_TYPE";
+
     public static final String KEY_ACCOUNT = "ACCOUNT";
+    
     public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE";
     public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
-    public static final String ACCOUNT_NAME = "ACCOUNT_NAME";    
-    public static final String KEY_MIME_TYPE = "MIME_TYPE";
     public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD";
+    public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
+    
+    public static final int LOCAL_BEHAVIOUR_COPY = 0;
+    public static final int LOCAL_BEHAVIOUR_MOVE = 1;
+    public static final int LOCAL_BEHAVIOUR_FORGET = 2;
 
     public static final int UPLOAD_SINGLE_FILE = 0;
     public static final int UPLOAD_MULTIPLE_FILES = 1;
@@ -150,7 +165,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
      */
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE)) {
+        if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE) || !(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
             Log.e(TAG, "Not enough information provided in intent");
             return Service.START_NOT_STICKY;
         }
@@ -161,55 +176,77 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         }
         Account account = intent.getParcelableExtra(KEY_ACCOUNT);
         
-        String[] localPaths, remotePaths, mimeTypes; 
+        String[] localPaths = null, remotePaths = null, mimeTypes = null;
+        OCFile[] files = null;
         if (uploadType == UPLOAD_SINGLE_FILE) {
-            localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
-            remotePaths = new String[] { intent
-                    .getStringExtra(KEY_REMOTE_FILE) };
-            mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) };
+            
+            if (intent.hasExtra(KEY_FILE)) {
+                files = new OCFile[] {intent.getParcelableExtra(KEY_FILE) };
+                
+            } else {
+                localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
+                remotePaths = new String[] { intent.getStringExtra(KEY_REMOTE_FILE) };
+                mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) };
+            }
             
         } else { // mUploadType == UPLOAD_MULTIPLE_FILES
-            localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
-            remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
-            mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
+            
+            if (intent.hasExtra(KEY_FILE)) {
+                files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE);    // TODO will this casting work fine?
+                
+            } else {
+                localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
+                remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
+                mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
+            }
         }
 
-        if (localPaths == null) {
-            Log.e(TAG, "Incorrect array for local paths provided in upload intent");
-            return Service.START_NOT_STICKY;
+        FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
+        
+        boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
+        boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false);
+        int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_COPY);
+        boolean fixed = false;
+        if (isInstant) {
+            fixed = checkAndFixInstantUploadDirectory(storageManager);  // MUST be done BEFORE calling obtainNewOCFileToUpload
         }
-        if (remotePaths == null) {
-            Log.e(TAG, "Incorrect array for remote paths provided in upload intent");
+        
+        if (intent.hasExtra(KEY_FILE) && files == null) {
+            Log.e(TAG, "Incorrect array for OCFiles provided in upload intent");
             return Service.START_NOT_STICKY;
-        }
             
-        if (localPaths.length != remotePaths.length) {
-            Log.e(TAG, "Different number of remote paths and local paths!");
-            return Service.START_NOT_STICKY;
+        } else if (!intent.hasExtra(KEY_FILE)) {
+            if (localPaths == null) {
+                Log.e(TAG, "Incorrect array for local paths provided in upload intent");
+                return Service.START_NOT_STICKY;
+            }
+            if (remotePaths == null) {
+                Log.e(TAG, "Incorrect array for remote paths provided in upload intent");
+                return Service.START_NOT_STICKY;
+            }
+            if (localPaths.length != remotePaths.length) {
+                Log.e(TAG, "Different number of remote paths and local paths!");
+                return Service.START_NOT_STICKY;
+            }
+            
+            files = new OCFile[localPaths.length];
+            for (int i=0; i < localPaths.length; i++) {
+                files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes!=null)?mimeTypes[i]:(String)null), storageManager);
+            }
         }
-        
-        boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false); 
-        boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
-        
+            
         OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account, AccountAuthenticator.KEY_OC_VERSION));
         boolean chunked = FileUploader.chunkedUploadIsSupported(ocv);
         AbstractList<String> requestedUploads = new Vector<String>();
         String uploadKey = null;
         UploadFileOperation newUpload = null;
-        OCFile file = null;
-        FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
-        boolean fixed = false;
-        if (isInstant) {
-            fixed = checkAndFixInstantUploadDirectory(storageManager);
-        }
         try {
-            for (int i=0; i < localPaths.length; i++) {
-                uploadKey = buildRemoteName(account, remotePaths[i]);
-                file = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes!=null)?mimeTypes[i]:(String)null), isInstant, forceOverwrite, storageManager);
+            for (int i=0; i < files.length; i++) {
+                uploadKey = buildRemoteName(account, files[i].getRemotePath());
                 if (chunked) {
-                    newUpload = new ChunkedUploadFileOperation(account, file, isInstant, forceOverwrite);
+                    newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction);
                 } else {
-                    newUpload = new UploadFileOperation(account, file, isInstant, forceOverwrite);
+                    newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction);
                 }
                 if (fixed && i==0) {
                     newUpload.setRemoteFolderToBeCreated();
@@ -281,12 +318,25 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         /**
          * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting for it
          * 
+         * 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 will be stored.
          * @param file          A file that could be in the queue of pending uploads
          */
         public boolean isUploading(Account account, OCFile file) {
+            String targetKey = buildRemoteName(account, file);
             synchronized (mPendingUploads) {
-                return (mPendingUploads.containsKey(buildRemoteName(account, file)));
+                if (file.isDirectory()) {
+                    // this can be slow if there are many downloads :(
+                    Iterator<String> it = mPendingUploads.keySet().iterator();
+                    boolean found = false;
+                    while (it.hasNext() && !found) {
+                        found = it.next().startsWith(targetKey);
+                    }
+                    return found;
+                } else {
+                    return (mPendingUploads.containsKey(targetKey));
+                }
             }
         }
     }
@@ -360,7 +410,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
             try {
                 uploadResult = mCurrentUpload.execute(mUploadClient);
                 if (uploadResult.isSuccess()) {
-                    saveUploadedFile(mCurrentUpload.getFile(), mStorageManager);
+                    saveUploadedFile();
                 }
                 
             } finally {
@@ -379,15 +429,69 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     }
 
     /**
-     * Saves a new OC File after a successful upload.
+     * Saves a OC File after a successful upload.
+     * 
+     * A PROPFIND is necessary to keep the props in the local database synchronized with the server, 
+     * specially the modification time and Etag (where available)
      * 
-     * @param file              OCFile describing the uploaded file
-     * @param storageManager    Interface to the database where the new OCFile has to be stored.
-     * @param parentDirId       Id of the parent OCFile.
+     * TODO refactor this ugly thing
      */
-    private void saveUploadedFile(OCFile file, FileDataStorageManager storageManager) {
-        file.setModificationTimestamp(System.currentTimeMillis());
-        storageManager.saveFile(file);
+    private void saveUploadedFile() {
+        OCFile file = mCurrentUpload.getFile();
+        long syncDate = System.currentTimeMillis();
+        file.setLastSyncDateForData(syncDate);
+        
+        /// new PROPFIND to keep data consistent with server in theory, should return the same we already have
+        PropFindMethod propfind = null;
+        RemoteOperationResult result = null;
+        try {
+          propfind = new PropFindMethod(mUploadClient.getBaseUri() + WebdavUtils.encodePath(mCurrentUpload.getRemotePath()));
+          int status = mUploadClient.executeMethod(propfind);
+          boolean isMultiStatus = (status == HttpStatus.SC_MULTI_STATUS);
+          if (isMultiStatus) {
+              MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
+              WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
+                                               mUploadClient.getBaseUri().getPath());
+              updateOCFile(file, we);
+              file.setLastSyncDateForProperties(syncDate);
+              
+          } else {
+              mUploadClient.exhaustResponse(propfind.getResponseBodyAsStream());
+          }
+          
+          result = new RemoteOperationResult(isMultiStatus, status);
+          Log.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + result.getLogMessage());
+          
+        } catch (Exception e) {
+            result = new RemoteOperationResult(e);
+            Log.e(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + result.getLogMessage(), e);
+
+        } finally {
+            if (propfind != null)
+                propfind.releaseConnection();
+        }
+
+        /// maybe this would be better as part of UploadFileOperation... or maybe all this method
+        if (mCurrentUpload.wasRenamed()) {
+            OCFile oldFile = mCurrentUpload.getOldFile();
+            if (oldFile.fileExists()) {
+                oldFile.setStoragePath(null);
+                mStorageManager.saveFile(oldFile);
+                
+            } // else: it was just an automatic renaming due to a name coincidence; nothing else is needed, the storagePath is right in the instance returned by mCurrentUpload.getFile()
+        }
+        
+        mStorageManager.saveFile(file);
+    }
+
+    
+    private void updateOCFile(OCFile file, WebdavEntry we) {
+        file.setCreationTimestamp(we.createTimestamp());
+        file.setFileLength(we.contentLength());
+        file.setMimetype(we.contentType());
+        file.setModificationTimestamp(we.modifiedTimestamp());
+        file.setModificationTimestampAtLastSyncForData(we.modifiedTimestamp());
+        // file.setEtag(mCurrentDownload.getEtag());    // TODO Etag, where available
     }
     
     
@@ -405,16 +509,17 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     }
 
     
-    private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, boolean isInstant, boolean forceOverwrite, FileDataStorageManager storageManager) {
+    private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, FileDataStorageManager storageManager) {
         OCFile newFile = new OCFile(remotePath);
         newFile.setStoragePath(localPath);
-        newFile.setLastSyncDate(0);
-        newFile.setKeepInSync(forceOverwrite);
+        newFile.setLastSyncDateForProperties(0);
+        newFile.setLastSyncDateForData(0);
         
         // size
         if (localPath != null && localPath.length() > 0) {
             File localFile = new File(localPath);
             newFile.setFileLength(localFile.length());
+            newFile.setLastSyncDateForData(localFile.lastModified());
         }   // don't worry about not assigning size, the problems with localPath are checked when the UploadFileOperation instance is created
         
         // MIME type
@@ -434,7 +539,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         
         // parent dir
         String parentPath = new File(remotePath).getParent();
-        parentPath = parentPath.endsWith("/")?parentPath:parentPath+"/" ;
+        parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR ;
         OCFile parentDir = storageManager.getFileByPath(parentPath);
         if (parentDir == null) {
             throw new IllegalStateException("Can not upload a file to a non existing remote location: " + parentPath);
@@ -458,7 +563,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         mDefaultNotificationContentView = mNotification.contentView;
         mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout);
         mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, false);
-        mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.uploader_upload_in_progress_content), 0, new File(upload.getStoragePath()).getName()));
+        mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName()));
         mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);
         
         /// includes a pending intent in the notification showing the details view of the file
@@ -523,7 +628,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
             
             mNotification.setLatestEventInfo(   getApplicationContext(), 
                                                 getString(R.string.uploader_upload_succeeded_ticker), 
-                                                String.format(getString(R.string.uploader_upload_succeeded_content_single), (new File(upload.getStoragePath())).getName()), 
+                                                String.format(getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()), 
                                                 mNotification.contentIntent);
             
             mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification);    // NOT AN ERROR; uploader_upload_in_progress_ticker is the target, not a new notification
@@ -542,9 +647,18 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
             finalNotification.flags |= Notification.FLAG_AUTO_CANCEL;
             // TODO put something smart in the contentIntent below
             finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);
+            
+            String content = null; 
+            if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL ||
+                uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) {
+                // TODO we need a class to provide error messages for the users from a RemoteOperationResult and a RemoteOperation 
+                content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(), getString(R.string.app_name));
+            } else {
+                content = String.format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName());
+            }
             finalNotification.setLatestEventInfo(   getApplicationContext(), 
                                                     getString(R.string.uploader_upload_failed_ticker), 
-                                                    String.format(getString(R.string.uploader_upload_failed_content_single), (new File(upload.getStoragePath())).getName()), 
+                                                    content, 
                                                     finalNotification.contentIntent);
             
             mNotificationManager.notify(R.string.uploader_upload_failed_ticker, finalNotification);
@@ -569,11 +683,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) {
         Intent end = new Intent(UPLOAD_FINISH_MESSAGE);
         end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath());    // real remote path, after possible automatic renaming
-        end.putExtra(EXTRA_FILE_PATH, upload.getStoragePath());
+        if (upload.wasRenamed()) {
+            end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath());
+        }
+        end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
         end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
         end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
-        end.putExtra(EXTRA_PARENT_DIR_ID, upload.getFile().getParentId());
-        sendBroadcast(end);
+        sendStickyBroadcast(end);
     }
 
 

+ 3 - 2
src/com/owncloud/android/location/LocationUpdateService.java

@@ -57,9 +57,10 @@ public class LocationUpdateService extends IntentService implements
 
         // Notify user if there is no way to track the device
         if (mLocationProvider == null) {
+            String message = String.format(getString(R.string.location_no_provider), getString(R.string.app_name));
             Toast.makeText(this,
-                    R.string.location_no_provider,
-                    Toast.LENGTH_LONG);
+                    message,
+                    Toast.LENGTH_LONG).show();
             stopSelf();
             return;
         }

+ 3 - 2
src/com/owncloud/android/operations/ChunkedUploadFileOperation.java

@@ -45,9 +45,10 @@ public class ChunkedUploadFileOperation extends UploadFileOperation {
     public ChunkedUploadFileOperation(  Account account,
                                         OCFile file,
                                         boolean isInstant, 
-                                        boolean forceOverwrite) {
+                                        boolean forceOverwrite,
+                                        int localBehaviour) {
         
-        super(account, file, isInstant, forceOverwrite);
+        super(account, file, isInstant, forceOverwrite, localBehaviour);
     }
 
     @Override

+ 21 - 5
src/com/owncloud/android/operations/DownloadFileOperation.java

@@ -22,19 +22,21 @@ import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.commons.httpclient.Header;
 import org.apache.commons.httpclient.HttpException;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.http.HttpStatus;
 
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.operations.RemoteOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.utils.FileStorageUtils;
 
 import eu.alefzero.webdav.OnDatatransferProgressListener;
 import eu.alefzero.webdav.WebdavClient;
@@ -56,6 +58,7 @@ public class DownloadFileOperation extends RemoteOperation {
     private OCFile mFile;
     private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
     private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+    private long mModificationTimestamp = 0;
 
     
     public DownloadFileOperation(Account account, OCFile file) {
@@ -78,11 +81,15 @@ public class DownloadFileOperation extends RemoteOperation {
     }
 
     public String getSavePath() {
-        return FileDownloader.getSavePath(mAccount.name) + mFile.getRemotePath();
+        String path = mFile.getStoragePath();   // re-downloads should be done over the original file 
+        if (path != null && path.length() > 0) {
+            return path;
+        }
+        return FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
     }
     
     public String getTmpPath() {
-        return FileDownloader.getTemporalPath(mAccount.name) + mFile.getRemotePath();
+        return FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
     }
     
     public String getRemotePath() {
@@ -90,7 +97,7 @@ public class DownloadFileOperation extends RemoteOperation {
     }
 
     public String getMimeType() {
-        String mimeType = mFile.getMimetype();  // TODO fix the mime types in OCFiles FOREVER
+        String mimeType = mFile.getMimetype();
         if (mimeType == null || mimeType.length() <= 0) {
             try {
                 mimeType = MimeTypeMap.getSingleton()
@@ -110,6 +117,10 @@ public class DownloadFileOperation extends RemoteOperation {
         return mFile.getFileLength();
     }
     
+    public long getModificationTimestamp() {
+        return (mModificationTimestamp > 0) ? mModificationTimestamp : mFile.getModificationTimestamp();
+    }
+    
     
     public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
         mDataTransferListeners.add(listener);
@@ -134,7 +145,7 @@ public class DownloadFileOperation extends RemoteOperation {
                 moved = tmpFile.renameTo(newFile);
             }
             if (!moved)
-                result = new RemoteOperationResult(RemoteOperationResult.ResultCode.STORAGE_ERROR_MOVING_FROM_TMP);
+                result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED);
             else
                 result = new RemoteOperationResult(isSuccess(status), status);
             Log.i(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage());
@@ -185,6 +196,11 @@ public class DownloadFileOperation extends RemoteOperation {
                     }
                 }
                 savedFile = true;
+                Header modificationTime = get.getResponseHeader("Last-Modified");
+                if (modificationTime != null) {
+                    Date d = WebdavUtils.parseResponseDate((String) modificationTime.getValue());
+                    mModificationTimestamp = (d != null) ? d.getTime() : 0;
+                }
                 
             } else {
                 client.exhaustResponse(get.getResponseBodyAsStream());

+ 11 - 6
src/com/owncloud/android/operations/RemoteOperationResult.java

@@ -44,10 +44,9 @@ import com.owncloud.android.network.CertificateCombinedException;
  */
 public class RemoteOperationResult implements Serializable {
     
-    /** Generated - to refresh every time the class changes */
+    /** Generated - should be refreshed every time the class changes!! */
     private static final long serialVersionUID = 5336333154035462033L;
     
-    
     public enum ResultCode { 
         OK,
         OK_SSL,
@@ -65,12 +64,15 @@ public class RemoteOperationResult implements Serializable {
         SSL_ERROR,
         SSL_RECOVERABLE_PEER_UNVERIFIED,
         BAD_OC_VERSION,
-        STORAGE_ERROR_MOVING_FROM_TMP,
         CANCELLED, 
         INVALID_LOCAL_FILE_NAME, 
         INVALID_OVERWRITE,
         CONFLICT, 
-        OAUTH2_ERROR
+        OAUTH2_ERROR,
+        SYNC_CONFLICT,
+        LOCAL_STORAGE_FULL, 
+        LOCAL_STORAGE_NOT_MOVED, 
+        LOCAL_STORAGE_NOT_COPIED
     }
 
     private boolean mSuccess = false;
@@ -254,8 +256,11 @@ public class RemoteOperationResult implements Serializable {
         } else if (mCode == ResultCode.BAD_OC_VERSION) {
             return "No valid ownCloud version was found at the server";
             
-        } else if (mCode == ResultCode.STORAGE_ERROR_MOVING_FROM_TMP) {
-            return "Error while moving file from temporal to final directory";
+        } else if (mCode == ResultCode.LOCAL_STORAGE_FULL) {
+            return "Local storage full";
+            
+        } else if (mCode == ResultCode.LOCAL_STORAGE_NOT_MOVED) {
+            return "Error while moving file to final directory";
         }
         
         return "Operation finished with HTTP status code " + mHttpCode + " (" + (isSuccess()?"success":"fail") + ")";

+ 18 - 3
src/com/owncloud/android/operations/RemoveFileOperation.java

@@ -18,6 +18,7 @@
 
 package com.owncloud.android.operations;
 
+import org.apache.commons.httpclient.HttpStatus;
 import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
 
 import android.util.Log;
@@ -59,6 +60,16 @@ public class RemoveFileOperation extends RemoteOperation {
     }
     
     
+    /**
+     * Getter for the file to remove (or removed, if the operation was successfully performed).
+     * 
+     * @return      File to remove or already removed.
+     */
+    public OCFile getFile() {
+        return mFileToRemove;
+    }
+    
+    
     /**
      * Performs the remove operation
      * 
@@ -71,11 +82,15 @@ public class RemoveFileOperation extends RemoteOperation {
         try {
             delete = new DeleteMethod(client.getBaseUri() + WebdavUtils.encodePath(mFileToRemove.getRemotePath()));
             int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT);
-            if (delete.succeeded()) {
-                mDataStorageManager.removeFile(mFileToRemove, mDeleteLocalCopy);
+            if (delete.succeeded() || status == HttpStatus.SC_NOT_FOUND) {
+                if (mFileToRemove.isDirectory()) {
+                    mDataStorageManager.removeDirectory(mFileToRemove, true, mDeleteLocalCopy);
+                } else {
+                    mDataStorageManager.removeFile(mFileToRemove, mDeleteLocalCopy);
+                }
             }
             delete.getResponseBodyAsString();   // exhaust the response, although not interesting
-            result = new RemoteOperationResult(delete.succeeded(), status);
+            result = new RemoteOperationResult((delete.succeeded() || status == HttpStatus.SC_NOT_FOUND), status);
             Log.i(TAG, "Remove " + mFileToRemove.getRemotePath() + ": " + result.getLogMessage());
             
         } catch (Exception e) {

+ 70 - 51
src/com/owncloud/android/operations/RenameFileOperation.java

@@ -24,12 +24,13 @@ import java.io.IOException;
 import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
 //import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
 
+import android.accounts.Account;
 import android.util.Log;
 
 import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.utils.FileStorageUtils;
 
 import eu.alefzero.webdav.WebdavClient;
 import eu.alefzero.webdav.WebdavUtils;
@@ -48,7 +49,9 @@ public class RenameFileOperation extends RemoteOperation {
     
 
     private OCFile mFile;
+    private Account mAccount;
     private String mNewName;
+    private String mNewRemotePath;
     private DataStorageManager mStorageManager;
     
     
@@ -56,12 +59,15 @@ public class RenameFileOperation extends RemoteOperation {
      * Constructor
      * 
      * @param file                  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 storageManager        Reference to the local database corresponding to the account where the file is contained. 
      */
-    public RenameFileOperation(OCFile file, String newName, DataStorageManager storageManager) {
+    public RenameFileOperation(OCFile file, Account account, String newName, DataStorageManager storageManager) {
         mFile = file;
+        mAccount = account;
         mNewName = newName;
+        mNewRemotePath = null;
         mStorageManager = storageManager;
     }
   
@@ -80,60 +86,63 @@ public class RenameFileOperation extends RemoteOperation {
         RemoteOperationResult result = null;
         
         LocalMoveMethod move = null;
-        //MoveMethod move = null;   // TODO find out why not use this
-        String newRemotePath = null;
+        mNewRemotePath = null;
         try {
             if (mNewName.equals(mFile.getFileName())) {
                 return new RemoteOperationResult(ResultCode.OK);
             }
         
-            newRemotePath = (new File(mFile.getRemotePath())).getParent() + mNewName;
+            String parent = (new File(mFile.getRemotePath())).getParent();
+            parent = (parent.endsWith(OCFile.PATH_SEPARATOR)) ? parent : parent + OCFile.PATH_SEPARATOR; 
+            mNewRemotePath =  parent + mNewName;
+            if (mFile.isDirectory()) {
+                mNewRemotePath += OCFile.PATH_SEPARATOR;
+            }
             
             // check if the new name is valid in the local file system
             if (!isValidNewName()) {
                 return new RemoteOperationResult(ResultCode.INVALID_LOCAL_FILE_NAME);
             }
         
-            // check if a remote file with the new name already exists
-            if (client.existsFile(newRemotePath)) {
+            // check if a file with the new name already exists
+            if (client.existsFile(mNewRemotePath) ||                             // remote check could fail by network failure, or by indeterminate behavior of HEAD for folders ... 
+                    mStorageManager.getFileByPath(mNewRemotePath) != null) {     // ... so local check is convenient
                 return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE);
             }
-            /*move = new MoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()), 
-                                                client.getBaseUri() + WebdavUtils.encodePath(newRemotePath),
-                                                false);*/
             move = new LocalMoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()),
-                                        client.getBaseUri() + WebdavUtils.encodePath(newRemotePath));
+                                        client.getBaseUri() + WebdavUtils.encodePath(mNewRemotePath));
             int status = client.executeMethod(move, RENAME_READ_TIMEOUT, RENAME_CONNECTION_TIMEOUT);
             if (move.succeeded()) {
 
-                // create new OCFile instance for the renamed file
-                OCFile newFile = obtainUpdatedFile();
-                OCFile oldFile = mFile;
-                mFile = newFile; 
-                
-                // try to rename the local copy of the file
-                if (oldFile.isDown()) {
-                    File f = new File(oldFile.getStoragePath());
-                    String newStoragePath = f.getParent() + mNewName;
-                    if (f.renameTo(new File(newStoragePath))) {
-                        mFile.setStoragePath(newStoragePath);
-                    }
-                    // else - NOTHING: the link to the local file is kept although the local name can't be updated
-                    // TODO - study conditions when this could be a problem
+                if (mFile.isDirectory()) {
+                    saveLocalDirectory();
+                    
+                } else {
+                    saveLocalFile();
+                    
                 }
-                
-                mStorageManager.removeFile(oldFile, false);
-                mStorageManager.saveFile(mFile);
+             
+            /* 
+             *} else if (mFile.isDirectory() && (status == 207 || status >= 500)) {
+             *   // TODO 
+             *   // if server fails in the rename of a folder, some children files could have been moved to a folder with the new name while some others
+             *   // stayed in the old folder;
+             *   //
+             *   // easiest and heaviest solution is synchronizing the parent folder (or the full account);
+             *   //
+             *   // a better solution is synchronizing the folders with the old and new names;
+             *}
+             */
                 
             }
             
             move.getResponseBodyAsString(); // exhaust response, although not interesting
             result = new RemoteOperationResult(move.succeeded(), status);
-            Log.i(TAG, "Rename " + mFile.getRemotePath() + " to " + newRemotePath + ": " + result.getLogMessage());
+            Log.i(TAG, "Rename " + mFile.getRemotePath() + " to " + mNewRemotePath + ": " + result.getLogMessage());
             
         } catch (Exception e) {
             result = new RemoteOperationResult(e);
-            Log.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((newRemotePath==null) ? mNewName : newRemotePath) + ": " + result.getLogMessage(), e);
+            Log.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((mNewRemotePath==null) ? mNewName : mNewRemotePath) + ": " + result.getLogMessage(), e);
             
         } finally {
             if (move != null)
@@ -143,6 +152,35 @@ public class RenameFileOperation extends RemoteOperation {
     }
 
     
+    private void saveLocalDirectory() {
+        mStorageManager.moveDirectory(mFile, mNewRemotePath);
+        String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
+        File localDir = new File(localPath);
+        if (localDir.exists()) {
+            localDir.renameTo(new File(FileStorageUtils.getSavePath(mAccount.name) + mNewRemotePath));
+            // TODO - if renameTo fails, children files that are already down will result unlinked
+        }
+    }
+
+    private void saveLocalFile() {
+        mFile.setFileName(mNewName);
+        
+        // try to rename the local copy of the file
+        if (mFile.isDown()) {
+            File f = new File(mFile.getStoragePath());
+            String parentStoragePath = f.getParent();
+            if (!parentStoragePath.endsWith(File.separator))
+                parentStoragePath += File.separator;
+            if (f.renameTo(new File(parentStoragePath + mNewName))) {
+                mFile.setStoragePath(parentStoragePath + mNewName);
+            }
+            // else - NOTHING: the link to the local file is kept although the local name can't be updated
+            // TODO - study conditions when this could be a problem
+        }
+        
+        mStorageManager.saveFile(mFile);
+    }
+
     /**
      * Checks if the new name to set is valid in the file system 
      * 
@@ -163,7 +201,7 @@ public class RenameFileOperation extends RemoteOperation {
             return false;
         }
         // create a test file
-        String tmpFolder = FileDownloader.getTemporalPath("");
+        String tmpFolder = FileStorageUtils.getTemporalPath("");
         File testFile = new File(tmpFolder + mNewName);
         try {
             testFile.createNewFile();   // return value is ignored; it could be 'false' because the file already existed, that doesn't invalidate the name
@@ -180,26 +218,7 @@ public class RenameFileOperation extends RemoteOperation {
     }
 
 
-    /**
-     * Creates a new OCFile for the new remote name of the renamed file.
-     * 
-     * @return      OCFile object with the same information than mFile, but the renamed remoteFile and the storagePath (empty)
-     */
-    private OCFile obtainUpdatedFile() {
-        OCFile file = new OCFile(mStorageManager.getFileById(mFile.getParentId()).getRemotePath() + mNewName);
-        file.setCreationTimestamp(mFile.getCreationTimestamp());
-        file.setFileId(mFile.getFileId());
-        file.setFileLength(mFile.getFileLength());
-        file.setKeepInSync(mFile.keepInSync());
-        file.setLastSyncDate(mFile.getLastSyncDate());
-        file.setMimetype(mFile.getMimetype());
-        file.setModificationTimestamp(mFile.getModificationTimestamp());
-        file.setParentId(mFile.getParentId());
-        return file;
-    }
-
-
-    // move operation - TODO: find out why org.apache.jackrabbit.webdav.client.methods.MoveMethod is not used instead ¿?
+    // move operation
     private class LocalMoveMethod extends DavMethodBase {
 
         public LocalMoveMethod(String uri, String dest) {

+ 151 - 30
src/com/owncloud/android/operations/SynchronizeFileOperation.java

@@ -24,10 +24,14 @@ import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
 
 import android.accounts.Account;
 import android.content.Context;
+import android.content.Intent;
 import android.util.Log;
 
 import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
 
 import eu.alefzero.webdav.WebdavClient;
 import eu.alefzero.webdav.WebdavEntry;
@@ -36,53 +40,128 @@ import eu.alefzero.webdav.WebdavUtils;
 public class SynchronizeFileOperation extends RemoteOperation {
 
     private String TAG = SynchronizeFileOperation.class.getSimpleName();
+    private static final int SYNC_READ_TIMEOUT = 10000;
+    private static final int SYNC_CONNECTION_TIMEOUT = 5000;
     
-    private String mRemotePath;
-    
+    private OCFile mLocalFile;
+    private OCFile mServerFile;
     private DataStorageManager mStorageManager;
-    
     private Account mAccount;
+    private boolean mSyncFileContents;
+    private boolean mLocalChangeAlreadyKnown;
+    private Context mContext;
+    
+    private boolean mTransferWasRequested = false;
     
     public SynchronizeFileOperation(
-            String remotePath, 
-            DataStorageManager dataStorageManager, 
+            OCFile localFile,
+            OCFile serverFile,          // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation 
+            DataStorageManager storageManager, 
             Account account, 
-            Context context ) {
-        mRemotePath = remotePath;
-        mStorageManager = dataStorageManager;
+            boolean syncFileContents,
+            boolean localChangeAlreadyKnown, 
+            Context context) {
+        
+        mLocalFile = localFile;
+        mServerFile = serverFile;
+        mStorageManager = storageManager;
         mAccount = account;
+        mSyncFileContents = syncFileContents;
+        mLocalChangeAlreadyKnown = localChangeAlreadyKnown;
+        mContext = context;
     }
 
+
     @Override
     protected RemoteOperationResult run(WebdavClient client) {
+        
         PropFindMethod propfind = null;
         RemoteOperationResult result = null;
+        mTransferWasRequested = false;
         try {
-          propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
-          int status = client.executeMethod(propfind);
-          boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS;
-          Boolean isConflict = Boolean.FALSE;
-          if (isMultiStatus) {
-              MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
-              WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
+            if (!mLocalFile.isDown()) {
+                /// easy decision
+                requestForDownload(mLocalFile);
+                result = new RemoteOperationResult(ResultCode.OK);
+                
+            } else {
+                /// local copy in the device -> need to think a bit more before do anything
+                
+                if (mServerFile == null) {
+                    /// take the duty of check the server for the current state of the file there
+                    propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mLocalFile.getRemotePath()));
+                    int status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT);
+                    boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS;
+                    if (isMultiStatus) {
+                        MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
+                        WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
                                                client.getBaseUri().getPath());
-              OCFile file = fillOCFile(we);
-              OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath());
-              if (oldFile.getFileLength() != file.getFileLength() ||
-                  oldFile.getModificationTimestamp() != file.getModificationTimestamp()) {
-                  isConflict = Boolean.TRUE;
-              }
+                        mServerFile = fillOCFile(we);
+                        mServerFile.setLastSyncDateForProperties(System.currentTimeMillis());
+                        
+                    } else {
+                        client.exhaustResponse(propfind.getResponseBodyAsStream());
+                        result = new RemoteOperationResult(false, status);
+                    }
+                }
+                
+                if (result == null) {   // true if the server was not checked, or nothing was wrong with the remote request
+              
+                    /// check changes in server and local file
+                    boolean serverChanged = false;
+                    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?
+                    } else {
+                        // server without etags
+                        serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestampAtLastSyncForData());
+                    }
+                    boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData());
+                        // TODO this will be always true after the app is upgraded to database version 3; will result in unnecessary uploads
+              
+                    /// decide action to perform depending upon changes
+                    if (localChanged && serverChanged) {
+                        result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
+                  
+                    } else if (localChanged) {
+                        if (mSyncFileContents) {
+                            requestForUpload(mLocalFile);
+                            // the local update of file properties will be done by the FileUploader service when the upload finishes
+                        } else {
+                            // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid; 
+                            // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect
+                            // that an upload is necessary (for instance, in FileObserverService).
+                        }
+                        result = new RemoteOperationResult(ResultCode.OK);
+                  
+                    } else if (serverChanged) {
+                        if (mSyncFileContents) {
+                            requestForDownload(mLocalFile); // local, not server; we won't to keep the value of keepInSync!
+                            // the update of local data will be done later by the FileUploader service when the upload finishes
+                        } else {
+                            // TODO CHECK: is this really useful in some point in the code?
+                            mServerFile.setKeepInSync(mLocalFile.keepInSync());
+                            mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData());
+                            mServerFile.setStoragePath(mLocalFile.getStoragePath());
+                            mServerFile.setParentId(mLocalFile.getParentId());
+                            mStorageManager.saveFile(mServerFile);
+                            
+                        }
+                        result = new RemoteOperationResult(ResultCode.OK);
+              
+                    } else {
+                        // nothing changed, nothing to do
+                        result = new RemoteOperationResult(ResultCode.OK);
+                    }
               
-          } else {
-              client.exhaustResponse(propfind.getResponseBodyAsStream());
-          }
+                } 
+          
+            }
+            
+            Log.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage());
           
-          result = new RemoteOperationResult(isMultiStatus, status);
-          result.setExtraData(isConflict);
-          Log.i(TAG, "Synchronizing " + mAccount.name + ", file " + mRemotePath + ": " + result.getLogMessage());
         } catch (Exception e) {
             result = new RemoteOperationResult(e);
-            Log.e(TAG, "Synchronizing " + mAccount.name + ", file " + mRemotePath + ": " + result.getLogMessage(), result.getException());
+            Log.e(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage(), result.getException());
 
         } finally {
             if (propfind != null)
@@ -90,7 +169,40 @@ public class SynchronizeFileOperation extends RemoteOperation {
         }
         return result;
     }
+
     
+    /**
+     * Requests for an upload to the FileUploader service
+     * 
+     * @param file     OCFile object representing the file to upload
+     */
+    private void requestForUpload(OCFile file) {
+        Intent i = new Intent(mContext, FileUploader.class);
+        i.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
+        i.putExtra(FileUploader.KEY_FILE, file);
+        /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath);    // doing this we would lose the value of keepInSync in the road, and maybe it's not updated in the database when the FileUploader service gets it!  
+        i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/
+        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
+        i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
+        mContext.startService(i);
+        mTransferWasRequested = true;
+    }
+
+
+    /**
+     * Requests for a download to the FileDownloader service
+     * 
+     * @param file     OCFile object representing the file to download
+     */
+    private void requestForDownload(OCFile file) {
+        Intent i = new Intent(mContext, FileDownloader.class);
+        i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+        i.putExtra(FileDownloader.EXTRA_FILE, file);
+        mContext.startService(i);
+        mTransferWasRequested = true;
+    }
+
+
     /**
      * Creates and populates a new {@link OCFile} object with the data read from the server.
      * 
@@ -102,9 +214,18 @@ public class SynchronizeFileOperation extends RemoteOperation {
         file.setCreationTimestamp(we.createTimestamp());
         file.setFileLength(we.contentLength());
         file.setMimetype(we.contentType());
-        file.setModificationTimestamp(we.modifiedTimesamp());
-        file.setLastSyncDate(System.currentTimeMillis());
+        file.setModificationTimestamp(we.modifiedTimestamp());
         return file;
     }
 
+
+    public boolean transferWasRequested() {
+        return mTransferWasRequested;
+    }
+
+
+    public OCFile getLocalFile() {
+        return mLocalFile;
+    }
+
 }

+ 156 - 37
src/com/owncloud/android/operations/SynchronizeFolderOperation.java

@@ -18,7 +18,15 @@
 
 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.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Vector;
 
 import org.apache.http.HttpStatus;
@@ -27,13 +35,12 @@ import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
 
 import android.accounts.Account;
 import android.content.Context;
-import android.content.Intent;
 import android.util.Log;
 
 import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileDownloader;
-import com.owncloud.android.files.services.FileObserverService;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.utils.FileStorageUtils;
 
 import eu.alefzero.webdav.WebdavClient;
 import eu.alefzero.webdav.WebdavEntry;
@@ -69,6 +76,12 @@ public class SynchronizeFolderOperation extends RemoteOperation {
     
     /** Files and folders contained in the synchronized folder */
     private List<OCFile> mChildren;
+
+    private int mConflictsFound;
+
+    private int mFailsInFavouritesFound;
+
+    private Map<String, String> mForgottenLocalFiles;
     
     
     public SynchronizeFolderOperation(  String remotePath, 
@@ -83,9 +96,22 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         mStorageManager = dataStorageManager;
         mAccount = account;
         mContext = context;
+        mForgottenLocalFiles = new HashMap<String, String>();
     }
     
     
+    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.
      * 
@@ -99,6 +125,9 @@ public class SynchronizeFolderOperation extends RemoteOperation {
     @Override
     protected RemoteOperationResult run(WebdavClient client) {
         RemoteOperationResult result = null;
+        mFailsInFavouritesFound = 0;
+        mConflictsFound = 0;
+        mForgottenLocalFiles.clear();
         
         // code before in FileSyncAdapter.fetchData
         PropFindMethod query = null;
@@ -117,24 +146,49 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                 if (mParentId == DataStorageManager.ROOT_PARENT_ID) {
                     WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath());
                     OCFile parent = fillOCFile(we);
-                    parent.setParentId(mParentId);
                     mStorageManager.saveFile(parent);
                     mParentId = parent.getFileId();
                 }
                 
                 // read contents in folder
                 List<OCFile> updatedFiles = new Vector<OCFile>(resp.getResponses().length - 1);
+                List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
                 for (int i = 1; i < resp.getResponses().length; ++i) {
+                    /// new OCFile instance with the data from the server
                     WebdavEntry we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath());
                     OCFile file = fillOCFile(we);
-                    file.setParentId(mParentId);
+                    
+                    /// set data about local state, keeping unchanged former data if existing
+                    file.setLastSyncDateForProperties(mCurrentSyncTime);
                     OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath());
                     if (oldFile != null) {
-                        if (oldFile.keepInSync() && file.getModificationTimestamp() > oldFile.getModificationTimestamp()) {
-                            disableObservance(file);        // first disable observer so we won't get file upload right after download
-                            requestContentDownload(file);
-                        }
                         file.setKeepInSync(oldFile.keepInSync());
+                        file.setLastSyncDateForData(oldFile.getLastSyncDateForData());
+                        file.setModificationTimestampAtLastSyncForData(oldFile.getModificationTimestampAtLastSyncForData());    // must be kept unchanged when the file contents are not updated
+                        checkAndFixForeignStoragePath(oldFile);
+                        file.setStoragePath(oldFile.getStoragePath());
+                    }
+
+                    /// scan default location if local copy of file is not linked in OCFile instance
+                    if (file.getStoragePath() == null && !file.isDirectory()) {
+                        File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
+                        if (f.exists()) {
+                            file.setStoragePath(f.getAbsolutePath());
+                            file.setLastSyncDateForData(f.lastModified());
+                        }
+                    }
+                    
+                    /// prepare content synchronization for kept-in-sync files
+                    if (file.keepInSync()) {
+                        SynchronizeFileOperation operation = new SynchronizeFileOperation(  oldFile,        
+                                                                                            file, 
+                                                                                            mStorageManager,
+                                                                                            mAccount,       
+                                                                                            true, 
+                                                                                            false,          
+                                                                                            mContext
+                                                                                            );
+                        filesToSyncContents.add(operation);
                     }
                 
                     updatedFiles.add(file);
@@ -142,15 +196,35 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                                 
                 // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed)
                 mStorageManager.saveFiles(updatedFiles);
-
                 
+                // request for the synchronization of files AFTER saving last properties
+                SynchronizeFileOperation op = null;
+                RemoteOperationResult contentsResult = null;
+                for (int i=0; i < filesToSyncContents.size(); i++) {
+                    op = filesToSyncContents.get(i);
+                    contentsResult = op.execute(client);   // returns without waiting for upload or download finishes
+                    if (!contentsResult.isSuccess()) {
+                        if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
+                            mConflictsFound++;
+                        } else {
+                            mFailsInFavouritesFound++;
+                            if (contentsResult.getException() != null) {
+                                Log.d(TAG, "Error while synchronizing favourites : " +  contentsResult.getLogMessage(), contentsResult.getException());
+                            } else {
+                                Log.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage());
+                            }
+                        }
+                    }   // won't let these fails break the synchronization process
+                }
+
+                    
                 // removal of obsolete files
                 mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId));
                 OCFile file;
-                String currentSavePath = FileDownloader.getSavePath(mAccount.name);
+                String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
                 for (int i=0; i < mChildren.size(); ) {
                     file = mChildren.get(i);
-                    if (file.getLastSyncDate() != mCurrentSyncTime) {
+                    if (file.getLastSyncDateForProperties() != mCurrentSyncTime) {
                         Log.d(TAG, "removing file: " + file);
                         mStorageManager.removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath)));
                         mChildren.remove(i);
@@ -164,7 +238,16 @@ public class SynchronizeFolderOperation extends RemoteOperation {
             }
             
             // prepare result object
-            result = new RemoteOperationResult(isMultiStatus(status), status);
+            if (isMultiStatus(status)) {
+                if (mConflictsFound > 0  || mFailsInFavouritesFound > 0) { 
+                    result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);   // should be different result, but will do the job
+                            
+                } else {
+                    result = new RemoteOperationResult(true, status);
+                }
+            } else {
+                result = new RemoteOperationResult(false, status);
+            }
             Log.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
             
             
@@ -197,37 +280,73 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         file.setCreationTimestamp(we.createTimestamp());
         file.setFileLength(we.contentLength());
         file.setMimetype(we.contentType());
-        file.setModificationTimestamp(we.modifiedTimesamp());
-        file.setLastSyncDate(mCurrentSyncTime);
+        file.setModificationTimestamp(we.modifiedTimestamp());
+        file.setParentId(mParentId);
         return file;
     }
     
-    
+
     /**
-     * Request to stop the observance of local updates for a 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. 
      * 
-     * @param file      OCFile representing the remote file to stop to monitor for local updates
-     */
-    private void disableObservance(OCFile file) {
-        Log.d(TAG, "Disabling observation of remote file" + file.getRemotePath());
-        Intent intent = new Intent(mContext, FileObserverService.class);
-        intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_ADD_DOWNLOADING_FILE);
-        intent.putExtra(FileObserverService.KEY_CMD_ARG, file.getRemotePath());
-        mContext.startService(intent);
-        
-    }
-
-
-    /** 
-     * Requests a download to the file download service
+     * If the copy fails, the link to the local file is nullified. The account of forgotten files is kept in 
+     * {@link #mForgottenLocalFiles}
      * 
-     * @param   file    OCFile representing the remote file to download
+     * @param file      File to check and fix.
      */
-    private void requestContentDownload(OCFile file) {
-        Intent intent = new Intent(mContext, FileDownloader.class);
-        intent.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
-        intent.putExtra(FileDownloader.EXTRA_FILE, file);
-        mContext.startService(intent);
+    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.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.d(TAG, "Weird exception while closing input stream for " + storagePath + " (ignoring)", e);
+                    }
+                    try {
+                        if (out != null) out.close();
+                    } catch (Exception e) {
+                        Log.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e);
+                    }
+                }
+            }
+        }
     }
 
 

+ 1 - 1
src/com/owncloud/android/operations/UpdateOCVersionOperation.java

@@ -42,7 +42,7 @@ import eu.alefzero.webdav.WebdavClient;
  */
 public class UpdateOCVersionOperation extends RemoteOperation {
 
-    private static final String TAG = UploadFileOperation.class.getSimpleName();
+    private static final String TAG = UpdateOCVersionOperation.class.getSimpleName();
 
     private Account mAccount;
     private Context mContext;

+ 172 - 12
src/com/owncloud/android/operations/UploadFileOperation.java

@@ -19,7 +19,11 @@
 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.HashSet;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -29,8 +33,11 @@ import org.apache.commons.httpclient.methods.PutMethod;
 import org.apache.http.HttpStatus;
 
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.operations.RemoteOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.utils.FileStorageUtils;
 
 import eu.alefzero.webdav.FileRequestEntity;
 import eu.alefzero.webdav.OnDatatransferProgressListener;
@@ -50,18 +57,25 @@ public class UploadFileOperation extends RemoteOperation {
 
     private Account mAccount;
     private OCFile mFile;
+    private OCFile mOldFile;
     private String mRemotePath = null;
     private boolean mIsInstant = false;
     private boolean mRemoteFolderToBeCreated = false;
     private boolean mForceOverwrite = false;
+    private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
+    private boolean mWasRenamed = false;
+    private String mOriginalFileName = null;
+    private String mOriginalStoragePath = null;
     PutMethod mPutMethod = null;
     private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
     private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+
     
     public UploadFileOperation( Account account,
                                 OCFile file,
                                 boolean isInstant, 
-                                boolean forceOverwrite) {
+                                boolean forceOverwrite,
+                                int localBehaviour) {
         if (account == null)
             throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation");
         if (file == null)
@@ -75,6 +89,9 @@ public class UploadFileOperation extends RemoteOperation {
         mRemotePath = file.getRemotePath();
         mIsInstant = isInstant;
         mForceOverwrite = forceOverwrite;
+        mLocalBehaviour = localBehaviour;
+        mOriginalStoragePath = mFile.getStoragePath();
+        mOriginalFileName = mFile.getFileName();
     }
 
 
@@ -82,17 +99,28 @@ public class UploadFileOperation extends RemoteOperation {
         return mAccount;
     }
     
+    public String getFileName() {
+        return mOriginalFileName;
+    }
+    
     public OCFile getFile() {
         return mFile;
     }
     
+    public OCFile getOldFile() {
+        return mOldFile; 
+    }
+    
+    public String getOriginalStoragePath() {
+        return mOriginalStoragePath;
+    }
+    
     public String getStoragePath() {
         return mFile.getStoragePath();
     }
 
     public String getRemotePath() {
-        //return mFile.getRemotePath(); // DON'T MAKE THIS ; the remotePath used can be different to mFile.getRemotePath() if mForceOverwrite is 'false'; see run(...)
-        return mRemotePath;
+        return mFile.getRemotePath(); 
     }
 
     public String getMimeType() {
@@ -115,6 +143,9 @@ public class UploadFileOperation extends RemoteOperation {
         return mForceOverwrite;
     }
     
+    public boolean wasRenamed() {
+        return mWasRenamed;
+    }
     
     public Set<OnDatatransferProgressListener> getDataTransferListeners() {
         return mDataTransferListeners;
@@ -124,44 +155,173 @@ public class UploadFileOperation extends RemoteOperation {
         mDataTransferListeners.add(listener);
     }
     
-
     @Override
     protected RemoteOperationResult run(WebdavClient client) {
         RemoteOperationResult result = null;
-        boolean nameCheckPassed = false;
+        boolean localCopyPassed = false, nameCheckPassed = false;
+        File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
         try {
             /// rename the file to upload, if necessary
             if (!mForceOverwrite) {
-                mRemotePath = getAvailableRemotePath(client, mRemotePath);
+                String remotePath = getAvailableRemotePath(client, mRemotePath);
+                mWasRenamed = !remotePath.equals(mRemotePath);
+                if (mWasRenamed) {
+                   createNewOCFile(remotePath);
+                }
             }
+            nameCheckPassed = true;
         
+            String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);  /// not before getAvailableRemotePath() !!!
+            expectedFile = new File(expectedPath);
+            
+            /// check location of local file; if not the expected, copy to a temporal file before upload (if COPY is the expected behaviour)
+            if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) {
+
+                if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
+                    result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
+                    return result;  // error condition when the file should be copied
+                        
+                } else {
+                    String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
+                    mFile.setStoragePath(temporalPath);
+                    temporalFile = new File(temporalPath);
+                    if (!mOriginalStoragePath.equals(temporalPath)) {   // preventing weird but possible situation
+                        InputStream in = null;
+                        OutputStream out = null;
+                        try {
+                            File temporalParent = temporalFile.getParentFile();
+                            temporalParent.mkdirs();
+                            if (!temporalParent.isDirectory()) {
+                                throw new IOException("Unexpected error: parent directory could not be created");
+                            }
+                            temporalFile.createNewFile();
+                            if (!temporalFile.isFile()) {
+                                throw new IOException("Unexpected error: target file could not be created");
+                            }                    
+                            in = new FileInputStream(originalFile);
+                            out = new FileOutputStream(temporalFile);
+                            byte[] buf = new byte[1024];
+                            int len;
+                            while ((len = in.read(buf)) > 0){
+                                out.write(buf, 0, len);
+                            }
+                            
+                        } catch (Exception e) {
+                            result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
+                            return result;
+                            
+                        } finally {
+                            try {
+                                if (in != null) in.close();
+                            } catch (Exception e) {
+                                Log.d(TAG, "Weird exception while closing input stream for " + mOriginalStoragePath + " (ignoring)", e);
+                            }
+                            try {
+                                if (out != null) out.close();
+                            } catch (Exception e) {
+                                Log.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e);
+                            }
+                        }
+                    }
+                }
+            }
+            localCopyPassed = true;
+            
             /// perform the upload
-            nameCheckPassed = true;
             synchronized(mCancellationRequested) {
                 if (mCancellationRequested.get()) {
                     throw new OperationCancelledException();
                 } else {
-                    mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
+                    mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()));
                 }
             }
             int status = uploadFile(client);
+            
+            
+            /// move local temporal file or original file to its corresponding location in the ownCloud local folder
+            if (isSuccess(status)) {
+                if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) {
+                    mFile.setStoragePath(null);
+                    
+                } else {
+                    mFile.setStoragePath(expectedPath);
+                    File fileToMove = null;
+                    if (temporalFile != null) {             // FileUploader.LOCAL_BEHAVIOUR_COPY ; see where temporalFile was set
+                        fileToMove = temporalFile;
+                    } else {                                // FileUploader.LOCAL_BEHAVIOUR_MOVE
+                        fileToMove = originalFile;
+                    }
+                    if (!expectedFile.equals(fileToMove)) {
+                        File expectedFolder = expectedFile.getParentFile();
+                        expectedFolder.mkdirs();
+                        if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) {
+                            mFile.setStoragePath(null); // forget the local file
+                            // by now, treat this as a success; the file was uploaded; the user won't like that the local file is not linked, but this should be a veeery rare fail;
+                            // the best option could be show a warning message (but not a fail)
+                            //result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
+                            //return result;
+                        }
+                    }
+                } 
+            }
+            
             result = new RemoteOperationResult(isSuccess(status), status);
-            Log.i(TAG, "Upload of " + mFile.getStoragePath() + " to " + mRemotePath + ": " + result.getLogMessage());
-
+            
+            
         } catch (Exception e) {
-            // TODO something cleaner
+            // TODO something cleaner with cancellations
             if (mCancellationRequested.get()) {
                 result = new RemoteOperationResult(new OperationCancelledException());
             } else {
                 result = new RemoteOperationResult(e);
             }
-            Log.e(TAG, "Upload of " + mFile.getStoragePath() + " to " + mRemotePath + ": " + result.getLogMessage() + (nameCheckPassed?"":" (while checking file existence in server)"), result.getException());
+            
+            
+        } finally {
+            if (temporalFile != null && !originalFile.equals(temporalFile)) {
+                temporalFile.delete();
+            }
+            if (result.isSuccess()) {
+                Log.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
+                    
+            } else {
+                if (result.getException() != null) {
+                    String complement = "";
+                    if (!nameCheckPassed) {
+                        complement = " (while checking file existence in server)";
+                    } else if (!localCopyPassed) {
+                        complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name) + ")";
+                    }
+                    Log.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage() + complement, result.getException());
+                } else {
+                    Log.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
+                }
+            }
         }
         
         return result;
     }
 
     
+    private void createNewOCFile(String newRemotePath) {
+        // a new OCFile instance must be created for a new remote path
+        OCFile newFile = new OCFile(newRemotePath);
+        newFile.setCreationTimestamp(mFile.getCreationTimestamp());
+        newFile.setFileLength(mFile.getFileLength());
+        newFile.setMimetype(mFile.getMimetype());
+        newFile.setModificationTimestamp(mFile.getModificationTimestamp());
+        newFile.setModificationTimestampAtLastSyncForData(mFile.getModificationTimestampAtLastSyncForData());
+        // newFile.setEtag(mFile.getEtag())
+        newFile.setKeepInSync(mFile.keepInSync());
+        newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
+        newFile.setLastSyncDateForData(mFile.getLastSyncDateForData());
+        newFile.setStoragePath(mFile.getStoragePath());
+        newFile.setParentId(mFile.getParentId());
+        mOldFile = mFile;
+        mFile = newFile;
+    }
+
+
     public boolean isSuccess(int status) {
         return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT));
     }

+ 51 - 3
src/com/owncloud/android/providers/FileContentProvider.java

@@ -62,6 +62,8 @@ public class FileContentProvider extends ContentProvider {
                 ProviderTableMeta.FILE_CREATION);
         mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED,
                 ProviderTableMeta.FILE_MODIFIED);
+        mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,
+                ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA);
         mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH,
                 ProviderTableMeta.FILE_CONTENT_LENGTH);
         mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE,
@@ -70,6 +72,8 @@ public class FileContentProvider extends ContentProvider {
                 ProviderTableMeta.FILE_STORAGE_PATH);
         mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE,
                 ProviderTableMeta.FILE_LAST_SYNC_DATE);
+        mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA,
+                ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA);
         mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC,
                 ProviderTableMeta.FILE_KEEP_IN_SYNC);
         mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER,
@@ -221,18 +225,62 @@ public class FileContentProvider extends ContentProvider {
                     + ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, "
                     + ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, "
                     + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "
-                    + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER );");
+                    + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "
+                    + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, "
+                    + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER );"
+                    );
         }
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.i("SQL", "Entering in onUpgrade");
+            boolean upgraded = false; 
             if (oldVersion == 1 && newVersion >= 2) {
-                Log.i("SQL", "Entering in the ADD in onUpgrade");
+                Log.i("SQL", "Entering in the #1 ADD in onUpgrade");
                 db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
                            " ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC  + " INTEGER " +
                            " DEFAULT 0");
-            } else Log.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);
+                upgraded = true;
+            }
+            if (oldVersion < 3 && newVersion >= 3) {
+                Log.i("SQL", "Entering in the #2 ADD in onUpgrade");
+                db.beginTransaction();
+                try {
+                    db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
+                               " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA  + " INTEGER " +
+                               " DEFAULT 0");
+                    
+                    // assume there are not local changes pending to upload
+                    db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + 
+                            " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + System.currentTimeMillis() + 
+                            " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");
+                 
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+            if (oldVersion < 4 && newVersion >= 4) {
+                Log.i("SQL", "Entering in the #3 ADD in onUpgrade");
+                db.beginTransaction();
+                try {
+                    db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
+                           " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA  + " INTEGER " +
+                           " DEFAULT 0");
+                
+                    db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + 
+                           " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + ProviderTableMeta.FILE_MODIFIED + 
+                           " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");
+                
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+            if (!upgraded)
+                Log.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);
         }
 
     }

+ 97 - 39
src/com/owncloud/android/syncadapter/FileSyncAdapter.java

@@ -20,7 +20,10 @@ package com.owncloud.android.syncadapter;
 
 import java.io.IOException;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.jackrabbit.webdav.DavException;
 
@@ -28,16 +31,11 @@ import com.owncloud.android.R;
 import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-//<<<<<<< HEAD
 import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UpdateOCVersionOperation;
-/*=======
-import com.owncloud.android.files.services.FileDownloader;
-import com.owncloud.android.files.services.FileObserverService;
-import com.owncloud.android.utils.OwnCloudVersion;
->>>>>>> origin/master*/
-
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity;
 import android.accounts.Account;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -71,6 +69,10 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
     private int mFailedResultsCounter;    
     private RemoteOperationResult mLastFailedResult;
     private SyncResult mSyncResult;
+    private int mConflictsFound;
+    private int mFailsInFavouritesFound;
+    private Map<String, String> mForgottenLocalFiles;
+
     
     public FileSyncAdapter(Context context, boolean autoInitialize) {
         super(context, autoInitialize);
@@ -88,8 +90,13 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
         mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
         mFailedResultsCounter = 0;
         mLastFailedResult = null;
+        mConflictsFound = 0;
+        mFailsInFavouritesFound = 0;
+        mForgottenLocalFiles = new HashMap<String, String>();
         mSyncResult = syncResult;
-        
+        mSyncResult.fullSyncRequested = false;
+        mSyncResult.delayUntil = 60*60*24; // sync after 24h
+
         this.setAccount(account);
         this.setContentProvider(provider);
         this.setStorageManager(new FileDataStorageManager(account, getContentProvider()));
@@ -126,13 +133,20 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
                 
                 /// notify the user about the failure of MANUAL synchronization
                 notifyFailedSynchronization();
+                
+            }
+            if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
+                notifyFailsInFavourites();
+            }
+            if (mForgottenLocalFiles.size() > 0) {
+                notifyForgottenLocalFiles();
+                
             }
             sendStickyBroadcast(false, null, mLastFailedResult);        // message to signal the end to the UI
         }
         
     }
-    
-    
+
     
     /**
      * Called by system SyncManager when a synchronization is required to be cancelled.
@@ -186,12 +200,19 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
         // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess
         sendStickyBroadcast(true, remotePath, null);
         
-        if (result.isSuccess()) {
+        if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) {
+            
+            if (result.getCode() == ResultCode.SYNC_CONFLICT) {
+                mConflictsFound += synchFolderOp.getConflictsFound();
+                mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound();
+            }
+            if (synchFolderOp.getForgottenLocalFiles().size() > 0) {
+                mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles());
+            }
             // synchronize children folders 
             List<OCFile> children = synchFolderOp.getChildren();
             fetchChildren(children);    // beware of the 'hidden' recursion here!
             
-//<<<<<<< HEAD
         } else {
             if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED) {
                 mSyncResult.stats.numAuthExceptions++;
@@ -201,33 +222,6 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
                 
             } else if (result.getException() instanceof IOException) { 
                 mSyncResult.stats.numIoExceptions++;
-/*=======
-                // insertion or update of files
-                List<OCFile> updatedFiles = new Vector<OCFile>(resp.getResponses().length - 1);
-                for (int i = 1; i < resp.getResponses().length; ++i) {
-                    WebdavEntry we = new WebdavEntry(resp.getResponses()[i], getUri().getPath());
-                    OCFile file = fillOCFile(we);
-                    file.setParentId(parentId);
-                    if (getStorageManager().getFileByPath(file.getRemotePath()) != null &&
-                            getStorageManager().getFileByPath(file.getRemotePath()).keepInSync() &&
-                            file.getModificationTimestamp() > getStorageManager().getFileByPath(file.getRemotePath())
-                                                                         .getModificationTimestamp()) {
-                        // first disable observer so we won't get file upload right after download
-                        Log.d(TAG, "Disabling observation of remote file" + file.getRemotePath());
-                        Intent intent = new Intent(getContext(), FileObserverService.class);
-                        intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_ADD_DOWNLOADING_FILE);
-                        intent.putExtra(FileObserverService.KEY_CMD_ARG, file.getRemotePath());
-                        getContext().startService(intent);
-                        intent = new Intent(this.getContext(), FileDownloader.class);
-                        intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());
-                        intent.putExtra(FileDownloader.EXTRA_FILE, file);
-                        file.setKeepInSync(true);
-                        getContext().startService(intent);
-                    }
-                    if (getStorageManager().getFileByPath(file.getRemotePath()) != null)
-                        file.setKeepInSync(getStorageManager().getFileByPath(file.getRemotePath()).keepInSync());
->>>>>>> origin/master*/
-                
             }
             mFailedResultsCounter++;
             mLastFailedResult = result;
@@ -306,6 +300,70 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
         ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_ticker, notification);
     }
 
+
+    /**
+     * Notifies the user about conflicts and strange fails when trying to synchronize the contents of kept-in-sync files.
+     * 
+     * By now, we won't consider a failed synchronization.
+     */
+    private void notifyFailsInFavourites() {
+        if (mFailedResultsCounter > 0) {
+            Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis());
+            notification.flags |= Notification.FLAG_AUTO_CANCEL;
+            // TODO put something smart in the contentIntent below
+            notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);
+            notification.setLatestEventInfo(getContext().getApplicationContext(), 
+                                            getContext().getString(R.string.sync_fail_in_favourites_ticker), 
+                                            String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), 
+                                            notification.contentIntent);
+            ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification);
+            
+        } else {
+            Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis());
+            notification.flags |= Notification.FLAG_AUTO_CANCEL;
+            // TODO put something smart in the contentIntent below
+            notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);
+            notification.setLatestEventInfo(getContext().getApplicationContext(), 
+                                            getContext().getString(R.string.sync_conflicts_in_favourites_ticker), 
+                                            String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), 
+                                            notification.contentIntent);
+            ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification);
+        } 
+    }
+
     
+    /**
+     * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because 
+     * copying them inside the ownCloud local directory was not possible.
+     * 
+     * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have 
+     * synchronization problems if a local file is linked to more than one remote file.
+     * 
+     * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory.
+     */
+    private void notifyForgottenLocalFiles() {
+        Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis());
+        notification.flags |= Notification.FLAG_AUTO_CANCEL;
 
+        /// includes a pending intent in the notification showing a more detailed explanation
+        Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class);
+        explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount());
+        ArrayList<String> remotePaths = new ArrayList<String>();
+        ArrayList<String> localPaths = new ArrayList<String>();
+        remotePaths.addAll(mForgottenLocalFiles.keySet());
+        localPaths.addAll(mForgottenLocalFiles.values());
+        explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_LOCAL_PATHS, localPaths);
+        explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths);  
+        explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        
+        notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0);
+        notification.setLatestEventInfo(getContext().getApplicationContext(), 
+                                        getContext().getString(R.string.sync_foreign_files_forgotten_ticker), 
+                                        String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size()), 
+                                        notification.contentIntent);
+        ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification);
+        
+    }
+    
+    
 }

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

@@ -166,7 +166,7 @@ public class AccountSelectActivity extends SherlockListActivity implements
             }
         }
 
-        return false;
+        return true;
     }
 
     private void populateAccountList() {

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

@@ -72,6 +72,7 @@ import android.view.View.OnFocusChangeListener;
 import android.view.Window;
 import android.widget.CheckBox;
 import android.widget.EditText;
+import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
 import com.owncloud.android.R;
@@ -201,9 +202,12 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         tv.setOnFocusChangeListener(this);
         tv2.setOnFocusChangeListener(this);
         
+        Button b = (Button) findViewById(R.id.account_register);
+        if (b != null) {
+            b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name)));
+        }
+
         mNewCapturedUriFromOAuth2Redirection = null;
-        
-        Log.d(TAG, "onCreate");
     }
 
     
@@ -443,7 +447,11 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
                 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
             }
             TextView tv = (TextView) findViewById(R.id.account_username);
-            tv.setError(message);
+            tv.setError(message + "        ");  // the extra spaces are a workaround for an ugly bug: 
+                                                // 1. insert wrong credentials and connect
+                                                // 2. put the focus on the user name field with using hardware controls (don't touch the screen); the error is shown UNDER the field
+                                                // 3. touch the user name field; the software keyboard appears; the error popup is moved OVER the field and SHRINKED in width, losing the last word
+                                                // Seen, at least, in Android 2.x devices
         }
     }
     public void onCancelClick(View view) {
@@ -484,7 +492,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     }
 
     public void onRegisterClick(View view) {
-        Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse("https://owncloud.com/mobile/new"));
+        Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register)));
         setResult(RESULT_CANCELED);
         startActivity(register);
     }
@@ -666,8 +674,14 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             onFocusChange(findViewById(R.id.host_URL), false);
         } else if (v.getId() == R.id.viewPassword) {
             TextView view = (TextView) findViewById(R.id.account_password);
-            int input_type = InputType.TYPE_CLASS_TEXT
-                    | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
+            int input_type = view.getInputType();
+            if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
+                input_type = InputType.TYPE_CLASS_TEXT
+                        | InputType.TYPE_TEXT_VARIATION_PASSWORD;
+            } else {
+                input_type = InputType.TYPE_CLASS_TEXT
+                        | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
+            }
             view.setInputType(input_type);
         }
     }

+ 18 - 9
src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java

@@ -19,6 +19,7 @@
 package com.owncloud.android.ui.activity;
 
 import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.ui.dialog.ConflictsResolveDialog;
 import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision;
@@ -38,21 +39,27 @@ import android.util.Log;
  */
 public class ConflictsResolveActivity extends SherlockFragmentActivity implements OnConflictDecisionMadeListener {
 
+    public static final String EXTRA_FILE = "FILE";
+    public static final String EXTRA_ACCOUNT = "ACCOUNT";
+
     private String TAG = ConflictsResolveActivity.class.getSimpleName();
     
-    private String mRemotePath;
+    //private String mRemotePath;
     
-    private String mLocalPath;
+    //private String mLocalPath;
     
+    private OCFile mFile;
     private Account mOCAccount;
     
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mRemotePath = getIntent().getStringExtra("remotepath");
-        mLocalPath = getIntent().getStringExtra("localpath");
-        mOCAccount = getIntent().getParcelableExtra("account");
-        ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(mRemotePath, this);
+        
+        //mRemotePath = getIntent().getStringExtra("remotepath");
+        //mLocalPath = getIntent().getStringExtra("localpath");
+        mFile = getIntent().getParcelableExtra(EXTRA_FILE);
+        mOCAccount = getIntent().getParcelableExtra(EXTRA_ACCOUNT);
+        ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(mFile.getRemotePath(), this);
         d.showDialog(this);
     }
 
@@ -62,18 +69,20 @@ public class ConflictsResolveActivity extends SherlockFragmentActivity implement
         
         switch (decision) {
             case CANCEL:
+                finish();
                 return;
             case OVERWRITE:
                 i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
-            case KEEP_BOTH: // fallthrough
+                break;
+            case KEEP_BOTH:
+                i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
                 break;
             default:
                 Log.wtf(TAG, "Unhandled conflict decision " + decision);
                 return;
         }
         i.putExtra(FileUploader.KEY_ACCOUNT, mOCAccount);
-        i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath);
-        i.putExtra(FileUploader.KEY_LOCAL_FILE, mLocalPath);
+        i.putExtra(FileUploader.KEY_FILE, mFile);
         i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
         
         startService(i);

+ 258 - 0
src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java

@@ -0,0 +1,258 @@
+package com.owncloud.android.ui.activity;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.DialogFragment;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
+import com.owncloud.android.utils.FileStorageUtils;
+
+
+/**
+ * Activity reporting errors occurred when local files uploaded to an ownCloud account with an app in
+ * version under 1.3.16 where being copied to the ownCloud local folder.
+ * 
+ * Allows the user move the files to the ownCloud local folder, or let them unlinked to the remote
+ * files.
+ * 
+ * Shown when the error notification summarizing the list of errors is clicked by the user.
+ * 
+ * @author David A. Velasco
+ */
+public class ErrorsWhileCopyingHandlerActivity  extends SherlockFragmentActivity implements OnClickListener {
+
+    private static final String TAG = ErrorsWhileCopyingHandlerActivity.class.getSimpleName();
+    
+    public static final String EXTRA_ACCOUNT = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_ACCOUNT";
+    public static final String EXTRA_LOCAL_PATHS = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_LOCAL_PATHS";
+    public static final String EXTRA_REMOTE_PATHS = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_REMOTE_PATHS";
+
+    private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG";
+    
+    protected Account mAccount;
+    protected FileDataStorageManager mStorageManager;
+    protected ArrayList<String> mLocalPaths;
+    protected ArrayList<String> mRemotePaths;
+    protected ArrayAdapter<String> mAdapter;
+    protected Handler mHandler;
+    private DialogFragment mCurrentDialog;
+    
+    /**
+     * {@link}
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        /// read extra parameters in intent
+        Intent intent = getIntent();
+        mAccount = intent.getParcelableExtra(EXTRA_ACCOUNT);
+        mRemotePaths = intent.getStringArrayListExtra(EXTRA_REMOTE_PATHS);
+        mLocalPaths = intent.getStringArrayListExtra(EXTRA_LOCAL_PATHS);
+        mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
+        mHandler = new Handler();
+        if (mCurrentDialog != null) {
+            mCurrentDialog.dismiss();
+            mCurrentDialog = null;
+        }
+        
+        /// load generic layout
+        setContentView(R.layout.generic_explanation);
+        
+        /// customize text message
+        TextView textView = (TextView) findViewById(R.id.message);
+        String appName = getString(R.string.app_name);
+        String message = String.format(getString(R.string.sync_foreign_files_forgotten_explanation), appName, appName, appName, appName, mAccount.name);
+        textView.setText(message);
+        textView.setMovementMethod(new ScrollingMovementMethod());
+        
+        /// load the list of local and remote files that failed
+        ListView listView = (ListView) findViewById(R.id.list);
+        if (mLocalPaths != null && mLocalPaths.size() > 0) {
+            mAdapter = new ErrorsWhileCopyingListAdapter();
+            listView.setAdapter(mAdapter);
+        } else {
+            listView.setVisibility(View.GONE);
+            mAdapter = null;
+        }
+        
+        /// customize buttons
+        Button cancelBtn = (Button) findViewById(R.id.cancel);
+        Button okBtn = (Button) findViewById(R.id.ok);
+        okBtn.setText(R.string.foreign_files_move);
+        cancelBtn.setOnClickListener(this);
+        okBtn.setOnClickListener(this);
+    }
+    
+    
+    /**
+     * Customized adapter, showing the local files as main text in two-lines list item and the remote files
+     * as the secondary text. 
+     * 
+     * @author David A. Velasco
+     */
+    public class ErrorsWhileCopyingListAdapter extends ArrayAdapter<String> {
+        
+        ErrorsWhileCopyingListAdapter() {
+            super(ErrorsWhileCopyingHandlerActivity.this, android.R.layout.two_line_list_item, android.R.id.text1, mLocalPaths);
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return false;
+        }
+        
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public View getView (int position, View convertView, ViewGroup parent) {
+            View view = convertView;
+            if (view == null) {
+                LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                view = vi.inflate(android.R.layout.two_line_list_item, null);
+            }
+            if (view != null)  {
+                String localPath = getItem(position);
+                if (localPath != null) {
+                    TextView text1 = (TextView) view.findViewById(android.R.id.text1);
+                    if (text1 != null) {
+                        text1.setText(String.format(getString(R.string.foreign_files_local_text), localPath));
+                    }
+                }
+                if (mRemotePaths != null && mRemotePaths.size() > 0 && position >= 0 && position < mRemotePaths.size()) {
+                    TextView text2 = (TextView) view.findViewById(android.R.id.text2);
+                    String remotePath = mRemotePaths.get(position);
+                    if (text2 != null && remotePath != null) {
+                        text2.setText(String.format(getString(R.string.foreign_files_remote_text), remotePath));
+                    }
+                }
+            }
+            return view;
+        }
+    }
+
+
+    /**
+     * Listener method to perform the MOVE / CANCEL action available in this activity.
+     * 
+     * @param v     Clicked view (button MOVE or CANCEL)
+     */
+    @Override
+    public void onClick(View v) {
+        if (v.getId() == R.id.ok) {
+            /// perform movement operation in background thread
+            Log.d(TAG, "Clicked MOVE, start movement");
+            new MoveFilesTask().execute();            
+            
+        } else if (v.getId() == R.id.cancel) {
+            /// just finish
+            Log.d(TAG, "Clicked CANCEL, bye");
+            finish();
+            
+        } else {
+            Log.e(TAG, "Clicked phantom button, id: " + v.getId());
+        }
+    }
+
+    
+    /**
+     * Asynchronous task performing the move of all the local files to the ownCloud folder.
+     * 
+     * @author David A. Velasco
+     */
+    private class MoveFilesTask extends AsyncTask<Void, Void, Boolean> {
+
+        /**
+         * Updates the UI before trying the movement
+         */
+        @Override
+        protected void onPreExecute () {
+            /// progress dialog and disable 'Move' button
+            mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false);
+            mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
+            findViewById(R.id.ok).setEnabled(false);
+        }
+        
+        
+        /**
+         * Performs the movement
+         * 
+         * @return     'False' when the movement of any file fails.
+         */
+        @Override
+        protected Boolean doInBackground(Void... params) {
+            while (!mLocalPaths.isEmpty()) {
+                String currentPath = mLocalPaths.get(0);
+                File currentFile = new File(currentPath);
+                String expectedPath = FileStorageUtils.getSavePath(mAccount.name) + mRemotePaths.get(0);
+                File expectedFile = new File(expectedPath);
+
+                if (expectedFile.equals(currentFile) || currentFile.renameTo(expectedFile)) {
+                    // SUCCESS
+                    OCFile file = mStorageManager.getFileByPath(mRemotePaths.get(0));
+                    file.setStoragePath(expectedPath);
+                    mStorageManager.saveFile(file);
+                    mRemotePaths.remove(0);
+                    mLocalPaths.remove(0);
+                        
+                } else {
+                    // FAIL
+                    return false;   
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Updates the activity UI after the movement of local files is tried.
+         * 
+         * If the movement was successful for all the files, finishes the activity immediately.
+         * 
+         * In other case, the list of remaining files is still available to retry the movement.
+         * 
+         * @param result      'True' when the movement was successful.
+         */
+        @Override
+        protected void onPostExecute(Boolean result) {
+            mAdapter.notifyDataSetChanged();
+            mCurrentDialog.dismiss();
+            mCurrentDialog = null;
+            findViewById(R.id.ok).setEnabled(true);
+            
+            if (result) {
+                // nothing else to do in this activity
+                Toast t = Toast.makeText(ErrorsWhileCopyingHandlerActivity.this, getString(R.string.foreign_files_success), Toast.LENGTH_LONG);
+                t.show();
+                finish();
+                
+            } else {
+                Toast t = Toast.makeText(ErrorsWhileCopyingHandlerActivity.this, getString(R.string.foreign_files_fail), Toast.LENGTH_LONG);
+                t.show();
+            }
+        }
+    }    
+
+}

+ 5 - 2
src/com/owncloud/android/ui/activity/FileDetailActivity.java

@@ -114,7 +114,7 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
             }
             FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
             if (fragment != null)
-                fragment.updateFileDetails();   // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())
+                fragment.updateFileDetails(false);   // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())
         }
 
         @Override
@@ -152,6 +152,9 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
         case android.R.id.home:
             backToDisplayActivity();
             returnValue = true;
+            break;
+        default:
+        	returnValue = super.onOptionsItemSelected(item);
         }
         
         return returnValue;
@@ -165,7 +168,7 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
         super.onResume();
         if (!mConfigurationChangedToLandscape) { 
             FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
-            fragment.updateFileDetails();
+            fragment.updateFileDetails(false);
         }
     }
     

+ 205 - 23
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -39,6 +39,8 @@ import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -72,7 +74,13 @@ import com.owncloud.android.files.services.FileObserverService;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.operations.OnRemoteOperationListener;
+import com.owncloud.android.operations.RemoteOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoveFileOperation;
+import com.owncloud.android.operations.RenameFileOperation;
+import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.syncadapter.FileSyncService;
 import com.owncloud.android.ui.dialog.SslValidatorDialog;
 import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
@@ -90,7 +98,7 @@ import eu.alefzero.webdav.WebdavClient;
  */
 
 public class FileDisplayActivity extends SherlockFragmentActivity implements
-    OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener {
+    OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener {
     
     private ArrayAdapter<String> mDirectories;
     private OCFile mCurrentDir = null;
@@ -122,6 +130,8 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
     private static final int ACTION_SELECT_MULTIPLE_FILES = 2;
     
     private static final String TAG = "FileDisplayActivity";
+
+    private static int[] mMenuIdentifiersToPatch = {R.id.about_app};
     
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -270,9 +280,32 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
     public boolean onCreateOptionsMenu(Menu menu) {
         MenuInflater inflater = getSherlock().getMenuInflater();
             inflater.inflate(R.menu.menu, menu);
+            
+            patchHiddenAccents(menu);
+            
             return true;
     }
 
+    /**
+     * Workaround for this: <a href="http://code.google.com/p/android/issues/detail?id=3974">http://code.google.com/p/android/issues/detail?id=3974</a> 
+     * 
+     * @param menu      Menu to patch
+     */
+    private void patchHiddenAccents(Menu menu) {
+        for (int i = 0; i < mMenuIdentifiersToPatch.length ; i++) {
+            MenuItem aboutItem = menu.findItem(mMenuIdentifiersToPatch[i]);
+            if (aboutItem != null && aboutItem.getIcon() instanceof BitmapDrawable) {
+                // Clip off the bottom three (density independent) pixels of transparent padding
+                Bitmap original = ((BitmapDrawable) aboutItem.getIcon()).getBitmap();
+                float scale = getResources().getDisplayMetrics().density;
+                int clippedHeight = (int) (original.getHeight() - (3 * scale));
+                Bitmap scaled = Bitmap.createBitmap(original, 0, 0, original.getWidth(), clippedHeight);
+                aboutItem.setIcon(new BitmapDrawable(getResources(), scaled));
+            }
+        }
+    }
+
+
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         boolean retval = true;
@@ -305,7 +338,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
                 break;
             }
             default:
-                retval = false;
+                retval = super.onOptionsItemSelected(item);
         }
         return retval;
     }
@@ -339,16 +372,16 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
      */
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         
-        if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && resultCode == RESULT_OK) {
-            requestSimpleUpload(data);
+        if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
+            requestSimpleUpload(data, resultCode);
             
-        } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && resultCode == RESULT_OK) {
-            requestMultipleUpload(data);
+        } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
+            requestMultipleUpload(data, resultCode);
             
         }
     }
 
-    private void requestMultipleUpload(Intent data) {
+    private void requestMultipleUpload(Intent data, int resultCode) {
         String[] filePaths = data.getStringArrayExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);
         if (filePaths != null) {
             String[] remotePaths = new String[filePaths.length];
@@ -367,6 +400,8 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
             i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths);
             i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
             i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
+            if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)
+                i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
             startService(i);
             
         } else {
@@ -378,7 +413,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
     }
 
 
-    private void requestSimpleUpload(Intent data) {
+    private void requestSimpleUpload(Intent data, int resultCode) {
         String filepath = null;
         try {
             Uri selectedImageUri = data.getData();
@@ -418,6 +453,8 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
         i.putExtra(FileUploader.KEY_LOCAL_FILE, filepath);
         i.putExtra(FileUploader.KEY_REMOTE_FILE, remotepath);
         i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
+        if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)
+            i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
         startService(i);
     }
 
@@ -558,7 +595,8 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
                     dialog.dismiss();
                 }
             });
-            builder.setNegativeButton(R.string.common_exit, new OnClickListener() {
+            String message = String.format(getString(R.string.common_exit), getString(R.string.app_name));
+            builder.setNegativeButton(message, new OnClickListener() {
                 public void onClick(DialogInterface dialog, int which) {
                     dialog.dismiss();
                     finish();
@@ -574,7 +612,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
             PackageInfo pkg;
             try {
                 pkg = getPackageManager().getPackageInfo(getPackageName(), 0);
-                builder.setMessage(String.format(getString(R.string.about_message), pkg.versionName));
+                builder.setMessage(String.format(getString(R.string.about_message), getString(R.string.app_name), pkg.versionName));
                 builder.setIcon(android.R.drawable.ic_menu_info_details);
                 dialog = builder.create();
             } catch (NameNotFoundException e) {
@@ -650,6 +688,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
                     if (item == 0) {
                         //if (!mDualPane) { 
                             Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class);
+                            action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this));
                             startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES);
                         //} else {
                             // TODO create and handle new fragment LocalFileListFragment
@@ -847,6 +886,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
                 }
                 
                 setSupportProgressBarIndeterminateVisibility(inProgress);
+                removeStickyBroadcast(intent);
                 
             }
             
@@ -869,16 +909,11 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
          */
         @Override
         public void onReceive(Context context, Intent intent) {
-            long parentDirId = intent.getLongExtra(FileUploader.EXTRA_PARENT_DIR_ID, -1);
-            OCFile parentDir = mStorageManager.getFileById(parentDirId);
+            String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
             String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);
-
-            if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name) &&
-                    parentDir != null && 
-                    (   (mCurrentDir == null && parentDir.getFileName().equals("/")) ||
-                            parentDir.equals(mCurrentDir)
-                    )
-                ) {
+            boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);
+            boolean isDescendant = (mCurrentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(mCurrentDir.getRemotePath()));
+            if (sameAccount && isDescendant) {
                 OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
                 if (fileListFragment != null) { 
                     fileListFragment.listDirectory();
@@ -897,9 +932,9 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
         public void onReceive(Context context, Intent intent) {
             String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
             String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
-
-            if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name) &&
-                     mCurrentDir != null && mCurrentDir.getFileId() == mStorageManager.getFileByPath(downloadedRemotePath).getParentId()) {
+            boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);
+            boolean isDescendant = (mCurrentDir != null) && (downloadedRemotePath != null) && (downloadedRemotePath.startsWith(mCurrentDir.getRemotePath()));
+            if (sameAccount && isDescendant) {
                 OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
                 if (fileListFragment != null) { 
                     fileListFragment.listDirectory();
@@ -1024,7 +1059,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
             if (mDualPane) {
                 FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
                 if (fragment != null)
-                    fragment.updateFileDetails();
+                    fragment.updateFileDetails(false);
             }
         }
 
@@ -1069,4 +1104,151 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
     }
 
 
+    /**
+     * Updates the view associated to the activity after the finish of some operation over files
+     * in the current account.
+     * 
+     * @param operation     Removal operation performed.
+     * @param result        Result of the removal.
+     */
+    @Override
+    public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
+        if (operation instanceof RemoveFileOperation) {
+            onRemoveFileOperationFinish((RemoveFileOperation)operation, result);
+                
+        } else if (operation instanceof RenameFileOperation) {
+            onRenameFileOperationFinish((RenameFileOperation)operation, result);
+            
+        } else if (operation instanceof SynchronizeFileOperation) {
+            onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);
+        }
+    }
+
+
+    /**
+     * Updates the view associated to the activity after the finish of an operation trying to remove a 
+     * file. 
+     * 
+     * @param operation     Removal operation performed.
+     * @param result        Result of the removal.
+     */
+    private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {
+        dismissDialog(DIALOG_SHORT_WAIT);
+        if (result.isSuccess()) {
+            Toast msg = Toast.makeText(this, R.string.remove_success_msg, Toast.LENGTH_LONG);
+            msg.show();
+            OCFile removedFile = operation.getFile();
+            if (mDualPane) {
+                FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+                if (details != null && removedFile.equals(details.getDisplayedFile()) ) {
+                    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+                    transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment
+                    transaction.commit();
+                }
+            }
+            if (mStorageManager.getFileById(removedFile.getParentId()).equals(mCurrentDir)) {
+                mFileList.listDirectory();
+            }
+                
+        } else {
+            Toast msg = Toast.makeText(this, R.string.remove_fail_msg, Toast.LENGTH_LONG); 
+            msg.show();
+            if (result.isSslRecoverableException()) {
+                mLastSslUntrustedServerResult = result;
+                showDialog(DIALOG_SSL_VALIDATOR); 
+            }
+        }
+    }
+
+    /**
+     * Updates the view associated to the activity after the finish of an operation trying to rename a 
+     * file. 
+     * 
+     * @param operation     Renaming operation performed.
+     * @param result        Result of the renaming.
+     */
+    private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) {
+        dismissDialog(DIALOG_SHORT_WAIT);
+        OCFile renamedFile = operation.getFile();
+        if (result.isSuccess()) {
+            if (mDualPane) {
+                FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+                if (details != null && renamedFile.equals(details.getDisplayedFile()) ) {
+                    details.updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this));
+                }
+            }
+            if (mStorageManager.getFileById(renamedFile.getParentId()).equals(mCurrentDir)) {
+                mFileList.listDirectory();
+            }
+            
+        } else {
+            if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) {
+                Toast msg = Toast.makeText(this, R.string.rename_local_fail_msg, Toast.LENGTH_LONG); 
+                msg.show();
+                // TODO throw again the new rename dialog
+            } else {
+                Toast msg = Toast.makeText(this, R.string.rename_server_fail_msg, Toast.LENGTH_LONG); 
+                msg.show();
+                if (result.isSslRecoverableException()) {
+                    mLastSslUntrustedServerResult = result;
+                    showDialog(DIALOG_SSL_VALIDATOR); 
+                }
+            }
+        }
+    }
+
+
+    private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {
+        dismissDialog(DIALOG_SHORT_WAIT);
+        OCFile syncedFile = operation.getLocalFile();
+        if (!result.isSuccess()) {
+            if (result.getCode() == ResultCode.SYNC_CONFLICT) {
+                Intent i = new Intent(this, ConflictsResolveActivity.class);
+                i.putExtra(ConflictsResolveActivity.EXTRA_FILE, syncedFile);
+                i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+                startActivity(i);
+                
+            } else {
+                Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG); 
+                msg.show();
+            }
+            
+        } else {
+            if (operation.transferWasRequested()) {
+                mFileList.listDirectory();
+                onTransferStateChanged(syncedFile, true, true);
+                
+            } else {
+                Toast msg = Toast.makeText(this, R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); 
+                msg.show();
+            }
+        }
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {
+        /*OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
+        if (fileListFragment != null) { 
+            fileListFragment.listDirectory();
+        }*/
+        if (mDualPane) {
+            FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+            if (details != null && file.equals(details.getDisplayedFile()) ) {
+                if (downloading || uploading) {
+                    details.updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this));
+                } else {
+                    details.updateFileDetails(downloading || uploading);
+                }
+            }
+        }
+    }
+
+
+    
+
+
 }

+ 95 - 0
src/com/owncloud/android/ui/activity/GenericExplanationActivity.java

@@ -0,0 +1,95 @@
+package com.owncloud.android.ui.activity;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.method.ScrollingMovementMethod;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.owncloud.android.R;
+
+/**
+ * Activity showing a text message and, optionally, a couple list of single or paired text strings.
+ * 
+ * Added to show explanations for notifications when the user clicks on them, and there no place
+ * better to show them.
+ * 
+ * @author David A. Velasco
+ */
+public class GenericExplanationActivity  extends SherlockFragmentActivity {
+
+    public static final String EXTRA_LIST = GenericExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST";
+    public static final String EXTRA_LIST_2 = GenericExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST_2";
+    public static final String MESSAGE = GenericExplanationActivity.class.getCanonicalName() + ".MESSAGE";
+    
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        Intent intent = getIntent();
+        String message = intent.getStringExtra(MESSAGE); 
+        ArrayList<String> list = intent.getStringArrayListExtra(EXTRA_LIST);
+        ArrayList<String> list2 = intent.getStringArrayListExtra(EXTRA_LIST_2);
+        
+        setContentView(R.layout.generic_explanation);
+        
+        if (message != null) {
+            TextView textView = (TextView) findViewById(R.id.message);
+            textView.setText(message);
+            textView.setMovementMethod(new ScrollingMovementMethod());
+        }
+        
+        ListView listView = (ListView) findViewById(R.id.list);
+        if (list != null && list.size() > 0) {
+            //ListAdapter adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
+            ListAdapter adapter = new ExplanationListAdapterView(this, list, list2);
+            listView.setAdapter(adapter);
+        } else {
+            listView.setVisibility(View.GONE);
+        }
+    }
+    
+    public class ExplanationListAdapterView extends ArrayAdapter<String> {
+        
+        ArrayList<String> mList;
+        ArrayList<String> mList2;
+        
+        ExplanationListAdapterView(Context context, ArrayList<String> list, ArrayList<String> list2) {
+            super(context, android.R.layout.two_line_list_item, android.R.id.text1, list);
+            mList = list;
+            mList2 = list2;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return false;
+        }
+        
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public View getView (int position, View convertView, ViewGroup parent) {
+            View view = super.getView(position, convertView, parent);
+            if (view != null)  {
+                if (mList2 != null && mList2.size() > 0 && position >= 0 && position < mList2.size()) {
+                    TextView text2 = (TextView) view.findViewById(android.R.id.text2);
+                    if (text2 != null) {
+                        text2.setText(mList2.get(position));
+                    }
+                }
+            }
+            return view;
+        }
+    }
+
+}

+ 131 - 7
src/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -20,9 +20,12 @@ package com.owncloud.android.ui.activity;
 
 import java.io.File;
 
+import android.accounts.Account;
 import android.content.Intent;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Environment;
+import android.support.v4.app.DialogFragment;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -35,7 +38,11 @@ import com.actionbarsherlock.app.ActionBar;
 import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
 import com.actionbarsherlock.app.SherlockFragmentActivity;
 import com.actionbarsherlock.view.MenuItem;
+import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
+import com.owncloud.android.ui.fragment.ConfirmationDialogFragment;
 import com.owncloud.android.ui.fragment.LocalFileListFragment;
+import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
+import com.owncloud.android.utils.FileStorageUtils;
 
 import com.owncloud.android.R;
 
@@ -48,18 +55,25 @@ import com.owncloud.android.R;
  */
 
 public class UploadFilesActivity extends SherlockFragmentActivity implements
-    LocalFileListFragment.ContainerActivity, OnNavigationListener, OnClickListener {
+    LocalFileListFragment.ContainerActivity, OnNavigationListener, OnClickListener, ConfirmationDialogFragmentListener {
     
     private ArrayAdapter<String> mDirectories;
     private File mCurrentDir = null;
     private LocalFileListFragment mFileListFragment;
     private Button mCancelBtn;
     private Button mUploadBtn;
+    private Account mAccount;
+    private DialogFragment mCurrentDialog;
     
-    public static final String EXTRA_DIRECTORY_PATH = "com.owncloud.android.Directory"; 
-    public static final String EXTRA_CHOSEN_FILES = "com.owncloud.android.ChosenFiles";
+    public static final String EXTRA_ACCOUNT = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_ACCOUNT";
+    public static final String EXTRA_CHOSEN_FILES = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES";
+
+    public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER; 
     
+    private static final String KEY_DIRECTORY_PATH = UploadFilesActivity.class.getCanonicalName() + ".KEY_DIRECTORY_PATH";
     private static final String TAG = "UploadFilesActivity";
+    private static final String WAIT_DIALOG_TAG = "WAIT";
+    private static final String QUERY_TO_MOVE_DIALOG_TAG = "QUERY_TO_MOVE";
     
     
     @Override
@@ -68,11 +82,13 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements
         super.onCreate(savedInstanceState);
 
         if(savedInstanceState != null) {
-            mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.EXTRA_DIRECTORY_PATH));
+            mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.KEY_DIRECTORY_PATH));
         } else {
             mCurrentDir = Environment.getExternalStorageDirectory();
         }
         
+        mAccount = getIntent().getParcelableExtra(EXTRA_ACCOUNT);
+                
         /// USER INTERFACE
             
         // Drop-down navigation 
@@ -102,6 +118,12 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements
         actionBar.setDisplayShowTitleEnabled(false);
         actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
         actionBar.setListNavigationCallbacks(mDirectories, this);
+        
+        // wait dialog
+        if (mCurrentDialog != null) {
+            mCurrentDialog.dismiss();
+            mCurrentDialog = null;
+        }
             
         Log.d(TAG, "onCreate() end");
     }
@@ -118,7 +140,7 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements
                 break;
             }
             default:
-                retval = false;
+                retval = onOptionsItemSelected(item);
         }
         return retval;
     }
@@ -161,7 +183,7 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements
         // responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved
         Log.d(TAG, "onSaveInstanceState() start");
         super.onSaveInstanceState(outState);
-        outState.putString(UploadFilesActivity.EXTRA_DIRECTORY_PATH, mCurrentDir.getAbsolutePath());
+        outState.putString(UploadFilesActivity.KEY_DIRECTORY_PATH, mCurrentDir.getAbsolutePath());
         Log.d(TAG, "onSaveInstanceState() end");
     }
 
@@ -246,6 +268,9 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements
 
     /**
      * Performs corresponding action when user presses 'Cancel' or 'Upload' button
+     * 
+     * TODO Make here the real request to the Upload service ; will require to receive the account and 
+     * target folder where the upload must be done in the received intent.
      */
     @Override
     public void onClick(View v) {
@@ -254,11 +279,110 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements
             finish();
             
         } else if (v.getId() == R.id.upload_files_btn_upload) {
+            new CheckAvailableSpaceTask().execute();
+        }
+    }
+
+
+    /**
+     * Asynchronous task checking if there is space enough to copy all the files chosen
+     * to upload into the ownCloud local folder.
+     * 
+     * Maybe an AsyncTask is not strictly necessary, but who really knows.
+     * 
+     * @author David A. Velasco
+     */
+    private class CheckAvailableSpaceTask extends AsyncTask<Void, Void, Boolean> {
+
+        /**
+         * Updates the UI before trying the movement
+         */
+        @Override
+        protected void onPreExecute () {
+            /// progress dialog and disable 'Move' button
+            mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false);
+            mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
+        }
+        
+        
+        /**
+         * Checks the available space
+         * 
+         * @return     'True' if there is space enough.
+         */
+        @Override
+        protected Boolean doInBackground(Void... params) {
+            String[] checkedFilePaths = mFileListFragment.getCheckedFilePaths();
+            long total = 0;
+            for (int i=0; i < checkedFilePaths.length ; i++) {
+                String localPath = checkedFilePaths[i];
+                File localFile = new File(localPath);
+                total += localFile.length();
+            }
+            return (FileStorageUtils.getUsableSpace(mAccount.name) >= total);
+        }
+
+        /**
+         * Updates the activity UI after the check of space is done.
+         * 
+         * If there is not space enough. shows a new dialog to query the user if wants to move the files instead
+         * of copy them.
+         * 
+         * @param result        'True' when there is space enough to copy all the selected files.
+         */
+        @Override
+        protected void onPostExecute(Boolean result) {
+            mCurrentDialog.dismiss();
+            mCurrentDialog = null;
+            
+            if (result) {
+                // return the list of selected files (success)
+                Intent data = new Intent();
+                data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths());
+                setResult(RESULT_OK, data);
+                finish();
+                
+            } else {
+                // show a dialog to query the user if wants to move the selected files to the ownCloud folder instead of copying
+                String[] args = {getString(R.string.app_name)};
+                ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_query_move_foreign_files, args, R.string.common_yes, -1, R.string.common_no);
+                dialog.setOnConfirmationListener(UploadFilesActivity.this);
+                mCurrentDialog = dialog;
+                mCurrentDialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG);
+            }
+        }
+    }
+
+    @Override
+    public void onConfirmation(String callerTag) {
+        Log.d(TAG, "Positive button in dialog was clicked; dialog tag is " + callerTag);
+        if (callerTag.equals(QUERY_TO_MOVE_DIALOG_TAG)) {
+            // return the list of selected files to the caller activity (success), signaling that they should be moved to the ownCloud folder, instead of copied
             Intent data = new Intent();
             data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths());
-            setResult(RESULT_OK, data);
+            setResult(RESULT_OK_AND_MOVE, data);
             finish();
         }
+        mCurrentDialog.dismiss();
+        mCurrentDialog = null;
     }
+
+
+    @Override
+    public void onNeutral(String callerTag) {
+        Log.d(TAG, "Phantom neutral button in dialog was clicked; dialog tag is " + callerTag);
+        mCurrentDialog.dismiss();
+        mCurrentDialog = null;
+    }
+
+
+    @Override
+    public void onCancel(String callerTag) {
+        /// nothing to do; don't finish, let the user change the selection
+        Log.d(TAG, "Negative button in dialog was clicked; dialog tag is " + callerTag);
+        mCurrentDialog.dismiss();
+        mCurrentDialog = null;
+    }    
+
     
 }

+ 0 - 1
src/com/owncloud/android/ui/adapter/FileListListAdapter.java

@@ -121,7 +121,6 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                 fileIcon.setImageResource(R.drawable.ic_menu_archive);
             }
             ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2);
-            //if (FileDownloader.isDownloading(mAccount, file.getRemotePath())) {
             FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();
             if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {

+ 5 - 0
src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java

@@ -109,6 +109,11 @@ public class ConflictsResolveDialog extends SherlockDialogFragment {
         }
     }
     
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        mListener.ConflictDecisionMade(Decision.CANCEL);
+    }
+    
     public interface OnConflictDecisionMadeListener {
         public void ConflictDecisionMade(Decision decision);
     }

+ 81 - 26
src/com/owncloud/android/ui/dialog/EditNameDialog.java

@@ -19,13 +19,13 @@
 
 package com.owncloud.android.ui.dialog;
 
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.View.OnClickListener;
 import android.view.WindowManager.LayoutParams;
-import android.widget.Button;
 import android.widget.TextView;
 
 import com.actionbarsherlock.app.SherlockDialogFragment;
@@ -33,50 +33,93 @@ import com.owncloud.android.R;
 
 
 /**
- * Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
+ * Dialog to request the user to input a name, optionally initialized with a former name.
  * 
  * @author Bartek Przybylski
+ * @author David A. Velasco
  */
-public class EditNameDialog extends SherlockDialogFragment implements OnClickListener {
+public class EditNameDialog extends SherlockDialogFragment implements DialogInterface.OnClickListener {
 
+    public static final String TAG = EditNameDialog.class.getSimpleName();
+    
+    protected static final String ARG_TITLE = "title";
+    protected static final String ARG_NAME = "name";
+    
     private String mNewFilename;
     private boolean mResult;
     private EditNameDialogListener mListener;
     
-    static public EditNameDialog newInstance(String filename) {
+    /**
+     * Public factory method to get dialog instances.
+     * 
+     * @param title         Text to show as title in the dialog.
+     * @param name          Optional text to include in the text input field when the dialog is shown.
+     * @param listener      Instance to notify when the dialog is dismissed.
+     * @return              New dialog instance, ready to show.
+     */
+    static public EditNameDialog newInstance(String title, String name, EditNameDialogListener listener) {
         EditNameDialog f = new EditNameDialog();
         Bundle args = new Bundle();
-        args.putString("filename", filename);
+        args.putString(ARG_TITLE, title);
+        args.putString(ARG_NAME, name);
         f.setArguments(args);
+        f.setOnDismissListener(listener);
         return f;
     }
     
+    
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        View v = inflater.inflate(R.layout.edit_box_dialog, container, false);
-
-        String currentName = getArguments().getString("filename");
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        String currentName = getArguments().getString(ARG_NAME);
         if (currentName == null)
             currentName = "";
+        String title = getArguments().getString(ARG_TITLE);
         
-        ((Button)v.findViewById(R.id.cancel)).setOnClickListener(this);
-        ((Button)v.findViewById(R.id.ok)).setOnClickListener(this);
-        ((TextView)v.findViewById(R.id.user_input)).setText(currentName);
-        ((TextView)v.findViewById(R.id.user_input)).requestFocus();
-        getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+        // Inflate the layout for the dialog
+        LayoutInflater inflater = getSherlockActivity().getLayoutInflater();
+        View v = inflater.inflate(R.layout.edit_box_dialog, null);  // null parent view because it will go in the dialog layout
+        TextView inputText = ((TextView)v.findViewById(R.id.user_input));
+        inputText.setText(currentName);
+        
+        // Set it to the dialog 
+        AlertDialog.Builder builder = new AlertDialog.Builder(getSherlockActivity());
+        builder.setView(v)
+               .setPositiveButton(R.string.common_ok, this)
+               .setNegativeButton(R.string.common_cancel, this);
 
+        if (title != null) {
+            builder.setTitle(title);
+        }
+        
         mResult = false;
-        return v;
-    }
+        
+        Dialog d = builder.create();
+
+        inputText.requestFocus();
+        d.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+        return d;
+    }    
+
     
+    /**
+     * Performs the corresponding action when a dialog button is clicked.
+     * 
+     * Saves the text in the input field to be accessed through {@link #getNewFilename()} when the positive
+     * button is clicked.
+     * 
+     * Notify the current listener in any case.
+     */
     @Override
-    public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.ok: {
-                mNewFilename = ((TextView)getView().findViewById(R.id.user_input)).getText().toString();
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case AlertDialog.BUTTON_POSITIVE: {
+                mNewFilename = ((TextView)(getDialog().findViewById(R.id.user_input))).getText().toString();
                 mResult = true;
             }
-            case R.id.cancel: { // fallthought
+            case AlertDialog.BUTTON_NEGATIVE: { // fall through
                 dismiss();
                 if (mListener != null)
                     mListener.onDismiss(this);
@@ -84,23 +127,35 @@ public class EditNameDialog extends SherlockDialogFragment implements OnClickLis
         }
     }
     
-    public void setOnDismissListener(EditNameDialogListener listener) {
+    protected void setOnDismissListener(EditNameDialogListener listener) {
         mListener = listener;
     }
     
+    /**
+     * Returns the text in the input field after the user clicked the positive button.
+     * 
+     * @return      Text in the input field.
+     */
     public String getNewFilename() {
         return mNewFilename;
     }
     
-    // true if user clicked ok
+    /**
+     * 
+     * @return      True when the user clicked the positive button.
+     */
     public boolean getResult() {
         return mResult;
     }
 
     
+    /**
+     * Interface to receive a notification when any button in the dialog is clicked.
+     */
     public interface EditNameDialogListener {
         public void onDismiss(EditNameDialog dialog);
     }
-    
+
+
 }
 

+ 74 - 0
src/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.java

@@ -0,0 +1,74 @@
+package com.owncloud.android.ui.dialog;
+
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnKeyListener;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+import com.owncloud.android.R;
+
+public class IndeterminateProgressDialog extends SherlockDialogFragment {
+
+    private static final String ARG_MESSAGE_ID = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_MESSAGE_ID";
+    private static final String ARG_CANCELABLE = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_CANCELABLE";
+
+
+    /**
+     * Public factory method to get dialog instances.
+     * 
+     * @param messageId     Resource id for a message to show in the dialog.
+     * @param cancelable    If 'true', the dialog can be cancelled by the user input (BACK button, touch outside...)
+     * @return              New dialog instance, ready to show.
+     */
+    public static IndeterminateProgressDialog newInstance(int messageId, boolean cancelable) {
+        IndeterminateProgressDialog fragment = new IndeterminateProgressDialog();
+        Bundle args = new Bundle();
+        args.putInt(ARG_MESSAGE_ID, messageId);
+        args.putBoolean(ARG_CANCELABLE, cancelable);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        /// create indeterminate progress dialog
+        final ProgressDialog dialog = new ProgressDialog(getActivity());
+        dialog.setIndeterminate(true);
+        
+        /// set message
+        int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.text_placeholder);
+        dialog.setMessage(getString(messageId));
+        
+        /// set cancellation behavior
+        boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE, false);
+        if (!cancelable) {
+            dialog.setCancelable(false);
+            // disable the back button
+            OnKeyListener keyListener = new OnKeyListener() {
+                @Override
+                public boolean onKey(DialogInterface dialog, int keyCode,
+                        KeyEvent event) {
+
+                    if( keyCode == KeyEvent.KEYCODE_BACK){                  
+                        return true;
+                    }
+                    return false;
+                }
+
+            };
+            dialog.setOnKeyListener(keyListener);
+        }
+        
+        return dialog;
+    }    
+    
+}
+
+

+ 10 - 0
src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java

@@ -37,6 +37,16 @@ public class ConfirmationDialogFragment extends SherlockDialogFragment {
     
     private ConfirmationDialogFragmentListener mListener;
     
+    /**
+     * Public factory method to create new ConfirmationDialogFragment instances.
+     * 
+     * @param string_id         Resource id for a message to show in the dialog.
+     * @param arguments         Arguments to complete the message, if it's a format string.
+     * @param posBtn            Resource id for the text of the positive button.
+     * @param neuBtn            Resource id for the text of the neutral button.
+     * @param negBtn            Resource id for the text of the negative button.
+     * @return                  Dialog ready to show.
+     */
     public static ConfirmationDialogFragment newInstance(int string_id, String[] arguments, int posBtn, int neuBtn, int negBtn) {
         ConfirmationDialogFragment frag = new ConfirmationDialogFragment();
         Bundle args = new Bundle();

+ 130 - 42
src/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -50,6 +50,7 @@ import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.support.v4.app.DialogFragment;
 import android.support.v4.app.FragmentTransaction;
 import android.util.Log;
 import android.view.Display;
@@ -82,6 +83,8 @@ import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
+import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.ui.activity.ConflictsResolveActivity;
 import com.owncloud.android.ui.activity.FileDetailActivity;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.activity.TransferServiceGetter;
@@ -111,6 +114,7 @@ public class FileDetailFragment extends SherlockFragment implements
     private View mView;
     private OCFile mFile;
     private Account mAccount;
+    private FileDataStorageManager mStorageManager;
     private ImageView mPreview;
     
     private DownloadFinishReceiver mDownloadFinishReceiver;
@@ -118,8 +122,9 @@ public class FileDetailFragment extends SherlockFragment implements
     
     private Handler mHandler;
     private RemoteOperation mLastRemoteOperation;
+    private DialogFragment mCurrentDialog;
 
-    private static final String TAG = "FileDetailFragment";
+    private static final String TAG = FileDetailFragment.class.getSimpleName();
     public static final String FTAG = "FileDetails"; 
     public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";
 
@@ -132,6 +137,7 @@ public class FileDetailFragment extends SherlockFragment implements
     public FileDetailFragment() {
         mFile = null;
         mAccount = null;
+        mStorageManager = null;
         mLayout = R.layout.file_details_empty;
     }
     
@@ -147,6 +153,7 @@ public class FileDetailFragment extends SherlockFragment implements
     public FileDetailFragment(OCFile fileToDetail, Account ocAccount) {
         mFile = fileToDetail;
         mAccount = ocAccount;
+        mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment 
         mLayout = R.layout.file_details_empty;
         
         if(fileToDetail != null && ocAccount != null) {
@@ -186,7 +193,7 @@ public class FileDetailFragment extends SherlockFragment implements
             mPreview = (ImageView)mView.findViewById(R.id.fdPreview);
         }
         
-        updateFileDetails();
+        updateFileDetails(false);
         return view;
     }
     
@@ -203,6 +210,18 @@ public class FileDetailFragment extends SherlockFragment implements
             throw new ClassCastException(activity.toString() + " must implement " + FileDetailFragment.ContainerActivity.class.getSimpleName());
         }
     }
+    
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        if (mAccount != null) {
+            mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());;
+        }
+    }
         
 
     @Override
@@ -257,7 +276,6 @@ public class FileDetailFragment extends SherlockFragment implements
     public void onClick(View v) {
         switch (v.getId()) {
             case R.id.fdDownloadBtn: {
-                //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath())) {
                 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
                 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
                 if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {
@@ -289,47 +307,43 @@ public class FileDetailFragment extends SherlockFragment implements
                     }
                     
                 } else {
-                    Intent i = new Intent(getActivity(), FileDownloader.class);
-                    i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
-                    i.putExtra(FileDownloader.EXTRA_FILE, mFile);
-                    /*i.putExtra(FileDownloader.EXTRA_REMOTE_PATH, mFile.getRemotePath());
-                    i.putExtra(FileDownloader.EXTRA_FILE_PATH, mFile.getRemotePath());
-                    i.putExtra(FileDownloader.EXTRA_FILE_SIZE, mFile.getFileLength());*/
+                    mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity());
+                    WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
+                    mLastRemoteOperation.execute(wc, this, mHandler);
                 
                     // update ui 
-                    setButtonsForTransferring();
-                
-                    getActivity().startService(i);
-                    mContainerActivity.onFileStateChanged();    // this is not working; it is performed before the fileDownloadService registers it as 'in progress'
+                    boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+                    getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+                    setButtonsForTransferring(); // disable button immediately, although the synchronization does not result in a file transference
+                    
                 }
                 break;
             }
             case R.id.fdKeepInSync: {
                 CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync);
                 mFile.setKeepInSync(cb.isChecked());
-                FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());
-                fdsm.saveFile(mFile);
-                if (mFile.keepInSync()) {
-                    onClick(getView().findViewById(R.id.fdDownloadBtn));
-                } else {
-                    mContainerActivity.onFileStateChanged();    // put inside 'else' to not call it twice (here, and in the virtual click on fdDownloadBtn)
-                }
+                mStorageManager.saveFile(mFile);
                 
+                /// register the OCFile instance in the observer service to monitor local updates;
+                /// if necessary, the file is download 
                 Intent intent = new Intent(getActivity().getApplicationContext(),
                                            FileObserverService.class);
                 intent.putExtra(FileObserverService.KEY_FILE_CMD,
                            (cb.isChecked()?
                                    FileObserverService.CMD_ADD_OBSERVED_FILE:
                                    FileObserverService.CMD_DEL_OBSERVED_FILE));
-                intent.putExtra(FileObserverService.KEY_CMD_ARG, mFile.getStoragePath());
+                intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile);
+                intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount);
                 Log.e(TAG, "starting observer service");
                 getActivity().startService(intent);
                 
+                if (mFile.keepInSync()) {
+                    onClick(getView().findViewById(R.id.fdDownloadBtn));    // force an immediate synchronization
+                }
                 break;
             }
             case R.id.fdRenameBtn: {
-                EditNameDialog dialog = EditNameDialog.newInstance(mFile.getFileName());
-                dialog.setOnDismissListener(this);
+                EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), this);
                 dialog.show(getFragmentManager(), "nameeditdialog");
                 break;
             }   
@@ -341,7 +355,8 @@ public class FileDetailFragment extends SherlockFragment implements
                         mFile.isDown() ? R.string.confirmation_remove_local : -1,
                         R.string.common_cancel);
                 confDialog.setOnConfirmationListener(this);
-                confDialog.show(getFragmentManager(), FTAG_CONFIRMATION);
+                mCurrentDialog = confDialog;
+                mCurrentDialog.show(getFragmentManager(), FTAG_CONFIRMATION);
                 break;
             }
             case R.id.fdOpenBtn: {
@@ -360,8 +375,13 @@ public class FileDetailFragment extends SherlockFragment implements
                     try {
                         Intent i = new Intent(Intent.ACTION_VIEW);
                         mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
-                        if (mimeType != null && !mimeType.equals(mFile.getMimetype())) {
-                            i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
+                        if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {
+                            if (mimeType != null) {
+                                i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
+                            } else {
+                                // desperate try
+                                i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
+                            }
                             i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                             startActivity(i);
                             toastIt = false;
@@ -399,11 +419,10 @@ public class FileDetailFragment extends SherlockFragment implements
     @Override
     public void onConfirmation(String callerTag) {
         if (callerTag.equals(FTAG_CONFIRMATION)) {
-            FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getContentResolver());
-            if (fdsm.getFileById(mFile.getFileId()) != null) {
+            if (mStorageManager.getFileById(mFile.getFileId()) != null) {
                 mLastRemoteOperation = new RemoveFileOperation( mFile, 
                                                                 true, 
-                                                                new FileDataStorageManager(mAccount, getActivity().getContentResolver()));
+                                                                mStorageManager);
                 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
                 mLastRemoteOperation.execute(wc, this, mHandler);
                 
@@ -411,23 +430,28 @@ public class FileDetailFragment extends SherlockFragment implements
                 getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
             }
         }
+        mCurrentDialog.dismiss();
+        mCurrentDialog = null;
     }
     
     @Override
     public void onNeutral(String callerTag) {
-        FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getContentResolver());
         File f = null;
         if (mFile.isDown() && (f = new File(mFile.getStoragePath())).exists()) {
             f.delete();
             mFile.setStoragePath(null);
-            fdsm.saveFile(mFile);
+            mStorageManager.saveFile(mFile);
             updateFileDetails(mFile, mAccount);
         }
+        mCurrentDialog.dismiss();
+        mCurrentDialog = null;
     }
     
     @Override
     public void onCancel(String callerTag) {
         Log.d(TAG, "REMOVAL CANCELED");
+        mCurrentDialog.dismiss();
+        mCurrentDialog = null;
     }
     
     
@@ -456,15 +480,28 @@ public class FileDetailFragment extends SherlockFragment implements
      */
     public void updateFileDetails(OCFile file, Account ocAccount) {
         mFile = file;
+        if (ocAccount != null && ( 
+                mStorageManager == null || 
+                (mAccount != null && !mAccount.equals(ocAccount))
+           )) {
+            mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());
+        }
         mAccount = ocAccount;
-        updateFileDetails();
+        updateFileDetails(false);
     }
     
 
     /**
      * Updates the view with all relevant details about that file.
+     *
+     * TODO Remove parameter when the transferring state of files is kept in database. 
+     * 
+     * @param transferring      Flag signaling if the file should be considered as downloading or uploading, 
+     *                          although {@link FileDownloaderBinder#isDownloading(Account, OCFile)}  and 
+     *                          {@link FileUploaderBinder#isUploading(Account, OCFile)} return false.
+     * 
      */
-    public void updateFileDetails() {
+    public void updateFileDetails(boolean transferring) {
 
         if (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment) {
             
@@ -486,7 +523,7 @@ public class FileDetailFragment extends SherlockFragment implements
             //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) {
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
-            if ((downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {
+            if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {
                 setButtonsForTransferring();
                 
             } else if (mFile.isDown()) {
@@ -499,9 +536,11 @@ public class FileDetailFragment extends SherlockFragment implements
                 setButtonsForDown();
                 
             } else {
+                // TODO load default preview image; when the local file is removed, the preview remains there
                 setButtonsForRemote();
             }
         }
+        getView().invalidate();
     }
     
     
@@ -583,8 +622,7 @@ public class FileDetailFragment extends SherlockFragment implements
     private void setButtonsForDown() {
         if (!isEmpty()) {
             Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
-            downloadButton.setText(R.string.filedetails_redownload);
-            //downloadButton.setEnabled(true);
+            downloadButton.setText(R.string.filedetails_sync_file);
         
             ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true);
             ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);
@@ -667,9 +705,9 @@ public class FileDetailFragment extends SherlockFragment implements
                 String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
                 if (mFile.getRemotePath().equals(downloadedRemotePath)) {
                     if (downloadWasFine) {
-                        mFile.setStoragePath(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH));    // updates the local object without accessing the database again
+                        mFile = mStorageManager.getFileByPath(downloadedRemotePath);
                     }
-                    updateFileDetails();    // it updates the buttons; must be called although !downloadWasFine
+                    updateFileDetails(false);    // it updates the buttons; must be called although !downloadWasFine
                 }
             }
         }
@@ -694,12 +732,19 @@ public class FileDetailFragment extends SherlockFragment implements
             if (!isEmpty() && accountName.equals(mAccount.name)) {
                 boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false);
                 String uploadRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH);
-                if (mFile.getRemotePath().equals(uploadRemotePath)) {
+                boolean renamedInUpload = mFile.getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH));
+                if (mFile.getRemotePath().equals(uploadRemotePath) ||
+                    renamedInUpload) {
                     if (uploadWasFine) {
-                        FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());
-                        mFile = fdsm.getFileByPath(mFile.getRemotePath());
+                        mFile = mStorageManager.getFileByPath(mFile.getRemotePath());
+                    }
+                    if (renamedInUpload) {
+                        String newName = (new File(uploadRemotePath)).getName();
+                        Toast msg = Toast.makeText(getActivity().getApplicationContext(), String.format(getString(R.string.filedetails_renamed_in_upload_msg), newName), Toast.LENGTH_LONG);
+                        msg.show();
+                        getSherlockActivity().removeStickyBroadcast(intent);    // not the best place to do this; a small refactorization of BroadcastReceivers should be done
                     }
-                    updateFileDetails();    // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server
+                    updateFileDetails(false);    // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server
                 }
             }
         }
@@ -815,6 +860,7 @@ public class FileDetailFragment extends SherlockFragment implements
             String newFilename = dialog.getNewFilename();
             Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
             mLastRemoteOperation = new RenameFileOperation( mFile, 
+                                                            mAccount, 
                                                             newFilename, 
                                                             new FileDataStorageManager(mAccount, getActivity().getContentResolver()));
             WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
@@ -910,6 +956,9 @@ public class FileDetailFragment extends SherlockFragment implements
                 
             } else if (operation instanceof RenameFileOperation) {
                 onRenameFileOperationFinish((RenameFileOperation)operation, result);
+                
+            } else if (operation instanceof SynchronizeFileOperation) {
+                onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);
             }
         }
     }
@@ -964,5 +1013,44 @@ public class FileDetailFragment extends SherlockFragment implements
         }
     }
     
+    private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {
+        boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+        getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+
+        if (!result.isSuccess()) {
+            if (result.getCode() == ResultCode.SYNC_CONFLICT) {
+                Intent i = new Intent(getActivity(), ConflictsResolveActivity.class);
+                i.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile);
+                i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount);
+                startActivity(i);
+                
+            } else {
+                Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); 
+                msg.show();
+            }
+            
+            if (mFile.isDown()) {
+                setButtonsForDown();
+                
+            } else {
+                setButtonsForRemote();
+            }
+            
+        } else {
+            if (operation.transferWasRequested()) {
+                mContainerActivity.onFileStateChanged();    // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so 
+                                                            // checking the service to see if the file is downloading results in FALSE
+            } else {
+                Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); 
+                msg.show();
+                if (mFile.isDown()) {
+                    setButtonsForDown();
+                    
+                } else {
+                    setButtonsForRemote();
+                }
+            }
+        }
+    }
 
 }

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

@@ -17,17 +17,50 @@
  */
 package com.owncloud.android.ui.fragment;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.owncloud.android.AccountUtils;
+import com.owncloud.android.R;
 import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.operations.OnRemoteOperationListener;
+import com.owncloud.android.operations.RemoteOperation;
+import com.owncloud.android.operations.RemoveFileOperation;
+import com.owncloud.android.operations.RenameFileOperation;
+import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.ui.FragmentListView;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.activity.TransferServiceGetter;
 import com.owncloud.android.ui.adapter.FileListListAdapter;
+import com.owncloud.android.ui.dialog.EditNameDialog;
+import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
+import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
+
+import eu.alefzero.webdav.WebdavClient;
+import eu.alefzero.webdav.WebdavUtils;
 
+import android.accounts.Account;
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.DialogFragment;
 import android.util.Log;
+import android.view.ContextMenu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
+import android.webkit.MimeTypeMap;
 import android.widget.AdapterView;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
 
 /**
  * A Fragment that lists all files and folders in a given path.
@@ -35,7 +68,7 @@ import android.widget.AdapterView;
  * @author Bartek Przybylski
  * 
  */
-public class OCFileListFragment extends FragmentListView {
+public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener {
     private static final String TAG = "FileListFragment";
     private static final String SAVED_LIST_POSITION = "LIST_POSITION"; 
     
@@ -43,7 +76,11 @@ public class OCFileListFragment extends FragmentListView {
     
     private OCFile mFile = null;
     private FileListListAdapter mAdapter;
-
+    
+    private Handler mHandler;
+    private OCFile mTargetFile;
+    
+    private DialogFragment mCurrentDialog;
     
     /**
      * {@inheritDoc}
@@ -76,6 +113,11 @@ public class OCFileListFragment extends FragmentListView {
             setReferencePosition(position);
         }
         
+        registerForContextMenu(getListView());
+        getListView().setOnCreateContextMenuListener(this);        
+        
+        mHandler = new Handler();
+        
         Log.i(TAG, "onActivityCreated() stop");
     }
     
@@ -112,6 +154,198 @@ public class OCFileListFragment extends FragmentListView {
         }
         
     }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+        MenuInflater inflater = getActivity().getMenuInflater();
+        inflater.inflate(R.menu.file_context_menu, menu);
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
+        OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
+        List<Integer> toHide = new ArrayList<Integer>();    
+        List<Integer> toDisable = new ArrayList<Integer>();  
+        
+        MenuItem item = null;
+        if (targetFile.isDirectory()) {
+            // contextual menu for folders
+            toHide.add(R.id.open_file_item);
+            toHide.add(R.id.download_file_item);
+            toHide.add(R.id.cancel_download_item);
+            toHide.add(R.id.cancel_upload_item);
+            if (    mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
+                    mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)           ) {
+                toDisable.add(R.id.rename_file_item);
+                toDisable.add(R.id.remove_file_item);
+                
+            }
+            
+        } else {
+            // contextual menu for regular files
+            if (targetFile.isDown()) {
+                toHide.add(R.id.cancel_download_item);
+                toHide.add(R.id.cancel_upload_item);
+                item = menu.findItem(R.id.download_file_item);
+                if (item != null) {
+                    item.setTitle(R.string.filedetails_sync_file);
+                }
+            } else {
+                toHide.add(R.id.open_file_item);
+            }
+            if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
+                toHide.add(R.id.download_file_item);
+                toHide.add(R.id.cancel_upload_item);
+                toDisable.add(R.id.open_file_item);
+                toDisable.add(R.id.rename_file_item);
+                toDisable.add(R.id.remove_file_item);
+                    
+            } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
+                toHide.add(R.id.download_file_item);
+                toHide.add(R.id.cancel_download_item);
+                toDisable.add(R.id.open_file_item);
+                toDisable.add(R.id.rename_file_item);
+                toDisable.add(R.id.remove_file_item);
+                    
+            } else {
+                toHide.add(R.id.cancel_download_item);
+                toHide.add(R.id.cancel_upload_item);
+            }
+        }
+
+        for (int i : toHide) {
+            item = menu.findItem(i);
+            if (item != null) {
+                item.setVisible(false);
+                item.setEnabled(false);
+            }
+        }
+        
+        for (int i : toDisable) {
+            item = menu.findItem(i);
+            if (item != null) {
+                item.setEnabled(false);
+            }
+        }
+    }
+    
+    
+    /**
+     * {@inhericDoc}
+     */
+    @Override
+    public boolean onContextItemSelected (MenuItem item) {
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();        
+        mTargetFile = (OCFile) mAdapter.getItem(info.position);
+        switch (item.getItemId()) {
+            case R.id.rename_file_item: {
+                EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mTargetFile.getFileName(), this);
+                dialog.show(getFragmentManager(), EditNameDialog.TAG);
+                return true;
+            }
+            case R.id.remove_file_item: {
+                int messageStringId = R.string.confirmation_remove_alert;
+                int posBtnStringId = R.string.confirmation_remove_remote;
+                int neuBtnStringId = -1;
+                if (mTargetFile.isDirectory()) {
+                    messageStringId = R.string.confirmation_remove_folder_alert;
+                    posBtnStringId = R.string.confirmation_remove_remote_and_local;
+                    neuBtnStringId = R.string.confirmation_remove_folder_local;
+                } else if (mTargetFile.isDown()) {
+                    posBtnStringId = R.string.confirmation_remove_remote_and_local;
+                    neuBtnStringId = R.string.confirmation_remove_local;
+                }
+                ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
+                        messageStringId,
+                        new String[]{mTargetFile.getFileName()},
+                        posBtnStringId,
+                        neuBtnStringId,
+                        R.string.common_cancel);
+                confDialog.setOnConfirmationListener(this);
+                mCurrentDialog = confDialog;
+                mCurrentDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
+                return true;
+            }
+            case R.id.open_file_item: {
+                String storagePath = mTargetFile.getStoragePath();
+                String encodedStoragePath = WebdavUtils.encodePath(storagePath);
+                try {
+                    Intent i = new Intent(Intent.ACTION_VIEW);
+                    i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mTargetFile.getMimetype());
+                    i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                    startActivity(i);
+                    
+                } catch (Throwable t) {
+                    Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mTargetFile.getMimetype());
+                    boolean toastIt = true; 
+                    String mimeType = "";
+                    try {
+                        Intent i = new Intent(Intent.ACTION_VIEW);
+                        mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
+                        if (mimeType == null || !mimeType.equals(mTargetFile.getMimetype())) {
+                            if (mimeType != null) {
+                                i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
+                            } else {
+                                // desperate try
+                                i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
+                            }
+                            i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                            startActivity(i);
+                            toastIt = false;
+                        }
+                        
+                    } catch (IndexOutOfBoundsException e) {
+                        Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
+                        
+                    } catch (ActivityNotFoundException e) {
+                        Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
+                        
+                    } catch (Throwable th) {
+                        Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
+                        
+                    } finally {
+                        if (toastIt) {
+                            Toast.makeText(getActivity(), "There is no application to handle file " + mTargetFile.getFileName(), Toast.LENGTH_SHORT).show();
+                        }
+                    }
+                    
+                }
+                return true;
+            }
+            case R.id.download_file_item: {
+                Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
+                RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
+                WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getSherlockActivity().getApplicationContext());
+                operation.execute(wc, mContainerActivity, mHandler);
+                getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
+                return true;
+            }
+            case R.id.cancel_download_item: {
+                FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
+                Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
+                if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
+                    downloaderBinder.cancel(account, mTargetFile);
+                    listDirectory();
+                    mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
+                }
+                return true;
+            }
+            case R.id.cancel_upload_item: {
+                FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
+                Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
+                if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
+                    uploaderBinder.cancel(account, mTargetFile);
+                    listDirectory();
+                    mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
+                }
+                return true;
+            }
+            default:
+                return super.onContextItemSelected(item); 
+        }
+    }
+    
 
     /**
      * Call this, when the user presses the up button
@@ -186,7 +420,7 @@ public class OCFileListFragment extends FragmentListView {
      * 
      * @author David A. Velasco
      */
-    public interface ContainerActivity extends TransferServiceGetter {
+    public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
 
         /**
          * Callback method invoked when a directory is clicked by the user on the files list
@@ -216,6 +450,89 @@ public class OCFileListFragment extends FragmentListView {
         public OCFile getInitialDirectory();
         
         
+        /**
+         * Callback method invoked when a the 'transfer state' of a file changes.
+         * 
+         * This happens when a download or upload is started or ended for a file.
+         * 
+         * This method is necessary by now to update the user interface of the double-pane layout in tablets
+         * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
+         * won't provide the needed response before the method where this is called finishes. 
+         * 
+         * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
+         * 
+         * @param file          OCFile which state changed.
+         * @param downloading   Flag signaling if the file is now downloading.
+         * @param uploading     Flag signaling if the file is now uploading.
+         */
+        public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
+        
+    }
+    
+    
+    @Override
+    public void onDismiss(EditNameDialog dialog) {
+        if (dialog.getResult()) {
+            String newFilename = dialog.getNewFilename();
+            Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
+            RemoteOperation operation = new RenameFileOperation(mTargetFile, 
+                                                                AccountUtils.getCurrentOwnCloudAccount(getActivity()), 
+                                                                newFilename, 
+                                                                mContainerActivity.getStorageManager());
+            WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
+            operation.execute(wc, mContainerActivity, mHandler);
+            getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
+        }
     }
 
+    
+    @Override
+    public void onConfirmation(String callerTag) {
+        if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
+            if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
+                RemoteOperation operation = new RemoveFileOperation( mTargetFile, 
+                                                                    true, 
+                                                                    mContainerActivity.getStorageManager());
+                WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
+                operation.execute(wc, mContainerActivity, mHandler);
+                
+                getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
+            }
+            if (mCurrentDialog != null) {
+                mCurrentDialog.dismiss();
+                mCurrentDialog = null;
+            }
+        }
+    }
+    
+    @Override
+    public void onNeutral(String callerTag) {
+        File f = null;
+        if (mTargetFile.isDirectory()) {
+            // TODO run in a secondary thread?
+            mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
+            
+        } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
+            f.delete();
+            mTargetFile.setStoragePath(null);
+            mContainerActivity.getStorageManager().saveFile(mTargetFile);
+        }
+        if (mCurrentDialog != null) {
+            mCurrentDialog.dismiss();
+            mCurrentDialog = null;
+        }
+        listDirectory();
+        mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
+    }
+    
+    @Override
+    public void onCancel(String callerTag) {
+        Log.d(TAG, "REMOVAL CANCELED");
+        if (mCurrentDialog != null) {
+            mCurrentDialog.dismiss();
+            mCurrentDialog = null;
+        }
+    }
+
+
 }

+ 60 - 0
src/com/owncloud/android/utils/FileStorageUtils.java

@@ -0,0 +1,60 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2012  Bartek Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   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.utils;
+
+import java.io.File;
+
+import android.net.Uri;
+import android.os.Environment;
+import android.os.StatFs;
+
+import com.owncloud.android.datamodel.OCFile;
+
+
+public class FileStorageUtils {
+    
+    public static final String getSavePath(String accountName) {
+        File sdCard = Environment.getExternalStorageDirectory();
+        return sdCard.getAbsolutePath() + "/owncloud/" + Uri.encode(accountName, "@");   
+            // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
+    }
+    
+    public static final String getDefaultSavePathFor(String accountName, OCFile file) {
+        return getSavePath(accountName) + file.getRemotePath();
+    }
+    
+    public static final String getTemporalPath(String accountName) {
+        File sdCard = Environment.getExternalStorageDirectory();
+        return sdCard.getAbsolutePath() + "/owncloud/tmp/" + Uri.encode(accountName, "@");
+            // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
+    }
+
+    public static final long getUsableSpace(String accountName) {
+        File savePath = Environment.getExternalStorageDirectory();
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
+            return savePath.getUsableSpace();
+            
+        } else {
+            StatFs stats = new StatFs(savePath.getAbsolutePath());
+            return stats.getAvailableBlocks() * stats.getBlockSize();
+        }
+        
+    }
+    
+}

+ 1 - 1
src/eu/alefzero/webdav/WebdavEntry.java

@@ -126,7 +126,7 @@ public class WebdavEntry {
         return mCreateTimestamp;
     }
 
-    public long modifiedTimesamp() {
+    public long modifiedTimestamp() {
         return mModifiedTimestamp;
     }