Removal of a folder (or file) not already existing in the server is considered a...
[pub/Android/ownCloud.git] / src / com / owncloud / android / datamodel / FileDataStorageManager.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.datamodel;
20
21 import java.io.File;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Vector;
27
28 import com.owncloud.android.db.ProviderMeta;
29 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
30 import com.owncloud.android.files.services.FileDownloader;
31
32 import android.accounts.Account;
33 import android.content.ContentProviderClient;
34 import android.content.ContentProviderOperation;
35 import android.content.ContentProviderResult;
36 import android.content.ContentResolver;
37 import android.content.ContentValues;
38 import android.content.OperationApplicationException;
39 import android.database.Cursor;
40 import android.net.Uri;
41 import android.os.RemoteException;
42 import android.util.Log;
43
44 public class FileDataStorageManager implements DataStorageManager {
45
46 private ContentResolver mContentResolver;
47 private ContentProviderClient mContentProvider;
48 private Account mAccount;
49
50 private static String TAG = "FileDataStorageManager";
51
52 public FileDataStorageManager(Account account, ContentResolver cr) {
53 mContentProvider = null;
54 mContentResolver = cr;
55 mAccount = account;
56 }
57
58 public FileDataStorageManager(Account account, ContentProviderClient cp) {
59 mContentProvider = cp;
60 mContentResolver = null;
61 mAccount = account;
62 }
63
64 @Override
65 public OCFile getFileByPath(String path) {
66 Cursor c = getCursorForValue(ProviderTableMeta.FILE_PATH, path);
67 OCFile file = null;
68 if (c.moveToFirst()) {
69 file = createFileInstance(c);
70 }
71 c.close();
72 return file;
73 }
74
75 @Override
76 public OCFile getFileById(long id) {
77 Cursor c = getCursorForValue(ProviderTableMeta._ID, String.valueOf(id));
78 OCFile file = null;
79 if (c.moveToFirst()) {
80 file = createFileInstance(c);
81 }
82 c.close();
83 return file;
84 }
85
86 public OCFile getFileByLocalPath(String path) {
87 Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path);
88 OCFile file = null;
89 if (c.moveToFirst()) {
90 file = createFileInstance(c);
91 }
92 c.close();
93 return file;
94 }
95
96 @Override
97 public boolean fileExists(long id) {
98 return fileExists(ProviderTableMeta._ID, String.valueOf(id));
99 }
100
101 @Override
102 public boolean fileExists(String path) {
103 return fileExists(ProviderTableMeta.FILE_PATH, path);
104 }
105
106 @Override
107 public boolean saveFile(OCFile file) {
108 boolean overriden = false;
109 ContentValues cv = new ContentValues();
110 cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
111 cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp());
112 cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
113 cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
114 cv.put(ProviderTableMeta.FILE_NAME, file.getFileName());
115 if (file.getParentId() != 0)
116 cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
117 cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
118 if (!file.isDirectory())
119 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
120 cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
121 cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDate());
122 cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0);
123
124 if (fileExists(file.getRemotePath())) {
125 OCFile oldFile = getFileByPath(file.getRemotePath());
126 if (file.getStoragePath() == null && oldFile.getStoragePath() != null)
127 file.setStoragePath(oldFile.getStoragePath());
128 if (!file.isDirectory());
129 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
130 file.setFileId(oldFile.getFileId());
131
132 overriden = true;
133 if (getContentResolver() != null) {
134 getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv,
135 ProviderTableMeta._ID + "=?",
136 new String[] { String.valueOf(file.getFileId()) });
137 } else {
138 try {
139 getContentProvider().update(ProviderTableMeta.CONTENT_URI,
140 cv, ProviderTableMeta._ID + "=?",
141 new String[] { String.valueOf(file.getFileId()) });
142 } catch (RemoteException e) {
143 Log.e(TAG,
144 "Fail to insert insert file to database "
145 + e.getMessage());
146 }
147 }
148 } else if (fileExists(file.getFileId())) { // for renamed files; no more delete and create
149 OCFile oldFile = getFileById(file.getFileId());
150 if (file.getStoragePath() == null && oldFile.getStoragePath() != null)
151 file.setStoragePath(oldFile.getStoragePath());
152 if (!file.isDirectory());
153 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
154
155 overriden = true;
156 if (getContentResolver() != null) {
157 getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv,
158 ProviderTableMeta._ID + "=?",
159 new String[] { String.valueOf(file.getFileId()) });
160 } else {
161 try {
162 getContentProvider().update(ProviderTableMeta.CONTENT_URI,
163 cv, ProviderTableMeta._ID + "=?",
164 new String[] { String.valueOf(file.getFileId()) });
165 } catch (RemoteException e) {
166 Log.e(TAG,
167 "Fail to insert insert file to database "
168 + e.getMessage());
169 }
170 }
171 } else {
172 Uri result_uri = null;
173 if (getContentResolver() != null) {
174 result_uri = getContentResolver().insert(
175 ProviderTableMeta.CONTENT_URI_FILE, cv);
176 } else {
177 try {
178 result_uri = getContentProvider().insert(
179 ProviderTableMeta.CONTENT_URI_FILE, cv);
180 } catch (RemoteException e) {
181 Log.e(TAG,
182 "Fail to insert insert file to database "
183 + e.getMessage());
184 }
185 }
186 if (result_uri != null) {
187 long new_id = Long.parseLong(result_uri.getPathSegments()
188 .get(1));
189 file.setFileId(new_id);
190 }
191 }
192
193 if (file.isDirectory() && file.needsUpdatingWhileSaving())
194 for (OCFile f : getDirectoryContent(file))
195 saveFile(f);
196
197 return overriden;
198 }
199
200
201 @Override
202 public void saveFiles(List<OCFile> files) {
203
204 Iterator<OCFile> filesIt = files.iterator();
205 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(files.size());
206 OCFile file = null;
207
208 // prepare operations to perform
209 while (filesIt.hasNext()) {
210 file = filesIt.next();
211 ContentValues cv = new ContentValues();
212 cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
213 cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp());
214 cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
215 cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
216 cv.put(ProviderTableMeta.FILE_NAME, file.getFileName());
217 if (file.getParentId() != 0)
218 cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
219 cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
220 if (!file.isDirectory())
221 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
222 cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
223 cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDate());
224 cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0);
225
226 if (fileExists(file.getRemotePath())) {
227 OCFile oldFile = getFileByPath(file.getRemotePath());
228 if (file.getStoragePath() == null && oldFile.getStoragePath() != null)
229 file.setStoragePath(oldFile.getStoragePath());
230 if (!file.isDirectory());
231 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
232 file.setFileId(oldFile.getFileId());
233
234 operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
235 withValues(cv).
236 withSelection( ProviderTableMeta._ID + "=?",
237 new String[] { String.valueOf(file.getFileId()) })
238 .build());
239
240 } else if (fileExists(file.getFileId())) {
241 OCFile oldFile = getFileById(file.getFileId());
242 if (file.getStoragePath() == null && oldFile.getStoragePath() != null)
243 file.setStoragePath(oldFile.getStoragePath());
244 if (!file.isDirectory());
245 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
246
247 operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
248 withValues(cv).
249 withSelection( ProviderTableMeta._ID + "=?",
250 new String[] { String.valueOf(file.getFileId()) })
251 .build());
252
253 } else {
254 operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build());
255 }
256 }
257
258 // apply operations in batch
259 ContentProviderResult[] results = null;
260 try {
261 if (getContentResolver() != null) {
262 results = getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations);
263
264 } else {
265 results = getContentProvider().applyBatch(operations);
266 }
267
268 } catch (OperationApplicationException e) {
269 Log.e(TAG, "Fail to update/insert list of files to database " + e.getMessage());
270
271 } catch (RemoteException e) {
272 Log.e(TAG, "Fail to update/insert list of files to database " + e.getMessage());
273 }
274
275 // update new id in file objects for insertions
276 if (results != null) {
277 long newId;
278 for (int i=0; i<results.length; i++) {
279 if (results[i].uri != null) {
280 newId = Long.parseLong(results[i].uri.getPathSegments().get(1));
281 files.get(i).setFileId(newId);
282 //Log.v(TAG, "Found and added id in insertion for " + files.get(i).getRemotePath());
283 }
284 }
285 }
286
287 for (OCFile aFile : files) {
288 if (aFile.isDirectory() && aFile.needsUpdatingWhileSaving())
289 saveFiles(getDirectoryContent(aFile));
290 }
291
292 }
293
294 public void setAccount(Account account) {
295 mAccount = account;
296 }
297
298 public Account getAccount() {
299 return mAccount;
300 }
301
302 public void setContentResolver(ContentResolver cr) {
303 mContentResolver = cr;
304 }
305
306 public ContentResolver getContentResolver() {
307 return mContentResolver;
308 }
309
310 public void setContentProvider(ContentProviderClient cp) {
311 mContentProvider = cp;
312 }
313
314 public ContentProviderClient getContentProvider() {
315 return mContentProvider;
316 }
317
318 @Override
319 public Vector<OCFile> getDirectoryContent(OCFile f) {
320 if (f != null && f.isDirectory() && f.getFileId() != -1) {
321 Vector<OCFile> ret = new Vector<OCFile>();
322
323 Uri req_uri = Uri.withAppendedPath(
324 ProviderTableMeta.CONTENT_URI_DIR,
325 String.valueOf(f.getFileId()));
326 Cursor c = null;
327
328 if (getContentProvider() != null) {
329 try {
330 c = getContentProvider().query(req_uri, null,
331 ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",
332 new String[] { mAccount.name }, null);
333 } catch (RemoteException e) {
334 Log.e(TAG, e.getMessage());
335 return ret;
336 }
337 } else {
338 c = getContentResolver().query(req_uri, null,
339 ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",
340 new String[] { mAccount.name }, null);
341 }
342
343 if (c.moveToFirst()) {
344 do {
345 OCFile child = createFileInstance(c);
346 ret.add(child);
347 } while (c.moveToNext());
348 }
349
350 c.close();
351
352 Collections.sort(ret);
353
354 return ret;
355 }
356 return null;
357 }
358
359 private boolean fileExists(String cmp_key, String value) {
360 Cursor c;
361 if (getContentResolver() != null) {
362 c = getContentResolver()
363 .query(ProviderTableMeta.CONTENT_URI,
364 null,
365 cmp_key + "=? AND "
366 + ProviderTableMeta.FILE_ACCOUNT_OWNER
367 + "=?",
368 new String[] { value, mAccount.name }, null);
369 } else {
370 try {
371 c = getContentProvider().query(
372 ProviderTableMeta.CONTENT_URI,
373 null,
374 cmp_key + "=? AND "
375 + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",
376 new String[] { value, mAccount.name }, null);
377 } catch (RemoteException e) {
378 Log.e(TAG,
379 "Couldn't determine file existance, assuming non existance: "
380 + e.getMessage());
381 return false;
382 }
383 }
384 boolean retval = c.moveToFirst();
385 c.close();
386 return retval;
387 }
388
389 private Cursor getCursorForValue(String key, String value) {
390 Cursor c = null;
391 if (getContentResolver() != null) {
392 c = getContentResolver()
393 .query(ProviderTableMeta.CONTENT_URI,
394 null,
395 key + "=? AND "
396 + ProviderTableMeta.FILE_ACCOUNT_OWNER
397 + "=?",
398 new String[] { value, mAccount.name }, null);
399 } else {
400 try {
401 c = getContentProvider().query(
402 ProviderTableMeta.CONTENT_URI,
403 null,
404 key + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER
405 + "=?", new String[] { value, mAccount.name },
406 null);
407 } catch (RemoteException e) {
408 Log.e(TAG, "Could not get file details: " + e.getMessage());
409 c = null;
410 }
411 }
412 return c;
413 }
414
415 private OCFile createFileInstance(Cursor c) {
416 OCFile file = null;
417 if (c != null) {
418 file = new OCFile(c.getString(c
419 .getColumnIndex(ProviderTableMeta.FILE_PATH)));
420 file.setFileId(c.getLong(c.getColumnIndex(ProviderTableMeta._ID)));
421 file.setParentId(c.getLong(c
422 .getColumnIndex(ProviderTableMeta.FILE_PARENT)));
423 file.setMimetype(c.getString(c
424 .getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)));
425 if (!file.isDirectory()) {
426 file.setStoragePath(c.getString(c
427 .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)));
428 if (file.getStoragePath() == null) {
429 // try to find existing file and bind it with current account
430 File f = new File(FileDownloader.getSavePath(mAccount.name) + file.getRemotePath());
431 if (f.exists())
432 file.setStoragePath(f.getAbsolutePath());
433 }
434 }
435 file.setFileLength(c.getLong(c
436 .getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH)));
437 file.setCreationTimestamp(c.getLong(c
438 .getColumnIndex(ProviderTableMeta.FILE_CREATION)));
439 file.setModificationTimestamp(c.getLong(c
440 .getColumnIndex(ProviderTableMeta.FILE_MODIFIED)));
441 file.setLastSyncDate(c.getLong(c
442 .getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE)));
443 file.setKeepInSync(c.getInt(
444 c.getColumnIndex(ProviderTableMeta.FILE_KEEP_IN_SYNC)) == 1 ? true : false);
445 }
446 return file;
447 }
448
449 @Override
450 public void removeFile(OCFile file, boolean removeLocalCopy) {
451 Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId());
452 if (getContentProvider() != null) {
453 try {
454 getContentProvider().delete(file_uri,
455 ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?",
456 new String[]{mAccount.name});
457 } catch (RemoteException e) {
458 e.printStackTrace();
459 }
460 } else {
461 getContentResolver().delete(file_uri,
462 ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?",
463 new String[]{mAccount.name});
464 }
465 if (file.isDown() && removeLocalCopy) {
466 new File(file.getStoragePath()).delete();
467 }
468 if (file.isDirectory() && removeLocalCopy) {
469 File f = new File(FileDownloader.getSavePath(mAccount.name) + file.getRemotePath());
470 if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) {
471 f.delete();
472 }
473 }
474 }
475
476 @Override
477 public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent) {
478 // TODO consider possible failures
479 if (dir != null && dir.isDirectory() && dir.getFileId() != -1) {
480 Vector<OCFile> children = getDirectoryContent(dir);
481 if (children != null) {
482 OCFile child = null;
483 for (int i=0; i<children.size(); i++) {
484 child = children.get(i);
485 if (child.isDirectory()) {
486 removeDirectory(child, removeDBData, removeLocalContent);
487 } else {
488 if (removeDBData) {
489 removeFile(child, removeLocalContent);
490 } else if (removeLocalContent) {
491 if (child.isDown()) {
492 new File(child.getStoragePath()).delete();
493 }
494 }
495 }
496 }
497 if (removeDBData) {
498 removeFile(dir, true);
499 }
500 }
501 }
502 }
503
504
505 /**
506 * Updates database for a folder that was moved to a different location.
507 *
508 * TODO explore better (faster) implementations
509 * TODO throw exceptions up !
510 */
511 @Override
512 public void moveDirectory(OCFile dir, String newPath) {
513 // TODO check newPath
514
515 if (dir != null && dir.isDirectory() && dir.fileExists() && !dir.getFileName().equals(OCFile.PATH_SEPARATOR)) {
516 /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir')
517 Cursor c = null;
518 if (getContentProvider() != null) {
519 try {
520 c = getContentProvider().query(ProviderTableMeta.CONTENT_URI,
521 null,
522 ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ?",
523 new String[] { mAccount.name, dir.getRemotePath() + "%" }, null);
524 } catch (RemoteException e) {
525 Log.e(TAG, e.getMessage());
526 }
527 } else {
528 c = getContentResolver().query(ProviderTableMeta.CONTENT_URI,
529 null,
530 ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ?",
531 new String[] { mAccount.name, dir.getRemotePath() + "%" }, null);
532 }
533
534 /// 2. prepare a batch of update operations to change all the descendants
535 ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(c.getCount());
536 int lengthOfOldPath = dir.getRemotePath().length();
537 String defaultSavePath = FileDownloader.getSavePath(mAccount.name);
538 int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath;
539 if (c.moveToFirst()) {
540 do {
541 ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object
542 OCFile child = createFileInstance(c);
543 cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath));
544 if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) {
545 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath));
546 }
547 operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
548 withValues(cv).
549 withSelection( ProviderTableMeta._ID + "=?",
550 new String[] { String.valueOf(child.getFileId()) })
551 .build());
552 } while (c.moveToNext());
553 }
554 c.close();
555
556 /// 3. apply updates in batch
557 try {
558 if (getContentResolver() != null) {
559 getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations);
560
561 } else {
562 getContentProvider().applyBatch(operations);
563 }
564
565 } catch (OperationApplicationException e) {
566 Log.e(TAG, "Fail to update descendants of " + dir.getFileId() + " in database", e);
567
568 } catch (RemoteException e) {
569 Log.e(TAG, "Fail to update desendants of " + dir.getFileId() + " in database", e);
570 }
571
572 }
573 }
574
575 }