Merge pull request #1048 from owncloud/shareWithYou_icon_in_fileList
[pub/Android/ownCloud.git] / src / com / owncloud / android / services / observer / FolderObserver.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author David A. Velasco
5 * Copyright (C) 2015 ownCloud Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 package com.owncloud.android.services.observer;
22
23 import java.io.File;
24 import java.util.HashMap;
25 import java.util.Map;
26
27 import android.accounts.Account;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.os.FileObserver;
31
32 import com.owncloud.android.datamodel.FileDataStorageManager;
33 import com.owncloud.android.datamodel.OCFile;
34 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
35 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
36 import com.owncloud.android.lib.common.utils.Log_OC;
37 import com.owncloud.android.operations.SynchronizeFileOperation;
38 import com.owncloud.android.ui.activity.ConflictsResolveActivity;
39
40 /**
41 * Observer watching a folder to request the synchronization of kept-in-sync files
42 * inside it.
43 *
44 * Takes into account two possible update cases:
45 * - an editor directly updates the file;
46 * - an editor works on a temporary file, and later replaces the kept-in-sync file with the
47 * former.
48 *
49 * The second case requires to monitor the folder parent of the files, since a direct
50 * {@link FileObserver} on it will not receive more events after the file is deleted to
51 * be replaced.
52 */
53 public class FolderObserver extends FileObserver {
54
55 private static String TAG = FolderObserver.class.getSimpleName();
56
57 private static int UPDATE_MASK = (
58 FileObserver.ATTRIB | FileObserver.MODIFY |
59 FileObserver.MOVED_TO | FileObserver.CLOSE_WRITE
60 );
61
62 private static int IN_IGNORE = 32768;
63 /*
64 private static int ALL_EVENTS_EVEN_THOSE_NOT_DOCUMENTED = 0x7fffffff; // NEVER use 0xffffffff
65 */
66
67 private String mPath;
68 private Account mAccount;
69 private Context mContext;
70 private Map<String, Boolean> mObservedChildren;
71
72 /**
73 * Constructor.
74 *
75 * Initializes the observer to receive events about the update of the passed folder, and
76 * its children files.
77 *
78 * @param path Absolute path to the local folder to watch.
79 * @param account OwnCloud account associated to the folder.
80 * @param context Used to start an operation to synchronize the file, when needed.
81 */
82 public FolderObserver(String path, Account account, Context context) {
83 super(path, UPDATE_MASK);
84
85 if (path == null)
86 throw new IllegalArgumentException("NULL path argument received");
87 if (account == null)
88 throw new IllegalArgumentException("NULL account argument received");
89 if (context == null)
90 throw new IllegalArgumentException("NULL context argument received");
91
92 mPath = path;
93 mAccount = account;
94 mContext = context;
95 mObservedChildren = new HashMap<String, Boolean>();
96 }
97
98
99 /**
100 * Receives and processes events about updates of the monitor folder and its children files.
101 *
102 * @param event Kind of event occurred.
103 * @param path Relative path of the file referred by the event.
104 */
105 @Override
106 public void onEvent(int event, String path) {
107 Log_OC.d(TAG, "Got event " + event + " on FOLDER " + mPath + " about "
108 + ((path != null) ? path : ""));
109
110 boolean shouldSynchronize = false;
111 synchronized(mObservedChildren) {
112 if (path != null && path.length() > 0 && mObservedChildren.containsKey(path)) {
113
114 if ( ((event & FileObserver.MODIFY) != 0) ||
115 ((event & FileObserver.ATTRIB) != 0) ||
116 ((event & FileObserver.MOVED_TO) != 0) ) {
117
118 if (mObservedChildren.get(path) != true) {
119 mObservedChildren.put(path, Boolean.valueOf(true));
120 }
121 }
122
123 if ((event & FileObserver.CLOSE_WRITE) != 0 && mObservedChildren.get(path)) {
124 mObservedChildren.put(path, Boolean.valueOf(false));
125 shouldSynchronize = true;
126 }
127 }
128 }
129 if (shouldSynchronize) {
130 startSyncOperation(path);
131 }
132
133 if ((event & IN_IGNORE) != 0 &&
134 (path == null || path.length() == 0)) {
135 Log_OC.d(TAG, "Stopping the observance on " + mPath);
136 }
137
138 }
139
140
141 /**
142 * Adds a child file to the list of files observed by the folder observer.
143 *
144 * @param fileName Name of a file inside the observed folder.
145 */
146 public void startWatching(String fileName) {
147 synchronized (mObservedChildren) {
148 if (!mObservedChildren.containsKey(fileName)) {
149 mObservedChildren.put(fileName, Boolean.valueOf(false));
150 }
151 }
152
153 if (new File(mPath).exists()) {
154 startWatching();
155 Log_OC.d(TAG, "Started watching parent folder " + mPath + "/");
156 }
157 // else - the observance can't be started on a file not existing;
158 }
159
160
161 /**
162 * Removes a child file from the list of files observed by the folder observer.
163 *
164 * @param fileName Name of a file inside the observed folder.
165 */
166 public void stopWatching(String fileName) {
167 synchronized (mObservedChildren) {
168 mObservedChildren.remove(fileName);
169 if (mObservedChildren.isEmpty()) {
170 stopWatching();
171 Log_OC.d(TAG, "Stopped watching parent folder " + mPath + "/");
172 }
173 }
174 }
175
176 /**
177 * @return 'True' when the folder is not watching any file inside.
178 */
179 public boolean isEmpty() {
180 synchronized (mObservedChildren) {
181 return mObservedChildren.isEmpty();
182 }
183 }
184
185
186 /**
187 * Triggers an operation to synchronize the contents of a file inside the observed folder with
188 * its remote counterpart in the associated ownCloud account.
189 *
190 * @param fileName Name of a file inside the watched folder.
191 */
192 private void startSyncOperation(String fileName) {
193 FileDataStorageManager storageManager =
194 new FileDataStorageManager(mAccount, mContext.getContentResolver());
195 // a fresh object is needed; many things could have occurred to the file
196 // since it was registered to observe again, assuming that local files
197 // are linked to a remote file AT MOST, SOMETHING TO BE DONE;
198 OCFile file = storageManager.getFileByLocalPath(mPath + File.separator + fileName);
199 SynchronizeFileOperation sfo =
200 new SynchronizeFileOperation(file, null, mAccount, true, mContext);
201 RemoteOperationResult result = sfo.execute(storageManager, mContext);
202 if (result.getCode() == ResultCode.SYNC_CONFLICT) {
203 // ISSUE 5: if the user is not running the app (this is a service!),
204 // this can be very intrusive; a notification should be preferred
205 Intent i = new Intent(mContext, ConflictsResolveActivity.class);
206 i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
207 i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file);
208 i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount);
209 mContext.startActivity(i);
210 }
211 // TODO save other errors in some point where the user can inspect them later;
212 // or maybe just toast them;
213 // or nothing, very strange fails
214 }
215
216 }