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