blob: fa3bdfe7cbfbe0a7bb6df3900349a6ade4839b04 [file] [log] [blame]
Mike Lockwood02503612010-07-02 14:03:31 -04001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "MtpSqliteDatabase"
18
19#include "MtpDebug.h"
20#include "MtpSqliteDatabase.h"
21#include "MtpDataPacket.h"
22#include "MtpUtils.h"
23#include "SqliteDatabase.h"
24#include "SqliteStatement.h"
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <sqlite3.h>
29
30namespace android {
31
32#define FILE_ID_COLUMN 1
33#define FILE_PATH_COLUMN 2
34#define FILE_FORMAT_COLUMN 3
35#define FILE_PARENT_COLUMN 4
36#define FILE_STORAGE_COLUMN 5
37#define FILE_SIZE_COLUMN 6
38#define FILE_MODIFIED_COLUMN 7
39
40#define AUDIO_ID_COLUMN 1
41#define AUDIO_TITLE_COLUMN 2
42#define AUDIO_ARTIST_COLUMN 3
43#define AUDIO_ALBUM_COLUMN 4
44#define AUDIO_ALBUM_ARTIST_COLUMN 5
45#define AUDIO_GENRE_COLUMN 6
46#define AUDIO_COMPOSER_COLUMN 7
47#define AUDIO_TRACK_NUMBER_COLUMN 8
48#define AUDIO_YEAR_COLUMN 9
49#define AUDIO_DURATION_COLUMN 10
50#define AUDIO_USE_COUNT_COLUMN 11
51#define AUDIO_SAMPLE_RATE_COLUMN 12
52#define AUDIO_NUM_CHANNELS_COLUMN 13
53#define AUDIO_AUDIO_WAVE_CODEC_COLUMN 14
54#define AUDIO_AUDIO_BIT_RATE_COLUMN 15
55
56#define FILE_TABLE_CREATE "CREATE TABLE IF NOT EXISTS files (" \
57 "_id INTEGER PRIMARY KEY," \
58 "path TEXT," \
59 "format INTEGER," \
60 "parent INTEGER," \
61 "storage INTEGER," \
62 "size INTEGER," \
63 "date_modified INTEGER" \
64 ");"
65
66#define AUDIO_TABLE_CREATE "CREATE TABLE IF NOT EXISTS audio (" \
67 "id INTEGER PRIMARY KEY," \
68 "title TEXT," \
69 "artist TEXT," \
70 "album TEXT," \
71 "album_artist TEXT," \
72 "genre TEXT," \
73 "composer TEXT," \
74 "track_number INTEGER," \
75 "year INTEGER," \
76 "duration INTEGER," \
77 "use_count INTEGER," \
78 "sample_rate INTEGER," \
79 "num_channels INTEGER," \
80 "audio_wave_codec TEXT," \
81 "audio_bit_rate INTEGER" \
82 ");"
83
84#define PATH_INDEX_CREATE "CREATE INDEX IF NOT EXISTS path_index on files(path);"
85
86#define FILE_ID_QUERY "SELECT _id,format FROM files WHERE path = ?;"
87#define FILE_PATH_QUERY "SELECT path,size FROM files WHERE _id = ?"
88
89#define GET_OBJECT_INFO_QUERY "SELECT storage,format,parent,path,size,date_modified FROM files WHERE _id = ?;"
90#define FILE_INSERT "INSERT INTO files VALUES(?,?,?,?,?,?,?);"
91#define FILE_DELETE "DELETE FROM files WHERE _id = ?;"
92
93#define AUDIO_INSERT "INSERT INTO audio VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"
94#define AUDIO_DELETE "DELETE FROM audio WHERE id = ?;"
95
96struct PropertyTableEntry {
97 MtpObjectProperty property;
98 int type;
99 const char* columnName;
100};
101
102static const PropertyTableEntry kPropertyTable[] = {
103 { MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32, "parent" },
104 { MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32, "storage" },
105 { MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT32, "format" },
106 { MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR, "path" },
107 { MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64, "size" },
108 { MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR, "date_modified" },
109};
110
111static bool getPropertyInfo(MtpObjectProperty property, int& type, const char*& columnName) {
112 int count = sizeof(kPropertyTable) / sizeof(kPropertyTable[0]);
113 const PropertyTableEntry* entry = kPropertyTable;
114 for (int i = 0; i < count; i++, entry++) {
115 if (entry->property == property) {
116 type = entry->type;
117 columnName = entry->columnName;
118 return true;
119 }
120 }
121 return false;
122}
123
124MtpSqliteDatabase::MtpSqliteDatabase()
125 : mDatabase(NULL),
126 mFileIdQuery(NULL),
127 mFilePathQuery(NULL),
128 mObjectInfoQuery(NULL),
129 mFileInserter(NULL),
130 mFileDeleter(NULL),
131 mAudioInserter(NULL),
132 mAudioDeleter(NULL)
133{
134}
135
136MtpSqliteDatabase::~MtpSqliteDatabase() {
137 delete mDatabase;
138 delete mFileIdQuery;
139 delete mFilePathQuery;
140 delete mObjectInfoQuery;
141 delete mFileInserter;
142 delete mFileDeleter;
143 delete mAudioInserter;
144 delete mAudioDeleter;
145}
146
147bool MtpSqliteDatabase::open(const char* path, bool create) {
148 mDatabase = new SqliteDatabase;
149
150 if (!mDatabase->open(path, create))
151 goto fail;
152
153 // create tables and indices if necessary
154 if (!mDatabase->exec(FILE_TABLE_CREATE)) {
155 LOGE("could not create file table");
156 goto fail;
157 }
158 if (!mDatabase->exec(PATH_INDEX_CREATE)) {
159 LOGE("could not path index on file table");
160 goto fail;
161 }
162 if (!mDatabase->exec(AUDIO_TABLE_CREATE)) {
163 LOGE("could not create file table");
164 goto fail;
165 }
166
167 if (!mFileIdQuery) {
168 mFileIdQuery = new SqliteStatement(mDatabase);
169 if (!mFileIdQuery->prepare(FILE_ID_QUERY)) {
170 LOGE("could not compile FILE_ID_QUERY");
171 goto fail;
172 }
173 }
174 if (!mFilePathQuery) {
175 mFilePathQuery = new SqliteStatement(mDatabase);
176 if (!mFilePathQuery->prepare(FILE_PATH_QUERY)) {
177 LOGE("could not compile FILE_PATH_QUERY");
178 goto fail;
179 }
180 }
181 if (!mObjectInfoQuery) {
182 mObjectInfoQuery = new SqliteStatement(mDatabase);
183 if (!mObjectInfoQuery->prepare(GET_OBJECT_INFO_QUERY)) {
184 LOGE("could not compile GET_OBJECT_INFO_QUERY");
185 goto fail;
186 }
187 }
188 if (!mFileInserter) {
189 mFileInserter = new SqliteStatement(mDatabase);
190 if (!mFileInserter->prepare(FILE_INSERT)) {
191 LOGE("could not compile FILE_INSERT\n");
192 goto fail;
193 }
194 }
195 if (!mFileDeleter) {
196 mFileDeleter = new SqliteStatement(mDatabase);
197 if (!mFileDeleter->prepare(FILE_DELETE)) {
198 LOGE("could not compile FILE_DELETE\n");
199 goto fail;
200 }
201 }
202 if (!mAudioInserter) {
203 mAudioInserter = new SqliteStatement(mDatabase);
204 if (!mAudioInserter->prepare(AUDIO_INSERT)) {
205 LOGE("could not compile AUDIO_INSERT\n");
206 goto fail;
207 }
208 }
209 if (!mAudioDeleter) {
210 mAudioDeleter = new SqliteStatement(mDatabase);
211 if (!mAudioDeleter->prepare(AUDIO_DELETE)) {
212 LOGE("could not compile AUDIO_DELETE\n");
213 goto fail;
214 }
215 }
216
217 return true;
218
219fail:
220 delete mDatabase;
221 delete mFileIdQuery;
222 delete mFilePathQuery;
223 delete mObjectInfoQuery;
224 delete mFileInserter;
225 delete mFileDeleter;
226 delete mAudioInserter;
227 delete mAudioDeleter;
228 mDatabase = NULL;
229 mFileIdQuery = NULL;
230 mFilePathQuery = NULL;
231 mObjectInfoQuery = NULL;
232 mFileInserter = NULL;
233 mFileDeleter = NULL;
234 mAudioInserter = NULL;
235 mAudioDeleter = NULL;
236 return false;
237}
238
239void MtpSqliteDatabase::close() {
240 if (mDatabase) {
241 mDatabase->close();
242 mDatabase = NULL;
243 }
244}
245
246MtpObjectHandle MtpSqliteDatabase::getObjectHandle(const char* path) {
247 mFileIdQuery->reset();
248 mFileIdQuery->bind(1, path);
249 if (mFileIdQuery->step()) {
250 int row = mFileIdQuery->getColumnInt(0);
251 if (row > 0) {
252 MtpObjectFormat format = mFileIdQuery->getColumnInt(1);
253 row |= getTableForFile(format);
254 return row;
255 }
256 }
257
258 return 0;
259}
260
261MtpObjectHandle MtpSqliteDatabase::addFile(const char* path,
262 MtpObjectFormat format,
263 MtpObjectHandle parent,
264 MtpStorageID storage,
265 uint64_t size,
266 time_t modified) {
267 mFileInserter->bind(FILE_PATH_COLUMN, path);
268 mFileInserter->bind(FILE_FORMAT_COLUMN, format);
269 mFileInserter->bind(FILE_PARENT_COLUMN, parent);
270 mFileInserter->bind(FILE_STORAGE_COLUMN, storage);
271 mFileInserter->bind(FILE_SIZE_COLUMN, size);
272 mFileInserter->bind(FILE_MODIFIED_COLUMN, modified);
273 mFileInserter->step();
274 mFileInserter->reset();
275 int result = mDatabase->lastInsertedRow();
276 return (result <= 0 ? kInvalidObjectHandle : result);
277}
278
279MtpObjectHandle MtpSqliteDatabase::addAudioFile(MtpObjectHandle handle) {
280 mAudioInserter->bind(AUDIO_ID_COLUMN, handle);
281 mAudioInserter->step();
282 mAudioInserter->reset();
283 int result = mDatabase->lastInsertedRow();
284 handle |= kObjectHandleTableAudio;
285 return (result > 0 ? handle : kInvalidObjectHandle);
286}
287
288MtpObjectHandle MtpSqliteDatabase::addAudioFile(MtpObjectHandle handle,
289 const char* title,
290 const char* artist,
291 const char* album,
292 const char* albumArtist,
293 const char* genre,
294 const char* composer,
295 const char* mimeType,
296 int track,
297 int year,
298 int duration) {
299 mAudioInserter->bind(AUDIO_ID_COLUMN, handle);
300 if (title) mAudioInserter->bind(AUDIO_TITLE_COLUMN, title);
301 if (artist) mAudioInserter->bind(AUDIO_ARTIST_COLUMN, artist);
302 if (album) mAudioInserter->bind(AUDIO_ALBUM_COLUMN, album);
303 if (albumArtist) mAudioInserter->bind(AUDIO_ALBUM_ARTIST_COLUMN, albumArtist);
304 if (genre) mAudioInserter->bind(AUDIO_GENRE_COLUMN, genre);
305 if (composer) mAudioInserter->bind(AUDIO_COMPOSER_COLUMN, composer);
306 if (track) mAudioInserter->bind(AUDIO_TRACK_NUMBER_COLUMN, track);
307 if (year) mAudioInserter->bind(AUDIO_YEAR_COLUMN, year);
308 if (duration) mAudioInserter->bind(AUDIO_DURATION_COLUMN, duration);
309 mAudioInserter->step();
310 mAudioInserter->reset();
311 int result = mDatabase->lastInsertedRow();
312 if (result <= 0)
313 return kInvalidObjectHandle;
314 result |= kObjectHandleTableAudio;
315 return result;
316}
317
318MtpObjectHandleList* MtpSqliteDatabase::getObjectList(MtpStorageID storageID,
319 MtpObjectFormat format,
320 MtpObjectHandle parent) {
321 bool whereStorage = (storageID != 0xFFFFFFFF);
322 bool whereFormat = (format != 0);
323 bool whereParent = (parent != 0);
324 char intBuffer[20];
325
326 MtpString query("SELECT _id,format FROM files");
327 if (whereStorage || whereFormat || whereParent)
328 query += " WHERE";
329 if (whereStorage) {
330 snprintf(intBuffer, sizeof(intBuffer), "%d", storageID);
331 query += " storage = ";
332 query += intBuffer;
333 }
334 if (whereFormat) {
335 snprintf(intBuffer, sizeof(intBuffer), "%d", format);
336 if (whereStorage)
337 query += " AND";
338 query += " format = ";
339 query += intBuffer;
340 }
341 if (whereParent) {
342 if (parent != MTP_PARENT_ROOT)
343 parent &= kObjectHandleIndexMask;
344 snprintf(intBuffer, sizeof(intBuffer), "%d", parent);
345 if (whereStorage || whereFormat)
346 query += " AND";
347 query += " parent = ";
348 query += intBuffer;
349 }
350 query += ";";
351
352 SqliteStatement stmt(mDatabase);
353 LOGV("%s", (const char *)query);
354 stmt.prepare(query);
355
356 MtpObjectHandleList* list = new MtpObjectHandleList();
357 while (!stmt.isDone()) {
358 if (stmt.step()) {
359 int index = stmt.getColumnInt(0);
360 LOGV("stmt.getColumnInt returned %d", index);
361 if (index > 0) {
362 MtpObjectFormat format = stmt.getColumnInt(1);
363 index |= getTableForFile(format);
364 list->push(index);
365 }
366 }
367 }
368 LOGV("list size: %d", list->size());
369 return list;
370}
371
372
373MtpResponseCode MtpSqliteDatabase::getObjectProperty(MtpObjectHandle handle,
374 MtpObjectProperty property,
375 MtpDataPacket& packet) {
376 int type;
377 const char* columnName;
378 char intBuffer[20];
379
380 if (handle != MTP_PARENT_ROOT)
381 handle &= kObjectHandleIndexMask;
382
383 if (!getPropertyInfo(property, type, columnName))
384 return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
385 snprintf(intBuffer, sizeof(intBuffer), "%d", handle);
386
387 MtpString query("SELECT ");
388 query += columnName;
389 query += " FROM files WHERE _id = ";
390 query += intBuffer;
391 query += ";";
392
393 SqliteStatement stmt(mDatabase);
394 LOGV("%s", (const char *)query);
395 stmt.prepare(query);
396
397 if (!stmt.step())
398 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
399
400 switch (type) {
401 case MTP_TYPE_INT8:
402 packet.putInt8(stmt.getColumnInt(0));
403 break;
404 case MTP_TYPE_UINT8:
405 packet.putUInt8(stmt.getColumnInt(0));
406 break;
407 case MTP_TYPE_INT16:
408 packet.putInt16(stmt.getColumnInt(0));
409 break;
410 case MTP_TYPE_UINT16:
411 packet.putUInt16(stmt.getColumnInt(0));
412 break;
413 case MTP_TYPE_INT32:
414 packet.putInt32(stmt.getColumnInt(0));
415 break;
416 case MTP_TYPE_UINT32:
417 packet.putUInt32(stmt.getColumnInt(0));
418 break;
419 case MTP_TYPE_INT64:
420 packet.putInt64(stmt.getColumnInt64(0));
421 break;
422 case MTP_TYPE_UINT64:
423 packet.putUInt64(stmt.getColumnInt64(0));
424 break;
425 case MTP_TYPE_STR:
426 packet.putString(stmt.getColumnString(0));
427 break;
428 default:
429 LOGE("unsupported object type\n");
430 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
431 }
432 return MTP_RESPONSE_OK;
433}
434
435MtpResponseCode MtpSqliteDatabase::getObjectInfo(MtpObjectHandle handle,
436 MtpDataPacket& packet) {
437 char date[20];
438
439 if (handle != MTP_PARENT_ROOT)
440 handle &= kObjectHandleIndexMask;
441
442 mObjectInfoQuery->reset();
443 mObjectInfoQuery->bind(1, handle);
444 if (!mObjectInfoQuery->step())
445 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
446
447 MtpStorageID storageID = mObjectInfoQuery->getColumnInt(0);
448 MtpObjectFormat format = mObjectInfoQuery->getColumnInt(1);
449 MtpObjectHandle parent = mObjectInfoQuery->getColumnInt(2);
450 // extract name from path. do we want a separate database entry for this?
451 const char* name = mObjectInfoQuery->getColumnString(3);
452 const char* lastSlash = strrchr(name, '/');
453 if (lastSlash)
454 name = lastSlash + 1;
455 int64_t size = mObjectInfoQuery->getColumnInt64(4);
456 time_t modified = mObjectInfoQuery->getColumnInt(5);
457 int associationType = (format == MTP_FORMAT_ASSOCIATION ?
458 MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
459 MTP_ASSOCIATION_TYPE_UNDEFINED);
460
461 LOGV("storageID: %d, format: %d, parent: %d", storageID, format, parent);
462
463 packet.putUInt32(storageID);
464 packet.putUInt16(format);
465 packet.putUInt16(0); // protection status
466 packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size));
467 packet.putUInt16(0); // thumb format
468 packet.putUInt32(0); // thumb compressed size
469 packet.putUInt32(0); // thumb pix width
470 packet.putUInt32(0); // thumb pix height
471 packet.putUInt32(0); // image pix width
472 packet.putUInt32(0); // image pix height
473 packet.putUInt32(0); // image bit depth
474 packet.putUInt32(parent);
475 packet.putUInt16(associationType);
476 packet.putUInt32(0); // association desc
477 packet.putUInt32(0); // sequence number
478 packet.putString(name); // file name
479 packet.putEmptyString();
480 formatDateTime(modified, date, sizeof(date));
481 packet.putString(date); // date modified
482 packet.putEmptyString(); // keywords
483
484 return MTP_RESPONSE_OK;
485}
486
487bool MtpSqliteDatabase::getObjectFilePath(MtpObjectHandle handle,
488 MtpString& filePath,
489 int64_t& fileLength) {
490 if (handle != MTP_PARENT_ROOT)
491 handle &= kObjectHandleIndexMask;
492 mFilePathQuery->reset();
493 mFilePathQuery->bind(1, handle);
494 if (!mFilePathQuery->step())
495 return false;
496
497 const char* path = mFilePathQuery->getColumnString(0);
498 if (!path)
499 return false;
500 filePath = path;
501 fileLength = mFilePathQuery->getColumnInt64(1);
502 return true;
503}
504
505bool MtpSqliteDatabase::deleteFile(MtpObjectHandle handle) {
506 uint32_t table = handle & kObjectHandleTableMask;
507 handle &= kObjectHandleIndexMask;
508 mFileDeleter->bind(1, handle);
509 mFileDeleter->step();
510 mFileDeleter->reset();
511 if (table == kObjectHandleTableAudio) {
512 mAudioDeleter->bind(1, handle);
513 mAudioDeleter->step();
514 mAudioDeleter->reset();
515 }
516
517 return true;
518}
519
520MtpObjectHandle* MtpSqliteDatabase::getFileList(int& outCount) {
521 MtpObjectHandle* result = NULL;
522 int count = 0;
523 SqliteStatement stmt(mDatabase);
524 stmt.prepare("SELECT count(*) FROM files;");
525
526 MtpObjectHandleList* list = new MtpObjectHandleList();
527 if (stmt.step())
528 count = stmt.getColumnInt(0);
529
530 if (count > 0) {
531 result = new MtpObjectHandle[count];
532 memset(result, 0, count * sizeof(*result));
533 SqliteStatement stmt2(mDatabase);
534 stmt2.prepare("SELECT _id,format FROM files;");
535
536 for (int i = 0; i < count; i++) {
537 if (!stmt2.step()) {
538 LOGW("getFileList ended early");
539 count = i;
540 break;
541 }
542 MtpObjectHandle handle = stmt2.getColumnInt(0);
543 MtpObjectFormat format = stmt2.getColumnInt(1);
544 handle |= getTableForFile(format);
545 result[i] = handle;
546 }
547 }
548 outCount = count;
549 return result;
550}
551
552void MtpSqliteDatabase::beginTransaction() {
553 mDatabase->beginTransaction();
554}
555
556void MtpSqliteDatabase::commitTransaction() {
557 mDatabase->commitTransaction();
558}
559
560void MtpSqliteDatabase::rollbackTransaction() {
561 mDatabase->rollbackTransaction();
562}
563
564} // namespace android