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