Persist conflicts in local database and show with an icon in the list of files
[pub/Android/ownCloud.git] / src / com / owncloud / android / providers / FileContentProvider.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author Bartek Przybylski
5 * @author David A. Velasco
6 * Copyright (C) 2011 Bartek Przybylski
7 * Copyright (C) 2015 ownCloud Inc.
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2,
11 * as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22
23 package com.owncloud.android.providers;
24
25 import java.io.File;
26 import java.security.Provider;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29
30 import com.owncloud.android.MainApp;
31 import com.owncloud.android.R;
32 import com.owncloud.android.datamodel.OCFile;
33 import com.owncloud.android.db.ProviderMeta;
34 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
35 import com.owncloud.android.lib.common.accounts.AccountUtils;
36 import com.owncloud.android.lib.common.utils.Log_OC;
37 import com.owncloud.android.lib.resources.shares.ShareType;
38 import com.owncloud.android.utils.FileStorageUtils;
39
40 import android.accounts.Account;
41 import android.accounts.AccountManager;
42 import android.content.ContentProvider;
43 import android.content.ContentProviderOperation;
44 import android.content.ContentProviderResult;
45 import android.content.ContentUris;
46 import android.content.ContentValues;
47 import android.content.Context;
48 import android.content.OperationApplicationException;
49 import android.content.UriMatcher;
50 import android.database.Cursor;
51 import android.database.SQLException;
52 import android.database.sqlite.SQLiteDatabase;
53 import android.database.sqlite.SQLiteOpenHelper;
54 import android.database.sqlite.SQLiteQueryBuilder;
55 import android.net.Uri;
56 import android.text.TextUtils;
57
58 /**
59 * The ContentProvider for the ownCloud App.
60 */
61 public class FileContentProvider extends ContentProvider {
62
63 private DataBaseHelper mDbHelper;
64
65 // Projection for filelist table
66 private static HashMap<String, String> mFileProjectionMap;
67 static {
68 mFileProjectionMap = new HashMap<String, String>();
69 mFileProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID);
70 mFileProjectionMap.put(ProviderTableMeta.FILE_PARENT,
71 ProviderTableMeta.FILE_PARENT);
72 mFileProjectionMap.put(ProviderTableMeta.FILE_PATH,
73 ProviderTableMeta.FILE_PATH);
74 mFileProjectionMap.put(ProviderTableMeta.FILE_NAME,
75 ProviderTableMeta.FILE_NAME);
76 mFileProjectionMap.put(ProviderTableMeta.FILE_CREATION,
77 ProviderTableMeta.FILE_CREATION);
78 mFileProjectionMap.put(ProviderTableMeta.FILE_MODIFIED,
79 ProviderTableMeta.FILE_MODIFIED);
80 mFileProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,
81 ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA);
82 mFileProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH,
83 ProviderTableMeta.FILE_CONTENT_LENGTH);
84 mFileProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE,
85 ProviderTableMeta.FILE_CONTENT_TYPE);
86 mFileProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH,
87 ProviderTableMeta.FILE_STORAGE_PATH);
88 mFileProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE,
89 ProviderTableMeta.FILE_LAST_SYNC_DATE);
90 mFileProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA,
91 ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA);
92 mFileProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC,
93 ProviderTableMeta.FILE_KEEP_IN_SYNC);
94 mFileProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER,
95 ProviderTableMeta.FILE_ACCOUNT_OWNER);
96 mFileProjectionMap.put(ProviderTableMeta.FILE_ETAG,
97 ProviderTableMeta.FILE_ETAG);
98 mFileProjectionMap.put(ProviderTableMeta.FILE_SHARE_BY_LINK,
99 ProviderTableMeta.FILE_SHARE_BY_LINK);
100 mFileProjectionMap.put(ProviderTableMeta.FILE_PUBLIC_LINK,
101 ProviderTableMeta.FILE_PUBLIC_LINK);
102 mFileProjectionMap.put(ProviderTableMeta.FILE_PERMISSIONS,
103 ProviderTableMeta.FILE_PERMISSIONS);
104 mFileProjectionMap.put(ProviderTableMeta.FILE_REMOTE_ID,
105 ProviderTableMeta.FILE_REMOTE_ID);
106 mFileProjectionMap.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL,
107 ProviderTableMeta.FILE_UPDATE_THUMBNAIL);
108 mFileProjectionMap.put(ProviderTableMeta.FILE_IS_DOWNLOADING,
109 ProviderTableMeta.FILE_IS_DOWNLOADING);
110 mFileProjectionMap.put(ProviderTableMeta.FILE_IN_CONFLICT,
111 ProviderTableMeta.FILE_IN_CONFLICT);
112 }
113
114 private static final int SINGLE_FILE = 1;
115 private static final int DIRECTORY = 2;
116 private static final int ROOT_DIRECTORY = 3;
117 private static final int SHARES = 4;
118
119 private static final String TAG = FileContentProvider.class.getSimpleName();
120
121 // Projection for ocshares table
122 private static HashMap<String, String> mOCSharesProjectionMap;
123 static {
124 mOCSharesProjectionMap = new HashMap<String, String>();
125 mOCSharesProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID);
126 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_FILE_SOURCE,
127 ProviderTableMeta.OCSHARES_FILE_SOURCE);
128 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_ITEM_SOURCE,
129 ProviderTableMeta.OCSHARES_ITEM_SOURCE);
130 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_SHARE_TYPE,
131 ProviderTableMeta.OCSHARES_SHARE_TYPE);
132 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_SHARE_WITH,
133 ProviderTableMeta.OCSHARES_SHARE_WITH);
134 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_PATH,
135 ProviderTableMeta.OCSHARES_PATH);
136 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_PERMISSIONS,
137 ProviderTableMeta.OCSHARES_PERMISSIONS);
138 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_SHARED_DATE,
139 ProviderTableMeta.OCSHARES_SHARED_DATE);
140 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_EXPIRATION_DATE,
141 ProviderTableMeta.OCSHARES_EXPIRATION_DATE);
142 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_TOKEN,
143 ProviderTableMeta.OCSHARES_TOKEN);
144 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME,
145 ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME);
146 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY,
147 ProviderTableMeta.OCSHARES_IS_DIRECTORY);
148 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_USER_ID,
149 ProviderTableMeta.OCSHARES_USER_ID);
150 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED,
151 ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED);
152 mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER,
153 ProviderTableMeta.OCSHARES_ACCOUNT_OWNER);
154 }
155
156 private UriMatcher mUriMatcher;
157
158 @Override
159 public int delete(Uri uri, String where, String[] whereArgs) {
160 //Log_OC.d(TAG, "Deleting " + uri + " at provider " + this);
161 int count = 0;
162 SQLiteDatabase db = mDbHelper.getWritableDatabase();
163 db.beginTransaction();
164 try {
165 count = delete(db, uri, where, whereArgs);
166 db.setTransactionSuccessful();
167 } finally {
168 db.endTransaction();
169 }
170 getContext().getContentResolver().notifyChange(uri, null);
171 return count;
172 }
173
174 private int delete(SQLiteDatabase db, Uri uri, String where, String[] whereArgs) {
175 int count = 0;
176 switch (mUriMatcher.match(uri)) {
177 case SINGLE_FILE:
178 Cursor c = query(db, uri, null, where, whereArgs, null);
179 String remoteId = "";
180 if (c != null && c.moveToFirst()) {
181 remoteId = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID));
182 //ThumbnailsCacheManager.removeFileFromCache(remoteId);
183 }
184 Log_OC.d(TAG, "Removing FILE " + remoteId);
185
186 count = db.delete(ProviderTableMeta.FILE_TABLE_NAME,
187 ProviderTableMeta._ID
188 + "="
189 + uri.getPathSegments().get(1)
190 + (!TextUtils.isEmpty(where) ? " AND (" + where
191 + ")" : ""), whereArgs);
192 /* just for log
193 if (c!=null) {
194 c.close();
195 }
196 */
197 break;
198 case DIRECTORY:
199 // deletion of folder is recursive
200 /*
201 Uri folderUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, Long.parseLong(uri.getPathSegments().get(1)));
202 Cursor folder = query(db, folderUri, null, null, null, null);
203 String folderName = "(unknown)";
204 if (folder != null && folder.moveToFirst()) {
205 folderName = folder.getString(folder.getColumnIndex(ProviderTableMeta.FILE_PATH));
206 }
207 */
208 Cursor children = query(uri, null, null, null, null);
209 if (children != null && children.moveToFirst()) {
210 long childId;
211 boolean isDir;
212 while (!children.isAfterLast()) {
213 childId = children.getLong(children.getColumnIndex(ProviderTableMeta._ID));
214 isDir = "DIR".equals(children.getString(
215 children.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)
216 ));
217 //remotePath = children.getString(children.getColumnIndex(ProviderTableMeta.FILE_PATH));
218 if (isDir) {
219 count += delete(
220 db,
221 ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId),
222 null,
223 null
224 );
225 } else {
226 count += delete(
227 db,
228 ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId),
229 null,
230 null
231 );
232 }
233 children.moveToNext();
234 }
235 children.close();
236 } /*else {
237 Log_OC.d(TAG, "No child to remove in DIRECTORY " + folderName);
238 }
239 Log_OC.d(TAG, "Removing DIRECTORY " + folderName + " (or maybe not) ");
240 */
241 count += db.delete(ProviderTableMeta.FILE_TABLE_NAME,
242 ProviderTableMeta._ID
243 + "="
244 + uri.getPathSegments().get(1)
245 + (!TextUtils.isEmpty(where) ? " AND (" + where
246 + ")" : ""), whereArgs);
247 /* Just for log
248 if (folder != null) {
249 folder.close();
250 }*/
251 break;
252 case ROOT_DIRECTORY:
253 //Log_OC.d(TAG, "Removing ROOT!");
254 count = db.delete(ProviderTableMeta.FILE_TABLE_NAME, where, whereArgs);
255 break;
256 case SHARES:
257 count = db.delete(ProviderTableMeta.OCSHARES_TABLE_NAME, where, whereArgs);
258 break;
259 default:
260 //Log_OC.e(TAG, "Unknown uri " + uri);
261 throw new IllegalArgumentException("Unknown uri: " + uri.toString());
262 }
263 return count;
264 }
265
266 @Override
267 public String getType(Uri uri) {
268 switch (mUriMatcher.match(uri)) {
269 case ROOT_DIRECTORY:
270 return ProviderTableMeta.CONTENT_TYPE;
271 case SINGLE_FILE:
272 return ProviderTableMeta.CONTENT_TYPE_ITEM;
273 default:
274 throw new IllegalArgumentException("Unknown Uri id."
275 + uri.toString());
276 }
277 }
278
279 @Override
280 public Uri insert(Uri uri, ContentValues values) {
281 Uri newUri = null;
282 SQLiteDatabase db = mDbHelper.getWritableDatabase();
283 db.beginTransaction();
284 try {
285 newUri = insert(db, uri, values);
286 db.setTransactionSuccessful();
287 } finally {
288 db.endTransaction();
289 }
290 getContext().getContentResolver().notifyChange(newUri, null);
291 return newUri;
292 }
293
294 private Uri insert(SQLiteDatabase db, Uri uri, ContentValues values) {
295 switch (mUriMatcher.match(uri)){
296 case ROOT_DIRECTORY:
297 case SINGLE_FILE:
298 String remotePath = values.getAsString(ProviderTableMeta.FILE_PATH);
299 String accountName = values.getAsString(ProviderTableMeta.FILE_ACCOUNT_OWNER);
300 String[] projection = new String[] {
301 ProviderTableMeta._ID, ProviderTableMeta.FILE_PATH,
302 ProviderTableMeta.FILE_ACCOUNT_OWNER
303 };
304 String where = ProviderTableMeta.FILE_PATH + "=? AND " +
305 ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
306 String[] whereArgs = new String[] {remotePath, accountName};
307 Cursor doubleCheck = query(db, uri, projection, where, whereArgs, null);
308 // ugly patch; serious refactorization is needed to reduce work in
309 // FileDataStorageManager and bring it to FileContentProvider
310 if (doubleCheck == null || !doubleCheck.moveToFirst()) {
311 long rowId = db.insert(ProviderTableMeta.FILE_TABLE_NAME, null, values);
312 if (rowId > 0) {
313 Uri insertedFileUri =
314 ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId);
315 return insertedFileUri;
316 } else {
317 throw new SQLException("ERROR " + uri);
318 }
319 } else {
320 // file is already inserted; race condition, let's avoid a duplicated entry
321 Uri insertedFileUri = ContentUris.withAppendedId(
322 ProviderTableMeta.CONTENT_URI_FILE,
323 doubleCheck.getLong(doubleCheck.getColumnIndex(ProviderTableMeta._ID))
324 );
325 doubleCheck.close();
326
327 return insertedFileUri;
328 }
329
330 case SHARES:
331 String path = values.getAsString(ProviderTableMeta.OCSHARES_PATH);
332 String accountNameShare= values.getAsString(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER);
333 String[] projectionShare = new String[] {
334 ProviderTableMeta._ID, ProviderTableMeta.OCSHARES_PATH,
335 ProviderTableMeta.OCSHARES_ACCOUNT_OWNER
336 };
337 String whereShare = ProviderTableMeta.OCSHARES_PATH + "=? AND " +
338 ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?";
339 String[] whereArgsShare = new String[] {path, accountNameShare};
340 Uri insertedShareUri = null;
341 Cursor doubleCheckShare =
342 query(db, uri, projectionShare, whereShare, whereArgsShare, null);
343 // ugly patch; serious refactorization is needed to reduce work in
344 // FileDataStorageManager and bring it to FileContentProvider
345 if (doubleCheckShare == null || !doubleCheckShare.moveToFirst()) {
346 long rowId = db.insert(ProviderTableMeta.OCSHARES_TABLE_NAME, null, values);
347 if (rowId >0) {
348 insertedShareUri =
349 ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, rowId);
350 } else {
351 throw new SQLException("ERROR " + uri);
352
353 }
354 } else {
355 // file is already inserted; race condition, let's avoid a duplicated entry
356 insertedShareUri = ContentUris.withAppendedId(
357 ProviderTableMeta.CONTENT_URI_SHARE,
358 doubleCheckShare.getLong(
359 doubleCheckShare.getColumnIndex(ProviderTableMeta._ID)
360 )
361 );
362 doubleCheckShare.close();
363 }
364 updateFilesTableAccordingToShareInsertion(db, uri, values);
365 return insertedShareUri;
366
367
368 default:
369 throw new IllegalArgumentException("Unknown uri id: " + uri);
370 }
371
372 }
373
374 private void updateFilesTableAccordingToShareInsertion(
375 SQLiteDatabase db, Uri uri, ContentValues shareValues
376 ) {
377 ContentValues fileValues = new ContentValues();
378 fileValues.put(
379 ProviderTableMeta.FILE_SHARE_BY_LINK,
380 ShareType.PUBLIC_LINK.getValue() ==
381 shareValues.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE) ? 1 : 0
382 );
383 String whereShare = ProviderTableMeta.FILE_PATH + "=? AND " +
384 ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
385 String[] whereArgsShare = new String[] {
386 shareValues.getAsString(ProviderTableMeta.OCSHARES_PATH),
387 shareValues.getAsString(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER)
388 };
389 db.update(ProviderTableMeta.FILE_TABLE_NAME, fileValues, whereShare, whereArgsShare);
390 }
391
392
393 @Override
394 public boolean onCreate() {
395 mDbHelper = new DataBaseHelper(getContext());
396
397 String authority = getContext().getResources().getString(R.string.authority);
398 mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
399 mUriMatcher.addURI(authority, null, ROOT_DIRECTORY);
400 mUriMatcher.addURI(authority, "file/", SINGLE_FILE);
401 mUriMatcher.addURI(authority, "file/#", SINGLE_FILE);
402 mUriMatcher.addURI(authority, "dir/", DIRECTORY);
403 mUriMatcher.addURI(authority, "dir/#", DIRECTORY);
404 mUriMatcher.addURI(authority, "shares/", SHARES);
405 mUriMatcher.addURI(authority, "shares/#", SHARES);
406
407 return true;
408 }
409
410
411 @Override
412 public Cursor query(
413 Uri uri,
414 String[] projection,
415 String selection,
416 String[] selectionArgs,
417 String sortOrder
418 ) {
419
420 Cursor result = null;
421 SQLiteDatabase db = mDbHelper.getReadableDatabase();
422 db.beginTransaction();
423 try {
424 result = query(db, uri, projection, selection, selectionArgs, sortOrder);
425 db.setTransactionSuccessful();
426 } finally {
427 db.endTransaction();
428 }
429 return result;
430 }
431
432 private Cursor query(
433 SQLiteDatabase db,
434 Uri uri,
435 String[] projection,
436 String selection,
437 String[] selectionArgs,
438 String sortOrder
439 ) {
440
441 SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder();
442
443 sqlQuery.setTables(ProviderTableMeta.FILE_TABLE_NAME);
444 sqlQuery.setProjectionMap(mFileProjectionMap);
445
446 switch (mUriMatcher.match(uri)) {
447 case ROOT_DIRECTORY:
448 break;
449 case DIRECTORY:
450 String folderId = uri.getPathSegments().get(1);
451 sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "="
452 + folderId);
453 break;
454 case SINGLE_FILE:
455 if (uri.getPathSegments().size() > 1) {
456 sqlQuery.appendWhere(ProviderTableMeta._ID + "="
457 + uri.getPathSegments().get(1));
458 }
459 break;
460 case SHARES:
461 sqlQuery.setTables(ProviderTableMeta.OCSHARES_TABLE_NAME);
462 sqlQuery.setProjectionMap(mOCSharesProjectionMap);
463 if (uri.getPathSegments().size() > 1) {
464 sqlQuery.appendWhere(ProviderTableMeta._ID + "="
465 + uri.getPathSegments().get(1));
466 }
467 break;
468 default:
469 throw new IllegalArgumentException("Unknown uri id: " + uri);
470 }
471
472 String order;
473 if (TextUtils.isEmpty(sortOrder)) {
474 if (mUriMatcher.match(uri) == SHARES) {
475 order = ProviderTableMeta.OCSHARES_DEFAULT_SORT_ORDER;
476 } else {
477
478 order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER;
479 }
480 } else {
481 order = sortOrder;
482 }
483
484 // DB case_sensitive
485 db.execSQL("PRAGMA case_sensitive_like = true");
486 Cursor c = sqlQuery.query(db, projection, selection, selectionArgs, null, null, order);
487 c.setNotificationUri(getContext().getContentResolver(), uri);
488 return c;
489 }
490
491 @Override
492 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
493
494 int count = 0;
495 SQLiteDatabase db = mDbHelper.getWritableDatabase();
496 db.beginTransaction();
497 try {
498 count = update(db, uri, values, selection, selectionArgs);
499 db.setTransactionSuccessful();
500 } finally {
501 db.endTransaction();
502 }
503 getContext().getContentResolver().notifyChange(uri, null);
504 return count;
505 }
506
507
508
509 private int update(
510 SQLiteDatabase db,
511 Uri uri,
512 ContentValues values,
513 String selection,
514 String[] selectionArgs
515 ) {
516 switch (mUriMatcher.match(uri)) {
517 case DIRECTORY:
518 return 0; //updateFolderSize(db, selectionArgs[0]);
519 case SHARES:
520 return db.update(
521 ProviderTableMeta.OCSHARES_TABLE_NAME, values, selection, selectionArgs
522 );
523 default:
524 return db.update(
525 ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs
526 );
527 }
528 }
529
530 /*
531 private int updateFolderSize(SQLiteDatabase db, String folderId) {
532 int count = 0;
533 String [] whereArgs = new String[] { folderId };
534
535 // read current size saved for the folder
536 long folderSize = 0;
537 long folderParentId = -1;
538 Uri selectFolderUri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, folderId);
539 String[] folderProjection = new String[] { ProviderTableMeta.FILE_CONTENT_LENGTH, ProviderTableMeta.FILE_PARENT};
540 String folderWhere = ProviderTableMeta._ID + "=?";
541 Cursor folderCursor = query(db, selectFolderUri, folderProjection, folderWhere, whereArgs, null);
542 if (folderCursor != null && folderCursor.moveToFirst()) {
543 folderSize = folderCursor.getLong(folderCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH));;
544 folderParentId = folderCursor.getLong(folderCursor.getColumnIndex(ProviderTableMeta.FILE_PARENT));;
545 }
546 folderCursor.close();
547
548 // read and sum sizes of children
549 long childrenSize = 0;
550 Uri selectChildrenUri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, folderId);
551 String[] childrenProjection = new String[] { ProviderTableMeta.FILE_CONTENT_LENGTH, ProviderTableMeta.FILE_PARENT};
552 String childrenWhere = ProviderTableMeta.FILE_PARENT + "=?";
553 Cursor childrenCursor = query(db, selectChildrenUri, childrenProjection, childrenWhere, whereArgs, null);
554 if (childrenCursor != null && childrenCursor.moveToFirst()) {
555 while (!childrenCursor.isAfterLast()) {
556 childrenSize += childrenCursor.getLong(childrenCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH));
557 childrenCursor.moveToNext();
558 }
559 }
560 childrenCursor.close();
561
562 // update if needed
563 if (folderSize != childrenSize) {
564 Log_OC.d("FileContentProvider", "Updating " + folderSize + " to " + childrenSize);
565 ContentValues cv = new ContentValues();
566 cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, childrenSize);
567 count = db.update(ProviderTableMeta.FILE_TABLE_NAME, cv, folderWhere, whereArgs);
568
569 // propagate update until root
570 if (folderParentId > FileDataStorageManager.ROOT_PARENT_ID) {
571 Log_OC.d("FileContentProvider", "Propagating update to " + folderParentId);
572 updateFolderSize(db, String.valueOf(folderParentId));
573 } else {
574 Log_OC.d("FileContentProvider", "NOT propagating to " + folderParentId);
575 }
576 } else {
577 Log_OC.d("FileContentProvider", "NOT updating, sizes are " + folderSize + " and " + childrenSize);
578 }
579 return count;
580 }
581 */
582
583 @Override
584 public ContentProviderResult[] applyBatch (ArrayList<ContentProviderOperation> operations)
585 throws OperationApplicationException {
586 Log_OC.d("FileContentProvider", "applying batch in provider " + this +
587 " (temporary: " + isTemporary() + ")" );
588 ContentProviderResult[] results = new ContentProviderResult[operations.size()];
589 int i=0;
590
591 SQLiteDatabase db = mDbHelper.getWritableDatabase();
592 db.beginTransaction(); // it's supposed that transactions can be nested
593 try {
594 for (ContentProviderOperation operation : operations) {
595 results[i] = operation.apply(this, results, i);
596 i++;
597 }
598 db.setTransactionSuccessful();
599 } finally {
600 db.endTransaction();
601 }
602 Log_OC.d("FileContentProvider", "applied batch in provider " + this);
603 return results;
604 }
605
606
607 class DataBaseHelper extends SQLiteOpenHelper {
608
609 public DataBaseHelper(Context context) {
610 super(context, ProviderMeta.DB_NAME, null, ProviderMeta.DB_VERSION);
611
612 }
613
614 @Override
615 public void onCreate(SQLiteDatabase db) {
616 // files table
617 Log_OC.i("SQL", "Entering in onCreate");
618 db.execSQL("CREATE TABLE " + ProviderTableMeta.FILE_TABLE_NAME + "("
619 + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
620 + ProviderTableMeta.FILE_NAME + " TEXT, "
621 + ProviderTableMeta.FILE_PATH + " TEXT, "
622 + ProviderTableMeta.FILE_PARENT + " INTEGER, "
623 + ProviderTableMeta.FILE_CREATION + " INTEGER, "
624 + ProviderTableMeta.FILE_MODIFIED + " INTEGER, "
625 + ProviderTableMeta.FILE_CONTENT_TYPE + " TEXT, "
626 + ProviderTableMeta.FILE_CONTENT_LENGTH + " INTEGER, "
627 + ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, "
628 + ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, "
629 + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "
630 + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "
631 + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, "
632 + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER, "
633 + ProviderTableMeta.FILE_ETAG + " TEXT, "
634 + ProviderTableMeta.FILE_SHARE_BY_LINK + " INTEGER, "
635 + ProviderTableMeta.FILE_PUBLIC_LINK + " TEXT, "
636 + ProviderTableMeta.FILE_PERMISSIONS + " TEXT null,"
637 + ProviderTableMeta.FILE_REMOTE_ID + " TEXT null,"
638 + ProviderTableMeta.FILE_UPDATE_THUMBNAIL + " INTEGER," //boolean
639 + ProviderTableMeta.FILE_IS_DOWNLOADING + " INTEGER," //boolean
640 + ProviderTableMeta.FILE_IN_CONFLICT + " INTEGER);" //boolean
641 );
642
643 // Create table ocshares
644 db.execSQL("CREATE TABLE " + ProviderTableMeta.OCSHARES_TABLE_NAME + "("
645 + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
646 + ProviderTableMeta.OCSHARES_FILE_SOURCE + " INTEGER, "
647 + ProviderTableMeta.OCSHARES_ITEM_SOURCE + " INTEGER, "
648 + ProviderTableMeta.OCSHARES_SHARE_TYPE + " INTEGER, "
649 + ProviderTableMeta.OCSHARES_SHARE_WITH + " TEXT, "
650 + ProviderTableMeta.OCSHARES_PATH + " TEXT, "
651 + ProviderTableMeta.OCSHARES_PERMISSIONS+ " INTEGER, "
652 + ProviderTableMeta.OCSHARES_SHARED_DATE + " INTEGER, "
653 + ProviderTableMeta.OCSHARES_EXPIRATION_DATE + " INTEGER, "
654 + ProviderTableMeta.OCSHARES_TOKEN + " TEXT, "
655 + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME + " TEXT, "
656 + ProviderTableMeta.OCSHARES_IS_DIRECTORY + " INTEGER, " // boolean
657 + ProviderTableMeta.OCSHARES_USER_ID + " INTEGER, "
658 + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + " INTEGER,"
659 + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + " TEXT );" );
660 }
661
662 @Override
663 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
664 Log_OC.i("SQL", "Entering in onUpgrade");
665 boolean upgraded = false;
666 if (oldVersion == 1 && newVersion >= 2) {
667 Log_OC.i("SQL", "Entering in the #1 ADD in onUpgrade");
668 db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
669 " ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " +
670 " DEFAULT 0");
671 upgraded = true;
672 }
673 if (oldVersion < 3 && newVersion >= 3) {
674 Log_OC.i("SQL", "Entering in the #2 ADD in onUpgrade");
675 db.beginTransaction();
676 try {
677 db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
678 " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA +
679 " INTEGER " + " DEFAULT 0");
680
681 // assume there are not local changes pending to upload
682 db.execSQL("UPDATE " + ProviderTableMeta.FILE_TABLE_NAME +
683 " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = "
684 + System.currentTimeMillis() +
685 " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");
686
687 upgraded = true;
688 db.setTransactionSuccessful();
689 } finally {
690 db.endTransaction();
691 }
692 }
693 if (oldVersion < 4 && newVersion >= 4) {
694 Log_OC.i("SQL", "Entering in the #3 ADD in onUpgrade");
695 db.beginTransaction();
696 try {
697 db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
698 " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA +
699 " INTEGER " + " DEFAULT 0");
700
701 db.execSQL("UPDATE " + ProviderTableMeta.FILE_TABLE_NAME +
702 " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " +
703 ProviderTableMeta.FILE_MODIFIED +
704 " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");
705
706 upgraded = true;
707 db.setTransactionSuccessful();
708 } finally {
709 db.endTransaction();
710 }
711 }
712 if (!upgraded)
713 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
714 ", newVersion == " + newVersion);
715
716 if (oldVersion < 5 && newVersion >= 5) {
717 Log_OC.i("SQL", "Entering in the #4 ADD in onUpgrade");
718 db.beginTransaction();
719 try {
720 db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
721 " ADD COLUMN " + ProviderTableMeta.FILE_ETAG + " TEXT " +
722 " DEFAULT NULL");
723
724 upgraded = true;
725 db.setTransactionSuccessful();
726 } finally {
727 db.endTransaction();
728 }
729 }
730 if (!upgraded)
731 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
732 ", newVersion == " + newVersion);
733
734 if (oldVersion < 6 && newVersion >= 6) {
735 Log_OC.i("SQL", "Entering in the #5 ADD in onUpgrade");
736 db.beginTransaction();
737 try {
738 db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
739 " ADD COLUMN " + ProviderTableMeta.FILE_SHARE_BY_LINK + " INTEGER " +
740 " DEFAULT 0");
741
742 db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
743 " ADD COLUMN " + ProviderTableMeta.FILE_PUBLIC_LINK + " TEXT " +
744 " DEFAULT NULL");
745
746 // Create table ocshares
747 db.execSQL("CREATE TABLE " + ProviderTableMeta.OCSHARES_TABLE_NAME + "("
748 + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
749 + ProviderTableMeta.OCSHARES_FILE_SOURCE + " INTEGER, "
750 + ProviderTableMeta.OCSHARES_ITEM_SOURCE + " INTEGER, "
751 + ProviderTableMeta.OCSHARES_SHARE_TYPE + " INTEGER, "
752 + ProviderTableMeta.OCSHARES_SHARE_WITH + " TEXT, "
753 + ProviderTableMeta.OCSHARES_PATH + " TEXT, "
754 + ProviderTableMeta.OCSHARES_PERMISSIONS + " INTEGER, "
755 + ProviderTableMeta.OCSHARES_SHARED_DATE + " INTEGER, "
756 + ProviderTableMeta.OCSHARES_EXPIRATION_DATE + " INTEGER, "
757 + ProviderTableMeta.OCSHARES_TOKEN + " TEXT, "
758 + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME + " TEXT, "
759 + ProviderTableMeta.OCSHARES_IS_DIRECTORY + " INTEGER, " // boolean
760 + ProviderTableMeta.OCSHARES_USER_ID + " INTEGER, "
761 + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + " INTEGER,"
762 + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + " TEXT );");
763
764 upgraded = true;
765 db.setTransactionSuccessful();
766 } finally {
767 db.endTransaction();
768 }
769 }
770 if (!upgraded)
771 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
772 ", newVersion == " + newVersion);
773
774 if (oldVersion < 7 && newVersion >= 7) {
775 Log_OC.i("SQL", "Entering in the #7 ADD in onUpgrade");
776 db.beginTransaction();
777 try {
778 db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
779 " ADD COLUMN " + ProviderTableMeta.FILE_PERMISSIONS + " TEXT " +
780 " DEFAULT NULL");
781
782 db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
783 " ADD COLUMN " + ProviderTableMeta.FILE_REMOTE_ID + " TEXT " +
784 " DEFAULT NULL");
785
786 upgraded = true;
787 db.setTransactionSuccessful();
788 } finally {
789 db.endTransaction();
790 }
791 }
792 if (!upgraded)
793 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
794 ", newVersion == " + newVersion);
795
796 if (oldVersion < 8 && newVersion >= 8) {
797 Log_OC.i("SQL", "Entering in the #8 ADD in onUpgrade");
798 db.beginTransaction();
799 try {
800 db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
801 " ADD COLUMN " + ProviderTableMeta.FILE_UPDATE_THUMBNAIL + " INTEGER " +
802 " DEFAULT 0");
803
804 upgraded = true;
805 db.setTransactionSuccessful();
806 } finally {
807 db.endTransaction();
808 }
809 }
810 if (!upgraded)
811 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
812 ", newVersion == " + newVersion);
813
814 if (oldVersion < 9 && newVersion >= 9) {
815 Log_OC.i("SQL", "Entering in the #9 ADD in onUpgrade");
816 db.beginTransaction();
817 try {
818 db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
819 " ADD COLUMN " + ProviderTableMeta.FILE_IS_DOWNLOADING + " INTEGER " +
820 " DEFAULT 0");
821
822 upgraded = true;
823 db.setTransactionSuccessful();
824 } finally {
825 db.endTransaction();
826 }
827 }
828 if (!upgraded)
829 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
830 ", newVersion == " + newVersion);
831
832 if (oldVersion < 10 && newVersion >= 10) {
833 Log_OC.i("SQL", "Entering in the #10 ADD in onUpgrade");
834 updateAccountName(db);
835 upgraded = true;
836 }
837 if (!upgraded)
838 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
839 ", newVersion == " + newVersion);
840
841 if (oldVersion < 11 && newVersion >= 11) {
842 Log_OC.i("SQL", "Entering in the #11 ADD in onUpgrade");
843 db.beginTransaction();
844 try {
845 db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
846 " ADD COLUMN " + ProviderTableMeta.FILE_IN_CONFLICT + " INTEGER " +
847 " DEFAULT 0");
848 upgraded = true;
849 db.setTransactionSuccessful();
850 } finally {
851 db.endTransaction();
852 }
853 }
854 if (!upgraded)
855 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
856 ", newVersion == " + newVersion);
857
858 }
859 }
860
861
862 /**
863 * Version 10 of database does not modify its scheme. It coincides with the upgrade of the ownCloud account names
864 * structure to include in it the path to the server instance. Updating the account names and path to local files
865 * in the files table is a must to keep the existing account working and the database clean.
866 *
867 * See {@link com.owncloud.android.authentication.AccountUtils#updateAccountVersion(android.content.Context)}
868 *
869 * @param db Database where table of files is included.
870 */
871 private void updateAccountName(SQLiteDatabase db){
872 Log_OC.d("SQL", "THREAD: "+ Thread.currentThread().getName());
873 AccountManager ama = AccountManager.get(getContext());
874 try {
875 // get accounts from AccountManager ; we can't be sure if accounts in it are updated or not although
876 // we know the update was previously done in {link @FileActivity#onCreate} because the changes through
877 // AccountManager are not synchronous
878 Account[] accounts = AccountManager.get(getContext()).getAccountsByType(
879 MainApp.getAccountType());
880 String serverUrl, username, oldAccountName, newAccountName;
881 for (Account account : accounts) {
882 // build both old and new account name
883 serverUrl = ama.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL);
884 username = account.name.substring(0, account.name.lastIndexOf('@'));
885 oldAccountName = AccountUtils.buildAccountNameOld(Uri.parse(serverUrl), username);
886 newAccountName = AccountUtils.buildAccountName(Uri.parse(serverUrl), username);
887
888 // update values in database
889 db.beginTransaction();
890 try {
891 ContentValues cv = new ContentValues();
892 cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, newAccountName);
893 int num = db.update(ProviderTableMeta.FILE_TABLE_NAME,
894 cv,
895 ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",
896 new String[]{oldAccountName});
897
898 Log_OC.d("SQL", "Updated account in database: old name == " + oldAccountName +
899 ", new name == " + newAccountName + " (" + num + " rows updated )");
900
901 // update path for downloaded files
902 updateDownloadedFiles(db, newAccountName, oldAccountName);
903
904 db.setTransactionSuccessful();
905
906 } catch (SQLException e) {
907 Log_OC.e(TAG, "SQL Exception upgrading account names or paths in database", e);
908 } finally {
909 db.endTransaction();
910 }
911 }
912 } catch (Exception e) {
913 Log_OC.e(TAG, "Exception upgrading account names or paths in database", e);
914 }
915 }
916
917
918 /**
919 * Rename the local ownCloud folder of one account to match the a rename of the account itself. Updates the
920 * table of files in database so that the paths to the local files keep being the same.
921 *
922 * @param db Database where table of files is included.
923 * @param newAccountName New name for the target OC account.
924 * @param oldAccountName Old name of the target OC account.
925 */
926 private void updateDownloadedFiles(SQLiteDatabase db, String newAccountName,
927 String oldAccountName) {
928
929 String whereClause = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " +
930 ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL";
931
932 Cursor c = db.query(ProviderTableMeta.FILE_TABLE_NAME,
933 null,
934 whereClause,
935 new String[] { newAccountName },
936 null, null, null);
937
938 try {
939 if (c.moveToFirst()) {
940 // create storage path
941 String oldAccountPath = FileStorageUtils.getSavePath(oldAccountName);
942 String newAccountPath = FileStorageUtils.getSavePath(newAccountName);
943
944 // move files
945 File oldAccountFolder = new File(oldAccountPath);
946 File newAccountFolder = new File(newAccountPath);
947 oldAccountFolder.renameTo(newAccountFolder);
948
949 // update database
950 do {
951 // Update database
952 String oldPath = c.getString(
953 c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH));
954 OCFile file = new OCFile(
955 c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)));
956 String newPath = FileStorageUtils.getDefaultSavePathFor(newAccountName, file);
957
958 ContentValues cv = new ContentValues();
959 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, newPath);
960 db.update(ProviderTableMeta.FILE_TABLE_NAME,
961 cv,
962 ProviderTableMeta.FILE_STORAGE_PATH + "=?",
963 new String[]{oldPath});
964
965 Log_OC.v("SQL", "Updated path of downloaded file: old file name == " + oldPath +
966 ", new file name == " + newPath);
967
968 } while (c.moveToNext());
969 }
970 } finally {
971 c.close();
972 }
973
974 }
975
976 }