along with this program. If not, see <http://www.gnu.org/licenses/>.\r
-->\r
<manifest package="com.owncloud.android"\r
- android:versionCode="103014"\r
- android:versionName="1.3.14" xmlns:android="http://schemas.android.com/apk/res/android">\r
+ android:versionCode="103017"\r
+ android:versionName="1.3.17" xmlns:android="http://schemas.android.com/apk/res/android">\r
\r
<uses-permission android:name="android.permission.GET_ACCOUNTS" />\r
<uses-permission android:name="android.permission.USE_CREDENTIALS" />\r
<activity android:name=".extensions.ExtensionsListActivity"></activity>\r
<activity android:name=".ui.activity.AccountSelectActivity" android:uiOptions="none" android:label="@string/prefs_accounts"></activity>\r
<activity android:name=".ui.activity.ConflictsResolveActivity"/>
-
+ <activity android:name=".ui.activity.GenericExplanationActivity"/>\r
+ <activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"/>\r
+
<service android:name=".files.services.FileUploader" >\r
</service>
<service android:name=".files.services.InstantUploadService" />
android:layout_weight="1"
android:ems="10"
android:hint="@string/auth_host_url"
- android:singleLine="true"
android:inputType="textNoSuggestions">
<requestFocus />
</EditText>
android:layout_weight="1"
android:ems="10"
android:hint="@string/auth_username"
- android:singleLine="true"
android:inputType="textNoSuggestions" />
<FrameLayout
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"
android:onClick="onOkClick"
android:text="@string/setup_btn_connect"
android:textColor="@android:color/black" />
+
</LinearLayout>
<Button
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>
--- /dev/null
+<?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>
\ No newline at end of file
android:layout_weight="1"
android:ems="10"
android:hint="@string/auth_host_url"
- android:singleLine="true"
android:inputType="textNoSuggestions" >
<requestFocus />
</EditText>
android:layout_height="0dp"
android:layout_weight="1"
android:ems="10"
- android:singleLine="true"
android:hint="@string/auth_username"
android:inputType="textNoSuggestions" />
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"
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>
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>
--- /dev/null
+<?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>
\ No newline at end of file
--- /dev/null
+<?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>
<?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_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 & schließen</string>
- <string name="common_exit">Verlasse ownCloud</string>
+ <string name="common_save_exit">Speichern & 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>
<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>
</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>
<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>
<?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>
<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 & 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…</string>
- <string name="uploader_info_dirname">Verzeichnisname</string>
- <string name="uploader_upload_in_progress_ticker">Hochladen…</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>
<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…</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>
<item>30</item>
<item>60</item>
</string-array>
- <string name="auth_trying_to_login">Anmeldungsversuch…</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…</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>
<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>
<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>
<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>
<?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>
<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>
<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>
<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 & 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…</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>
<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 …</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>
<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>
<item>30</item>
<item>60</item>
</string-array>
- <string name="auth_trying_to_login">Intentado iniciar sesión…</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…</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>
<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>
--- /dev/null
+<!-- Large screen boolean values -->
+<resources>
+ <bool name="large_layout">true</bool>
+</resources>
\ No newline at end of file
--- /dev/null
+<!-- Default boolean values -->
+<resources>
+ <bool name="large_layout">false</bool>
+</resources>
\ No newline at end of file
<?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>
<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>
<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>
<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 & 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>
<string name="uploader_upload_in_progress_ticker">Uploading …</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 …</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>
</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>
<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>
<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>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="url_account_register">"https://owncloud.com/mobile/new"</string>
+</resources>
\ No newline at end of file
case DIALOG_NO_ACCOUNT:\r
builder.setIcon(android.R.drawable.ic_dialog_alert);\r
builder.setTitle(R.string.uploader_wrn_no_account_title);\r
- builder.setMessage(R.string.uploader_wrn_no_account_text);\r
+ builder.setMessage(String.format(getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name)));\r
builder.setCancelable(false);\r
builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {\r
@Override\r
// click on folder in the list\r
Log.d(TAG, "on item click");\r
Vector<OCFile> tmpfiles = mStorageManager.getDirectoryContent(mFile);\r
- if (tmpfiles == null) return;\r
+ if (tmpfiles.size() <= 0) return;\r
// filter on dirtype\r
Vector<OCFile> files = new Vector<OCFile>();\r
for (OCFile f : tmpfiles)\r
mFile = mStorageManager.getFileByPath(full_path);\r
if (mFile != null) {\r
Vector<OCFile> files = mStorageManager.getDirectoryContent(mFile);\r
- if (files != null) {\r
+ if (files.size() > 0) {\r
List<HashMap<String, Object>> data = new LinkedList<HashMap<String,Object>>();\r
for (OCFile f : files) {\r
HashMap<String, Object> h = new HashMap<String, Object>();\r
finish();\r
\r
} catch (SecurityException e) {\r
- Toast.makeText(this, getString(R.string.uploader_error_forbidden_content), Toast.LENGTH_LONG).show();\r
+ String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name));\r
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show(); \r
}\r
}\r
\r
import org.apache.commons.httpclient.HttpStatus;
+import com.owncloud.android.R;
import com.owncloud.android.network.OwnCloudClientUtils;
import eu.alefzero.webdav.WebdavClient;
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));
}
}
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);
}
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;
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());
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) {
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());
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());
}
@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,
Collections.sort(ret);
- return ret;
}
- return null;
+ return ret;
}
private boolean fileExists(String cmp_key, String value) {
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
.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);
}
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);
+ }
+
+ }
}
}
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
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.
*
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
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);
}
/**
}
/**
- * 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;
/**
* 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
*
*/
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);
+ }
}
/**
mLength = 0;
mCreationTimestamp = 0;
mModifiedTimestamp = 0;
- mLastSyncDate = 0;
+ mModifiedTimestampAtLastSyncForData = 0;
+ mLastSyncDateForProperties = 0;
+ mLastSyncDateForData = 0;
mKeepInSync = false;
mNeedsUpdating = false;
}
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) {
@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;
+ }
+
}
*/\r
public class DbHandler {\r
private SQLiteDatabase mDB;\r
- private OpenerHepler mHelper;\r
+ private OpenerHelper mHelper;\r
private final String mDatabaseName = "ownCloud";\r
private final int mDatabaseVersion = 1;\r
\r
private final String TABLE_INSTANT_UPLOAD = "instant_upload";\r
\r
public DbHandler(Context context) {\r
- mHelper = new OpenerHepler(context);\r
+ mHelper = new OpenerHelper(context);\r
mDB = mHelper.getWritableDatabase();\r
}\r
\r
\r
}\r
\r
- private class OpenerHepler extends SQLiteOpenHelper {\r
- public OpenerHepler(Context context) {\r
+ private class OpenerHelper extends SQLiteOpenHelper {\r
+ public OpenerHelper(Context context) {\r
super(context, mDatabaseName, null, mDatabaseVersion);\r
}\r
\r
@Override\r
public void onCreate(SQLiteDatabase db) {\r
db.execSQL("CREATE TABLE " + TABLE_INSTANT_UPLOAD + " ("\r
- + " _id INTEGET PRIMARY KEY, "\r
+ + " _id INTEGER PRIMARY KEY, "\r
+ " path TEXT,"\r
+ " account TEXT);");\r
}\r
public static final String AUTHORITY_FILES = "org.owncloud";\r
public static final String DB_FILE = "owncloud.db";\r
public static final String DB_NAME = "filelist";\r
- public static final int DB_VERSION = 2;\r
+ public static final int DB_VERSION = 4;\r
\r
private ProviderMeta() {\r
}\r
public static final String FILE_NAME = "filename";\r
public static final String FILE_CREATION = "created";\r
public static final String FILE_MODIFIED = "modified";\r
+ public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data";\r
public static final String FILE_CONTENT_LENGTH = "content_length";\r
public static final String FILE_CONTENT_TYPE = "content_type";\r
public static final String FILE_STORAGE_PATH = "media_path";\r
public static final String FILE_PATH = "path";\r
public static final String FILE_ACCOUNT_OWNER = "file_owner";\r
- public static final String FILE_LAST_SYNC_DATE = "last_sync_date";\r
+ public static final String FILE_LAST_SYNC_DATE = "last_sync_date"; // _for_properties, but let's keep it as it is\r
+ public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";\r
public static final String FILE_KEEP_IN_SYNC = "keep_in_sync";\r
\r
public static final String DEFAULT_SORT_ORDER = FILE_NAME\r
// 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);
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;
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
}
}
import java.util.concurrent.ConcurrentHashMap;\r
import java.util.concurrent.ConcurrentMap;\r
\r
+import com.owncloud.android.datamodel.FileDataStorageManager;\r
import com.owncloud.android.datamodel.OCFile;\r
-import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;\r
import eu.alefzero.webdav.OnDatatransferProgressListener;\r
\r
import com.owncloud.android.network.OwnCloudClientUtils;\r
import android.app.NotificationManager;\r
import android.app.PendingIntent;\r
import android.app.Service;\r
-import android.content.ContentValues;\r
import android.content.Intent;\r
-import android.net.Uri;\r
import android.os.Binder;\r
-import android.os.Environment;\r
import android.os.Handler;\r
import android.os.HandlerThread;\r
import android.os.IBinder;\r
public static final String EXTRA_ACCOUNT = "ACCOUNT";\r
public static final String EXTRA_FILE = "FILE";\r
\r
+ public static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";\r
public static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";\r
public static final String EXTRA_DOWNLOAD_RESULT = "RESULT"; \r
public static final String EXTRA_FILE_PATH = "FILE_PATH";\r
private IBinder mBinder;\r
private WebdavClient mDownloadClient = null;\r
private Account mLastAccount = null;\r
+ private FileDataStorageManager mStorageManager;\r
\r
private ConcurrentMap<String, DownloadFileOperation> mPendingDownloads = new ConcurrentHashMap<String, DownloadFileOperation>();\r
private DownloadFileOperation mCurrentDownload = null;\r
private String buildRemoteName(Account account, OCFile file) {\r
return account.name + file.getRemotePath();\r
}\r
- \r
- public static final String getSavePath(String accountName) {\r
- File sdCard = Environment.getExternalStorageDirectory();\r
- return sdCard.getAbsolutePath() + "/owncloud/" + Uri.encode(accountName, "@"); \r
- // 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\r
- }\r
- \r
- public static final String getTemporalPath(String accountName) {\r
- File sdCard = Environment.getExternalStorageDirectory();\r
- return sdCard.getAbsolutePath() + "/owncloud/tmp/" + Uri.encode(accountName, "@");\r
- // 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\r
- }\r
\r
\r
/**\r
mPendingDownloads.putIfAbsent(downloadKey, newDownload);\r
newDownload.addDatatransferProgressListener(this);\r
requestedDownloads.add(downloadKey);\r
+ sendBroadcastNewDownload(newDownload);\r
\r
} catch (IllegalArgumentException e) {\r
Log.e(TAG, "Not enough information provided in intent: " + e.getMessage());\r
\r
\r
/**\r
- * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download\r
+ * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download.\r
+ * \r
+ * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. \r
* \r
* @param account Owncloud account where the remote file is stored.\r
* @param file A file that could be in the queue of downloads.\r
*/\r
public boolean isDownloading(Account account, OCFile file) {\r
+ String targetKey = buildRemoteName(account, file);\r
synchronized (mPendingDownloads) {\r
- return (mPendingDownloads.containsKey(buildRemoteName(account, file)));\r
+ if (file.isDirectory()) {\r
+ // this can be slow if there are many downloads :(\r
+ Iterator<String> it = mPendingDownloads.keySet().iterator();\r
+ boolean found = false;\r
+ while (it.hasNext() && !found) {\r
+ found = it.next().startsWith(targetKey);\r
+ }\r
+ return found;\r
+ } else {\r
+ return (mPendingDownloads.containsKey(targetKey));\r
+ }\r
}\r
}\r
}\r
/// prepare client object to send the request to the ownCloud server\r
if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) {\r
mLastAccount = mCurrentDownload.getAccount();\r
+ mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver());\r
mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext());\r
}\r
\r
try {\r
downloadResult = mCurrentDownload.execute(mDownloadClient);\r
if (downloadResult.isSuccess()) {\r
- ContentValues cv = new ContentValues();\r
- cv.put(ProviderTableMeta.FILE_STORAGE_PATH, mCurrentDownload.getSavePath());\r
- getContentResolver().update(\r
- ProviderTableMeta.CONTENT_URI,\r
- cv,\r
- ProviderTableMeta.FILE_NAME + "=? AND "\r
- + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",\r
- new String[] {\r
- mCurrentDownload.getSavePath().substring(mCurrentDownload.getSavePath().lastIndexOf('/') + 1),\r
- mLastAccount.name });\r
+ saveDownloadedFile();\r
}\r
\r
} finally {\r
/// notify result\r
notifyDownloadResult(mCurrentDownload, downloadResult);\r
\r
- sendFinalBroadcast(mCurrentDownload, downloadResult);\r
+ sendBroadcastDownloadFinished(mCurrentDownload, downloadResult);\r
}\r
}\r
\r
- \r
+\r
+ /**\r
+ * Updates the OC File after a successful download.\r
+ */\r
+ private void saveDownloadedFile() {\r
+ OCFile file = mCurrentDownload.getFile();\r
+ long syncDate = System.currentTimeMillis();\r
+ file.setLastSyncDateForProperties(syncDate);\r
+ file.setLastSyncDateForData(syncDate);\r
+ file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());\r
+ file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());\r
+ // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available\r
+ file.setMimetype(mCurrentDownload.getMimeType());\r
+ file.setStoragePath(mCurrentDownload.getSavePath());\r
+ file.setFileLength((new File(mCurrentDownload.getSavePath()).length()));\r
+ mStorageManager.saveFile(file);\r
+ }\r
+\r
+\r
/**\r
* Creates a status notification to show the download progress\r
* \r
\r
\r
/**\r
- * Sends a broadcast in order to the interested activities can update their view\r
+ * Sends a broadcast when a download finishes in order to the interested activities can update their view\r
* \r
* @param download Finished download operation\r
* @param downloadResult Result of the download operation\r
*/\r
- private void sendFinalBroadcast(DownloadFileOperation download, RemoteOperationResult downloadResult) {\r
+ private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) {\r
Intent end = new Intent(DOWNLOAD_FINISH_MESSAGE);\r
end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());\r
end.putExtra(ACCOUNT_NAME, download.getAccount().name);\r
end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());\r
- if (downloadResult.isSuccess()) {\r
- end.putExtra(EXTRA_FILE_PATH, download.getSavePath());\r
- }\r
- sendBroadcast(end);\r
+ end.putExtra(EXTRA_FILE_PATH, download.getSavePath());\r
+ sendStickyBroadcast(end);\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Sends a broadcast when a new download is added to the queue.\r
+ * \r
+ * @param download Added download operation\r
+ */\r
+ private void sendBroadcastNewDownload(DownloadFileOperation download) {\r
+ Intent added = new Intent(DOWNLOAD_ADDED_MESSAGE);\r
+ /*added.putExtra(ACCOUNT_NAME, download.getAccount().name);\r
+ added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());*/\r
+ added.putExtra(EXTRA_FILE_PATH, download.getSavePath());\r
+ sendStickyBroadcast(added);\r
}\r
\r
}\r
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;
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 {
}
@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;
}
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");
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,
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);
- }
}
+
}
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;
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;
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;
*/
@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;
}
}
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();
/**
* 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));
+ }
}
}
}
try {
uploadResult = mCurrentUpload.execute(mUploadClient);
if (uploadResult.isSuccess()) {
- saveUploadedFile(mCurrentUpload.getFile(), mStorageManager);
+ saveUploadedFile();
}
} finally {
}
/**
- * 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
}
}
- 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
// 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);
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
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
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);
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);
}
// 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;
}
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
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;
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) {
}
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() {
}
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()
return mFile.getFileLength();
}
+ public long getModificationTimestamp() {
+ return (mModificationTimestamp > 0) ? mModificationTimestamp : mFile.getModificationTimestamp();
+ }
+
public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
mDataTransferListeners.add(listener);
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());
}
}
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());
*/
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,
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;
} 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") + ")";
package com.owncloud.android.operations;
+import org.apache.commons.httpclient.HttpStatus;
import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
import android.util.Log;
/**
+ * 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
*
* @param client Client object to communicate with the remote ownCloud server.
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) {
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;
private OCFile mFile;
+ private Account mAccount;
private String mNewName;
+ private String mNewRemotePath;
private DataStorageManager mStorageManager;
* 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;
}
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)
}
+ 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
*
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
}
- /**
- * 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) {
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;
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)
}
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.
*
* @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder).
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;
+ }
+
}
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;
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;
/** 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,
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.
*
@Override
protected RemoteOperationResult run(WebdavClient client) {
RemoteOperationResult result = null;
+ mFailsInFavouritesFound = 0;
+ mConflictsFound = 0;
+ mForgottenLocalFiles.clear();
// code before in FileSyncAdapter.fetchData
PropFindMethod query = null;
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);
// 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);
}
// 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());
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);
+ }
+ }
+ }
+ }
}
*/
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;
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;
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;
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)
mRemotePath = file.getRemotePath();
mIsInstant = isInstant;
mForceOverwrite = forceOverwrite;
+ mLocalBehaviour = localBehaviour;
+ mOriginalStoragePath = mFile.getStoragePath();
+ mOriginalFileName = mFile.getFileName();
}
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() {
return mForceOverwrite;
}
+ public boolean wasRenamed() {
+ return mWasRenamed;
+ }
public Set<OnDatatransferProgressListener> getDataTransferListeners() {
return mDataTransferListeners;
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));
}
ProviderTableMeta.FILE_CREATION);\r
mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED,\r
ProviderTableMeta.FILE_MODIFIED);\r
+ mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,\r
+ ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA);\r
mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH,\r
ProviderTableMeta.FILE_CONTENT_LENGTH);\r
mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE,\r
ProviderTableMeta.FILE_STORAGE_PATH);\r
mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE,\r
ProviderTableMeta.FILE_LAST_SYNC_DATE);\r
+ mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA,\r
+ ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA);\r
mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC,\r
ProviderTableMeta.FILE_KEEP_IN_SYNC);\r
mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER,\r
+ ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, "\r
+ ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, "\r
+ ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "\r
- + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER );");\r
+ + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "\r
+ + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, "\r
+ + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER );"\r
+ );\r
}\r
\r
@Override\r
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\r
Log.i("SQL", "Entering in onUpgrade");\r
+ boolean upgraded = false; \r
if (oldVersion == 1 && newVersion >= 2) {\r
- Log.i("SQL", "Entering in the ADD in onUpgrade");\r
+ Log.i("SQL", "Entering in the #1 ADD in onUpgrade");\r
db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +\r
" ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " +\r
" DEFAULT 0");\r
- } else Log.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);\r
+ upgraded = true;\r
+ }\r
+ if (oldVersion < 3 && newVersion >= 3) {\r
+ Log.i("SQL", "Entering in the #2 ADD in onUpgrade");\r
+ db.beginTransaction();\r
+ try {\r
+ db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +\r
+ " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER " +\r
+ " DEFAULT 0");\r
+ \r
+ // assume there are not local changes pending to upload\r
+ db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + \r
+ " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + System.currentTimeMillis() + \r
+ " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");\r
+ \r
+ upgraded = true;\r
+ db.setTransactionSuccessful();\r
+ } finally {\r
+ db.endTransaction();\r
+ }\r
+ }\r
+ if (oldVersion < 4 && newVersion >= 4) {\r
+ Log.i("SQL", "Entering in the #3 ADD in onUpgrade");\r
+ db.beginTransaction();\r
+ try {\r
+ db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +\r
+ " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER " +\r
+ " DEFAULT 0");\r
+ \r
+ db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + \r
+ " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + ProviderTableMeta.FILE_MODIFIED + \r
+ " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");\r
+ \r
+ upgraded = true;\r
+ db.setTransactionSuccessful();\r
+ } finally {\r
+ db.endTransaction();\r
+ }\r
+ }\r
+ if (!upgraded)\r
+ Log.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);\r
}\r
\r
}\r
\r
import java.io.IOException;\r
import java.net.UnknownHostException;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
import java.util.List;\r
+import java.util.Map;\r
\r
import org.apache.jackrabbit.webdav.DavException;\r
\r
import com.owncloud.android.datamodel.DataStorageManager;\r
import com.owncloud.android.datamodel.FileDataStorageManager;\r
import com.owncloud.android.datamodel.OCFile;\r
-//<<<<<<< HEAD
import com.owncloud.android.operations.RemoteOperationResult;\r
import com.owncloud.android.operations.SynchronizeFolderOperation;\r
import com.owncloud.android.operations.UpdateOCVersionOperation;\r
-/*=======
-import com.owncloud.android.files.services.FileDownloader;\r
-import com.owncloud.android.files.services.FileObserverService;\r
-import com.owncloud.android.utils.OwnCloudVersion;\r
->>>>>>> origin/master*/
-\r
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
+import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity;\r
import android.accounts.Account;\r
import android.app.Notification;\r
import android.app.NotificationManager;\r
private int mFailedResultsCounter; \r
private RemoteOperationResult mLastFailedResult;\r
private SyncResult mSyncResult;\r
+ private int mConflictsFound;\r
+ private int mFailsInFavouritesFound;\r
+ private Map<String, String> mForgottenLocalFiles;\r
+\r
\r
public FileSyncAdapter(Context context, boolean autoInitialize) {\r
super(context, autoInitialize);\r
mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);\r
mFailedResultsCounter = 0;\r
mLastFailedResult = null;\r
+ mConflictsFound = 0;\r
+ mFailsInFavouritesFound = 0;\r
+ mForgottenLocalFiles = new HashMap<String, String>();\r
mSyncResult = syncResult;\r
- \r
+ mSyncResult.fullSyncRequested = false;\r
+ mSyncResult.delayUntil = 60*60*24; // sync after 24h\r
+\r
this.setAccount(account);\r
this.setContentProvider(provider);\r
this.setStorageManager(new FileDataStorageManager(account, getContentProvider()));\r
\r
/// notify the user about the failure of MANUAL synchronization\r
notifyFailedSynchronization();\r
+ \r
+ }\r
+ if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {\r
+ notifyFailsInFavourites();\r
+ }\r
+ if (mForgottenLocalFiles.size() > 0) {\r
+ notifyForgottenLocalFiles();\r
+ \r
}\r
sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI\r
}\r
\r
}\r
- \r
- \r
+\r
\r
/**\r
* Called by system SyncManager when a synchronization is required to be cancelled.\r
// synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess\r
sendStickyBroadcast(true, remotePath, null);\r
\r
- if (result.isSuccess()) {\r
+ if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) {\r
+ \r
+ if (result.getCode() == ResultCode.SYNC_CONFLICT) {\r
+ mConflictsFound += synchFolderOp.getConflictsFound();\r
+ mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound();\r
+ }\r
+ if (synchFolderOp.getForgottenLocalFiles().size() > 0) {\r
+ mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles());\r
+ }\r
// synchronize children folders \r
List<OCFile> children = synchFolderOp.getChildren();\r
fetchChildren(children); // beware of the 'hidden' recursion here!\r
\r
-//<<<<<<< HEAD
} else {\r
if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED) {\r
mSyncResult.stats.numAuthExceptions++;\r
\r
} else if (result.getException() instanceof IOException) { \r
mSyncResult.stats.numIoExceptions++;\r
-/*=======
- // insertion or update of files\r
- List<OCFile> updatedFiles = new Vector<OCFile>(resp.getResponses().length - 1);\r
- for (int i = 1; i < resp.getResponses().length; ++i) {\r
- WebdavEntry we = new WebdavEntry(resp.getResponses()[i], getUri().getPath());\r
- OCFile file = fillOCFile(we);\r
- file.setParentId(parentId);\r
- if (getStorageManager().getFileByPath(file.getRemotePath()) != null &&\r
- getStorageManager().getFileByPath(file.getRemotePath()).keepInSync() &&\r
- file.getModificationTimestamp() > getStorageManager().getFileByPath(file.getRemotePath())\r
- .getModificationTimestamp()) {\r
- // first disable observer so we won't get file upload right after download\r
- Log.d(TAG, "Disabling observation of remote file" + file.getRemotePath());\r
- Intent intent = new Intent(getContext(), FileObserverService.class);\r
- intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_ADD_DOWNLOADING_FILE);\r
- intent.putExtra(FileObserverService.KEY_CMD_ARG, file.getRemotePath());\r
- getContext().startService(intent);\r
- intent = new Intent(this.getContext(), FileDownloader.class);\r
- intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());\r
- intent.putExtra(FileDownloader.EXTRA_FILE, file);\r
- file.setKeepInSync(true);\r
- getContext().startService(intent);\r
- }\r
- if (getStorageManager().getFileByPath(file.getRemotePath()) != null)\r
- file.setKeepInSync(getStorageManager().getFileByPath(file.getRemotePath()).keepInSync());\r
->>>>>>> origin/master*/
- \r
}\r
mFailedResultsCounter++;\r
mLastFailedResult = result;\r
((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_ticker, notification);\r
}\r
\r
+\r
+ /**\r
+ * Notifies the user about conflicts and strange fails when trying to synchronize the contents of kept-in-sync files.\r
+ * \r
+ * By now, we won't consider a failed synchronization.\r
+ */\r
+ private void notifyFailsInFavourites() {\r
+ if (mFailedResultsCounter > 0) {\r
+ Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis());\r
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
+ // TODO put something smart in the contentIntent below\r
+ notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
+ notification.setLatestEventInfo(getContext().getApplicationContext(), \r
+ getContext().getString(R.string.sync_fail_in_favourites_ticker), \r
+ String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), \r
+ notification.contentIntent);\r
+ ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification);\r
+ \r
+ } else {\r
+ Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis());\r
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
+ // TODO put something smart in the contentIntent below\r
+ notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
+ notification.setLatestEventInfo(getContext().getApplicationContext(), \r
+ getContext().getString(R.string.sync_conflicts_in_favourites_ticker), \r
+ String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), \r
+ notification.contentIntent);\r
+ ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification);\r
+ } \r
+ }\r
+\r
\r
+ /**\r
+ * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because \r
+ * copying them inside the ownCloud local directory was not possible.\r
+ * \r
+ * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have \r
+ * synchronization problems if a local file is linked to more than one remote file.\r
+ * \r
+ * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory.\r
+ */\r
+ private void notifyForgottenLocalFiles() {\r
+ Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis());\r
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
\r
+ /// includes a pending intent in the notification showing a more detailed explanation\r
+ Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class);\r
+ explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount());\r
+ ArrayList<String> remotePaths = new ArrayList<String>();\r
+ ArrayList<String> localPaths = new ArrayList<String>();\r
+ remotePaths.addAll(mForgottenLocalFiles.keySet());\r
+ localPaths.addAll(mForgottenLocalFiles.values());\r
+ explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_LOCAL_PATHS, localPaths);\r
+ explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); \r
+ explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\r
+ \r
+ notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0);\r
+ notification.setLatestEventInfo(getContext().getApplicationContext(), \r
+ getContext().getString(R.string.sync_foreign_files_forgotten_ticker), \r
+ String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size()), \r
+ notification.contentIntent);\r
+ ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification);\r
+ \r
+ }\r
+ \r
+ \r
}\r
}
}
- return false;
+ return true;
}
private void populateAccountList() {
import android.view.Window;\r
import android.widget.CheckBox;\r
import android.widget.EditText;\r
+import android.widget.Button;\r
import android.widget.ImageView;\r
import android.widget.TextView;\r
import com.owncloud.android.R;\r
tv.setOnFocusChangeListener(this);\r
tv2.setOnFocusChangeListener(this);\r
\r
+ Button b = (Button) findViewById(R.id.account_register);\r
+ if (b != null) {\r
+ b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name)));\r
+ }\r
+\r
mNewCapturedUriFromOAuth2Redirection = null;\r
- \r
- Log.d(TAG, "onCreate");\r
}\r
\r
\r
// NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens\r
}\r
TextView tv = (TextView) findViewById(R.id.account_username);\r
- tv.setError(message);\r
+ tv.setError(message + " "); // the extra spaces are a workaround for an ugly bug: \r
+ // 1. insert wrong credentials and connect\r
+ // 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\r
+ // 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\r
+ // Seen, at least, in Android 2.x devices\r
}\r
}\r
public void onCancelClick(View view) {\r
}\r
\r
public void onRegisterClick(View view) {\r
- Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse("https://owncloud.com/mobile/new"));\r
+ Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register)));\r
setResult(RESULT_CANCELED);\r
startActivity(register);\r
}\r
onFocusChange(findViewById(R.id.host_URL), false);\r
} else if (v.getId() == R.id.viewPassword) {\r
TextView view = (TextView) findViewById(R.id.account_password);\r
- int input_type = InputType.TYPE_CLASS_TEXT\r
- | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;\r
+ int input_type = view.getInputType();\r
+ if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {\r
+ input_type = InputType.TYPE_CLASS_TEXT\r
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD;\r
+ } else {\r
+ input_type = InputType.TYPE_CLASS_TEXT\r
+ | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;\r
+ }\r
view.setInputType(input_type);\r
}\r
}\r
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;
*/
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);
}
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);
--- /dev/null
+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();
+ }
+ }
+ }
+
+}
}\r
FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
if (fragment != null)\r
- fragment.updateFileDetails(); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())\r
+ fragment.updateFileDetails(false); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())\r
}\r
\r
@Override\r
case android.R.id.home:\r
backToDisplayActivity();\r
returnValue = true;\r
+ break;\r
+ default:\r
+ returnValue = super.onOptionsItemSelected(item);\r
}\r
\r
return returnValue;\r
super.onResume();\r
if (!mConfigurationChangedToLandscape) { \r
FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- fragment.updateFileDetails();\r
+ fragment.updateFileDetails(false);\r
}\r
}\r
\r
import android.content.pm.PackageManager.NameNotFoundException;\r
import android.content.res.Resources.NotFoundException;\r
import android.database.Cursor;\r
+import android.graphics.Bitmap;\r
+import android.graphics.drawable.BitmapDrawable;\r
import android.net.Uri;\r
import android.os.Bundle;\r
import android.os.Handler;\r
import com.owncloud.android.files.services.FileUploader;\r
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
import com.owncloud.android.network.OwnCloudClientUtils;\r
+import com.owncloud.android.operations.OnRemoteOperationListener;\r
+import com.owncloud.android.operations.RemoteOperation;\r
import com.owncloud.android.operations.RemoteOperationResult;\r
+import com.owncloud.android.operations.RemoveFileOperation;\r
+import com.owncloud.android.operations.RenameFileOperation;\r
+import com.owncloud.android.operations.SynchronizeFileOperation;\r
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
import com.owncloud.android.syncadapter.FileSyncService;\r
import com.owncloud.android.ui.dialog.SslValidatorDialog;\r
import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;\r
*/\r
\r
public class FileDisplayActivity extends SherlockFragmentActivity implements\r
- OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener {\r
+ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener {\r
\r
private ArrayAdapter<String> mDirectories;\r
private OCFile mCurrentDir = null;\r
private static final int ACTION_SELECT_MULTIPLE_FILES = 2;\r
\r
private static final String TAG = "FileDisplayActivity";\r
+\r
+ private static int[] mMenuIdentifiersToPatch = {R.id.about_app};\r
\r
@Override\r
public void onCreate(Bundle savedInstanceState) {\r
public boolean onCreateOptionsMenu(Menu menu) {\r
MenuInflater inflater = getSherlock().getMenuInflater();\r
inflater.inflate(R.menu.menu, menu);\r
+ \r
+ patchHiddenAccents(menu);\r
+ \r
return true;\r
}\r
\r
+ /**\r
+ * 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> \r
+ * \r
+ * @param menu Menu to patch\r
+ */\r
+ private void patchHiddenAccents(Menu menu) {\r
+ for (int i = 0; i < mMenuIdentifiersToPatch.length ; i++) {\r
+ MenuItem aboutItem = menu.findItem(mMenuIdentifiersToPatch[i]);\r
+ if (aboutItem != null && aboutItem.getIcon() instanceof BitmapDrawable) {\r
+ // Clip off the bottom three (density independent) pixels of transparent padding\r
+ Bitmap original = ((BitmapDrawable) aboutItem.getIcon()).getBitmap();\r
+ float scale = getResources().getDisplayMetrics().density;\r
+ int clippedHeight = (int) (original.getHeight() - (3 * scale));\r
+ Bitmap scaled = Bitmap.createBitmap(original, 0, 0, original.getWidth(), clippedHeight);\r
+ aboutItem.setIcon(new BitmapDrawable(getResources(), scaled));\r
+ }\r
+ }\r
+ }\r
+\r
+\r
@Override\r
public boolean onOptionsItemSelected(MenuItem item) {\r
boolean retval = true;\r
break;\r
}\r
default:\r
- retval = false;\r
+ retval = super.onOptionsItemSelected(item);\r
}\r
return retval;\r
}\r
*/\r
public void onActivityResult(int requestCode, int resultCode, Intent data) {\r
\r
- if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && resultCode == RESULT_OK) {\r
- requestSimpleUpload(data);\r
+ if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {\r
+ requestSimpleUpload(data, resultCode);\r
\r
- } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && resultCode == RESULT_OK) {\r
- requestMultipleUpload(data);\r
+ } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {\r
+ requestMultipleUpload(data, resultCode);\r
\r
}\r
}\r
\r
- private void requestMultipleUpload(Intent data) {\r
+ private void requestMultipleUpload(Intent data, int resultCode) {\r
String[] filePaths = data.getStringArrayExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);\r
if (filePaths != null) {\r
String[] remotePaths = new String[filePaths.length];\r
i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths);\r
i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);\r
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);\r
+ if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)\r
+ i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);\r
startService(i);\r
\r
} else {\r
}\r
\r
\r
- private void requestSimpleUpload(Intent data) {\r
+ private void requestSimpleUpload(Intent data, int resultCode) {\r
String filepath = null;\r
try {\r
Uri selectedImageUri = data.getData();\r
i.putExtra(FileUploader.KEY_LOCAL_FILE, filepath);\r
i.putExtra(FileUploader.KEY_REMOTE_FILE, remotepath);\r
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);\r
+ if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)\r
+ i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);\r
startService(i);\r
}\r
\r
dialog.dismiss();\r
}\r
});\r
- builder.setNegativeButton(R.string.common_exit, new OnClickListener() {\r
+ String message = String.format(getString(R.string.common_exit), getString(R.string.app_name));\r
+ builder.setNegativeButton(message, new OnClickListener() {\r
public void onClick(DialogInterface dialog, int which) {\r
dialog.dismiss();\r
finish();\r
PackageInfo pkg;\r
try {\r
pkg = getPackageManager().getPackageInfo(getPackageName(), 0);\r
- builder.setMessage(String.format(getString(R.string.about_message), pkg.versionName));\r
+ builder.setMessage(String.format(getString(R.string.about_message), getString(R.string.app_name), pkg.versionName));\r
builder.setIcon(android.R.drawable.ic_menu_info_details);\r
dialog = builder.create();\r
} catch (NameNotFoundException e) {\r
if (item == 0) {\r
//if (!mDualPane) { \r
Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class);\r
+ action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this));\r
startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES);\r
//} else {\r
// TODO create and handle new fragment LocalFileListFragment\r
}\r
\r
setSupportProgressBarIndeterminateVisibility(inProgress);\r
+ removeStickyBroadcast(intent);\r
\r
}\r
\r
*/\r
@Override\r
public void onReceive(Context context, Intent intent) {\r
- long parentDirId = intent.getLongExtra(FileUploader.EXTRA_PARENT_DIR_ID, -1);\r
- OCFile parentDir = mStorageManager.getFileById(parentDirId);\r
+ String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);\r
-\r
- if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name) &&\r
- parentDir != null && \r
- ( (mCurrentDir == null && parentDir.getFileName().equals("/")) ||\r
- parentDir.equals(mCurrentDir)\r
- )\r
- ) {\r
+ boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);\r
+ boolean isDescendant = (mCurrentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(mCurrentDir.getRemotePath()));\r
+ if (sameAccount && isDescendant) {\r
OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
if (fileListFragment != null) { \r
fileListFragment.listDirectory();\r
public void onReceive(Context context, Intent intent) {\r
String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);\r
-\r
- if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name) &&\r
- mCurrentDir != null && mCurrentDir.getFileId() == mStorageManager.getFileByPath(downloadedRemotePath).getParentId()) {\r
+ boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);\r
+ boolean isDescendant = (mCurrentDir != null) && (downloadedRemotePath != null) && (downloadedRemotePath.startsWith(mCurrentDir.getRemotePath()));\r
+ if (sameAccount && isDescendant) {\r
OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
if (fileListFragment != null) { \r
fileListFragment.listDirectory();\r
if (mDualPane) {\r
FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
if (fragment != null)\r
- fragment.updateFileDetails();\r
+ fragment.updateFileDetails(false);\r
}\r
}\r
\r
}\r
\r
\r
+ /**\r
+ * Updates the view associated to the activity after the finish of some operation over files\r
+ * in the current account.\r
+ * \r
+ * @param operation Removal operation performed.\r
+ * @param result Result of the removal.\r
+ */\r
+ @Override\r
+ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {\r
+ if (operation instanceof RemoveFileOperation) {\r
+ onRemoveFileOperationFinish((RemoveFileOperation)operation, result);\r
+ \r
+ } else if (operation instanceof RenameFileOperation) {\r
+ onRenameFileOperationFinish((RenameFileOperation)operation, result);\r
+ \r
+ } else if (operation instanceof SynchronizeFileOperation) {\r
+ onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Updates the view associated to the activity after the finish of an operation trying to remove a \r
+ * file. \r
+ * \r
+ * @param operation Removal operation performed.\r
+ * @param result Result of the removal.\r
+ */\r
+ private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {\r
+ dismissDialog(DIALOG_SHORT_WAIT);\r
+ if (result.isSuccess()) {\r
+ Toast msg = Toast.makeText(this, R.string.remove_success_msg, Toast.LENGTH_LONG);\r
+ msg.show();\r
+ OCFile removedFile = operation.getFile();\r
+ if (mDualPane) {\r
+ FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (details != null && removedFile.equals(details.getDisplayedFile()) ) {\r
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment\r
+ transaction.commit();\r
+ }\r
+ }\r
+ if (mStorageManager.getFileById(removedFile.getParentId()).equals(mCurrentDir)) {\r
+ mFileList.listDirectory();\r
+ }\r
+ \r
+ } else {\r
+ Toast msg = Toast.makeText(this, R.string.remove_fail_msg, Toast.LENGTH_LONG); \r
+ msg.show();\r
+ if (result.isSslRecoverableException()) {\r
+ mLastSslUntrustedServerResult = result;\r
+ showDialog(DIALOG_SSL_VALIDATOR); \r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Updates the view associated to the activity after the finish of an operation trying to rename a \r
+ * file. \r
+ * \r
+ * @param operation Renaming operation performed.\r
+ * @param result Result of the renaming.\r
+ */\r
+ private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) {\r
+ dismissDialog(DIALOG_SHORT_WAIT);\r
+ OCFile renamedFile = operation.getFile();\r
+ if (result.isSuccess()) {\r
+ if (mDualPane) {\r
+ FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (details != null && renamedFile.equals(details.getDisplayedFile()) ) {\r
+ details.updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ }\r
+ }\r
+ if (mStorageManager.getFileById(renamedFile.getParentId()).equals(mCurrentDir)) {\r
+ mFileList.listDirectory();\r
+ }\r
+ \r
+ } else {\r
+ if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) {\r
+ Toast msg = Toast.makeText(this, R.string.rename_local_fail_msg, Toast.LENGTH_LONG); \r
+ msg.show();\r
+ // TODO throw again the new rename dialog\r
+ } else {\r
+ Toast msg = Toast.makeText(this, R.string.rename_server_fail_msg, Toast.LENGTH_LONG); \r
+ msg.show();\r
+ if (result.isSslRecoverableException()) {\r
+ mLastSslUntrustedServerResult = result;\r
+ showDialog(DIALOG_SSL_VALIDATOR); \r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {\r
+ dismissDialog(DIALOG_SHORT_WAIT);\r
+ OCFile syncedFile = operation.getLocalFile();\r
+ if (!result.isSuccess()) {\r
+ if (result.getCode() == ResultCode.SYNC_CONFLICT) {\r
+ Intent i = new Intent(this, ConflictsResolveActivity.class);\r
+ i.putExtra(ConflictsResolveActivity.EXTRA_FILE, syncedFile);\r
+ i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ startActivity(i);\r
+ \r
+ } else {\r
+ Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG); \r
+ msg.show();\r
+ }\r
+ \r
+ } else {\r
+ if (operation.transferWasRequested()) {\r
+ mFileList.listDirectory();\r
+ onTransferStateChanged(syncedFile, true, true);\r
+ \r
+ } else {\r
+ Toast msg = Toast.makeText(this, R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); \r
+ msg.show();\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * {@inheritDoc}\r
+ */\r
+ @Override\r
+ public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {\r
+ /*OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
+ if (fileListFragment != null) { \r
+ fileListFragment.listDirectory();\r
+ }*/\r
+ if (mDualPane) {\r
+ FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
+ if (details != null && file.equals(details.getDisplayedFile()) ) {\r
+ if (downloading || uploading) {\r
+ details.updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this));\r
+ } else {\r
+ details.updateFileDetails(downloading || uploading);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ \r
+\r
+\r
}\r
--- /dev/null
+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;
+ }
+ }
+
+}
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;
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;
*/
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
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
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");
}
break;
}
default:
- retval = false;
+ retval = onOptionsItemSelected(item);
}
return retval;
}
// 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");
}
/**
* 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) {
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;
+ }
+
}
fileIcon.setImageResource(R.drawable.ic_menu_archive);\r
}\r
ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2);\r
- //if (FileDownloader.isDownloading(mAccount, file.getRemotePath())) {\r
FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();\r
FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();\r
if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {\r
}
}
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mListener.ConflictDecisionMade(Decision.CANCEL);
+ }
+
public interface OnConflictDecisionMadeListener {
public void ConflictDecisionMade(Decision decision);
}
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;
/**
- * 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);
}
}
- 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);
}
-
+
+
}
--- /dev/null
+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;
+ }
+
+}
+
+
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();
import android.os.AsyncTask;\r
import android.os.Bundle;\r
import android.os.Handler;\r
+import android.support.v4.app.DialogFragment;\r
import android.support.v4.app.FragmentTransaction;\r
import android.util.Log;\r
import android.view.Display;\r
import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
import com.owncloud.android.operations.RemoveFileOperation;\r
import com.owncloud.android.operations.RenameFileOperation;\r
+import com.owncloud.android.operations.SynchronizeFileOperation;\r
+import com.owncloud.android.ui.activity.ConflictsResolveActivity;\r
import com.owncloud.android.ui.activity.FileDetailActivity;\r
import com.owncloud.android.ui.activity.FileDisplayActivity;\r
import com.owncloud.android.ui.activity.TransferServiceGetter;\r
private View mView;\r
private OCFile mFile;\r
private Account mAccount;\r
+ private FileDataStorageManager mStorageManager;\r
private ImageView mPreview;\r
\r
private DownloadFinishReceiver mDownloadFinishReceiver;\r
\r
private Handler mHandler;\r
private RemoteOperation mLastRemoteOperation;\r
+ private DialogFragment mCurrentDialog;\r
\r
- private static final String TAG = "FileDetailFragment";\r
+ private static final String TAG = FileDetailFragment.class.getSimpleName();\r
public static final String FTAG = "FileDetails"; \r
public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";\r
\r
public FileDetailFragment() {\r
mFile = null;\r
mAccount = null;\r
+ mStorageManager = null;\r
mLayout = R.layout.file_details_empty;\r
}\r
\r
public FileDetailFragment(OCFile fileToDetail, Account ocAccount) {\r
mFile = fileToDetail;\r
mAccount = ocAccount;\r
+ mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment \r
mLayout = R.layout.file_details_empty;\r
\r
if(fileToDetail != null && ocAccount != null) {\r
mPreview = (ImageView)mView.findViewById(R.id.fdPreview);\r
}\r
\r
- updateFileDetails();\r
+ updateFileDetails(false);\r
return view;\r
}\r
\r
throw new ClassCastException(activity.toString() + " must implement " + FileDetailFragment.ContainerActivity.class.getSimpleName());\r
}\r
}\r
+ \r
+ \r
+ /**\r
+ * {@inheritDoc}\r
+ */\r
+ @Override\r
+ public void onActivityCreated(Bundle savedInstanceState) {\r
+ super.onActivityCreated(savedInstanceState);\r
+ if (mAccount != null) {\r
+ mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());;\r
+ }\r
+ }\r
\r
\r
@Override\r
public void onClick(View v) {\r
switch (v.getId()) {\r
case R.id.fdDownloadBtn: {\r
- //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath())) {\r
FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();\r
FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();\r
if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {\r
}\r
\r
} else {\r
- Intent i = new Intent(getActivity(), FileDownloader.class);\r
- i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);\r
- i.putExtra(FileDownloader.EXTRA_FILE, mFile);\r
- /*i.putExtra(FileDownloader.EXTRA_REMOTE_PATH, mFile.getRemotePath());\r
- i.putExtra(FileDownloader.EXTRA_FILE_PATH, mFile.getRemotePath());\r
- i.putExtra(FileDownloader.EXTRA_FILE_SIZE, mFile.getFileLength());*/\r
+ mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity());\r
+ WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());\r
+ mLastRemoteOperation.execute(wc, this, mHandler);\r
\r
// update ui \r
- setButtonsForTransferring();\r
- \r
- getActivity().startService(i);\r
- mContainerActivity.onFileStateChanged(); // this is not working; it is performed before the fileDownloadService registers it as 'in progress'\r
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
+ getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
+ setButtonsForTransferring(); // disable button immediately, although the synchronization does not result in a file transference\r
+ \r
}\r
break;\r
}\r
case R.id.fdKeepInSync: {\r
CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync);\r
mFile.setKeepInSync(cb.isChecked());\r
- FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());\r
- fdsm.saveFile(mFile);\r
- if (mFile.keepInSync()) {\r
- onClick(getView().findViewById(R.id.fdDownloadBtn));\r
- } else {\r
- mContainerActivity.onFileStateChanged(); // put inside 'else' to not call it twice (here, and in the virtual click on fdDownloadBtn)\r
- }\r
+ mStorageManager.saveFile(mFile);\r
\r
+ /// register the OCFile instance in the observer service to monitor local updates;\r
+ /// if necessary, the file is download \r
Intent intent = new Intent(getActivity().getApplicationContext(),\r
FileObserverService.class);\r
intent.putExtra(FileObserverService.KEY_FILE_CMD,\r
(cb.isChecked()?\r
FileObserverService.CMD_ADD_OBSERVED_FILE:\r
FileObserverService.CMD_DEL_OBSERVED_FILE));\r
- intent.putExtra(FileObserverService.KEY_CMD_ARG, mFile.getStoragePath());\r
+ intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile);\r
+ intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount);\r
Log.e(TAG, "starting observer service");\r
getActivity().startService(intent);\r
\r
+ if (mFile.keepInSync()) {\r
+ onClick(getView().findViewById(R.id.fdDownloadBtn)); // force an immediate synchronization\r
+ }\r
break;\r
}\r
case R.id.fdRenameBtn: {\r
- EditNameDialog dialog = EditNameDialog.newInstance(mFile.getFileName());\r
- dialog.setOnDismissListener(this);\r
+ EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), this);\r
dialog.show(getFragmentManager(), "nameeditdialog");\r
break;\r
} \r
mFile.isDown() ? R.string.confirmation_remove_local : -1,\r
R.string.common_cancel);\r
confDialog.setOnConfirmationListener(this);\r
- confDialog.show(getFragmentManager(), FTAG_CONFIRMATION);\r
+ mCurrentDialog = confDialog;\r
+ mCurrentDialog.show(getFragmentManager(), FTAG_CONFIRMATION);\r
break;\r
}\r
case R.id.fdOpenBtn: {\r
try {\r
Intent i = new Intent(Intent.ACTION_VIEW);\r
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));\r
- if (mimeType != null && !mimeType.equals(mFile.getMimetype())) {\r
- i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);\r
+ if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {\r
+ if (mimeType != null) {\r
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);\r
+ } else {\r
+ // desperate try\r
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");\r
+ }\r
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\r
startActivity(i);\r
toastIt = false;\r
@Override\r
public void onConfirmation(String callerTag) {\r
if (callerTag.equals(FTAG_CONFIRMATION)) {\r
- FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getContentResolver());\r
- if (fdsm.getFileById(mFile.getFileId()) != null) {\r
+ if (mStorageManager.getFileById(mFile.getFileId()) != null) {\r
mLastRemoteOperation = new RemoveFileOperation( mFile, \r
true, \r
- new FileDataStorageManager(mAccount, getActivity().getContentResolver()));\r
+ mStorageManager);\r
WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());\r
mLastRemoteOperation.execute(wc, this, mHandler);\r
\r
getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
}\r
}\r
+ mCurrentDialog.dismiss();\r
+ mCurrentDialog = null;\r
}\r
\r
@Override\r
public void onNeutral(String callerTag) {\r
- FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getContentResolver());\r
File f = null;\r
if (mFile.isDown() && (f = new File(mFile.getStoragePath())).exists()) {\r
f.delete();\r
mFile.setStoragePath(null);\r
- fdsm.saveFile(mFile);\r
+ mStorageManager.saveFile(mFile);\r
updateFileDetails(mFile, mAccount);\r
}\r
+ mCurrentDialog.dismiss();\r
+ mCurrentDialog = null;\r
}\r
\r
@Override\r
public void onCancel(String callerTag) {\r
Log.d(TAG, "REMOVAL CANCELED");\r
+ mCurrentDialog.dismiss();\r
+ mCurrentDialog = null;\r
}\r
\r
\r
*/\r
public void updateFileDetails(OCFile file, Account ocAccount) {\r
mFile = file;\r
+ if (ocAccount != null && ( \r
+ mStorageManager == null || \r
+ (mAccount != null && !mAccount.equals(ocAccount))\r
+ )) {\r
+ mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());\r
+ }\r
mAccount = ocAccount;\r
- updateFileDetails();\r
+ updateFileDetails(false);\r
}\r
\r
\r
/**\r
* Updates the view with all relevant details about that file.\r
+ *\r
+ * TODO Remove parameter when the transferring state of files is kept in database. \r
+ * \r
+ * @param transferring Flag signaling if the file should be considered as downloading or uploading, \r
+ * although {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and \r
+ * {@link FileUploaderBinder#isUploading(Account, OCFile)} return false.\r
+ * \r
*/\r
- public void updateFileDetails() {\r
+ public void updateFileDetails(boolean transferring) {\r
\r
if (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment) {\r
\r
//if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) {\r
FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();\r
FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();\r
- if ((downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {\r
+ if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {\r
setButtonsForTransferring();\r
\r
} else if (mFile.isDown()) {\r
setButtonsForDown();\r
\r
} else {\r
+ // TODO load default preview image; when the local file is removed, the preview remains there\r
setButtonsForRemote();\r
}\r
}\r
+ getView().invalidate();\r
}\r
\r
\r
private void setButtonsForDown() {\r
if (!isEmpty()) {\r
Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);\r
- downloadButton.setText(R.string.filedetails_redownload);\r
- //downloadButton.setEnabled(true);\r
+ downloadButton.setText(R.string.filedetails_sync_file);\r
\r
((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true);\r
((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);\r
String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
if (mFile.getRemotePath().equals(downloadedRemotePath)) {\r
if (downloadWasFine) {\r
- mFile.setStoragePath(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH)); // updates the local object without accessing the database again\r
+ mFile = mStorageManager.getFileByPath(downloadedRemotePath);\r
}\r
- updateFileDetails(); // it updates the buttons; must be called although !downloadWasFine\r
+ updateFileDetails(false); // it updates the buttons; must be called although !downloadWasFine\r
}\r
}\r
}\r
if (!isEmpty() && accountName.equals(mAccount.name)) {\r
boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false);\r
String uploadRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH);\r
- if (mFile.getRemotePath().equals(uploadRemotePath)) {\r
+ boolean renamedInUpload = mFile.getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH));\r
+ if (mFile.getRemotePath().equals(uploadRemotePath) ||\r
+ renamedInUpload) {\r
if (uploadWasFine) {\r
- FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());\r
- mFile = fdsm.getFileByPath(mFile.getRemotePath());\r
+ mFile = mStorageManager.getFileByPath(mFile.getRemotePath());\r
+ }\r
+ if (renamedInUpload) {\r
+ String newName = (new File(uploadRemotePath)).getName();\r
+ Toast msg = Toast.makeText(getActivity().getApplicationContext(), String.format(getString(R.string.filedetails_renamed_in_upload_msg), newName), Toast.LENGTH_LONG);\r
+ msg.show();\r
+ getSherlockActivity().removeStickyBroadcast(intent); // not the best place to do this; a small refactorization of BroadcastReceivers should be done\r
}\r
- updateFileDetails(); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server\r
+ updateFileDetails(false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server\r
}\r
}\r
}\r
String newFilename = dialog.getNewFilename();\r
Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);\r
mLastRemoteOperation = new RenameFileOperation( mFile, \r
+ mAccount, \r
newFilename, \r
new FileDataStorageManager(mAccount, getActivity().getContentResolver()));\r
WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());\r
\r
} else if (operation instanceof RenameFileOperation) {\r
onRenameFileOperationFinish((RenameFileOperation)operation, result);\r
+ \r
+ } else if (operation instanceof SynchronizeFileOperation) {\r
+ onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);\r
}\r
}\r
}\r
}\r
}\r
\r
+ private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {\r
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
+ getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
+\r
+ if (!result.isSuccess()) {\r
+ if (result.getCode() == ResultCode.SYNC_CONFLICT) {\r
+ Intent i = new Intent(getActivity(), ConflictsResolveActivity.class);\r
+ i.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile);\r
+ i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount);\r
+ startActivity(i);\r
+ \r
+ } else {\r
+ Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); \r
+ msg.show();\r
+ }\r
+ \r
+ if (mFile.isDown()) {\r
+ setButtonsForDown();\r
+ \r
+ } else {\r
+ setButtonsForRemote();\r
+ }\r
+ \r
+ } else {\r
+ if (operation.transferWasRequested()) {\r
+ mContainerActivity.onFileStateChanged(); // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so \r
+ // checking the service to see if the file is downloading results in FALSE\r
+ } else {\r
+ Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); \r
+ msg.show();\r
+ if (mFile.isDown()) {\r
+ setButtonsForDown();\r
+ \r
+ } else {\r
+ setButtonsForRemote();\r
+ }\r
+ }\r
+ }\r
+ }\r
\r
}\r
*/
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.
* @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";
private OCFile mFile = null;
private FileListListAdapter mAdapter;
-
+
+ private Handler mHandler;
+ private OCFile mTargetFile;
+
+ private DialogFragment mCurrentDialog;
/**
* {@inheritDoc}
setReferencePosition(position);
}
+ registerForContextMenu(getListView());
+ getListView().setOnCreateContextMenuListener(this);
+
+ mHandler = new Handler();
+
Log.i(TAG, "onActivityCreated() stop");
}
}
}
+
+ /**
+ * {@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
*
* @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
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;
+ }
+ }
+
+
}
--- /dev/null
+/* 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();
+ }
+
+ }
+
+}
\ No newline at end of file
return mCreateTimestamp;
}
- public long modifiedTimesamp() {
+ public long modifiedTimestamp() {
return mModifiedTimestamp;
}