1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2013 ownCloud Inc.
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.
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.
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/>.
20 package com
.owncloud
.android
.datamodel
;
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
;
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
;
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
;
46 public class FileDataStorageManager
implements DataStorageManager
{
48 private ContentResolver mContentResolver
;
49 private ContentProviderClient mContentProvider
;
50 private Account mAccount
;
52 private static String TAG
= "FileDataStorageManager";
54 public FileDataStorageManager(Account account
, ContentResolver cr
) {
55 mContentProvider
= null
;
56 mContentResolver
= cr
;
60 public FileDataStorageManager(Account account
, ContentProviderClient cp
) {
61 mContentProvider
= cp
;
62 mContentResolver
= null
;
67 public OCFile
getFileByPath(String path
) {
68 Cursor c
= getCursorForValue(ProviderTableMeta
.FILE_PATH
, path
);
70 if (c
.moveToFirst()) {
71 file
= createFileInstance(c
);
74 if (file
== null
&& OCFile
.PATH_SEPARATOR
.equals(path
)) {
75 return createRootDir(); // root should always exist
81 private OCFile
createRootDir() {
82 OCFile file
= new OCFile(OCFile
.PATH_SEPARATOR
);
83 file
.setMimetype("DIR");
84 file
.setParentId(DataStorageManager
.ROOT_PARENT_ID
);
90 public OCFile
getFileById(long id
) {
91 Cursor c
= getCursorForValue(ProviderTableMeta
._ID
, String
.valueOf(id
));
93 if (c
.moveToFirst()) {
94 file
= createFileInstance(c
);
100 public OCFile
getFileByLocalPath(String path
) {
101 Cursor c
= getCursorForValue(ProviderTableMeta
.FILE_STORAGE_PATH
, path
);
103 if (c
.moveToFirst()) {
104 file
= createFileInstance(c
);
111 public boolean fileExists(long id
) {
112 return fileExists(ProviderTableMeta
._ID
, String
.valueOf(id
));
116 public boolean fileExists(String path
) {
117 return fileExists(ProviderTableMeta
.FILE_PATH
, path
);
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);
140 boolean sameRemotePath
= fileExists(file
.getRemotePath());
141 if (sameRemotePath
||
142 fileExists(file
.getFileId()) ) { // for renamed files; no more delete and create
144 if (sameRemotePath
) {
145 OCFile oldFile
= getFileByPath(file
.getRemotePath());
146 file
.setFileId(oldFile
.getFileId());
150 if (getContentResolver() != null
) {
151 getContentResolver().update(ProviderTableMeta
.CONTENT_URI
, cv
,
152 ProviderTableMeta
._ID
+ "=?",
153 new String
[] { String
.valueOf(file
.getFileId()) });
156 getContentProvider().update(ProviderTableMeta
.CONTENT_URI
,
157 cv
, ProviderTableMeta
._ID
+ "=?",
158 new String
[] { String
.valueOf(file
.getFileId()) });
159 } catch (RemoteException e
) {
161 "Fail to insert insert file to database "
166 Uri result_uri
= null
;
167 if (getContentResolver() != null
) {
168 result_uri
= getContentResolver().insert(
169 ProviderTableMeta
.CONTENT_URI_FILE
, cv
);
172 result_uri
= getContentProvider().insert(
173 ProviderTableMeta
.CONTENT_URI_FILE
, cv
);
174 } catch (RemoteException e
) {
176 "Fail to insert insert file to database "
180 if (result_uri
!= null
) {
181 long new_id
= Long
.parseLong(result_uri
.getPathSegments()
183 file
.setFileId(new_id
);
187 if (file
.isDirectory() && file
.needsUpdatingWhileSaving())
188 for (OCFile f
: getDirectoryContent(file
))
196 public void saveFiles(List
<OCFile
> files
) {
198 Iterator
<OCFile
> filesIt
= files
.iterator();
199 ArrayList
<ContentProviderOperation
> operations
= new ArrayList
<ContentProviderOperation
>(files
.size());
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);
222 if (fileExists(file
.getRemotePath())) {
223 OCFile oldFile
= getFileByPath(file
.getRemotePath());
224 file
.setFileId(oldFile
.getFileId());
225 operations
.add(ContentProviderOperation
.newUpdate(ProviderTableMeta
.CONTENT_URI
).
227 withSelection( ProviderTableMeta
._ID
+ "=?",
228 new String
[] { String
.valueOf(file
.getFileId()) })
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());
238 operations
.add(ContentProviderOperation
.newUpdate(ProviderTableMeta
.CONTENT_URI
).
240 withSelection( ProviderTableMeta
._ID
+ "=?",
241 new String
[] { String
.valueOf(file
.getFileId()) })
245 operations
.add(ContentProviderOperation
.newInsert(ProviderTableMeta
.CONTENT_URI
).withValues(cv
).build());
249 // apply operations in batch
250 ContentProviderResult
[] results
= null
;
252 if (getContentResolver() != null
) {
253 results
= getContentResolver().applyBatch(ProviderMeta
.AUTHORITY_FILES
, operations
);
256 results
= getContentProvider().applyBatch(operations
);
259 } catch (OperationApplicationException e
) {
260 Log_OC
.e(TAG
, "Fail to update/insert list of files to database " + e
.getMessage());
262 } catch (RemoteException e
) {
263 Log_OC
.e(TAG
, "Fail to update/insert list of files to database " + e
.getMessage());
266 // update new id in file objects for insertions
267 if (results
!= null
) {
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());
278 for (OCFile aFile
: files
) {
279 if (aFile
.isDirectory() && aFile
.needsUpdatingWhileSaving())
280 saveFiles(getDirectoryContent(aFile
));
285 public void setAccount(Account account
) {
289 public Account
getAccount() {
293 public void setContentResolver(ContentResolver cr
) {
294 mContentResolver
= cr
;
297 public ContentResolver
getContentResolver() {
298 return mContentResolver
;
301 public void setContentProvider(ContentProviderClient cp
) {
302 mContentProvider
= cp
;
305 public ContentProviderClient
getContentProvider() {
306 return mContentProvider
;
310 public Vector
<OCFile
> getDirectoryContent(OCFile f
) {
311 Vector
<OCFile
> ret
= new Vector
<OCFile
>();
312 if (f
!= null
&& f
.isDirectory() && f
.getFileId() != -1) {
314 Uri req_uri
= Uri
.withAppendedPath(
315 ProviderTableMeta
.CONTENT_URI_DIR
,
316 String
.valueOf(f
.getFileId()));
319 if (getContentProvider() != null
) {
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());
329 c
= getContentResolver().query(req_uri
, null
,
330 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=?",
331 new String
[] { mAccount
.name
}, null
);
334 if (c
.moveToFirst()) {
336 OCFile child
= createFileInstance(c
);
338 } while (c
.moveToNext());
343 Collections
.sort(ret
);
349 private boolean fileExists(String cmp_key
, String value
) {
351 if (getContentResolver() != null
) {
352 c
= getContentResolver()
353 .query(ProviderTableMeta
.CONTENT_URI
,
356 + ProviderTableMeta
.FILE_ACCOUNT_OWNER
358 new String
[] { value
, mAccount
.name
}, null
);
361 c
= getContentProvider().query(
362 ProviderTableMeta
.CONTENT_URI
,
365 + ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=?",
366 new String
[] { value
, mAccount
.name
}, null
);
367 } catch (RemoteException e
) {
369 "Couldn't determine file existance, assuming non existance: "
374 boolean retval
= c
.moveToFirst();
379 private Cursor
getCursorForValue(String key
, String value
) {
381 if (getContentResolver() != null
) {
382 c
= getContentResolver()
383 .query(ProviderTableMeta
.CONTENT_URI
,
386 + ProviderTableMeta
.FILE_ACCOUNT_OWNER
388 new String
[] { value
, mAccount
.name
}, null
);
391 c
= getContentProvider().query(
392 ProviderTableMeta
.CONTENT_URI
,
394 key
+ "=? AND " + ProviderTableMeta
.FILE_ACCOUNT_OWNER
395 + "=?", new String
[] { value
, mAccount
.name
},
397 } catch (RemoteException e
) {
398 Log_OC
.e(TAG
, "Could not get file details: " + e
.getMessage());
405 private OCFile
createFileInstance(Cursor c
) {
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
));
422 file
.setStoragePath(f
.getAbsolutePath());
423 file
.setLastSyncDateForData(f
.lastModified());
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
);
446 public void removeFile(OCFile file
, boolean removeLocalCopy
) {
447 Uri file_uri
= Uri
.withAppendedPath(ProviderTableMeta
.CONTENT_URI_FILE
, ""+file
.getFileId());
448 if (getContentProvider() != null
) {
450 getContentProvider().delete(file_uri
,
451 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+"=?",
452 new String
[]{mAccount
.name
});
453 } catch (RemoteException e
) {
457 getContentResolver().delete(file_uri
,
458 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+"=?",
459 new String
[]{mAccount
.name
});
461 if (file
.isDown() && removeLocalCopy
) {
462 new File(file
.getStoragePath()).delete();
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)) {
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) {
479 for (int i
=0; i
<children
.size(); i
++) {
480 child
= children
.get(i
);
481 if (child
.isDirectory()) {
482 removeDirectory(child
, removeDBData
, removeLocalContent
);
485 removeFile(child
, removeLocalContent
);
486 } else if (removeLocalContent
) {
487 if (child
.isDown()) {
488 new File(child
.getStoragePath()).delete();
495 removeFile(dir
, true
);
502 * Updates database for a folder that was moved to a different location.
504 * TODO explore better (faster) implementations
505 * TODO throw exceptions up !
508 public void moveDirectory(OCFile dir
, String newPath
) {
509 // TODO check newPath
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')
514 if (getContentProvider() != null
) {
516 c
= getContentProvider().query(ProviderTableMeta
.CONTENT_URI
,
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());
524 c
= getContentResolver().query(ProviderTableMeta
.CONTENT_URI
,
526 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=? AND " + ProviderTableMeta
.FILE_PATH
+ " LIKE ?",
527 new String
[] { mAccount
.name
, dir
.getRemotePath() + "%" }, null
);
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()) {
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
));
543 operations
.add(ContentProviderOperation
.newUpdate(ProviderTableMeta
.CONTENT_URI
).
545 withSelection( ProviderTableMeta
._ID
+ "=?",
546 new String
[] { String
.valueOf(child
.getFileId()) })
548 } while (c
.moveToNext());
552 /// 3. apply updates in batch
554 if (getContentResolver() != null
) {
555 getContentResolver().applyBatch(ProviderMeta
.AUTHORITY_FILES
, operations
);
558 getContentProvider().applyBatch(operations
);
561 } catch (OperationApplicationException e
) {
562 Log_OC
.e(TAG
, "Fail to update descendants of " + dir
.getFileId() + " in database", e
);
564 } catch (RemoteException e
) {
565 Log_OC
.e(TAG
, "Fail to update desendants of " + dir
.getFileId() + " in database", e
);