· 6 years ago · Jun 14, 2019, 07:10 AM
1private static final String t = "FormsProvider";
2
3private static final String DATABASE_NAME = "forms.db";
4private static final int DATABASE_VERSION = 4;
5private static final String FORMS_TABLE_NAME = "forms";
6
7private static HashMap<String, String> sFormsProjectionMap;
8
9private static final int FORMS = 1;
10private static final int FORM_ID = 2;
11
12private static final UriMatcher sUriMatcher;
13
14/**
15 * This class helps open, create, and upgrade the database file.
16 */
17private static class DatabaseHelper extends ODKSQLiteOpenHelper {
18 // These exist in database versions 2 and 3, but not in 4...
19 private static final String TEMP_FORMS_TABLE_NAME = "forms_v4";
20 private static final String MODEL_VERSION = "modelVersion";
21
22 DatabaseHelper(String databaseName) {
23 super(Collect.METADATA_PATH, databaseName, null, DATABASE_VERSION);
24 }
25
26
27 @Override
28 public void onCreate(SQLiteDatabase db) {
29 onCreateNamed(db, FORMS_TABLE_NAME);
30 }
31
32 private void onCreateNamed(SQLiteDatabase db, String tableName) {
33 db.execSQL("CREATE TABLE " + tableName + " ("
34 + FormsColumns._ID + " integer primary key, "
35 + FormsColumns.DISPLAY_NAME + " text not null, "
36 + FormsColumns.DISPLAY_SUBTEXT + " text not null, "
37 + FormsColumns.DESCRIPTION + " text, "
38 + FormsColumns.JR_FORM_ID + " text not null, "
39 + FormsColumns.JR_VERSION + " text, "
40 + FormsColumns.MD5_HASH + " text not null, "
41 + FormsColumns.DATE + " integer not null, " // milliseconds
42 + FormsColumns.FORM_MEDIA_PATH + " text not null, "
43 + FormsColumns.FORM_FILE_PATH + " text not null, "
44 + FormsColumns.LANGUAGE + " text, "
45 + FormsColumns.SUBMISSION_URI + " text, "
46 + FormsColumns.BASE64_RSA_PUBLIC_KEY + " text, "
47 + FormsColumns.JRCACHE_FILE_PATH + " text not null );");
48 }
49
50 @Override
51 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
52 int initialVersion = oldVersion;
53 if ( oldVersion < 2 ) {
54 Log.w(t, "Upgrading database from version " + oldVersion + " to " + newVersion
55 + ", which will destroy all old data");
56 db.execSQL("DROP TABLE IF EXISTS " + FORMS_TABLE_NAME);
57 onCreate(db);
58 return;
59 } else {
60 // adding BASE64_RSA_PUBLIC_KEY and changing type and name of integer MODEL_VERSION to text VERSION
61 db.execSQL("DROP TABLE IF EXISTS " + TEMP_FORMS_TABLE_NAME);
62 onCreateNamed(db, TEMP_FORMS_TABLE_NAME);
63 db.execSQL("INSERT INTO " + TEMP_FORMS_TABLE_NAME + " ("
64 + FormsColumns._ID + ", "
65 + FormsColumns.DISPLAY_NAME + ", "
66 + FormsColumns.DISPLAY_SUBTEXT + ", "
67 + FormsColumns.DESCRIPTION + ", "
68 + FormsColumns.JR_FORM_ID + ", "
69 + FormsColumns.MD5_HASH + ", "
70 + FormsColumns.DATE + ", " // milliseconds
71 + FormsColumns.FORM_MEDIA_PATH + ", "
72 + FormsColumns.FORM_FILE_PATH + ", "
73 + FormsColumns.LANGUAGE + ", "
74 + FormsColumns.SUBMISSION_URI + ", "
75 + FormsColumns.JR_VERSION + ", "
76 + ((oldVersion != 3) ? "" : (FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "))
77 + FormsColumns.JRCACHE_FILE_PATH + ") SELECT "
78 + FormsColumns._ID + ", "
79 + FormsColumns.DISPLAY_NAME + ", "
80 + FormsColumns.DISPLAY_SUBTEXT + ", "
81 + FormsColumns.DESCRIPTION + ", "
82 + FormsColumns.JR_FORM_ID + ", "
83 + FormsColumns.MD5_HASH + ", "
84 + FormsColumns.DATE + ", " // milliseconds
85 + FormsColumns.FORM_MEDIA_PATH + ", "
86 + FormsColumns.FORM_FILE_PATH + ", "
87 + FormsColumns.LANGUAGE + ", "
88 + FormsColumns.SUBMISSION_URI + ", "
89 + "CASE WHEN " + MODEL_VERSION + " IS NOT NULL THEN " +
90 "CAST(" + MODEL_VERSION + " AS TEXT) ELSE NULL END, "
91 + ((oldVersion != 3) ? "" : (FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "))
92 + FormsColumns.JRCACHE_FILE_PATH + " FROM " + FORMS_TABLE_NAME);
93
94 // risky failures here...
95 db.execSQL("DROP TABLE IF EXISTS " + FORMS_TABLE_NAME);
96 onCreateNamed(db, FORMS_TABLE_NAME);
97 db.execSQL("INSERT INTO " + FORMS_TABLE_NAME + " ("
98 + FormsColumns._ID + ", "
99 + FormsColumns.DISPLAY_NAME + ", "
100 + FormsColumns.DISPLAY_SUBTEXT + ", "
101 + FormsColumns.DESCRIPTION + ", "
102 + FormsColumns.JR_FORM_ID + ", "
103 + FormsColumns.MD5_HASH + ", "
104 + FormsColumns.DATE + ", " // milliseconds
105 + FormsColumns.FORM_MEDIA_PATH + ", "
106 + FormsColumns.FORM_FILE_PATH + ", "
107 + FormsColumns.LANGUAGE + ", "
108 + FormsColumns.SUBMISSION_URI + ", "
109 + FormsColumns.JR_VERSION + ", "
110 + FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "
111 + FormsColumns.JRCACHE_FILE_PATH + ") SELECT "
112 + FormsColumns._ID + ", "
113 + FormsColumns.DISPLAY_NAME + ", "
114 + FormsColumns.DISPLAY_SUBTEXT + ", "
115 + FormsColumns.DESCRIPTION + ", "
116 + FormsColumns.JR_FORM_ID + ", "
117 + FormsColumns.MD5_HASH + ", "
118 + FormsColumns.DATE + ", " // milliseconds
119 + FormsColumns.FORM_MEDIA_PATH + ", "
120 + FormsColumns.FORM_FILE_PATH + ", "
121 + FormsColumns.LANGUAGE + ", "
122 + FormsColumns.SUBMISSION_URI + ", "
123 + FormsColumns.JR_VERSION + ", "
124 + FormsColumns.BASE64_RSA_PUBLIC_KEY + ", "
125 + FormsColumns.JRCACHE_FILE_PATH + " FROM " + TEMP_FORMS_TABLE_NAME);
126 db.execSQL("DROP TABLE IF EXISTS " + TEMP_FORMS_TABLE_NAME);
127
128 Log.w(t, "Successfully upgraded database from version " + initialVersion + " to " + newVersion
129 + ", without destroying all the old data");
130 }
131 }
132}
133
134private DatabaseHelper mDbHelper;
135
136
137@Override
138public boolean onCreate() {
139
140 // must be at the beginning of any activity that can be called from an external intent
141 Collect.createODKDirs();
142
143 mDbHelper = new DatabaseHelper(DATABASE_NAME);
144 return true;
145}
146
147
148@Override
149public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
150 String sortOrder) {
151 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
152 qb.setTables(FORMS_TABLE_NAME);
153
154 switch (sUriMatcher.match(uri)) {
155 case FORMS:
156 qb.setProjectionMap(sFormsProjectionMap);
157 break;
158
159 case FORM_ID:
160 qb.setProjectionMap(sFormsProjectionMap);
161 qb.appendWhere(FormsColumns._ID + "=" + uri.getPathSegments().get(1));
162 break;
163
164 default:
165 throw new IllegalArgumentException("Unknown URI " + uri);
166 }
167
168 // Get the database and run the query
169 SQLiteDatabase db = mDbHelper.getReadableDatabase();
170 Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
171
172 // Tell the cursor what uri to watch, so it knows when its source data changes
173 c.setNotificationUri(getContext().getContentResolver(), uri);
174 return c;
175}
176
177
178@Override
179public String getType(Uri uri) {
180 switch (sUriMatcher.match(uri)) {
181 case FORMS:
182 return FormsColumns.CONTENT_TYPE;
183
184 case FORM_ID:
185 return FormsColumns.CONTENT_ITEM_TYPE;
186
187 default:
188 throw new IllegalArgumentException("Unknown URI " + uri);
189 }
190}
191
192
193@Override
194public synchronized Uri insert(Uri uri, ContentValues initialValues) {
195 // Validate the requested uri
196 if (sUriMatcher.match(uri) != FORMS) {
197 throw new IllegalArgumentException("Unknown URI " + uri);
198 }
199
200 ContentValues values;
201 if (initialValues != null) {
202 values = new ContentValues(initialValues);
203 } else {
204 values = new ContentValues();
205 }
206
207 if (!values.containsKey(FormsColumns.FORM_FILE_PATH)) {
208 throw new IllegalArgumentException(FormsColumns.FORM_FILE_PATH + " must be specified.");
209 }
210
211 // Normalize the file path.
212 // (don't trust the requester).
213 String filePath = values.getAsString(FormsColumns.FORM_FILE_PATH);
214 File form = new File(filePath);
215 filePath = form.getAbsolutePath(); // normalized
216 values.put(FormsColumns.FORM_FILE_PATH, filePath);
217
218 Long now = Long.valueOf(System.currentTimeMillis());
219
220 // Make sure that the necessary fields are all set
221 if (values.containsKey(FormsColumns.DATE) == false) {
222 values.put(FormsColumns.DATE, now);
223 }
224
225 if (values.containsKey(FormsColumns.DISPLAY_SUBTEXT) == false) {
226 Date today = new Date();
227 String ts = new SimpleDateFormat(getContext().getString(R.string.added_on_date_at_time)).format(today);
228 values.put(FormsColumns.DISPLAY_SUBTEXT, ts);
229 }
230
231 if (values.containsKey(FormsColumns.DISPLAY_NAME) == false) {
232 values.put(FormsColumns.DISPLAY_NAME, form.getName());
233 }
234
235 // don't let users put in a manual md5 hash
236 if (values.containsKey(FormsColumns.MD5_HASH)) {
237 values.remove(FormsColumns.MD5_HASH);
238 }
239 String md5 = FileUtils.getMd5Hash(form);
240 values.put(FormsColumns.MD5_HASH, md5);
241
242 if (values.containsKey(FormsColumns.JRCACHE_FILE_PATH) == false) {
243 String cachePath = Collect.CACHE_PATH + File.separator + md5 + ".formdef";
244 values.put(FormsColumns.JRCACHE_FILE_PATH, cachePath);
245 }
246 if (values.containsKey(FormsColumns.FORM_MEDIA_PATH) == false) {
247 String pathNoExtension = filePath.substring(0, filePath.lastIndexOf("."));
248 String mediaPath = pathNoExtension + "-media";
249 values.put(FormsColumns.FORM_MEDIA_PATH, mediaPath);
250 }
251
252 SQLiteDatabase db = mDbHelper.getWritableDatabase();
253
254 // first try to see if a record with this filename already exists...
255 String[] projection = {
256 FormsColumns._ID, FormsColumns.FORM_FILE_PATH
257 };
258 String[] selectionArgs = { filePath };
259 String selection = FormsColumns.FORM_FILE_PATH + "=?";
260 Cursor c = null;
261 try {
262 c = db.query(FORMS_TABLE_NAME, projection, selection, selectionArgs, null, null, null);
263 if ( c.getCount() > 0 ) {
264 // already exists
265 throw new SQLException("FAILED Insert into " + uri + " -- row already exists for form definition file: " + filePath);
266 }
267 } finally {
268 if ( c != null ) {
269 c.close();
270 }
271 }
272
273 long rowId = db.insert(FORMS_TABLE_NAME, null, values);
274 if (rowId > 0) {
275 Uri formUri = ContentUris.withAppendedId(FormsColumns.CONTENT_URI, rowId);
276 getContext().getContentResolver().notifyChange(formUri, null);
277 Collect.getInstance().getActivityLogger().logActionParam(this, "insert",
278 formUri.toString(), values.getAsString(FormsColumns.FORM_FILE_PATH));
279 return formUri;
280 }
281
282 throw new SQLException("Failed to insert row into " + uri);
283}
284
285
286private void deleteFileOrDir(String fileName) {
287 File file = new File(fileName);
288 if (file.exists()) {
289 if (file.isDirectory()) {
290 // delete any media entries for files in this directory...
291 int images = MediaUtils.deleteImagesInFolderFromMediaProvider(file);
292 int audio = MediaUtils.deleteAudioInFolderFromMediaProvider(file);
293 int video = MediaUtils.deleteVideoInFolderFromMediaProvider(file);
294
295 Log.i(t, "removed from content providers: " + images
296 + " image files, " + audio + " audio files,"
297 + " and " + video + " video files.");
298
299 // delete all the containing files
300 File[] files = file.listFiles();
301 for (File f : files) {
302 // should make this recursive if we get worried about
303 // the media directory containing directories
304 Log.i(t, "attempting to delete file: " + f.getAbsolutePath());
305 f.delete();
306 }
307 }
308 file.delete();
309 Log.i(t, "attempting to delete file: " + file.getAbsolutePath());
310 }
311}
312
313
314/**
315 * This method removes the entry from the content provider, and also removes any associated
316 * files. files: form.xml, [formmd5].formdef, formname-media {directory}
317 */
318@Override
319public int delete(Uri uri, String where, String[] whereArgs) {
320 SQLiteDatabase db = mDbHelper.getWritableDatabase();
321 int count;
322
323 switch (sUriMatcher.match(uri)) {
324 case FORMS:
325 Cursor del = null;
326 try {
327 del = this.query(uri, null, where, whereArgs, null);
328 del.moveToPosition(-1);
329 while (del.moveToNext()) {
330 deleteFileOrDir(del.getString(del
331 .getColumnIndex(FormsColumns.JRCACHE_FILE_PATH)));
332 String formFilePath = del.getString(del.getColumnIndex(FormsColumns.FORM_FILE_PATH));
333 Collect.getInstance().getActivityLogger().logAction(this, "delete", formFilePath);
334 deleteFileOrDir(formFilePath);
335 deleteFileOrDir(del.getString(del.getColumnIndex(FormsColumns.FORM_MEDIA_PATH)));
336 }
337 } finally {
338 if ( del != null ) {
339 del.close();
340 }
341 }
342 count = db.delete(FORMS_TABLE_NAME, where, whereArgs);
343 break;
344
345 case FORM_ID:
346 String formId = uri.getPathSegments().get(1);
347
348 Cursor c = null;
349 try {
350 c = this.query(uri, null, where, whereArgs, null);
351 // This should only ever return 1 record.
352 c.moveToPosition(-1);
353 while (c.moveToNext()) {
354 deleteFileOrDir(c.getString(c.getColumnIndex(FormsColumns.JRCACHE_FILE_PATH)));
355 String formFilePath = c.getString(c.getColumnIndex(FormsColumns.FORM_FILE_PATH));
356 Collect.getInstance().getActivityLogger().logAction(this, "delete", formFilePath);
357 deleteFileOrDir(formFilePath);
358 deleteFileOrDir(c.getString(c.getColumnIndex(FormsColumns.FORM_MEDIA_PATH)));
359 }
360 } finally {
361 if ( c != null ) {
362 c.close();
363 }
364 }
365
366 count =
367 db.delete(FORMS_TABLE_NAME,
368 FormsColumns._ID + "=" + formId
369 + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""),
370 whereArgs);
371 break;
372
373 default:
374 throw new IllegalArgumentException("Unknown URI " + uri);
375 }
376
377 getContext().getContentResolver().notifyChange(uri, null);
378 return count;
379}
380
381
382@Override
383public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
384 SQLiteDatabase db = mDbHelper.getWritableDatabase();
385 int count = 0;
386 switch (sUriMatcher.match(uri)) {
387 case FORMS:
388 // don't let users manually update md5
389 if (values.containsKey(FormsColumns.MD5_HASH)) {
390 values.remove(FormsColumns.MD5_HASH);
391 }
392 // if values contains path, then all filepaths and md5s will get updated
393 // this probably isn't a great thing to do.
394 if (values.containsKey(FormsColumns.FORM_FILE_PATH)) {
395 String formFile = values.getAsString(FormsColumns.FORM_FILE_PATH);
396 values.put(FormsColumns.MD5_HASH, FileUtils.getMd5Hash(new File(formFile)));
397 }
398
399 Cursor c = null;
400 try {
401 c = this.query(uri, null, where, whereArgs, null);
402
403 if (c.getCount() > 0) {
404 c.moveToPosition(-1);
405 while (c.moveToNext()) {
406 // before updating the paths, delete all the files
407 if (values.containsKey(FormsColumns.FORM_FILE_PATH)) {
408 String newFile = values.getAsString(FormsColumns.FORM_FILE_PATH);
409 String delFile =
410 c.getString(c.getColumnIndex(FormsColumns.FORM_FILE_PATH));
411 if (newFile.equalsIgnoreCase(delFile)) {
412 // same file, so don't delete anything
413 } else {
414 // different files, delete the old one
415 deleteFileOrDir(delFile);
416 }
417
418 // either way, delete the old cache because we'll calculate a new one.
419 deleteFileOrDir(c.getString(c
420 .getColumnIndex(FormsColumns.JRCACHE_FILE_PATH)));
421 }
422 }
423 }
424 } finally {
425 if ( c != null ) {
426 c.close();
427 }
428 }
429
430 // Make sure that the necessary fields are all set
431 if (values.containsKey(FormsColumns.DATE) == true) {
432 Date today = new Date();
433 String ts = new SimpleDateFormat(getContext().getString(R.string.added_on_date_at_time)).format(today);
434 values.put(FormsColumns.DISPLAY_SUBTEXT, ts);
435 }
436
437 count = db.update(FORMS_TABLE_NAME, values, where, whereArgs);
438 break;
439
440 case FORM_ID:
441 String formId = uri.getPathSegments().get(1);
442 // Whenever file paths are updated, delete the old files.
443
444 Cursor update = null;
445 try {
446 update = this.query(uri, null, where, whereArgs, null);
447
448 // This should only ever return 1 record.
449 if (update.getCount() > 0) {
450 update.moveToFirst();
451
452 // don't let users manually update md5
453 if (values.containsKey(FormsColumns.MD5_HASH)) {
454 values.remove(FormsColumns.MD5_HASH);
455 }
456
457 // the order here is important (jrcache needs to be before form file)
458 // because we update the jrcache file if there's a new form file
459 if (values.containsKey(FormsColumns.JRCACHE_FILE_PATH)) {
460 deleteFileOrDir(update.getString(update
461 .getColumnIndex(FormsColumns.JRCACHE_FILE_PATH)));
462 }
463
464 if (values.containsKey(FormsColumns.FORM_FILE_PATH)) {
465 String formFile = values.getAsString(FormsColumns.FORM_FILE_PATH);
466 String oldFile =
467 update.getString(update.getColumnIndex(FormsColumns.FORM_FILE_PATH));
468
469 if (formFile != null && formFile.equalsIgnoreCase(oldFile)) {
470 // Files are the same, so we may have just copied over something we had
471 // already
472 } else {
473 // New file name. This probably won't ever happen, though.
474 deleteFileOrDir(oldFile);
475 }
476
477 // we're updating our file, so update the md5
478 // and get rid of the cache (doesn't harm anything)
479 deleteFileOrDir(update.getString(update
480 .getColumnIndex(FormsColumns.JRCACHE_FILE_PATH)));
481 String newMd5 = FileUtils.getMd5Hash(new File(formFile));
482 values.put(FormsColumns.MD5_HASH, newMd5);
483 values.put(FormsColumns.JRCACHE_FILE_PATH,
484 Collect.CACHE_PATH + File.separator + newMd5 + ".formdef");
485 }
486
487 // Make sure that the necessary fields are all set
488 if (values.containsKey(FormsColumns.DATE) == true) {
489 Date today = new Date();
490 String ts =
491 new SimpleDateFormat(getContext().getString(R.string.added_on_date_at_time)).format(today);
492 values.put(FormsColumns.DISPLAY_SUBTEXT, ts);
493 }
494
495 count =
496 db.update(FORMS_TABLE_NAME, values, FormsColumns._ID + "=" + formId
497 + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""),
498 whereArgs);
499 } else {
500 Log.e(t, "Attempting to update row that does not exist");
501 }
502 } finally {
503 if ( update != null ) {
504 update.close();
505 }
506 }
507 break;
508
509 default:
510 throw new IllegalArgumentException("Unknown URI " + uri);
511 }
512
513 getContext().getContentResolver().notifyChange(uri, null);
514 return count;
515}
516
517static {
518 sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
519 sUriMatcher.addURI(FormsProviderAPI.AUTHORITY, "forms", FORMS);
520 sUriMatcher.addURI(FormsProviderAPI.AUTHORITY, "forms/#", FORM_ID);
521
522 sFormsProjectionMap = new HashMap<String, String>();
523 sFormsProjectionMap.put(FormsColumns._ID, FormsColumns._ID);
524 sFormsProjectionMap.put(FormsColumns.DISPLAY_NAME, FormsColumns.DISPLAY_NAME);
525 sFormsProjectionMap.put(FormsColumns.DISPLAY_SUBTEXT, FormsColumns.DISPLAY_SUBTEXT);
526 sFormsProjectionMap.put(FormsColumns.DESCRIPTION, FormsColumns.DESCRIPTION);
527 sFormsProjectionMap.put(FormsColumns.JR_FORM_ID, FormsColumns.JR_FORM_ID);
528 sFormsProjectionMap.put(FormsColumns.JR_VERSION, FormsColumns.JR_VERSION);
529 sFormsProjectionMap.put(FormsColumns.SUBMISSION_URI, FormsColumns.SUBMISSION_URI);
530 sFormsProjectionMap.put(FormsColumns.BASE64_RSA_PUBLIC_KEY, FormsColumns.BASE64_RSA_PUBLIC_KEY);
531 sFormsProjectionMap.put(FormsColumns.MD5_HASH, FormsColumns.MD5_HASH);
532 sFormsProjectionMap.put(FormsColumns.DATE, FormsColumns.DATE);
533 sFormsProjectionMap.put(FormsColumns.FORM_MEDIA_PATH, FormsColumns.FORM_MEDIA_PATH);
534 sFormsProjectionMap.put(FormsColumns.FORM_FILE_PATH, FormsColumns.FORM_FILE_PATH);
535 sFormsProjectionMap.put(FormsColumns.JRCACHE_FILE_PATH, FormsColumns.JRCACHE_FILE_PATH);
536 sFormsProjectionMap.put(FormsColumns.LANGUAGE, FormsColumns.LANGUAGE);
537}