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