Bläddra i källkod

Files uploaded in the past are copied to the local ownCloud directory during account synchronization

David A. Velasco 12 år sedan
förälder
incheckning
2a913bfbeb

+ 4 - 3
AndroidManifest.xml

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

+ 44 - 0
res/layout/explanation.xml

@@ -0,0 +1,44 @@
+<?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="1dp"
+		android:layout_weight="1"
+	    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="1"
+	    android:padding="10dip"
+	    />
+	    
+</LinearLayout>

+ 7 - 0
res/values/strings.xml

@@ -116,6 +116,12 @@
 	<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">"From version 1.3.16, uploaded files are copied to the local ownCloud folder to avoid problems when the same local file is uploaded to different folders or accounts.\n\nSome files uploaded in the past could not be copied during the account synchronization. %1$s will not track their current location anymore. You will need to download them again to access their contents from this app.\n\nSee below the list of untracked local files and the remote files in in %2$s they were linked to:</string>
+    <string name="sync_foreign_files_forgotten_remote_prefix">"Remote: "</string>
+    <string name="sync_foreign_files_forgotten_local_prefix">"Local: "</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>
     
@@ -234,4 +240,5 @@
     <string name="conflict_keep_both">Keep both</string>
     <string name="conflict_overwrite">Overwrite</string>
     <string name="conflict_dont_upload">Don\'t upload</string>
+    
 </resources>

+ 74 - 0
src/com/owncloud/android/operations/SynchronizeFolderOperation.java

@@ -19,7 +19,13 @@
 package com.owncloud.android.operations;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+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;
@@ -73,6 +79,8 @@ public class SynchronizeFolderOperation extends RemoteOperation {
     private int mConflictsFound;
 
     private int mFailsInFavouritesFound;
+
+    private Map<String, String> mForgottenLocalFiles;
     
     
     public SynchronizeFolderOperation(  String remotePath, 
@@ -87,6 +95,7 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         mStorageManager = dataStorageManager;
         mAccount = account;
         mContext = context;
+        mForgottenLocalFiles = new HashMap<String, String>();
     }
     
     
@@ -98,6 +107,10 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         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.
      * 
@@ -113,6 +126,7 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         RemoteOperationResult result = null;
         mFailsInFavouritesFound = 0;
         mConflictsFound = 0;
+        mForgottenLocalFiles.clear();
         
         // code before in FileSyncAdapter.fetchData
         PropFindMethod query = null;
@@ -149,6 +163,7 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                     if (oldFile != null) {
                         file.setKeepInSync(oldFile.keepInSync());
                         file.setLastSyncDateForData(oldFile.getLastSyncDateForData());
+                        checkAndFixForeignStoragePath(oldFile);
                         file.setStoragePath(oldFile.getStoragePath());
                     }
 
@@ -269,4 +284,63 @@ public class SynchronizeFolderOperation extends RemoteOperation {
     }
     
 
+    /**
+     * Checks the storage path of the OCFile received as parameter. If it's out of the local ownCloud folder,
+     * tries to copy the file inside it. 
+     * 
+     * If the copy fails, the link to the local file is nullified. The account of forgotten files is kept in 
+     * {@link #mForgottenLocalFiles}
+     * 
+     * @param file      File to check and fix.
+     */
+    private void checkAndFixForeignStoragePath(OCFile file) {
+        String storagePath = file.getStoragePath();
+        String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
+        File ocLocalFolder = new File(FileStorageUtils.getSavePath(mAccount.name));
+        if (storagePath != null && !storagePath.equals(expectedPath)) {
+            /// fix storagePaths out of the local ownCloud folder
+            File originalFile = new File(storagePath);
+            mForgottenLocalFiles.put(file.getRemotePath(), storagePath);    // TODO REMOVE
+            
+            /*  TO TEST NOTIFICATION!!! - TODO UNCOMMENT
+            if (ocLocalFolder.getUsableSpace() < originalFile.length()) {
+                mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+                file.setStoragePath(null);
+                    
+            } else {
+                InputStream in = null;
+                OutputStream out = null;
+                try {
+                    File expectedFile = new File(expectedPath);
+                    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) {
+                    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);
+                    }
+                }
+            }
+            */
+        }
+    }
+
+
 }

+ 21 - 8
src/com/owncloud/android/operations/UploadFileOperation.java

@@ -177,15 +177,28 @@ public class UploadFileOperation extends RemoteOperation {
                     mFile.setStoragePath(temporalPath);
                     temporalFile = new File(temporalPath);
                     if (!originalStoragePath.equals(temporalPath)) {   // preventing weird but possible situation
-                        InputStream in = new FileInputStream(originalFile);
-                        OutputStream out = new FileOutputStream(temporalFile);
-                        byte[] buf = new byte[1024];
-                        int len;
-                        while ((len = in.read(buf)) > 0){
-                            out.write(buf, 0, len);
+                        InputStream in = null;
+                        OutputStream out = null;
+                        try {
+                            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);
+                            }
+                        } finally {
+                            try {
+                                if (in != null) in.close();
+                            } catch (Exception e) {
+                                Log.d(TAG, "Weird exception while closing input stream for " + originalStoragePath + " (ignoring)", e);
+                            }
+                            try {
+                                if (out != null) out.close();
+                            } catch (Exception e) {
+                                Log.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e);
+                            }
                         }
