Mike Lockwood | 16864ba | 2010-05-11 17:16:59 -0400 | [diff] [blame] | 1 | /* |
| 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 | #include "MtpDatabase.h" |
| 18 | #include "MtpDataPacket.h" |
| 19 | #include "SqliteDatabase.h" |
| 20 | #include "SqliteStatement.h" |
| 21 | |
| 22 | #include <stdio.h> |
| 23 | #include <sqlite3.h> |
| 24 | |
| 25 | #define ID_COLUMN 1 |
| 26 | #define PATH_COLUMN 2 |
| 27 | #define FORMAT_COLUMN 3 |
| 28 | #define PARENT_COLUMN 4 |
| 29 | #define STORAGE_COLUMN 5 |
| 30 | #define SIZE_COLUMN 6 |
| 31 | #define CREATED_COLUMN 7 |
| 32 | #define MODIFIED_COLUMN 8 |
| 33 | |
| 34 | #define TABLE_CREATE "CREATE TABLE IF NOT EXISTS files (" \ |
| 35 | "_id INTEGER PRIMARY KEY," \ |
| 36 | "path TEXT," \ |
| 37 | "format INTEGER," \ |
| 38 | "parent INTEGER," \ |
| 39 | "storage INTEGER," \ |
| 40 | "size INTEGER," \ |
| 41 | "date_created INTEGER," \ |
| 42 | "date_modified INTEGER" \ |
| 43 | ");" |
| 44 | |
| 45 | #define PATH_INDEX_CREATE "CREATE INDEX IF NOT EXISTS path_index on files(path);" |
| 46 | |
| 47 | #define FILE_ID_QUERY "SELECT _id FROM files WHERE path = ?;" |
| 48 | #define FILE_PATH_QUERY "SELECT path,size FROM files WHERE _id = ?" |
| 49 | |
| 50 | #define GET_OBJECT_INFO_QUERY "SELECT storage,format,parent,path,size,date_created,date_modified FROM files WHERE _id = ?;" |
| 51 | #define FILE_INSERT "INSERT INTO files VALUES(?,?,?,?,?,?,?,?);" |
| 52 | #define FILE_DELETE "DELETE FROM files WHERE path = ?;" |
| 53 | |
| 54 | |
| 55 | struct PropertyTableEntry { |
| 56 | MtpObjectProperty property; |
| 57 | int type; |
| 58 | const char* columnName; |
| 59 | }; |
| 60 | |
| 61 | static const PropertyTableEntry kPropertyTable[] = { |
| 62 | { MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32, "parent" }, |
| 63 | { MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32, "storage" }, |
| 64 | { MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT32, "format" }, |
| 65 | { MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR, "path" }, |
| 66 | { MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64, "size" }, |
| 67 | { MTP_PROPERTY_DATE_CREATED, MTP_TYPE_STR, "date_created" }, |
| 68 | { MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR, "date_modified" }, |
| 69 | }; |
| 70 | |
| 71 | static bool getPropertyInfo(MtpObjectProperty property, int& type, const char*& columnName) { |
| 72 | int count = sizeof(kPropertyTable) / sizeof(kPropertyTable[0]); |
| 73 | const PropertyTableEntry* entry = kPropertyTable; |
| 74 | for (int i = 0; i < count; i++, entry++) { |
| 75 | if (entry->property == property) { |
| 76 | type = entry->type; |
| 77 | columnName = entry->columnName; |
| 78 | return true; |
| 79 | } |
| 80 | } |
| 81 | return false; |
| 82 | } |
| 83 | |
| 84 | |
| 85 | MtpDatabase::MtpDatabase() |
| 86 | : mFileIdQuery(NULL), |
| 87 | mObjectInfoQuery(NULL), |
| 88 | mFileInserter(NULL), |
| 89 | mFileDeleter(NULL) |
| 90 | { |
| 91 | } |
| 92 | |
| 93 | MtpDatabase::~MtpDatabase() { |
| 94 | } |
| 95 | |
| 96 | bool MtpDatabase::open(const char* path, bool create) { |
| 97 | if (!SqliteDatabase::open(path, create)) |
| 98 | return false; |
| 99 | |
| 100 | // create the table if necessary |
| 101 | if (!exec(TABLE_CREATE)) { |
| 102 | fprintf(stderr, "could not create table\n"); |
| 103 | return false; |
| 104 | } |
| 105 | if (!exec(PATH_INDEX_CREATE)) { |
| 106 | fprintf(stderr, "could not path index\n"); |
| 107 | return false; |
| 108 | } |
| 109 | return true; |
| 110 | } |
| 111 | |
| 112 | MtpObjectHandle MtpDatabase::addFile(const char* path, |
| 113 | MtpObjectFormat format, |
| 114 | MtpObjectHandle parent, |
| 115 | MtpStorageID storage, |
| 116 | uint64_t size, |
| 117 | time_t created, |
| 118 | time_t modified) { |
| 119 | |
| 120 | // first check to see if the file exists |
| 121 | if (mFileIdQuery) |
| 122 | mFileIdQuery->reset(); |
| 123 | else { |
| 124 | mFileIdQuery = new SqliteStatement(this); |
| 125 | if (!mFileIdQuery->prepare(FILE_ID_QUERY)) { |
| 126 | fprintf(stderr, "could not compile FILE_ID_QUERY\n"); |
| 127 | delete mFileIdQuery; |
| 128 | mFileIdQuery = NULL; |
| 129 | return kInvalidObjectHandle; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | mFileIdQuery->bind(1, path); |
| 134 | if (mFileIdQuery->step()) { |
| 135 | int row = mFileIdQuery->getColumnInt(0); |
| 136 | if (row > 0) |
| 137 | return row; |
| 138 | } |
| 139 | |
| 140 | if (!mFileInserter) { |
| 141 | mFileInserter = new SqliteStatement(this); |
| 142 | if (!mFileInserter->prepare(FILE_INSERT)) { |
| 143 | fprintf(stderr, "could not compile FILE_INSERT\n"); |
| 144 | delete mFileInserter; |
| 145 | mFileInserter = NULL; |
| 146 | return kInvalidObjectHandle; |
| 147 | } |
| 148 | } |
| 149 | mFileInserter->bind(PATH_COLUMN, path); |
| 150 | mFileInserter->bind(FORMAT_COLUMN, format); |
| 151 | mFileInserter->bind(PARENT_COLUMN, parent); |
| 152 | mFileInserter->bind(STORAGE_COLUMN, storage); |
| 153 | mFileInserter->bind(SIZE_COLUMN, size); |
| 154 | mFileInserter->bind(CREATED_COLUMN, created); |
| 155 | mFileInserter->bind(MODIFIED_COLUMN, modified); |
| 156 | mFileInserter->step(); |
| 157 | mFileInserter->reset(); |
| 158 | int row = lastInsertedRow(); |
| 159 | return (row > 0 ? row : kInvalidObjectHandle); |
| 160 | } |
| 161 | |
| 162 | MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID, |
| 163 | MtpObjectFormat format, |
| 164 | MtpObjectHandle parent) { |
| 165 | bool whereStorage = (storageID != 0xFFFFFFFF); |
| 166 | bool whereFormat = (format != 0); |
| 167 | bool whereParent = (parent != 0); |
| 168 | char intBuffer[20]; |
| 169 | |
| 170 | MtpString query("SELECT _id FROM files"); |
| 171 | if (whereStorage || whereFormat || whereParent) |
| 172 | query += " WHERE"; |
| 173 | if (whereStorage) { |
| 174 | snprintf(intBuffer, sizeof(intBuffer), "%d", storageID); |
| 175 | query += " storage = "; |
| 176 | query += intBuffer; |
| 177 | } |
| 178 | if (whereFormat) { |
| 179 | snprintf(intBuffer, sizeof(intBuffer), "%d", format); |
| 180 | if (whereStorage) |
| 181 | query += " AND"; |
| 182 | query += " format = "; |
| 183 | query += intBuffer; |
| 184 | } |
| 185 | if (whereParent) { |
| 186 | snprintf(intBuffer, sizeof(intBuffer), "%d", parent); |
| 187 | if (whereStorage || whereFormat) |
| 188 | query += " AND"; |
| 189 | query += " parent = "; |
| 190 | query += intBuffer; |
| 191 | } |
| 192 | query += ";"; |
| 193 | |
| 194 | SqliteStatement stmt(this); |
| 195 | printf("%s\n", (const char *)query); |
| 196 | stmt.prepare(query); |
| 197 | |
| 198 | MtpObjectHandleList* list = new MtpObjectHandleList(); |
| 199 | while (!stmt.isDone()) { |
| 200 | if (stmt.step()) { |
| 201 | int index = stmt.getColumnInt(0); |
| 202 | printf("stmt.getColumnInt returned %d\n", index); |
| 203 | if (index > 0) |
| 204 | list->push(index); |
| 205 | } |
| 206 | } |
| 207 | printf("list size: %d\n", list->size()); |
| 208 | return list; |
| 209 | } |
| 210 | |
| 211 | MtpResponseCode MtpDatabase::getObjectProperty(MtpObjectHandle handle, |
| 212 | MtpObjectProperty property, |
| 213 | MtpDataPacket& packet) { |
| 214 | int type; |
| 215 | const char* columnName; |
| 216 | char intBuffer[20]; |
| 217 | |
| 218 | if (!getPropertyInfo(property, type, columnName)) |
| 219 | return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE; |
| 220 | snprintf(intBuffer, sizeof(intBuffer), "%d", handle); |
| 221 | |
| 222 | MtpString query("SELECT "); |
| 223 | query += columnName; |
| 224 | query += " FROM files WHERE _id = "; |
| 225 | query += intBuffer; |
| 226 | query += ";"; |
| 227 | |
| 228 | SqliteStatement stmt(this); |
| 229 | printf("%s\n", (const char *)query); |
| 230 | stmt.prepare(query); |
| 231 | |
| 232 | if (!stmt.step()) |
| 233 | return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
| 234 | |
| 235 | switch (type) { |
| 236 | case MTP_TYPE_INT8: |
| 237 | packet.putInt8(stmt.getColumnInt(0)); |
| 238 | break; |
| 239 | case MTP_TYPE_UINT8: |
| 240 | packet.putUInt8(stmt.getColumnInt(0)); |
| 241 | break; |
| 242 | case MTP_TYPE_INT16: |
| 243 | packet.putInt16(stmt.getColumnInt(0)); |
| 244 | break; |
| 245 | case MTP_TYPE_UINT16: |
| 246 | packet.putUInt16(stmt.getColumnInt(0)); |
| 247 | break; |
| 248 | case MTP_TYPE_INT32: |
| 249 | packet.putInt32(stmt.getColumnInt(0)); |
| 250 | break; |
| 251 | case MTP_TYPE_UINT32: |
| 252 | packet.putUInt32(stmt.getColumnInt(0)); |
| 253 | break; |
| 254 | case MTP_TYPE_INT64: |
| 255 | packet.putInt64(stmt.getColumnInt64(0)); |
| 256 | break; |
| 257 | case MTP_TYPE_UINT64: |
| 258 | packet.putUInt64(stmt.getColumnInt64(0)); |
| 259 | break; |
| 260 | case MTP_TYPE_STR: |
| 261 | packet.putString(stmt.getColumnString(0)); |
| 262 | break; |
| 263 | default: |
| 264 | fprintf(stderr, "unsupported object type\n"); |
| 265 | return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
| 266 | } |
| 267 | return MTP_RESPONSE_OK; |
| 268 | } |
| 269 | |
| 270 | MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle, |
| 271 | MtpDataPacket& packet) { |
| 272 | char date[20]; |
| 273 | |
| 274 | if (mObjectInfoQuery) |
| 275 | mObjectInfoQuery->reset(); |
| 276 | else { |
| 277 | mObjectInfoQuery = new SqliteStatement(this); |
| 278 | if (!mObjectInfoQuery->prepare(GET_OBJECT_INFO_QUERY)) { |
| 279 | fprintf(stderr, "could not compile FILE_ID_QUERY\n"); |
| 280 | delete mObjectInfoQuery; |
| 281 | mObjectInfoQuery = NULL; |
| 282 | return MTP_RESPONSE_GENERAL_ERROR; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | mObjectInfoQuery->bind(1, handle); |
| 287 | if (!mObjectInfoQuery->step()) |
| 288 | return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
| 289 | |
| 290 | MtpStorageID storageID = mObjectInfoQuery->getColumnInt(0); |
| 291 | MtpObjectFormat format = mObjectInfoQuery->getColumnInt(1); |
| 292 | MtpObjectHandle parent = mObjectInfoQuery->getColumnInt(2); |
| 293 | // extract name from path. do we want a separate database entry for this? |
| 294 | const char* name = mObjectInfoQuery->getColumnString(3); |
| 295 | const char* lastSlash = strrchr(name, '/'); |
| 296 | if (lastSlash) |
| 297 | name = lastSlash + 1; |
| 298 | int64_t size = mObjectInfoQuery->getColumnInt64(4); |
| 299 | time_t created = mObjectInfoQuery->getColumnInt(5); |
| 300 | time_t modified = mObjectInfoQuery->getColumnInt(6); |
| 301 | int associationType = (format == MTP_FORMAT_ASSOCIATION ? |
| 302 | MTP_ASSOCIATION_TYPE_GENERIC_FOLDER : |
| 303 | MTP_ASSOCIATION_TYPE_UNDEFINED); |
| 304 | |
| 305 | printf("storageID: %d, format: %d, parent: %d\n", storageID, format, parent); |
| 306 | |
| 307 | packet.putUInt32(storageID); |
| 308 | packet.putUInt16(format); |
| 309 | packet.putUInt16(0); // protection status |
| 310 | packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size)); |
| 311 | packet.putUInt16(0); // thumb format |
| 312 | packet.putUInt32(0); // thumb compressed size |
| 313 | packet.putUInt32(0); // thumb pix width |
| 314 | packet.putUInt32(0); // thumb pix height |
| 315 | packet.putUInt32(0); // image pix width |
| 316 | packet.putUInt32(0); // image pix height |
| 317 | packet.putUInt32(0); // image bit depth |
| 318 | packet.putUInt32(parent); |
| 319 | packet.putUInt16(associationType); |
| 320 | packet.putUInt32(0); // association desc |
| 321 | packet.putUInt32(0); // sequence number |
| 322 | packet.putString(name); // file name |
| 323 | formatDateTime(created, date, sizeof(date)); |
| 324 | packet.putString(date); // date created |
| 325 | formatDateTime(modified, date, sizeof(date)); |
| 326 | packet.putString(date); // date modified |
| 327 | packet.putEmptyString(); // keywords |
| 328 | |
| 329 | return MTP_RESPONSE_OK; |
| 330 | } |
| 331 | |
| 332 | bool MtpDatabase::getObjectFilePath(MtpObjectHandle handle, |
| 333 | MtpString& filePath, |
| 334 | int64_t& fileLength) { |
| 335 | if (mFilePathQuery) |
| 336 | mFilePathQuery->reset(); |
| 337 | else { |
| 338 | mFilePathQuery = new SqliteStatement(this); |
| 339 | if (!mFilePathQuery->prepare(FILE_PATH_QUERY)) { |
| 340 | fprintf(stderr, "could not compile FILE_ID_QUERY\n"); |
| 341 | delete mFilePathQuery; |
| 342 | mFilePathQuery = NULL; |
| 343 | return kInvalidObjectHandle; |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | mFilePathQuery->bind(1, handle); |
| 348 | if (!mFilePathQuery->step()) |
| 349 | return false; |
| 350 | |
| 351 | const char* path = mFilePathQuery->getColumnString(0); |
| 352 | if (!path) |
| 353 | return false; |
| 354 | filePath = path; |
| 355 | fileLength = mFilePathQuery->getColumnInt64(1); |
| 356 | return true; |
| 357 | } |
| 358 | |
| 359 | bool MtpDatabase::deleteFile(MtpObjectHandle handle) { |
| 360 | if (!mFileDeleter) { |
| 361 | mFileDeleter = new SqliteStatement(this); |
| 362 | if (!mFileDeleter->prepare(FILE_DELETE)) { |
| 363 | fprintf(stderr, "could not compile FILE_DELETE\n"); |
| 364 | delete mFileDeleter; |
| 365 | mFileDeleter = NULL; |
| 366 | return false; |
| 367 | } |
| 368 | } |
| 369 | printf("deleteFile %d\n", handle); |
| 370 | mFileDeleter->bind(1, handle); |
| 371 | mFileDeleter->step(); |
| 372 | mFileDeleter->reset(); |
| 373 | return true; |
| 374 | } |
| 375 | |
| 376 | /* |
| 377 | for getObjectPropDesc |
| 378 | |
| 379 | packet.putUInt16(property); |
| 380 | packet.putUInt16(dataType); |
| 381 | packet.putUInt8(getSet); |
| 382 | // default value DTS |
| 383 | packet.putUInt32(groupCode); |
| 384 | packet.putUInt8(formFlag); |
| 385 | // form, variable |
| 386 | */ |