Merge branch 'develop' into refresh_folder_contents_when_browsed_into
[pub/Android/ownCloud.git] / src / com / owncloud / android / providers / FileContentProvider.java
1 /* ownCloud Android client application
2 * Copyright (C) 2011 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 version 2,
7 * as published by the Free Software Foundation.
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.providers;
20
21 import java.util.ArrayList;
22 import java.util.HashMap;
23
24 import com.owncloud.android.Log_OC;
25 import com.owncloud.android.R;
26 import com.owncloud.android.datamodel.FileDataStorageManager;
27 import com.owncloud.android.db.ProviderMeta;
28 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
29
30
31 import android.content.ContentProvider;
32 import android.content.ContentProviderOperation;
33 import android.content.ContentProviderResult;
34 import android.content.ContentUris;
35 import android.content.ContentValues;
36 import android.content.Context;
37 import android.content.OperationApplicationException;
38 import android.content.UriMatcher;
39 import android.database.Cursor;
40 import android.database.SQLException;
41 import android.database.sqlite.SQLiteDatabase;
42 import android.database.sqlite.SQLiteOpenHelper;
43 import android.database.sqlite.SQLiteQueryBuilder;
44 import android.net.Uri;
45 import android.text.TextUtils;
46
47 /**
48 * The ContentProvider for the ownCloud App.
49 *
50 * @author Bartek Przybylski
51 * @author David A. Velasco
52 *
53 */
54 public class FileContentProvider extends ContentProvider {
55
56 private DataBaseHelper mDbHelper;
57
58 private static HashMap<String, String> mProjectionMap;
59 static {
60 mProjectionMap = new HashMap<String, String>();
61 mProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID);
62 mProjectionMap.put(ProviderTableMeta.FILE_PARENT,
63 ProviderTableMeta.FILE_PARENT);
64 mProjectionMap.put(ProviderTableMeta.FILE_PATH,
65 ProviderTableMeta.FILE_PATH);
66 mProjectionMap.put(ProviderTableMeta.FILE_NAME,
67 ProviderTableMeta.FILE_NAME);
68 mProjectionMap.put(ProviderTableMeta.FILE_CREATION,
69 ProviderTableMeta.FILE_CREATION);
70 mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED,
71 ProviderTableMeta.FILE_MODIFIED);
72 mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,
73 ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA);
74 mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH,
75 ProviderTableMeta.FILE_CONTENT_LENGTH);
76 mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE,
77 ProviderTableMeta.FILE_CONTENT_TYPE);
78 mProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH,
79 ProviderTableMeta.FILE_STORAGE_PATH);
80 mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE,
81 ProviderTableMeta.FILE_LAST_SYNC_DATE);
82 mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA,
83 ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA);
84 mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC,
85 ProviderTableMeta.FILE_KEEP_IN_SYNC);
86 mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER,
87 ProviderTableMeta.FILE_ACCOUNT_OWNER);
88 mProjectionMap.put(ProviderTableMeta.FILE_ETAG,
89 ProviderTableMeta.FILE_ETAG);
90 }
91
92 private static final int SINGLE_FILE = 1;
93 private static final int DIRECTORY = 2;
94 private static final int ROOT_DIRECTORY = 3;
95
96 private UriMatcher mUriMatcher;
97
98 @Override
99 public int delete(Uri uri, String where, String[] whereArgs) {
100 //Log_OC.d(TAG, "Deleting " + uri + " at provider " + this);
101 int count = 0;
102 SQLiteDatabase db = mDbHelper.getWritableDatabase();
103 db.beginTransaction();
104 try {
105 count = delete(db, uri, where, whereArgs);
106 db.setTransactionSuccessful();
107 } finally {
108 db.endTransaction();
109 }
110 getContext().getContentResolver().notifyChange(uri, null);
111 return count;
112 }
113
114
115 private int delete(SQLiteDatabase db, Uri uri, String where, String[] whereArgs) {
116 int count = 0;
117 switch (mUriMatcher.match(uri)) {
118 case SINGLE_FILE:
119 /*Cursor c = query(db, uri, null, where, whereArgs, null);
120 String remotePath = "(unexisting)";
121 if (c != null && c.moveToFirst()) {
122 remotePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH));
123 }
124 Log_OC.d(TAG, "Removing FILE " + remotePath);
125 */
126 count = db.delete(ProviderTableMeta.DB_NAME,
127 ProviderTableMeta._ID
128 + "="
129 + uri.getPathSegments().get(1)
130 + (!TextUtils.isEmpty(where) ? " AND (" + where
131 + ")" : ""), whereArgs);
132 /* just for log
133 if (c!=null) {
134 c.close();
135 }
136 */
137 break;
138 case DIRECTORY:
139 // deletion of folder is recursive
140 /*
141 Uri folderUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, Long.parseLong(uri.getPathSegments().get(1)));
142 Cursor folder = query(db, folderUri, null, null, null, null);
143 String folderName = "(unknown)";
144 if (folder != null && folder.moveToFirst()) {
145 folderName = folder.getString(folder.getColumnIndex(ProviderTableMeta.FILE_PATH));
146 }
147 */
148 Cursor children = query(uri, null, null, null, null);
149 if (children != null && children.moveToFirst()) {
150 long childId;
151 boolean isDir;
152 //String remotePath;
153 while (!children.isAfterLast()) {
154 childId = children.getLong(children.getColumnIndex(ProviderTableMeta._ID));
155 isDir = "DIR".equals(children.getString(children.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)));
156 //remotePath = children.getString(children.getColumnIndex(ProviderTableMeta.FILE_PATH));
157 if (isDir) {
158 count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId), null, null);
159 } else {
160 count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId), null, null);
161 }
162 children.moveToNext();
163 }
164 children.close();
165 } /*else {
166 Log_OC.d(TAG, "No child to remove in DIRECTORY " + folderName);
167 }
168 Log_OC.d(TAG, "Removing DIRECTORY " + folderName + " (or maybe not) ");
169 */
170 count += db.delete(ProviderTableMeta.DB_NAME,
171 ProviderTableMeta._ID
172 + "="
173 + uri.getPathSegments().get(1)
174 + (!TextUtils.isEmpty(where) ? " AND (" + where
175 + ")" : ""), whereArgs);
176 /* Just for log
177 if (folder != null) {
178 folder.close();
179 }*/
180 break;
181 case ROOT_DIRECTORY:
182 //Log_OC.d(TAG, "Removing ROOT!");
183 count = db.delete(ProviderTableMeta.DB_NAME, where, whereArgs);
184 break;
185 default:
186 //Log_OC.e(TAG, "Unknown uri " + uri);
187 throw new IllegalArgumentException("Unknown uri: " + uri.toString());
188 }
189 return count;
190 }
191
192
193 @Override
194 public String getType(Uri uri) {
195 switch (mUriMatcher.match(uri)) {
196 case ROOT_DIRECTORY:
197 return ProviderTableMeta.CONTENT_TYPE;
198 case SINGLE_FILE:
199 return ProviderTableMeta.CONTENT_TYPE_ITEM;
200 default:
201 throw new IllegalArgumentException("Unknown Uri id."
202 + uri.toString());
203 }
204 }
205
206 @Override
207 public Uri insert(Uri uri, ContentValues values) {
208 //Log_OC.d(TAG, "Inserting " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this);
209 Uri newUri = null;
210 SQLiteDatabase db = mDbHelper.getWritableDatabase();
211 db.beginTransaction();
212 try {
213 newUri = insert(db, uri, values);
214 db.setTransactionSuccessful();
215 } finally {
216 db.endTransaction();
217 }
218 getContext().getContentResolver().notifyChange(newUri, null);
219 return newUri;
220 }
221
222 private Uri insert(SQLiteDatabase db, Uri uri, ContentValues values) {
223 if (mUriMatcher.match(uri) != SINGLE_FILE &&
224 mUriMatcher.match(uri) != ROOT_DIRECTORY) {
225 //Log_OC.e(TAG, "Inserting invalid URI: " + uri);
226 throw new IllegalArgumentException("Unknown uri id: " + uri);
227 }
228
229 long rowId = db.insert(ProviderTableMeta.DB_NAME, null, values);
230 if (rowId > 0) {
231 Uri insertedFileUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId);
232 //Log_OC.d(TAG, "Inserted " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this);
233 return insertedFileUri;
234 } else {
235 //Log_OC.d(TAG, "Error while inserting " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this);
236 throw new SQLException("ERROR " + uri);
237 }
238 }
239
240
241 @Override
242 public boolean onCreate() {
243 mDbHelper = new DataBaseHelper(getContext());
244
245 String authority = getContext().getResources().getString(R.string.authority);
246 mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
247 mUriMatcher.addURI(authority, null, ROOT_DIRECTORY);
248 mUriMatcher.addURI(authority, "file/", SINGLE_FILE);
249 mUriMatcher.addURI(authority, "file/#", SINGLE_FILE);
250 mUriMatcher.addURI(authority, "dir/", DIRECTORY);
251 mUriMatcher.addURI(authority, "dir/#", DIRECTORY);
252
253 return true;
254 }
255
256
257 @Override
258 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
259 Cursor result = null;
260 SQLiteDatabase db = mDbHelper.getReadableDatabase();
261 db.beginTransaction();
262 try {
263 result = query(db, uri, projection, selection, selectionArgs, sortOrder);
264 db.setTransactionSuccessful();
265 } finally {
266 db.endTransaction();
267 }
268 return result;
269 }
270
271 private Cursor query(SQLiteDatabase db, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
272 SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder();
273
274 sqlQuery.setTables(ProviderTableMeta.DB_NAME);
275 sqlQuery.setProjectionMap(mProjectionMap);
276
277 switch (mUriMatcher.match(uri)) {
278 case ROOT_DIRECTORY:
279 break;
280 case DIRECTORY:
281 String folderId = uri.getPathSegments().get(1);
282 sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "="
283 + folderId);
284 break;
285 case SINGLE_FILE:
286 if (uri.getPathSegments().size() > 1) {
287 sqlQuery.appendWhere(ProviderTableMeta._ID + "="
288 + uri.getPathSegments().get(1));
289 }
290 break;
291 default:
292 throw new IllegalArgumentException("Unknown uri id: " + uri);
293 }
294
295 String order;
296 if (TextUtils.isEmpty(sortOrder)) {
297 order = ProviderTableMeta.DEFAULT_SORT_ORDER;
298 } else {
299 order = sortOrder;
300 }
301
302 // DB case_sensitive
303 db.execSQL("PRAGMA case_sensitive_like = true");
304 Cursor c = sqlQuery.query(db, projection, selection, selectionArgs, null, null, order);
305 c.setNotificationUri(getContext().getContentResolver(), uri);
306 return c;
307 }
308
309 @Override
310 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
311
312 //Log_OC.d(TAG, "Updating " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this);
313 int count = 0;
314 SQLiteDatabase db = mDbHelper.getWritableDatabase();
315 db.beginTransaction();
316 try {
317 count = update(db, uri, values, selection, selectionArgs);
318 db.setTransactionSuccessful();
319 } finally {
320 db.endTransaction();
321 }
322 getContext().getContentResolver().notifyChange(uri, null);
323 return count;
324 }
325
326
327 private int update(SQLiteDatabase db, Uri uri, ContentValues values, String selection, String[] selectionArgs) {
328 switch (mUriMatcher.match(uri)) {
329 case DIRECTORY:
330 return updateFolderSize(db, selectionArgs[0]);
331 default:
332 return db.update(ProviderTableMeta.DB_NAME, values, selection, selectionArgs);
333 }
334 }
335
336
337 private int updateFolderSize(SQLiteDatabase db, String folderId) {
338 int count = 0;
339 String [] whereArgs = new String[] { folderId };
340
341 // read current size saved for the folder
342 long folderSize = 0;
343 long folderParentId = -1;
344 Uri selectFolderUri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, folderId);
345 String[] folderProjection = new String[] { ProviderTableMeta.FILE_CONTENT_LENGTH, ProviderTableMeta.FILE_PARENT};
346 String folderWhere = ProviderTableMeta._ID + "=?";
347 Cursor folderCursor = query(db, selectFolderUri, folderProjection, folderWhere, whereArgs, null);
348 if (folderCursor != null && folderCursor.moveToFirst()) {
349 folderSize = folderCursor.getLong(folderCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH));;
350 folderParentId = folderCursor.getLong(folderCursor.getColumnIndex(ProviderTableMeta.FILE_PARENT));;
351 }
352 folderCursor.close();
353
354 // read and sum sizes of children
355 long childrenSize = 0;
356 Uri selectChildrenUri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, folderId);
357 String[] childrenProjection = new String[] { ProviderTableMeta.FILE_CONTENT_LENGTH, ProviderTableMeta.FILE_PARENT};
358 String childrenWhere = ProviderTableMeta.FILE_PARENT + "=?";
359 Cursor childrenCursor = query(db, selectChildrenUri, childrenProjection, childrenWhere, whereArgs, null);
360 if (childrenCursor != null && childrenCursor.moveToFirst()) {
361 while (!childrenCursor.isAfterLast()) {
362 childrenSize += childrenCursor.getLong(childrenCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH));
363 childrenCursor.moveToNext();
364 }
365 }
366 childrenCursor.close();
367
368 // update if needed
369 if (folderSize != childrenSize) {
370 Log_OC.d("FileContentProvider", "Updating " + folderSize + " to " + childrenSize);
371 ContentValues cv = new ContentValues();
372 cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, childrenSize);
373 count = db.update(ProviderTableMeta.DB_NAME, cv, folderWhere, whereArgs);
374
375 // propagate update until root
376 if (folderParentId > FileDataStorageManager.ROOT_PARENT_ID) {
377 Log_OC.d("FileContentProvider", "Propagating update to " + folderParentId);
378 updateFolderSize(db, String.valueOf(folderParentId));
379 } else {
380 Log_OC.d("FileContentProvider", "NOT propagating to " + folderParentId);
381 }
382 } else {
383 Log_OC.d("FileContentProvider", "NOT updating, sizes are " + folderSize + " and " + childrenSize);
384 }
385 return count;
386 }
387
388
389 @Override
390 public ContentProviderResult[] applyBatch (ArrayList<ContentProviderOperation> operations) throws OperationApplicationException {
391 Log_OC.d("FileContentProvider", "applying batch in provider " + this + " (temporary: " + isTemporary() + ")" );
392 ContentProviderResult[] results = new ContentProviderResult[operations.size()];
393 int i=0;
394
395 SQLiteDatabase db = mDbHelper.getWritableDatabase();
396 db.beginTransaction(); // it's supposed that transactions can be nested
397 try {
398 for (ContentProviderOperation operation : operations) {
399 results[i] = operation.apply(this, results, i);
400 i++;
401 }
402 db.setTransactionSuccessful();
403 } finally {
404 db.endTransaction();
405 }
406 Log_OC.d("FileContentProvider", "applied batch in provider " + this);
407 return results;
408 }
409
410
411 class DataBaseHelper extends SQLiteOpenHelper {
412
413 public DataBaseHelper(Context context) {
414 super(context, ProviderMeta.DB_NAME, null, ProviderMeta.DB_VERSION);
415
416 }
417
418 @Override
419 public void onCreate(SQLiteDatabase db) {
420 // files table
421 Log_OC.i("SQL", "Entering in onCreate");
422 db.execSQL("CREATE TABLE " + ProviderTableMeta.DB_NAME + "("
423 + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "
424 + ProviderTableMeta.FILE_NAME + " TEXT, "
425 + ProviderTableMeta.FILE_PATH + " TEXT, "
426 + ProviderTableMeta.FILE_PARENT + " INTEGER, "
427 + ProviderTableMeta.FILE_CREATION + " INTEGER, "
428 + ProviderTableMeta.FILE_MODIFIED + " INTEGER, "
429 + ProviderTableMeta.FILE_CONTENT_TYPE + " TEXT, "
430 + ProviderTableMeta.FILE_CONTENT_LENGTH + " INTEGER, "
431 + ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, "
432 + ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, "
433 + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "
434 + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "
435 + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, "
436 + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER, "
437 + ProviderTableMeta.FILE_ETAG + " TEXT );"
438 );
439 }
440
441 @Override
442 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
443 Log_OC.i("SQL", "Entering in onUpgrade");
444 boolean upgraded = false;
445 if (oldVersion == 1 && newVersion >= 2) {
446 Log_OC.i("SQL", "Entering in the #1 ADD in onUpgrade");
447 db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
448 " ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " +
449 " DEFAULT 0");
450 upgraded = true;
451 }
452 if (oldVersion < 3 && newVersion >= 3) {
453 Log_OC.i("SQL", "Entering in the #2 ADD in onUpgrade");
454 db.beginTransaction();
455 try {
456 db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
457 " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER " +
458 " DEFAULT 0");
459
460 // assume there are not local changes pending to upload
461 db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME +
462 " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + System.currentTimeMillis() +
463 " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");
464
465 upgraded = true;
466 db.setTransactionSuccessful();
467 } finally {
468 db.endTransaction();
469 }
470 }
471 if (oldVersion < 4 && newVersion >= 4) {
472 Log_OC.i("SQL", "Entering in the #3 ADD in onUpgrade");
473 db.beginTransaction();
474 try {
475 db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
476 " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER " +
477 " DEFAULT 0");
478
479 db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME +
480 " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + ProviderTableMeta.FILE_MODIFIED +
481 " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");
482
483 upgraded = true;
484 db.setTransactionSuccessful();
485 } finally {
486 db.endTransaction();
487 }
488 }
489 if (!upgraded)
490 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);
491
492 if (oldVersion < 5 && newVersion >= 5) {
493 Log_OC.i("SQL", "Entering in the #4 ADD in onUpgrade");
494 db.beginTransaction();
495 try {
496 db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
497 " ADD COLUMN " + ProviderTableMeta.FILE_ETAG + " TEXT " +
498 " DEFAULT NULL");
499
500 upgraded = true;
501 db.setTransactionSuccessful();
502 } finally {
503 db.endTransaction();
504 }
505 }
506 if (!upgraded)
507 Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);
508 }
509 }
510
511 }