1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
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.
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.
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/>.
19 package com
.owncloud
.android
.datamodel
;
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
;
28 import com
.owncloud
.android
.db
.ProviderMeta
;
29 import com
.owncloud
.android
.db
.ProviderMeta
.ProviderTableMeta
;
30 import com
.owncloud
.android
.files
.services
.FileDownloader
;
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
;
44 public class FileDataStorageManager
implements DataStorageManager
{
46 private ContentResolver mContentResolver
;
47 private ContentProviderClient mContentProvider
;
48 private Account mAccount
;
50 private static String TAG
= "FileDataStorageManager";
52 public FileDataStorageManager(Account account
, ContentResolver cr
) {
53 mContentProvider
= null
;
54 mContentResolver
= cr
;
58 public FileDataStorageManager(Account account
, ContentProviderClient cp
) {
59 mContentProvider
= cp
;
60 mContentResolver
= null
;
65 public OCFile
getFileByPath(String path
) {
66 Cursor c
= getCursorForValue(ProviderTableMeta
.FILE_PATH
, path
);
68 if (c
.moveToFirst()) {
69 file
= createFileInstance(c
);
76 public OCFile
getFileById(long id
) {
77 Cursor c
= getCursorForValue(ProviderTableMeta
._ID
, String
.valueOf(id
));
79 if (c
.moveToFirst()) {
80 file
= createFileInstance(c
);
86 public OCFile
getFileByLocalPath(String path
) {
87 Cursor c
= getCursorForValue(ProviderTableMeta
.FILE_STORAGE_PATH
, path
);
89 if (c
.moveToFirst()) {
90 file
= createFileInstance(c
);
97 public boolean fileExists(long id
) {
98 return fileExists(ProviderTableMeta
._ID
, String
.valueOf(id
));
102 public boolean fileExists(String path
) {
103 return fileExists(ProviderTableMeta
.FILE_PATH
, path
);
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_CREATION
, file
.getCreationTimestamp());
112 cv
.put(ProviderTableMeta
.FILE_CONTENT_LENGTH
, file
.getFileLength());
113 cv
.put(ProviderTableMeta
.FILE_CONTENT_TYPE
, file
.getMimetype());
114 cv
.put(ProviderTableMeta
.FILE_NAME
, file
.getFileName());
115 if (file
.getParentId() != 0)
116 cv
.put(ProviderTableMeta
.FILE_PARENT
, file
.getParentId());
117 cv
.put(ProviderTableMeta
.FILE_PATH
, file
.getRemotePath());
118 if (!file
.isDirectory())
119 cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, file
.getStoragePath());
120 cv
.put(ProviderTableMeta
.FILE_ACCOUNT_OWNER
, mAccount
.name
);
121 cv
.put(ProviderTableMeta
.FILE_LAST_SYNC_DATE
, file
.getLastSyncDate());
122 cv
.put(ProviderTableMeta
.FILE_KEEP_IN_SYNC
, file
.keepInSync() ?
1 : 0);
124 if (fileExists(file
.getRemotePath())) {
125 OCFile oldFile
= getFileByPath(file
.getRemotePath());
126 if (file
.getStoragePath() == null
&& oldFile
.getStoragePath() != null
)
127 file
.setStoragePath(oldFile
.getStoragePath());
128 if (!file
.isDirectory());
129 cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, file
.getStoragePath());
130 file
.setFileId(oldFile
.getFileId());
133 if (getContentResolver() != null
) {
134 getContentResolver().update(ProviderTableMeta
.CONTENT_URI
, cv
,
135 ProviderTableMeta
._ID
+ "=?",
136 new String
[] { String
.valueOf(file
.getFileId()) });
139 getContentProvider().update(ProviderTableMeta
.CONTENT_URI
,
140 cv
, ProviderTableMeta
._ID
+ "=?",
141 new String
[] { String
.valueOf(file
.getFileId()) });
142 } catch (RemoteException e
) {
144 "Fail to insert insert file to database "
148 } else if (fileExists(file
.getFileId())) { // for renamed files; no more delete and create
149 OCFile oldFile
= getFileById(file
.getFileId());
150 if (file
.getStoragePath() == null
&& oldFile
.getStoragePath() != null
)
151 file
.setStoragePath(oldFile
.getStoragePath());
152 if (!file
.isDirectory());
153 cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, file
.getStoragePath());
156 if (getContentResolver() != null
) {
157 getContentResolver().update(ProviderTableMeta
.CONTENT_URI
, cv
,
158 ProviderTableMeta
._ID
+ "=?",
159 new String
[] { String
.valueOf(file
.getFileId()) });
162 getContentProvider().update(ProviderTableMeta
.CONTENT_URI
,
163 cv
, ProviderTableMeta
._ID
+ "=?",
164 new String
[] { String
.valueOf(file
.getFileId()) });
165 } catch (RemoteException e
) {
167 "Fail to insert insert file to database "
172 Uri result_uri
= null
;
173 if (getContentResolver() != null
) {
174 result_uri
= getContentResolver().insert(
175 ProviderTableMeta
.CONTENT_URI_FILE
, cv
);
178 result_uri
= getContentProvider().insert(
179 ProviderTableMeta
.CONTENT_URI_FILE
, cv
);
180 } catch (RemoteException e
) {
182 "Fail to insert insert file to database "
186 if (result_uri
!= null
) {
187 long new_id
= Long
.parseLong(result_uri
.getPathSegments()
189 file
.setFileId(new_id
);
193 if (file
.isDirectory() && file
.needsUpdatingWhileSaving())
194 for (OCFile f
: getDirectoryContent(file
))
202 public void saveFiles(List
<OCFile
> files
) {
204 Iterator
<OCFile
> filesIt
= files
.iterator();
205 ArrayList
<ContentProviderOperation
> operations
= new ArrayList
<ContentProviderOperation
>(files
.size());
208 // prepare operations to perform
209 while (filesIt
.hasNext()) {
210 file
= filesIt
.next();
211 ContentValues cv
= new ContentValues();
212 cv
.put(ProviderTableMeta
.FILE_MODIFIED
, file
.getModificationTimestamp());
213 cv
.put(ProviderTableMeta
.FILE_CREATION
, file
.getCreationTimestamp());
214 cv
.put(ProviderTableMeta
.FILE_CONTENT_LENGTH
, file
.getFileLength());
215 cv
.put(ProviderTableMeta
.FILE_CONTENT_TYPE
, file
.getMimetype());
216 cv
.put(ProviderTableMeta
.FILE_NAME
, file
.getFileName());
217 if (file
.getParentId() != 0)
218 cv
.put(ProviderTableMeta
.FILE_PARENT
, file
.getParentId());
219 cv
.put(ProviderTableMeta
.FILE_PATH
, file
.getRemotePath());
220 if (!file
.isDirectory())
221 cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, file
.getStoragePath());
222 cv
.put(ProviderTableMeta
.FILE_ACCOUNT_OWNER
, mAccount
.name
);
223 cv
.put(ProviderTableMeta
.FILE_LAST_SYNC_DATE
, file
.getLastSyncDate());
224 cv
.put(ProviderTableMeta
.FILE_KEEP_IN_SYNC
, file
.keepInSync() ?
1 : 0);
226 if (fileExists(file
.getRemotePath())) {
227 OCFile oldFile
= getFileByPath(file
.getRemotePath());
228 if (file
.getStoragePath() == null
&& oldFile
.getStoragePath() != null
)
229 file
.setStoragePath(oldFile
.getStoragePath());
230 if (!file
.isDirectory());
231 cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, file
.getStoragePath());
232 file
.setFileId(oldFile
.getFileId());
234 operations
.add(ContentProviderOperation
.newUpdate(ProviderTableMeta
.CONTENT_URI
).
236 withSelection( ProviderTableMeta
._ID
+ "=?",
237 new String
[] { String
.valueOf(file
.getFileId()) })
240 } else if (fileExists(file
.getFileId())) {
241 OCFile oldFile
= getFileById(file
.getFileId());
242 if (file
.getStoragePath() == null
&& oldFile
.getStoragePath() != null
)
243 file
.setStoragePath(oldFile
.getStoragePath());
244 if (!file
.isDirectory());
245 cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, file
.getStoragePath());
247 operations
.add(ContentProviderOperation
.newUpdate(ProviderTableMeta
.CONTENT_URI
).
249 withSelection( ProviderTableMeta
._ID
+ "=?",
250 new String
[] { String
.valueOf(file
.getFileId()) })
254 operations
.add(ContentProviderOperation
.newInsert(ProviderTableMeta
.CONTENT_URI
).withValues(cv
).build());
258 // apply operations in batch
259 ContentProviderResult
[] results
= null
;
261 if (getContentResolver() != null
) {
262 results
= getContentResolver().applyBatch(ProviderMeta
.AUTHORITY_FILES
, operations
);
265 results
= getContentProvider().applyBatch(operations
);
268 } catch (OperationApplicationException e
) {
269 Log
.e(TAG
, "Fail to update/insert list of files to database " + e
.getMessage());
271 } catch (RemoteException e
) {
272 Log
.e(TAG
, "Fail to update/insert list of files to database " + e
.getMessage());
275 // update new id in file objects for insertions
276 if (results
!= null
) {
278 for (int i
=0; i
<results
.length
; i
++) {
279 if (results
[i
].uri
!= null
) {
280 newId
= Long
.parseLong(results
[i
].uri
.getPathSegments().get(1));
281 files
.get(i
).setFileId(newId
);
282 //Log.v(TAG, "Found and added id in insertion for " + files.get(i).getRemotePath());
287 for (OCFile aFile
: files
) {
288 if (aFile
.isDirectory() && aFile
.needsUpdatingWhileSaving())
289 saveFiles(getDirectoryContent(aFile
));
294 public void setAccount(Account account
) {
298 public Account
getAccount() {
302 public void setContentResolver(ContentResolver cr
) {
303 mContentResolver
= cr
;
306 public ContentResolver
getContentResolver() {
307 return mContentResolver
;
310 public void setContentProvider(ContentProviderClient cp
) {
311 mContentProvider
= cp
;
314 public ContentProviderClient
getContentProvider() {
315 return mContentProvider
;
319 public Vector
<OCFile
> getDirectoryContent(OCFile f
) {
320 if (f
!= null
&& f
.isDirectory() && f
.getFileId() != -1) {
321 Vector
<OCFile
> ret
= new Vector
<OCFile
>();
323 Uri req_uri
= Uri
.withAppendedPath(
324 ProviderTableMeta
.CONTENT_URI_DIR
,
325 String
.valueOf(f
.getFileId()));
328 if (getContentProvider() != null
) {
330 c
= getContentProvider().query(req_uri
, null
,
331 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=?",
332 new String
[] { mAccount
.name
}, null
);
333 } catch (RemoteException e
) {
334 Log
.e(TAG
, e
.getMessage());
338 c
= getContentResolver().query(req_uri
, null
,
339 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=?",
340 new String
[] { mAccount
.name
}, null
);
343 if (c
.moveToFirst()) {
345 OCFile child
= createFileInstance(c
);
347 } while (c
.moveToNext());
352 Collections
.sort(ret
);
359 private boolean fileExists(String cmp_key
, String value
) {
361 if (getContentResolver() != null
) {
362 c
= getContentResolver()
363 .query(ProviderTableMeta
.CONTENT_URI
,
366 + ProviderTableMeta
.FILE_ACCOUNT_OWNER
368 new String
[] { value
, mAccount
.name
}, null
);
371 c
= getContentProvider().query(
372 ProviderTableMeta
.CONTENT_URI
,
375 + ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=?",
376 new String
[] { value
, mAccount
.name
}, null
);
377 } catch (RemoteException e
) {
379 "Couldn't determine file existance, assuming non existance: "
384 boolean retval
= c
.moveToFirst();
389 private Cursor
getCursorForValue(String key
, String value
) {
391 if (getContentResolver() != null
) {
392 c
= getContentResolver()
393 .query(ProviderTableMeta
.CONTENT_URI
,
396 + ProviderTableMeta
.FILE_ACCOUNT_OWNER
398 new String
[] { value
, mAccount
.name
}, null
);
401 c
= getContentProvider().query(
402 ProviderTableMeta
.CONTENT_URI
,
404 key
+ "=? AND " + ProviderTableMeta
.FILE_ACCOUNT_OWNER
405 + "=?", new String
[] { value
, mAccount
.name
},
407 } catch (RemoteException e
) {
408 Log
.e(TAG
, "Could not get file details: " + e
.getMessage());
415 private OCFile
createFileInstance(Cursor c
) {
418 file
= new OCFile(c
.getString(c
419 .getColumnIndex(ProviderTableMeta
.FILE_PATH
)));
420 file
.setFileId(c
.getLong(c
.getColumnIndex(ProviderTableMeta
._ID
)));
421 file
.setParentId(c
.getLong(c
422 .getColumnIndex(ProviderTableMeta
.FILE_PARENT
)));
423 file
.setMimetype(c
.getString(c
424 .getColumnIndex(ProviderTableMeta
.FILE_CONTENT_TYPE
)));
425 if (!file
.isDirectory()) {
426 file
.setStoragePath(c
.getString(c
427 .getColumnIndex(ProviderTableMeta
.FILE_STORAGE_PATH
)));
428 if (file
.getStoragePath() == null
) {
429 // try to find existing file and bind it with current account
430 File f
= new File(FileDownloader
.getSavePath(mAccount
.name
) + file
.getRemotePath());
432 file
.setStoragePath(f
.getAbsolutePath());
435 file
.setFileLength(c
.getLong(c
436 .getColumnIndex(ProviderTableMeta
.FILE_CONTENT_LENGTH
)));
437 file
.setCreationTimestamp(c
.getLong(c
438 .getColumnIndex(ProviderTableMeta
.FILE_CREATION
)));
439 file
.setModificationTimestamp(c
.getLong(c
440 .getColumnIndex(ProviderTableMeta
.FILE_MODIFIED
)));
441 file
.setLastSyncDate(c
.getLong(c
442 .getColumnIndex(ProviderTableMeta
.FILE_LAST_SYNC_DATE
)));
443 file
.setKeepInSync(c
.getInt(
444 c
.getColumnIndex(ProviderTableMeta
.FILE_KEEP_IN_SYNC
)) == 1 ? true
: false
);
450 public void removeFile(OCFile file
, boolean removeLocalCopy
) {
451 Uri file_uri
= Uri
.withAppendedPath(ProviderTableMeta
.CONTENT_URI_FILE
, ""+file
.getFileId());
452 if (getContentProvider() != null
) {
454 getContentProvider().delete(file_uri
,
455 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+"=?",
456 new String
[]{mAccount
.name
});
457 } catch (RemoteException e
) {
461 getContentResolver().delete(file_uri
,
462 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+"=?",
463 new String
[]{mAccount
.name
});
465 if (file
.isDown() && removeLocalCopy
) {
466 new File(file
.getStoragePath()).delete();
468 if (file
.isDirectory() && removeLocalCopy
) {
469 File f
= new File(FileDownloader
.getSavePath(mAccount
.name
) + file
.getRemotePath());
470 if (f
.exists() && f
.isDirectory() && (f
.list() == null
|| f
.list().length
== 0)) {
477 public void removeDirectory(OCFile dir
, boolean removeDBData
, boolean removeLocalContent
) {
478 // TODO consider possible failures
479 if (dir
!= null
&& dir
.isDirectory() && dir
.getFileId() != -1) {
480 Vector
<OCFile
> children
= getDirectoryContent(dir
);
481 if (children
!= null
) {
483 for (int i
=0; i
<children
.size(); i
++) {
484 child
= children
.get(i
);
485 if (child
.isDirectory()) {
486 removeDirectory(child
, removeDBData
, removeLocalContent
);
489 removeFile(child
, removeLocalContent
);
490 } else if (removeLocalContent
) {
491 if (child
.isDown()) {
492 new File(child
.getStoragePath()).delete();
498 removeFile(dir
, true
);
506 * Updates database for a folder that was moved to a different location.
508 * TODO explore better (faster) implementations
509 * TODO throw exceptions up !
512 public void moveDirectory(OCFile dir
, String newPath
) {
513 // TODO check newPath
515 if (dir
!= null
&& dir
.isDirectory() && dir
.fileExists() && !dir
.getFileName().equals(OCFile
.PATH_SEPARATOR
)) {
516 /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir')
518 if (getContentProvider() != null
) {
520 c
= getContentProvider().query(ProviderTableMeta
.CONTENT_URI
,
522 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=? AND " + ProviderTableMeta
.FILE_PATH
+ " LIKE ?",
523 new String
[] { mAccount
.name
, dir
.getRemotePath() + "%" }, null
);
524 } catch (RemoteException e
) {
525 Log
.e(TAG
, e
.getMessage());
528 c
= getContentResolver().query(ProviderTableMeta
.CONTENT_URI
,
530 ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=? AND " + ProviderTableMeta
.FILE_PATH
+ " LIKE ?",
531 new String
[] { mAccount
.name
, dir
.getRemotePath() + "%" }, null
);
534 /// 2. prepare a batch of update operations to change all the descendants
535 ArrayList
<ContentProviderOperation
> operations
= new ArrayList
<ContentProviderOperation
>(c
.getCount());
536 int lengthOfOldPath
= dir
.getRemotePath().length();
537 String defaultSavePath
= FileDownloader
.getSavePath(mAccount
.name
);
538 int lengthOfOldStoragePath
= defaultSavePath
.length() + lengthOfOldPath
;
539 if (c
.moveToFirst()) {
541 ContentValues cv
= new ContentValues(); // don't take the constructor out of the loop and clear the object
542 OCFile child
= createFileInstance(c
);
543 cv
.put(ProviderTableMeta
.FILE_PATH
, newPath
+ child
.getRemotePath().substring(lengthOfOldPath
));
544 if (child
.getStoragePath() != null
&& child
.getStoragePath().startsWith(defaultSavePath
)) {
545 cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, defaultSavePath
+ newPath
+ child
.getStoragePath().substring(lengthOfOldStoragePath
));
547 operations
.add(ContentProviderOperation
.newUpdate(ProviderTableMeta
.CONTENT_URI
).
549 withSelection( ProviderTableMeta
._ID
+ "=?",
550 new String
[] { String
.valueOf(child
.getFileId()) })
552 } while (c
.moveToNext());
556 /// 3. apply updates in batch
558 if (getContentResolver() != null
) {
559 getContentResolver().applyBatch(ProviderMeta
.AUTHORITY_FILES
, operations
);
562 getContentProvider().applyBatch(operations
);
565 } catch (OperationApplicationException e
) {
566 Log
.e(TAG
, "Fail to update descendants of " + dir
.getFileId() + " in database", e
);
568 } catch (RemoteException e
) {
569 Log
.e(TAG
, "Fail to update desendants of " + dir
.getFileId() + " in database", e
);