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