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