-                        in.close();
-                        out.close();
                     }
                 }
             }

+ 55 - 3
src/com/owncloud/android/syncadapter/FileSyncAdapter.java

@@ -20,7 +20,10 @@ package com.owncloud.android.syncadapter;
 
 import java.io.IOException;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.jackrabbit.webdav.DavException;
 
@@ -32,7 +35,7 @@ import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UpdateOCVersionOperation;
 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
-
+import com.owncloud.android.ui.activity.ExplanationActivity;
 import android.accounts.Account;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -68,6 +71,8 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
     private SyncResult mSyncResult;
     private int mConflictsFound;
     private int mFailsInFavouritesFound;
+    private Map<String, String> mForgottenLocalFiles;
+
     
     public FileSyncAdapter(Context context, boolean autoInitialize) {
         super(context, autoInitialize);
@@ -87,6 +92,7 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
         mLastFailedResult = null;
         mConflictsFound = 0;
         mFailsInFavouritesFound = 0;
+        mForgottenLocalFiles = new HashMap<String, String>();
         mSyncResult = syncResult;
         mSyncResult.fullSyncRequested = false;
         mSyncResult.delayUntil = 60*60*24; // sync after 24h
@@ -128,9 +134,14 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
                 /// notify the user about the failure of MANUAL synchronization
                 notifyFailedSynchronization();
                 
-            } else if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
+            }
+            if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
                 notifyFailsInFavourites();
             }
+            if (mForgottenLocalFiles.size() > 0) {
+                notifyForgottenLocalFiles();
+                
+            }
             sendStickyBroadcast(false, null, mLastFailedResult);        // message to signal the end to the UI
         }
         
@@ -195,6 +206,9 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
                 mConflictsFound += synchFolderOp.getConflictsFound();
                 mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound();
             }
+            if (synchFolderOp.getForgottenLocalFiles().size() > 0) {
+                mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles());
+            }
             // synchronize children folders 
             List<OCFile> children = synchFolderOp.getChildren();
             fetchChildren(children);    // beware of the 'hidden' recursion here!
@@ -288,7 +302,7 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
 
 
     /**
-     * Notifies the user about conflicts and strange fails when trying to synchronize the contents of favourite files.
+     * Notifies the user about conflicts and strange fails when trying to synchronize the contents of kept-in-sync files.
      * 
      * By now, we won't consider a failed synchronization.
      */
@@ -317,4 +331,42 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
         } 
     }
 
+    
+    /**
+     * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because 
+     * copying them inside the ownCloud local directory was not possible.
+     * 
+     * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have 
+     * synchronization problems if a local file is linked to more than one remote file.
+     * 
+     * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory.
+     */
+    private void notifyForgottenLocalFiles() {
+        Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis());
+        notification.flags |= Notification.FLAG_AUTO_CANCEL;
+
+        /// includes a pending intent in the notification showing a more detailed explanation
+        Intent explanationIntent = new Intent(getContext(), ExplanationActivity.class);
+        String message = String.format(getContext().getString(R.string.sync_foreign_files_forgotten_explanation), getContext().getString(R.string.app_name), getAccount().name);
+        explanationIntent.putExtra(ExplanationActivity.MESSAGE, message);
+        ArrayList<String> remotePaths = new ArrayList<String>();
+        ArrayList<String> localPaths = new ArrayList<String>();
+        for (String remote : mForgottenLocalFiles.keySet()) {
+           remotePaths.add(getContext().getString(R.string.sync_foreign_files_forgotten_remote_prefix) + remote);
+           localPaths.add(getContext().getString(R.string.sync_foreign_files_forgotten_local_prefix) + mForgottenLocalFiles.get(remote));
+        }
+        explanationIntent.putExtra(ExplanationActivity.EXTRA_LIST, localPaths);
+        explanationIntent.putExtra(ExplanationActivity.EXTRA_LIST_2, remotePaths);  
+        explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        
+        notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0);
+        notification.setLatestEventInfo(getContext().getApplicationContext(), 
+                                        getContext().getString(R.string.sync_foreign_files_forgotten_ticker), 
+                                        String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size()), 
+                                        notification.contentIntent);
+        ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification);
+        
+    }
+    
+    
 }

+ 94 - 0
src/com/owncloud/android/ui/activity/ExplanationActivity.java

@@ -0,0 +1,94 @@
+package com.owncloud.android.ui.activity;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+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 of scrollable lists of texts.
+ * 
+ * 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 ExplanationActivity  extends SherlockFragmentActivity {
+
+    public static final String EXTRA_LIST = ExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST";
+    public static final String EXTRA_LIST_2 = ExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST_2";
+    public static final String MESSAGE = ExplanationActivity.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.explanation);
+        
+        if (message != null) {
+            TextView textView = (TextView) findViewById(R.id.message);
+            textView.setText(message);
+        }
+        
+        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);
+            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;
+        }
+    }
+
+}