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