00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00031
00032 #ifdef HAVE_CONFIG_H
00033 #include "autoconfig.h"
00034 #endif
00035
00036 #include <limits.h>
00037 #include "sql_storage.h"
00038 #include "tools.h"
00039 #include "update_manager.h"
00040 #include "string_converter.h"
00041 #include "config_manager.h"
00042 #include "filesystem.h"
00043
00044 using namespace zmm;
00045
00046 #define MAX_REMOVE_SIZE 10000
00047 #define MAX_REMOVE_RECURSION 500
00048
00049 #define SQL_NULL "NULL"
00050
00051 #define RESOURCE_SEP '|'
00052
00053 enum
00054 {
00055 _id = 0,
00056 _ref_id,
00057 _parent_id,
00058 _object_type,
00059 _upnp_class,
00060 _dc_title,
00061 _location,
00062 _location_hash,
00063 _metadata,
00064 _auxdata,
00065 _resources,
00066 _update_id,
00067 _mime_type,
00068 _flags,
00069 _track_number,
00070 _service_id,
00071 _ref_upnp_class,
00072 _ref_location,
00073 _ref_metadata,
00074 _ref_auxdata,
00075 _ref_resources,
00076 _ref_mime_type,
00077 _ref_service_id,
00078 _as_persistent
00079 };
00080
00081
00082 #define TQ(data) QTB << data << QTE
00083
00084 #define TQD(data1, data2) TQ(data1) << '.' << TQ(data2)
00085
00086 #define SEL_F_QUOTED << TQ('f') <<
00087 #define SEL_RF_QUOTED << TQ("rf") <<
00088
00089
00090 #define SEL_EQ_SP_FQ_DT_BQ << QTE << ',' << TQ('f') << '.' << QTB <<
00091 #define SEL_EQ_SP_RFQ_DT_BQ << QTE << ',' << TQ("rf") << '.' << QTB <<
00092
00093 #define SELECT_DATA_FOR_STRINGBUFFER \
00094 TQ('f') << '.' << QTB << "id" \
00095 SEL_EQ_SP_FQ_DT_BQ "ref_id" \
00096 SEL_EQ_SP_FQ_DT_BQ "parent_id" \
00097 SEL_EQ_SP_FQ_DT_BQ "object_type" \
00098 SEL_EQ_SP_FQ_DT_BQ "upnp_class" \
00099 SEL_EQ_SP_FQ_DT_BQ "dc_title" \
00100 SEL_EQ_SP_FQ_DT_BQ "location" \
00101 SEL_EQ_SP_FQ_DT_BQ "location_hash" \
00102 SEL_EQ_SP_FQ_DT_BQ "metadata" \
00103 SEL_EQ_SP_FQ_DT_BQ "auxdata" \
00104 SEL_EQ_SP_FQ_DT_BQ "resources" \
00105 SEL_EQ_SP_FQ_DT_BQ "update_id" \
00106 SEL_EQ_SP_FQ_DT_BQ "mime_type" \
00107 SEL_EQ_SP_FQ_DT_BQ "flags" \
00108 SEL_EQ_SP_FQ_DT_BQ "track_number" \
00109 SEL_EQ_SP_FQ_DT_BQ "service_id" \
00110 SEL_EQ_SP_RFQ_DT_BQ "upnp_class" \
00111 SEL_EQ_SP_RFQ_DT_BQ "location" \
00112 SEL_EQ_SP_RFQ_DT_BQ "metadata" \
00113 SEL_EQ_SP_RFQ_DT_BQ "auxdata" \
00114 SEL_EQ_SP_RFQ_DT_BQ "resources" \
00115 SEL_EQ_SP_RFQ_DT_BQ "mime_type" \
00116 SEL_EQ_SP_RFQ_DT_BQ "service_id" << QTE \
00117 << ',' << TQD("as","persistent")
00118
00119 #define SQL_QUERY_FOR_STRINGBUFFER "SELECT " << SELECT_DATA_FOR_STRINGBUFFER << \
00120 " FROM " << TQ(CDS_OBJECT_TABLE) << ' ' << TQ('f') << " LEFT JOIN " \
00121 << TQ(CDS_OBJECT_TABLE) << ' ' << TQ("rf") << " ON " << TQD('f',"ref_id") \
00122 << '=' << TQD("rf","id") << " LEFT JOIN " << TQ(AUTOSCAN_TABLE) << ' ' \
00123 << TQ("as") << " ON " << TQD("as","obj_id") << '=' << TQD('f',"id") << ' '
00124
00125 #define SQL_QUERY sql_query
00126
00127
00128
00129 SQLStorage::SQLStorage() : Storage()
00130 {
00131 table_quote_begin = '\0';
00132 table_quote_end = '\0';
00133 lastID = INVALID_OBJECT_ID;
00134 }
00135
00136 void SQLStorage::init()
00137 {
00138 if (table_quote_begin == '\0' || table_quote_end == '\0')
00139 throw _Exception(_("quote vars need to be overriden!"));
00140
00141 Ref<StringBuffer> buf(new StringBuffer());
00142 *buf << SQL_QUERY_FOR_STRINGBUFFER;
00143 this->sql_query = buf->toString();
00144
00145 if (ConfigManager::getInstance()->getBoolOption(CFG_SERVER_STORAGE_CACHING_ENABLED))
00146 {
00147 cache = Ref<StorageCache>(new StorageCache());
00148 insertBufferOn = true;
00149 }
00150 else
00151 {
00152 cache = nil;
00153 insertBufferOn = false;
00154 }
00155
00156 insertBufferEmpty = true;
00157 insertBufferMutex = Ref<Mutex>(new Mutex());
00158 insertBufferStatementCount = 0;
00159 insertBufferByteCount = 0;
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183 }
00184
00185 void SQLStorage::dbReady()
00186 {
00187 nextIDMutex = Ref<Mutex>(new Mutex());;
00188 loadLastID();
00189 }
00190
00191 void SQLStorage::shutdown()
00192 {
00193 flushInsertBuffer();
00194 shutdownDriver();
00195 }
00196
00197
00198
00199
00200
00201
00202
00203 Ref<CdsObject> SQLStorage::checkRefID(Ref<CdsObject> obj)
00204 {
00205 if (! obj->isVirtual()) throw _Exception(_("checkRefID called for a non-virtual object"));
00206 int refID = obj->getRefID();
00207 String location = obj->getLocation();
00208 if (! string_ok(location))
00209 throw _Exception(_("tried to check refID without a location set"));
00210 if (refID > 0)
00211 {
00212 try
00213 {
00214 Ref<CdsObject> refObj;
00215 refObj = loadObject(refID);
00216 if (refObj != nil && refObj->getLocation() == location)
00217 return refObj;
00218 }
00219 catch (Exception e)
00220 {
00221
00222 assert(0);
00223 throw _Exception(_("illegal refID was set"));
00224 }
00225 }
00226
00227
00228
00229
00230 assert(0);
00231
00232 return findObjectByPath(location);
00233 }
00234
00235 Ref<Array<SQLStorage::AddUpdateTable> > SQLStorage::_addUpdateObject(Ref<CdsObject> obj, bool isUpdate, int *changedContainer)
00236 {
00237 int objectType = obj->getObjectType();
00238 Ref<CdsObject> refObj = nil;
00239 bool hasReference = false;
00240 bool playlistRef = obj->getFlag(OBJECT_FLAG_PLAYLIST_REF);
00241 if (playlistRef)
00242 {
00243 if (IS_CDS_PURE_ITEM(objectType))
00244 throw _Exception(_("tried to add pure item with PLAYLIST_REF flag set"));
00245 if (obj->getRefID() <= 0)
00246 throw _Exception(_("PLAYLIST_REF flag set but refId is <=0"));
00247 refObj = loadObject(obj->getRefID());
00248 if (refObj == nil)
00249 throw _Exception(_("PLAYLIST_REF flag set but refId doesn't point to an existing object"));
00250 }
00251 else if (obj->isVirtual() && IS_CDS_PURE_ITEM(objectType))
00252 {
00253 hasReference = true;
00254 refObj = checkRefID(obj);
00255 if (refObj == nil)
00256 throw _Exception(_("tried to add or update a virtual object with illegal reference id and an illegal location"));
00257 }
00258 else if (obj->getRefID() > 0)
00259 {
00260 if (obj->getFlag(OBJECT_FLAG_ONLINE_SERVICE))
00261 {
00262 hasReference = true;
00263 refObj = loadObject(obj->getRefID());
00264 if (refObj == nil)
00265 throw _Exception(_("OBJECT_FLAG_ONLINE_SERVICE and refID set but refID doesn't point to an existing object"));
00266 }
00267 else if (IS_CDS_CONTAINER(objectType))
00268 {
00269
00270
00271 }
00272 else
00273 throw _Exception(_("refId set, but it makes no sense"));
00274 }
00275
00276 Ref<Array<AddUpdateTable> > returnVal(new Array<AddUpdateTable>(2));
00277 Ref<Dictionary> cdsObjectSql(new Dictionary());
00278 returnVal->append(Ref<AddUpdateTable> (new AddUpdateTable(_(CDS_OBJECT_TABLE), cdsObjectSql)));
00279
00280 cdsObjectSql->put(_("object_type"), quote(objectType));
00281
00282 if (hasReference || playlistRef)
00283 cdsObjectSql->put(_("ref_id"), quote(refObj->getID()));
00284 else if (isUpdate)
00285 cdsObjectSql->put(_("ref_id"), _(SQL_NULL));
00286
00287 if (! hasReference || refObj->getClass() != obj->getClass())
00288 cdsObjectSql->put(_("upnp_class"), quote(obj->getClass()));
00289 else if (isUpdate)
00290 cdsObjectSql->put(_("upnp_class"), _(SQL_NULL));
00291
00292
00293 cdsObjectSql->put(_("dc_title"), quote(obj->getTitle()));
00294
00295
00296
00297 if (isUpdate)
00298 cdsObjectSql->put(_("metadata"), _(SQL_NULL));
00299 Ref<Dictionary> dict = obj->getMetadata();
00300 if (dict->size() > 0)
00301 {
00302 if (! hasReference || ! refObj->getMetadata()->equals(obj->getMetadata()))
00303 {
00304 cdsObjectSql->put(_("metadata"), quote(dict->encode()));
00305 }
00306 }
00307
00308 if (isUpdate)
00309 cdsObjectSql->put(_("auxdata"), _(SQL_NULL));
00310 dict = obj->getAuxData();
00311 if (dict->size() > 0 && (! hasReference || ! refObj->getAuxData()->equals(obj->getAuxData())))
00312 {
00313 cdsObjectSql->put(_("auxdata"), quote(obj->getAuxData()->encode()));
00314 }
00315
00316 if (! hasReference || (! obj->getFlag(OBJECT_FLAG_USE_RESOURCE_REF) && ! refObj->resourcesEqual(obj)))
00317 {
00318
00319 Ref<StringBuffer> resBuf(new StringBuffer());
00320 for (int i = 0; i < obj->getResourceCount(); i++)
00321 {
00322 if (i > 0)
00323 *resBuf << RESOURCE_SEP;
00324 *resBuf << obj->getResource(i)->encode();
00325 }
00326 String resStr = resBuf->toString();
00327 if (string_ok(resStr))
00328 cdsObjectSql->put(_("resources"), quote(resStr));
00329 else
00330 cdsObjectSql->put(_("resources"), _(SQL_NULL));
00331 }
00332 else if (isUpdate)
00333 cdsObjectSql->put(_("resources"), _(SQL_NULL));
00334
00335 obj->clearFlag(OBJECT_FLAG_USE_RESOURCE_REF);
00336
00337 cdsObjectSql->put(_("flags"), quote(obj->getFlags()));
00338
00339 if (IS_CDS_CONTAINER(objectType))
00340 {
00341 if (! (isUpdate && obj->isVirtual()) )
00342 throw _Exception(_("tried to add a container or tried to update a non-virtual container via _addUpdateObject; is this correct?"));
00343 String dbLocation = addLocationPrefix(LOC_VIRT_PREFIX, obj->getLocation());
00344 cdsObjectSql->put(_("location"), quote(dbLocation));
00345 cdsObjectSql->put(_("location_hash"), quote(stringHash(dbLocation)));
00346 }
00347
00348 if (IS_CDS_ITEM(objectType))
00349 {
00350 Ref<CdsItem> item = RefCast(obj, CdsItem);
00351
00352 if (! hasReference)
00353 {
00354 String loc = item->getLocation();
00355 if (!string_ok(loc)) throw _Exception(_("tried to create or update a non-referenced item without a location set"));
00356 if (IS_CDS_PURE_ITEM(objectType))
00357 {
00358 Ref<Array<StringBase> > pathAr = split_path(loc);
00359 String path = pathAr->get(0);
00360 int parentID = ensurePathExistence(path, changedContainer);
00361 item->setParentID(parentID);
00362 String dbLocation = addLocationPrefix(LOC_FILE_PREFIX, loc);
00363 cdsObjectSql->put(_("location"), quote(dbLocation));
00364 cdsObjectSql->put(_("location_hash"), quote(stringHash(dbLocation)));
00365 }
00366 else
00367 {
00368
00369 cdsObjectSql->put(_("location"), quote(loc));
00370 cdsObjectSql->put(_("location_hash"), _(SQL_NULL));
00371 }
00372 }
00373 else
00374 {
00375 if (isUpdate)
00376 {
00377 cdsObjectSql->put(_("location"), _(SQL_NULL));
00378 cdsObjectSql->put(_("location_hash"), _(SQL_NULL));
00379 }
00380 }
00381
00382 if (item->getTrackNumber() > 0)
00383 {
00384 cdsObjectSql->put(_("track_number"), quote(item->getTrackNumber()));
00385 }
00386 else
00387 {
00388 if (isUpdate)
00389 cdsObjectSql->put(_("track_number"), _(SQL_NULL));
00390 }
00391
00392 if (string_ok(item->getServiceID()))
00393 {
00394 if (! hasReference || RefCast(refObj,CdsItem)->getServiceID() != item->getServiceID())
00395 cdsObjectSql->put(_("service_id"), quote(item->getServiceID()));
00396 else
00397 cdsObjectSql->put(_("service_id"), _(SQL_NULL));
00398 }
00399 else
00400 {
00401 if (isUpdate)
00402 cdsObjectSql->put(_("service_id"), _(SQL_NULL));
00403 }
00404
00405 cdsObjectSql->put(_("mime_type"), quote(item->getMimeType()));
00406 }
00407 if (IS_CDS_ACTIVE_ITEM(objectType))
00408 {
00409 Ref<Dictionary> cdsActiveItemSql(new Dictionary());
00410 returnVal->append(Ref<AddUpdateTable> (new AddUpdateTable(_(CDS_ACTIVE_ITEM_TABLE), cdsActiveItemSql)));
00411 Ref<CdsActiveItem> aitem = RefCast(obj, CdsActiveItem);
00412
00413 cdsActiveItemSql->put(_("id"), String::from(aitem->getID()));
00414 cdsActiveItemSql->put(_("action"), quote(aitem->getAction()));
00415 cdsActiveItemSql->put(_("state"), quote(aitem->getState()));
00416 }
00417
00418
00419
00420 if (hasReference && ! isUpdate)
00421 {
00422 Ref<StringBuffer> qb(new StringBuffer());
00423 *qb << "SELECT " << TQ("id")
00424 << " FROM " << TQ(CDS_OBJECT_TABLE)
00425 << " WHERE " << TQ("parent_id")
00426 << '=' << quote(obj->getParentID())
00427 << " AND " << TQ("ref_id")
00428 << '=' << quote(refObj->getID())
00429 << " AND " << TQ("dc_title")
00430 << '=' << quote(obj->getTitle())
00431 << " LIMIT 1";
00432 Ref<SQLResult> res = select(qb);
00433
00434 if (res != nil && (res->nextRow() != nil))
00435 return nil;
00436 }
00437
00438 if (obj->getParentID() == INVALID_OBJECT_ID)
00439 throw _Exception(_("tried to create or update an object with an illegal parent id"));
00440 cdsObjectSql->put(_("parent_id"), String::from(obj->getParentID()));
00441
00442 return returnVal;
00443 }
00444
00445 void SQLStorage::addObject(Ref<CdsObject> obj, int *changedContainer)
00446 {
00447 if (obj->getID() != INVALID_OBJECT_ID)
00448 throw _Exception(_("tried to add an object with an object ID set"));
00449
00450 Ref<Array<AddUpdateTable> > data = _addUpdateObject(obj, false, changedContainer);
00451 if (data == nil)
00452 return;
00453 int lastInsertID = INVALID_OBJECT_ID;
00454 for (int i = 0; i < data->size(); i++)
00455 {
00456 Ref<AddUpdateTable> addUpdateTable = data->get(i);
00457 String tableName = addUpdateTable->getTable();
00458 Ref<Array<DictionaryElement> > dataElements = addUpdateTable->getDict()->getElements();
00459
00460 Ref<StringBuffer> fields(new StringBuffer(128));
00461 Ref<StringBuffer> values(new StringBuffer(128));
00462
00463 for (int j = 0; j < dataElements->size(); j++)
00464 {
00465 Ref<DictionaryElement> element = dataElements->get(j);
00466 if (j != 0)
00467 {
00468 *fields << ',';
00469 *values << ',';
00470 }
00471 *fields << TQ(element->getKey());
00472 if (lastInsertID != INVALID_OBJECT_ID &&
00473 element->getKey() == "id" &&
00474 element->getValue().toInt() == INVALID_OBJECT_ID )
00475 *values << lastInsertID;
00476 else
00477 *values << element->getValue();
00478 }
00479
00480
00481 if (lastInsertID == INVALID_OBJECT_ID && tableName == _(CDS_OBJECT_TABLE))
00482 {
00483 lastInsertID = getNextID();
00484 obj->setID(lastInsertID);
00485 *fields << ',' << TQ("id");
00486 *values << ',' << quote(lastInsertID);
00487 }
00488
00489
00490 Ref<StringBuffer> qb(new StringBuffer(256));
00491 *qb << "INSERT INTO " << TQ(tableName) << " (" << fields->toString() <<
00492 ") VALUES (" << values->toString() << ')';
00493
00494 log_debug("insert_query: %s\n", qb->toString().c_str());
00495
00496
00497
00498
00499
00500
00501
00502
00503
00504
00505
00506 if (! doInsertBuffering())
00507 exec(qb);
00508 else
00509 addToInsertBuffer(qb);
00510 }
00511
00512
00513 if (cacheOn())
00514 {
00515 AUTOLOCK(cache->getMutex());
00516 cache->addChild(obj->getParentID());
00517 if (cache->flushed())
00518 flushInsertBuffer();
00519 addObjectToCache(obj, true);
00520 }
00521
00522 }
00523
00524 void SQLStorage::updateObject(zmm::Ref<CdsObject> obj, int *changedContainer)
00525 {
00526 flushInsertBuffer();
00527
00528 Ref<Array<AddUpdateTable> > data;
00529 if (obj->getID() == CDS_ID_FS_ROOT)
00530 {
00531 data = Ref<Array<AddUpdateTable> >(new Array<AddUpdateTable>(1));
00532 Ref<Dictionary> cdsObjectSql(new Dictionary());
00533 data->append(Ref<AddUpdateTable> (new AddUpdateTable(_(CDS_OBJECT_TABLE), cdsObjectSql)));
00534 cdsObjectSql->put(_("dc_title"), quote(obj->getTitle()));
00535 setFsRootName(obj->getTitle());
00536 cdsObjectSql->put(_("upnp_class"), quote(obj->getClass()));
00537 }
00538 else
00539 {
00540 if (IS_FORBIDDEN_CDS_ID(obj->getID()))
00541 throw _Exception(_("tried to update an object with a forbidden ID (")+obj->getID()+")!");
00542 data = _addUpdateObject(obj, true, changedContainer);
00543 if (data == nil)
00544 return;
00545 }
00546 for (int i = 0; i < data->size(); i++)
00547 {
00548 Ref<AddUpdateTable> addUpdateTable = data->get(i);
00549 String tableName = addUpdateTable->getTable();
00550 Ref<Array<DictionaryElement> > dataElements = addUpdateTable->getDict()->getElements();
00551
00552 Ref<StringBuffer> qb(new StringBuffer(256));
00553 *qb << "UPDATE " << TQ(tableName) << " SET ";
00554
00555 for (int j = 0; j < dataElements->size(); j++)
00556 {
00557 Ref<DictionaryElement> element = dataElements->get(j);
00558 if (j != 0)
00559 {
00560 *qb << ',';
00561 }
00562 *qb << TQ(element->getKey()) << '='
00563 << element->getValue();
00564 }
00565
00566 *qb << " WHERE id = " << obj->getID();
00567
00568 log_debug("upd_query: %s\n", qb->toString().c_str());
00569
00570 exec(qb);
00571 }
00572
00573 addObjectToCache(obj);
00574
00575 }
00576
00577 Ref<CdsObject> SQLStorage::loadObject(int objectID)
00578 {
00579
00580
00581 if (cacheOn())
00582 {
00583 AUTOLOCK(cache->getMutex());
00584 Ref<CacheObject> cObj = cache->getObject(objectID);
00585 if (cObj != nil)
00586 {
00587 if (cObj->knowsObject())
00588 return cObj->getObject();
00589 }
00590 }
00591
00592
00593
00594
00595
00596
00597
00598
00599 Ref<StringBuffer> qb(new StringBuffer());
00600
00601
00602
00603 *qb << SQL_QUERY << " WHERE " << TQD('f',"id") << '=' << objectID;
00604
00605 Ref<SQLResult> res = select(qb);
00606 Ref<SQLRow> row;
00607 if (res != nil && (row = res->nextRow()) != nil)
00608 {
00609 return createObjectFromRow(row);
00610 }
00611 throw _ObjectNotFoundException(_("Object not found: ") + objectID);
00612 }
00613
00614 Ref<CdsObject> SQLStorage::loadObjectByServiceID(String serviceID)
00615 {
00616 flushInsertBuffer();
00617
00618 Ref<StringBuffer> qb(new StringBuffer());
00619 *qb << SQL_QUERY << " WHERE " << TQD('f',"service_id") << '=' << quote(serviceID);
00620 Ref<SQLResult> res = select(qb);
00621 Ref<SQLRow> row;
00622 if (res != nil && (row = res->nextRow()) != nil)
00623 {
00624 return createObjectFromRow(row);
00625 }
00626
00627 return nil;
00628 }
00629
00630 Ref<IntArray> SQLStorage::getServiceObjectIDs(char servicePrefix)
00631 {
00632 flushInsertBuffer();
00633
00634 Ref<IntArray> objectIDs(new IntArray());
00635 Ref<StringBuffer> qb(new StringBuffer());
00636 *qb << "SELECT " << TQ("id")
00637 << " FROM " << TQ(CDS_OBJECT_TABLE)
00638 << " WHERE " << TQ("service_id")
00639 << " LIKE " << quote(String(servicePrefix)+'%');
00640
00641 Ref<SQLResult> res = select(qb);
00642 if (res == nil)
00643 throw _Exception(_("db error"));
00644
00645 Ref<SQLRow> row;
00646 while((row = res->nextRow()) != nil)
00647 {
00648 objectIDs->append(row->col(0).toInt());
00649 }
00650
00651 return objectIDs;
00652 }
00653
00654 Ref<Array<CdsObject> > SQLStorage::browse(Ref<BrowseParam> param)
00655 {
00656 flushInsertBuffer();
00657
00658 int objectID;
00659 int objectType = 0;
00660
00661 bool getContainers = param->getFlag(BROWSE_CONTAINERS);
00662 bool getItems = param->getFlag(BROWSE_ITEMS);
00663
00664 objectID = param->getObjectID();
00665
00666 Ref<SQLResult> res;
00667 Ref<SQLRow> row;
00668
00669 bool haveObjectType = false;
00670
00671
00672 if (cacheOn())
00673 {
00674 AUTOLOCK(cache->getMutex());
00675 Ref<CacheObject> cObj = cache->getObject(objectID);
00676 if (cObj != nil && cObj->knowsObjectType())
00677 {
00678 objectType = cObj->getObjectType();
00679 haveObjectType = true;
00680 }
00681 }
00682
00683
00684 Ref<StringBuffer> qb(new StringBuffer());
00685 if (! haveObjectType)
00686 {
00687 *qb << "SELECT " << TQ("object_type")
00688 << " FROM " << TQ(CDS_OBJECT_TABLE)
00689 << " WHERE " << TQ("id") << '=' << objectID;
00690 res = select(qb);
00691 if(res != nil && (row = res->nextRow()) != nil)
00692 {
00693 objectType = row->col(0).toInt();
00694 haveObjectType = true;
00695
00696
00697 if (cacheOn())
00698 {
00699 AUTOLOCK(cache->getMutex());
00700 cache->getObjectDefinitely(objectID)->setObjectType(objectType);
00701 if (cache->flushed())
00702 flushInsertBuffer();
00703 }
00704
00705 }
00706 else
00707 {
00708 throw _ObjectNotFoundException(_("Object not found: ") + objectID);
00709 }
00710
00711 row = nil;
00712 res = nil;
00713 }
00714
00715
00716 bool hideFsRoot = param->getFlag(BROWSE_HIDE_FS_ROOT);
00717
00718 if(param->getFlag(BROWSE_DIRECT_CHILDREN) && IS_CDS_CONTAINER(objectType))
00719 {
00720 param->setTotalMatches(getChildCount(objectID, getContainers, getItems, hideFsRoot));
00721 }
00722 else
00723 {
00724 param->setTotalMatches(1);
00725 }
00726
00727
00728 qb->clear();
00729 if (param->getFlag(BROWSE_TRACK_SORT))
00730 *qb << TQD('f',"track_number") << ',';
00731 *qb << TQD('f',"dc_title");
00732 String orderByCode = qb->toString();
00733
00734 qb->clear();
00735 *qb << SQL_QUERY << " WHERE ";
00736
00737 if(param->getFlag(BROWSE_DIRECT_CHILDREN) && IS_CDS_CONTAINER(objectType))
00738 {
00739 int count = param->getRequestedCount();
00740 bool doLimit = true;
00741 if (! count)
00742 {
00743 if (param->getStartingIndex())
00744 count = INT_MAX;
00745 else
00746 doLimit = false;
00747 }
00748
00749 *qb << TQD('f',"parent_id") << '=' << objectID;
00750
00751 if (objectID == CDS_ID_ROOT && hideFsRoot)
00752 *qb << " AND " << TQD('f',"id") << "!="
00753 << quote (CDS_ID_FS_ROOT);
00754
00755 if (! getContainers && ! getItems)
00756 {
00757 *qb << " AND 0=1";
00758 }
00759 else if (getContainers && ! getItems)
00760 {
00761 *qb << " AND " << TQD('f',"object_type") << '='
00762 << quote(OBJECT_TYPE_CONTAINER)
00763 << " ORDER BY " << orderByCode;
00764 }
00765 else if (! getContainers && getItems)
00766 {
00767 *qb << " AND (" << TQD('f',"object_type") << " & "
00768 << quote(OBJECT_TYPE_ITEM) << ") = "
00769 << quote(OBJECT_TYPE_ITEM)
00770 << " ORDER BY " << orderByCode;
00771 }
00772 else
00773 {
00774 *qb << " ORDER BY ("
00775 << TQD('f',"object_type") << '=' << quote(OBJECT_TYPE_CONTAINER)
00776 << ") DESC, " << orderByCode;
00777 }
00778 if (doLimit)
00779 *qb << " LIMIT " << count << " OFFSET " << param->getStartingIndex();
00780 }
00781 else
00782 {
00783 *qb << TQD('f',"id") << '=' << objectID << " LIMIT 1";
00784 }
00785 log_debug("QUERY: %s\n", qb->toString().c_str());
00786 res = select(qb);
00787
00788 Ref<Array<CdsObject> > arr(new Array<CdsObject>());
00789
00790 while((row = res->nextRow()) != nil)
00791 {
00792 Ref<CdsObject> obj = createObjectFromRow(row);
00793 arr->append(obj);
00794 row = nil;
00795 }
00796
00797 row = nil;
00798 res = nil;
00799
00800
00801 for (int i = 0; i < arr->size(); i++)
00802 {
00803 Ref<CdsObject> obj = arr->get(i);
00804 if (IS_CDS_CONTAINER(obj->getObjectType()))
00805 {
00806 Ref<CdsContainer> cont = RefCast(obj, CdsContainer);
00807 cont->setChildCount(getChildCount(cont->getID(), getContainers, getItems, hideFsRoot));
00808 }
00809 }
00810
00811 return arr;
00812 }
00813
00814 int SQLStorage::getChildCount(int contId, bool containers, bool items, bool hideFsRoot)
00815 {
00816 if (! containers && ! items)
00817 return 0;
00818
00819
00820 if (cacheOn() && containers && items && ! (contId == CDS_ID_ROOT && hideFsRoot))
00821 {
00822 AUTOLOCK(cache->getMutex());
00823 Ref<CacheObject> cObj = cache->getObject(contId);
00824 if (cObj != nil)
00825 {
00826 if (cObj->knowsNumChildren())
00827 return cObj->getNumChildren();
00828
00829 }
00830 }
00831
00832
00833 flushInsertBuffer();
00834
00835 Ref<SQLRow> row;
00836 Ref<SQLResult> res;
00837 Ref<StringBuffer> qb(new StringBuffer());
00838 *qb << "SELECT COUNT(*) FROM " << TQ(CDS_OBJECT_TABLE)
00839 << " WHERE " << TQ("parent_id") << '=' << contId;
00840 if (containers && ! items)
00841 *qb << " AND " << TQ("object_type") << '=' << OBJECT_TYPE_CONTAINER;
00842 else if (items && ! containers)
00843 *qb << " AND (" << TQ("object_type") << " & " << OBJECT_TYPE_ITEM
00844 << ") = " << OBJECT_TYPE_ITEM;
00845 if (contId == CDS_ID_ROOT && hideFsRoot)
00846 {
00847 *qb << " AND " << TQ("id") << "!=" << quote (CDS_ID_FS_ROOT);
00848 }
00849 res = select(qb);
00850 if (res != nil && (row = res->nextRow()) != nil)
00851 {
00852 int childCount = row->col(0).toInt();
00853
00854
00855 if (cacheOn() && containers && items && ! (contId == CDS_ID_ROOT && hideFsRoot))
00856 {
00857 AUTOLOCK(cache->getMutex());
00858 cache->getObjectDefinitely(contId)->setNumChildren(childCount);
00859 if (cache->flushed())
00860 flushInsertBuffer();
00861 }
00862
00863
00864 return childCount;
00865 }
00866 return 0;
00867 }
00868
00869 Ref<Array<StringBase> > SQLStorage::getMimeTypes()
00870 {
00871 flushInsertBuffer();
00872
00873 Ref<Array<StringBase> > arr(new Array<StringBase>());
00874
00875 Ref<StringBuffer> qb(new StringBuffer());
00876 *qb << "SELECT DISTINCT " << TQ("mime_type")
00877 << " FROM " << TQ(CDS_OBJECT_TABLE)
00878 << " WHERE " << TQ("mime_type") << " IS NOT NULL ORDER BY "
00879 << TQ("mime_type");
00880 Ref<SQLResult> res = select(qb);
00881 if (res == nil)
00882 throw _Exception(_("db error"));
00883
00884 Ref<SQLRow> row;
00885
00886 while ((row = res->nextRow()) != nil)
00887 {
00888 arr->append(String(row->col(0)));
00889 }
00890
00891 return arr;
00892 }
00893
00894 Ref<CdsObject> SQLStorage::_findObjectByPath(String fullpath)
00895 {
00896
00897 fullpath = fullpath.reduce(DIR_SEPARATOR);
00898
00899 Ref<Array<StringBase> > pathAr = split_path(fullpath);
00900 String path = pathAr->get(0);
00901 String filename = pathAr->get(1);
00902
00903 bool file = string_ok(filename);
00904
00905 String dbLocation;
00906 if (file)
00907 {
00908
00909 dbLocation = addLocationPrefix(LOC_FILE_PREFIX, fullpath);
00910 }
00911 else
00912 dbLocation = addLocationPrefix(LOC_DIR_PREFIX, path);
00913
00914
00915
00916 if (cacheOn())
00917 {
00918 AUTOLOCK(cache->getMutex());
00919 Ref<Array<CacheObject> > objects = cache->getObjects(dbLocation);
00920 if (objects != nil)
00921 {
00922 for (int i = 0; i < objects->size(); i++)
00923 {
00924 Ref<CacheObject> cObj = objects->get(i);
00925 if (cObj->knowsObject() && cObj->knowsVirtual() && !cObj->getVirtual())
00926 return cObj->getObject();
00927 }
00928 }
00929 }
00930
00931
00932 Ref<StringBuffer> qb(new StringBuffer());
00933 *qb << SQL_QUERY
00934 << " WHERE " << TQD('f',"location_hash") << '=' << quote(stringHash(dbLocation))
00935 << " AND " << TQD('f',"location") << '=' << quote(dbLocation)
00936 << " AND " << TQD('f',"ref_id") << " IS NULL "
00937 "LIMIT 1";
00938
00939 Ref<SQLResult> res = select(qb);
00940 if (res == nil)
00941 throw _Exception(_("error while doing select: ") + qb->toString());
00942
00943
00944 Ref<SQLRow> row = res->nextRow();
00945 if (row == nil)
00946 return nil;
00947 return createObjectFromRow(row);
00948 }
00949
00950 Ref<CdsObject> SQLStorage::findObjectByPath(String fullpath)
00951 {
00952 return _findObjectByPath(fullpath);
00953 }
00954
00955 int SQLStorage::findObjectIDByPath(String fullpath)
00956 {
00957 Ref<CdsObject> obj = _findObjectByPath(fullpath);
00958 if (obj == nil)
00959 return INVALID_OBJECT_ID;
00960 return obj->getID();
00961 }
00962
00963 int SQLStorage::ensurePathExistence(String path, int *changedContainer)
00964 {
00965 *changedContainer = INVALID_OBJECT_ID;
00966 String cleanPath = path.reduce(DIR_SEPARATOR);
00967 if (cleanPath == DIR_SEPARATOR)
00968 return CDS_ID_FS_ROOT;
00969 if (cleanPath.charAt(cleanPath.length() - 1) == DIR_SEPARATOR)
00970 cleanPath = cleanPath.substring(0, cleanPath.length() - 1);
00971 return _ensurePathExistence(cleanPath, changedContainer);
00972 }
00973
00974 int SQLStorage::_ensurePathExistence(String path, int *changedContainer)
00975 {
00976 if (path == DIR_SEPARATOR)
00977 return CDS_ID_FS_ROOT;
00978 Ref<CdsObject> obj = findObjectByPath(path + DIR_SEPARATOR);
00979 if (obj != nil)
00980 return obj->getID();
00981 Ref<Array<StringBase> > pathAr = split_path(path);
00982 String parent = pathAr->get(0);
00983 String folder = pathAr->get(1);
00984 int parentID;
00985 parentID = ensurePathExistence(parent, changedContainer);
00986
00987 Ref<StringConverter> f2i = StringConverter::f2i();
00988 if (changedContainer != NULL && *changedContainer == INVALID_OBJECT_ID)
00989 *changedContainer = parentID;
00990 return createContainer(parentID, f2i->convert(folder), path, false, nil, INVALID_OBJECT_ID);
00991 }
00992
00993 int SQLStorage::createContainer(int parentID, String name, String path, bool isVirtual, String upnpClass, int refID)
00994 {
00995 if (refID > 0)
00996 {
00997 Ref<CdsObject> refObj = loadObject(refID);
00998 if (refObj == nil)
00999 throw _Exception(_("tried to create container with refID set, but refID doesn't point to an existing object"));
01000 }
01001 String dbLocation = addLocationPrefix((isVirtual ? LOC_VIRT_PREFIX : LOC_DIR_PREFIX), path);
01002
01003 int newID = getNextID();
01004
01005 Ref<StringBuffer> qb(new StringBuffer());
01006 *qb << "INSERT INTO "
01007 << TQ(CDS_OBJECT_TABLE)
01008 << " ("
01009 << TQ("id") << ','
01010 << TQ("parent_id") << ','
01011 << TQ("object_type") << ','
01012 << TQ("upnp_class") << ','
01013 << TQ("dc_title") << ','
01014 << TQ("location") << ','
01015 << TQ("location_hash") << ','
01016 << TQ("ref_id") << ") VALUES ("
01017 << newID << ','
01018 << parentID << ','
01019 << OBJECT_TYPE_CONTAINER << ','
01020 << (string_ok(upnpClass) ? quote(upnpClass) : quote(_(UPNP_DEFAULT_CLASS_CONTAINER))) << ','
01021 << quote(name) << ','
01022 << quote(dbLocation) << ','
01023 << quote(stringHash(dbLocation)) << ','
01024 << (refID > 0 ? quote(refID) : _(SQL_NULL))
01025 << ')';
01026
01027 exec(qb);
01028
01029
01030 if (cacheOn())
01031 {
01032 AUTOLOCK(cache->getMutex());
01033 cache->addChild(parentID);
01034 if (cache->flushed())
01035 flushInsertBuffer();
01036 Ref<CacheObject> cObj = cache->getObjectDefinitely(newID);
01037 if (cache->flushed())
01038 flushInsertBuffer();
01039 cObj->setParentID(parentID);
01040 cObj->setNumChildren(0);
01041 cObj->setObjectType(OBJECT_TYPE_CONTAINER);
01042 cObj->setLocation(path);
01043 }
01044
01045
01046 return newID;
01047
01048
01049
01050 }
01051
01052 String SQLStorage::buildContainerPath(int parentID, String title)
01053 {
01054
01055 if (parentID == CDS_ID_ROOT)
01056 return String(VIRTUAL_CONTAINER_SEPARATOR) + title;
01057 Ref<StringBuffer> qb(new StringBuffer());
01058 *qb << "SELECT " << TQ("location") << " FROM " << TQ(CDS_OBJECT_TABLE) <<
01059 " WHERE " << TQ("id") << '=' << parentID << " LIMIT 1";
01060 Ref<SQLResult> res = select(qb);
01061 if (res == nil)
01062 return nil;
01063 Ref<SQLRow> row = res->nextRow();
01064 if (row == nil)
01065 return nil;
01066 char prefix;
01067 String path = stripLocationPrefix(&prefix, row->col(0)) + VIRTUAL_CONTAINER_SEPARATOR + title;
01068 if (prefix != LOC_VIRT_PREFIX)
01069 throw _Exception(_("tried to build a virtual container path with an non-virtual parentID"));
01070 return path;
01071 }
01072
01073 void SQLStorage::addContainerChain(String path, String lastClass, int lastRefID, int *containerID, int *updateID)
01074 {
01075 path = path.reduce(VIRTUAL_CONTAINER_SEPARATOR);
01076 if (path == VIRTUAL_CONTAINER_SEPARATOR)
01077 {
01078 *containerID = CDS_ID_ROOT;
01079 return;
01080 }
01081 Ref<StringBuffer> qb(new StringBuffer());
01082 String dbLocation = addLocationPrefix(LOC_VIRT_PREFIX, path);
01083 *qb << "SELECT " << TQ("id") << " FROM " << TQ(CDS_OBJECT_TABLE)
01084 << " WHERE " << TQ("location_hash") << '=' << quote(stringHash(dbLocation))
01085 << " AND " << TQ("location") << '=' << quote(dbLocation)
01086 << " LIMIT 1";
01087 Ref<SQLResult> res = select(qb);
01088 if (res != nil)
01089 {
01090 Ref<SQLRow> row = res->nextRow();
01091 if (row != nil)
01092 {
01093 if (containerID != NULL)
01094 *containerID = row->col(0).toInt();
01095 return;
01096 }
01097 }
01098 int parentContainerID;
01099 String newpath, container;
01100 stripAndUnescapeVirtualContainerFromPath(path, newpath, container);
01101 addContainerChain(newpath, nil, INVALID_OBJECT_ID, &parentContainerID, updateID);
01102 if (updateID != NULL && *updateID == INVALID_OBJECT_ID)
01103 *updateID = parentContainerID;
01104 *containerID = createContainer(parentContainerID, container, path, true, lastClass, lastRefID);
01105 }
01106
01107 String SQLStorage::addLocationPrefix(char prefix, String path)
01108 {
01109 return String(prefix) + path;
01110 }
01111
01112 String SQLStorage::stripLocationPrefix(char* prefix, String path)
01113 {
01114 if (path == nil)
01115 {
01116 *prefix = LOC_ILLEGAL_PREFIX;
01117 return nil;
01118 }
01119 *prefix = path.charAt(0);
01120 return path.substring(1);
01121 }
01122
01123 String SQLStorage::stripLocationPrefix(String path)
01124 {
01125 if (path == nil)
01126 return nil;
01127 return path.substring(1);
01128 }
01129
01130 Ref<CdsObject> SQLStorage::createObjectFromRow(Ref<SQLRow> row)
01131 {
01132 int objectType = row->col(_object_type).toInt();
01133 Ref<CdsObject> obj = CdsObject::createObject(objectType);
01134
01135
01136 obj->setID(row->col(_id).toInt());
01137 obj->setRefID(row->col(_ref_id).toInt());
01138
01139 obj->setParentID(row->col(_parent_id).toInt());
01140 obj->setTitle(row->col(_dc_title));
01141 obj->setClass(fallbackString(row->col(_upnp_class), row->col(_ref_upnp_class)));
01142 obj->setFlags(row->col(_flags).toUInt());
01143
01144 String metadataStr = fallbackString(row->col(_metadata), row->col(_ref_metadata));
01145 Ref<Dictionary> meta(new Dictionary());
01146 meta->decode(metadataStr);
01147 obj->setMetadata(meta);
01148
01149 String auxdataStr = fallbackString(row->col(_auxdata), row->col(_ref_auxdata));
01150 Ref<Dictionary> aux(new Dictionary());
01151 aux->decode(auxdataStr);
01152 obj->setAuxData(aux);
01153
01154 String resources_str = fallbackString(row->col(_resources), row->col(_ref_resources));
01155 bool resource_zero_ok = false;
01156 if (string_ok(resources_str))
01157 {
01158 Ref<Array<StringBase> > resources = split_string(resources_str,
01159 RESOURCE_SEP);
01160 for (int i = 0; i < resources->size(); i++)
01161 {
01162 if (i == 0)
01163 resource_zero_ok = true;
01164 obj->addResource(CdsResource::decode(resources->get(i)));
01165 }
01166 }
01167
01168 if ( (obj->getRefID() && IS_CDS_PURE_ITEM(objectType)) ||
01169 (IS_CDS_ITEM(objectType) && ! IS_CDS_PURE_ITEM(objectType)) )
01170 obj->setVirtual(true);
01171 else
01172 obj->setVirtual(false);
01173
01174 int matched_types = 0;
01175
01176 if (IS_CDS_CONTAINER(objectType))
01177 {
01178 Ref<CdsContainer> cont = RefCast(obj, CdsContainer);
01179 cont->setUpdateID(row->col(_update_id).toInt());
01180 char locationPrefix;
01181 cont->setLocation(stripLocationPrefix(&locationPrefix, row->col(_location)));
01182 if (locationPrefix == LOC_VIRT_PREFIX)
01183 cont->setVirtual(true);
01184
01185 String autoscanPersistent = row->col(_as_persistent);
01186 if (string_ok(autoscanPersistent))
01187 {
01188 if (remapBool(autoscanPersistent))
01189 cont->setAutoscanType(OBJECT_AUTOSCAN_CFG);
01190 else
01191 cont->setAutoscanType(OBJECT_AUTOSCAN_UI);
01192 }
01193 else
01194 cont->setAutoscanType(OBJECT_AUTOSCAN_NONE);
01195 matched_types++;
01196 }
01197
01198 if (IS_CDS_ITEM(objectType))
01199 {
01200 if (! resource_zero_ok)
01201 throw _Exception(_("tried to create object without at least one resource"));
01202
01203 Ref<CdsItem> item = RefCast(obj, CdsItem);
01204 item->setMimeType(fallbackString(row->col(_mime_type), row->col(_ref_mime_type)));
01205 if (IS_CDS_PURE_ITEM(objectType))
01206 {
01207 if (! obj->isVirtual())
01208 item->setLocation(stripLocationPrefix(row->col(_location)));
01209 else
01210 item->setLocation(stripLocationPrefix(row->col(_ref_location)));
01211 }
01212 else
01213 {
01214 item->setLocation(fallbackString(row->col(_location), row->col(_ref_location)));
01215 }
01216
01217 item->setTrackNumber(row->col(_track_number).toInt());
01218
01219 if (string_ok(row->col(_ref_service_id)))
01220 item->setServiceID(row->col(_ref_service_id));
01221 else
01222 item->setServiceID(row->col(_service_id));
01223
01224 matched_types++;
01225 }
01226
01227 if (IS_CDS_ACTIVE_ITEM(objectType))
01228 {
01229 Ref<CdsActiveItem> aitem = RefCast(obj, CdsActiveItem);
01230
01231 Ref<StringBuffer> query(new StringBuffer());
01232 *query << "SELECT " << TQ("id") << ',' << TQ("action") << ','
01233 << TQ("state") << " FROM " << TQ(CDS_ACTIVE_ITEM_TABLE)
01234 << " WHERE " << TQ("id") << '=' << quote(aitem->getID());
01235 Ref<SQLResult> resAI = select(query);
01236 Ref<SQLRow> rowAI;
01237 if (resAI != nil && (rowAI = resAI->nextRow()) != nil)
01238 {
01239 aitem->setAction(rowAI->col(1));
01240 aitem->setState(rowAI->col(2));
01241 }
01242 else
01243 throw _Exception(_("Active Item in cds_objects, but not in cds_active_item"));
01244
01245 matched_types++;
01246 }
01247
01248 if(! matched_types)
01249 {
01250 throw _StorageException(nil, _("unknown object type: ")+ objectType);
01251 }
01252
01253 addObjectToCache(obj);
01254 return obj;
01255 }
01256
01257 int SQLStorage::getTotalFiles()
01258 {
01259 flushInsertBuffer();
01260
01261 Ref<StringBuffer> query(new StringBuffer());
01262 *query << "SELECT COUNT(*) FROM " << TQ(CDS_OBJECT_TABLE) << " WHERE "
01263 << TQ("object_type") << " != " << quote(OBJECT_TYPE_CONTAINER);
01264
01265 Ref<SQLResult> res = select(query);
01266 Ref<SQLRow> row;
01267 if (res != nil && (row = res->nextRow()) != nil)
01268 {
01269 return row->col(0).toInt();
01270 }
01271 return 0;
01272 }
01273
01274 String SQLStorage::incrementUpdateIDs(int *ids, int size)
01275 {
01276 if (size <= 0)
01277 return nil;
01278 Ref<StringBuffer> inBuf(new StringBuffer());
01279 *inBuf << "IN (" << ids[0];
01280 for (int i = 1; i < size; i++)
01281 *inBuf << ',' << ids[i];
01282 *inBuf << ')';
01283
01284 Ref<StringBuffer> buf(new StringBuffer());
01285 *buf << "UPDATE " << TQ(CDS_OBJECT_TABLE) << " SET " << TQ("update_id") << '=' << TQ("update_id") << " + 1 WHERE " << TQ("id") << ' ';
01286 *buf << inBuf;
01287 exec(buf);
01288
01289 buf->clear();
01290 *buf << "SELECT " << TQ("id") << ',' << TQ("update_id") << " FROM " << TQ(CDS_OBJECT_TABLE) << " WHERE " << TQ("id") << ' ';
01291 *buf << inBuf;
01292 Ref<SQLResult> res = select(buf);
01293 if (res == nil)
01294 throw _Exception(_("Error while fetching update ids"));
01295 Ref<SQLRow> row;
01296 buf->clear();
01297 while((row = res->nextRow()) != nil)
01298 *buf << ',' << row->col(0) << ',' << row->col(1);
01299 if (buf->length() <= 0)
01300 return nil;
01301 return buf->toString(1);
01302 }
01303
01304
01305
01306
01307
01308
01309
01310
01311
01312
01313
01314
01315
01316
01317
01318
01319
01320
01321
01322
01323
01324
01325
01326
01327
01328
01329
01330
01331
01332
01333
01334
01335
01336
01337
01338
01339
01340
01341
01342
01343 Ref<DBRHash<int> > SQLStorage::getObjects(int parentID, bool withoutContainer)
01344 {
01345 flushInsertBuffer();
01346
01347 Ref<StringBuffer> q(new StringBuffer());
01348 *q << "SELECT " << TQ("id") << " FROM " << TQ(CDS_OBJECT_TABLE) << " WHERE ";
01349 if (withoutContainer)
01350 *q << TQ("object_type") << " != " << OBJECT_TYPE_CONTAINER << " AND ";
01351 *q << TQ("parent_id") << '=';
01352 *q << parentID;
01353 Ref<SQLResult> res = select(q);
01354 if (res == nil)
01355 throw _Exception(_("db error"));
01356 Ref<SQLRow> row;
01357
01358 if (res->getNumRows() <= 0)
01359 return nil;
01360 int capacity = res->getNumRows() * 5 + 1;
01361 if (capacity < 521)
01362 capacity = 521;
01363
01364 Ref<DBRHash<int> > ret(new DBRHash<int>(capacity, res->getNumRows(), INVALID_OBJECT_ID, INVALID_OBJECT_ID_2));
01365
01366 while ((row = res->nextRow()) != nil)
01367 {
01368 ret->put(row->col(0).toInt());
01369 }
01370 return ret;
01371 }
01372
01373 Ref<Storage::ChangedContainers> SQLStorage::removeObjects(zmm::Ref<DBRHash<int> > list, bool all)
01374 {
01375 flushInsertBuffer();
01376
01377 hash_data_array_t<int> hash_data_array;
01378 list->getAll(&hash_data_array);
01379 int count = hash_data_array.size;
01380 int *array = hash_data_array.data;
01381 if (count <= 0)
01382 return nil;
01383
01384 Ref<StringBuffer> idsBuf(new StringBuffer());
01385 *idsBuf << "SELECT " << TQ("id") << ',' << TQ("object_type")
01386 << " FROM " << TQ(CDS_OBJECT_TABLE)
01387 << " WHERE " << TQ("id") << " IN (";
01388 int firstComma = idsBuf->length();
01389 for (int i = 0; i < count; i++)
01390 {
01391 int id = array[i];
01392 if (IS_FORBIDDEN_CDS_ID(id))
01393 throw _Exception(_("tried to delete a forbidden ID (") + id + ")!");
01394 *idsBuf << ',' << id;
01395 }
01396 idsBuf->setCharAt(firstComma, ' ');
01397 *idsBuf << ')';
01398 Ref<SQLResult> res = select(idsBuf);
01399 idsBuf = nil;
01400 if (res == nil)
01401 throw _Exception(_("sql error"));
01402
01403 Ref<StringBuffer> items(new StringBuffer());
01404 Ref<StringBuffer> containers(new StringBuffer());
01405 Ref<SQLRow> row;
01406 while ((row = res->nextRow()) != nil)
01407 {
01408 int objectType = row->col(1).toInt();
01409 if (IS_CDS_CONTAINER(objectType))
01410 *containers << ',' << row->col_c_str(0);
01411 else
01412 *items << ',' << row->col_c_str(0);
01413 }
01414 return _purgeEmptyContainers(_recursiveRemove(items, containers, all));
01415 }
01416
01417 void SQLStorage::_removeObjects(Ref<StringBuffer> objectIDs, int offset)
01418 {
01419 Ref<StringBuffer> q(new StringBuffer());
01420 *q << "SELECT " << TQD('a',"id") << ',' << TQD('a',"persistent")
01421 << ',' << TQD('o',"location")
01422 << " FROM " << TQ(AUTOSCAN_TABLE) << " a"
01423 " JOIN " << TQ(CDS_OBJECT_TABLE) << " o"
01424 " ON " << TQD('o',"id") << '=' << TQD('a',"obj_id")
01425 << " WHERE " << TQD('o',"id") << " IN (";
01426 q->concat(objectIDs, offset);
01427 *q << ')';
01428
01429 log_debug("%s\n", q->c_str());
01430
01431 Ref<SQLResult> res = select(q);
01432 if (res != nil)
01433 {
01434 log_debug("relevant autoscans!\n");
01435 Ref<StringBuffer> delete_as(new StringBuffer());
01436 Ref<SQLRow> row;
01437 while((row = res->nextRow()) != nil)
01438 {
01439 bool persistent = remapBool(row->col(1));
01440 if (persistent)
01441 {
01442 String location = stripLocationPrefix(row->col(2));
01443 *q << "UPDATE " << TQ(AUTOSCAN_TABLE)
01444 << " SET " << TQ("obj_id") << "=" SQL_NULL
01445 << ',' << TQ("location") << '=' << quote(location)
01446 << " WHERE " << TQ("id") << '=' << quote(row->col(0));
01447 }
01448 else
01449 *delete_as << ',' << row->col_c_str(0);
01450 log_debug("relevant autoscan: %d; persistent: %d\n", row->col_c_str(0), persistent);
01451 }
01452
01453 if (delete_as->length() > 0)
01454 {
01455 q->clear();
01456 *q << "DELETE FROM " << TQ(AUTOSCAN_TABLE)
01457 << " WHERE " << TQ("id") << " IN (";
01458 q->concat(delete_as, 1);
01459 *q << ')';
01460 exec(q);
01461 log_debug("deleting autoscans: %s\n", delete_as->c_str());
01462 }
01463 }
01464
01465 q->clear();
01466 *q << "DELETE FROM " << TQ(CDS_ACTIVE_ITEM_TABLE)
01467 << " WHERE " << TQ("id") << " IN (";
01468 q->concat(objectIDs, offset);
01469 *q << ')';
01470 exec(q);
01471
01472 q->clear();
01473 *q << "DELETE FROM " << TQ(CDS_OBJECT_TABLE)
01474 << " WHERE " << TQ("id") << " IN (";
01475 q->concat(objectIDs, offset);
01476 *q << ')';
01477 exec(q);
01478 }
01479
01480 Ref<Storage::ChangedContainers> SQLStorage::removeObject(int objectID, bool all)
01481 {
01482 flushInsertBuffer();
01483
01484 Ref<StringBuffer> q(new StringBuffer());
01485 *q << "SELECT " << TQ("object_type") << ',' << TQ("ref_id")
01486 << " FROM " << TQ(CDS_OBJECT_TABLE)
01487 << " WHERE " << TQ("id") << '=' << quote(objectID) << " LIMIT 1";
01488 Ref<SQLResult> res = select(q);
01489 if (res == nil)
01490 return nil;
01491 Ref<SQLRow> row = res->nextRow();
01492 if (row == nil)
01493 return nil;
01494
01495 int objectType = row->col(0).toInt();
01496 bool isContainer = IS_CDS_CONTAINER(objectType);
01497 if (all && ! isContainer)
01498 {
01499 String ref_id_str = row->col(1);
01500 int ref_id;
01501 if (string_ok(ref_id_str))
01502 {
01503 ref_id = ref_id_str.toInt();
01504 if (! IS_FORBIDDEN_CDS_ID(ref_id))
01505 objectID = ref_id;
01506 }
01507 }
01508 if (IS_FORBIDDEN_CDS_ID(objectID))
01509 throw _Exception(_("tried to delete a forbidden ID (") + objectID + ")!");
01510 Ref<StringBuffer> idsBuf(new StringBuffer());
01511 *idsBuf << ',' << objectID;
01512 Ref<ChangedContainersStr> changedContainers = nil;
01513 if (isContainer)
01514 changedContainers = _recursiveRemove(nil, idsBuf, all);
01515 else
01516 changedContainers = _recursiveRemove(idsBuf, nil, all);
01517 return _purgeEmptyContainers(changedContainers);
01518 }
01519
01520 Ref<SQLStorage::ChangedContainersStr> SQLStorage::_recursiveRemove(Ref<StringBuffer> items, Ref<StringBuffer> containers, bool all)
01521 {
01522 log_debug("start\n");
01523 Ref<StringBuffer> recurseItems(new StringBuffer());
01524 *recurseItems << "SELECT DISTINCT " << TQ("id") << ',' << TQ("parent_id")
01525 << " FROM " << TQ(CDS_OBJECT_TABLE) <<
01526 " WHERE " << TQ("ref_id") << " IN (";
01527 int recurseItemsLen = recurseItems->length();
01528
01529 Ref<StringBuffer> recurseContainers(new StringBuffer());
01530 *recurseContainers << "SELECT DISTINCT " << TQ("id")
01531 << ',' << TQ("object_type");
01532 if (all)
01533 *recurseContainers << ',' << TQ("ref_id");
01534 *recurseContainers << " FROM " << TQ(CDS_OBJECT_TABLE) <<
01535 " WHERE " << TQ("parent_id") << " IN (";
01536 int recurseContainersLen = recurseContainers->length();
01537
01538 Ref<StringBuffer> removeAddParents(new StringBuffer());
01539 *removeAddParents << "SELECT DISTINCT " << TQ("parent_id")
01540 << " FROM " << TQ(CDS_OBJECT_TABLE)
01541 << " WHERE " << TQ("id") << " IN (";
01542 int removeAddParentsLen = removeAddParents->length();
01543
01544 Ref<StringBuffer> remove(new StringBuffer());
01545 Ref<ChangedContainersStr> changedContainers(new ChangedContainersStr());
01546
01547 Ref<SQLResult> res;
01548 Ref<SQLRow> row;
01549
01550 if (items != nil && items->length() > 1)
01551 {
01552 *recurseItems << items;
01553 *removeAddParents << items;
01554 }
01555
01556 if (containers != nil && containers->length() > 1)
01557 {
01558 *recurseContainers << containers;
01559
01560 *remove << containers;
01561 *removeAddParents << containers;
01562 removeAddParents->setCharAt(removeAddParentsLen, ' ');
01563 *removeAddParents << ')';
01564 res = select(removeAddParents);
01565 if (res == nil)
01566 throw _StorageException(nil, _("sql error"));
01567 removeAddParents->setLength(removeAddParentsLen);
01568 while ((row = res->nextRow()) != nil)
01569 *changedContainers->ui << ',' << row->col_c_str(0);
01570 }
01571
01572 int count = 0;
01573 while(recurseItems->length() > recurseItemsLen
01574 || removeAddParents->length() > removeAddParentsLen
01575 || recurseContainers->length() > recurseContainersLen)
01576 {
01577 if (removeAddParents->length() > removeAddParentsLen)
01578 {
01579
01580 *remove << removeAddParents->c_str(removeAddParentsLen);
01581
01582 removeAddParents->setCharAt(removeAddParentsLen, ' ');
01583 *removeAddParents << ')';
01584 res = select(removeAddParents);
01585 if (res == nil)
01586 throw _StorageException(nil, _("sql error"));
01587
01588 removeAddParents->setLength(removeAddParentsLen);
01589 while ((row = res->nextRow()) != nil)
01590 *changedContainers->upnp << ',' << row->col_c_str(0);
01591 }
01592
01593 if (recurseItems->length() > recurseItemsLen)
01594 {
01595 recurseItems->setCharAt(recurseItemsLen, ' ');
01596 *recurseItems << ')';
01597 res = select(recurseItems);
01598 if (res == nil)
01599 throw _StorageException(nil, _("sql error"));
01600 recurseItems->setLength(recurseItemsLen);
01601 while ((row = res->nextRow()) != nil)
01602 {
01603 *remove << ',' << row->col_c_str(0);
01604 *changedContainers->upnp << ',' << row->col_c_str(1);
01605
01606 }
01607 }
01608
01609 if (recurseContainers->length() > recurseContainersLen)
01610 {
01611 recurseContainers->setCharAt(recurseContainersLen, ' ');
01612 *recurseContainers << ')';
01613 res = select(recurseContainers);
01614 if (res == nil)
01615 throw _StorageException(nil, _("sql error"));
01616 recurseContainers->setLength(recurseContainersLen);
01617 while ((row = res->nextRow()) != nil)
01618 {
01619
01620
01621 int objectType = row->col(1).toInt();
01622 if (IS_CDS_CONTAINER(objectType))
01623 {
01624 *recurseContainers << ',' << row->col_c_str(0);
01625 *remove << ',' << row->col_c_str(0);
01626 }
01627 else
01628 {
01629 if (all)
01630 {
01631 String refId = row->col(2);
01632 if (string_ok(refId))
01633 {
01634 *removeAddParents << ',' << refId;
01635 *recurseItems << ',' << refId;
01636
01637 }
01638 else
01639 {
01640 *remove << ',' << row->col_c_str(0);
01641 *recurseItems << ',' << row->col_c_str(0);
01642 }
01643 }
01644 else
01645 {
01646 *remove << ',' << row->col_c_str(0);
01647 *recurseItems << ',' << row->col_c_str(0);
01648 }
01649 }
01650
01651 }
01652 }
01653
01654 if (remove->length() > MAX_REMOVE_SIZE)
01655 {
01656 _removeObjects(remove, 1);
01657 remove->clear();
01658 }
01659
01660 if (count++ > MAX_REMOVE_RECURSION)
01661 throw _Exception(_("there seems to be an infinite loop..."));
01662 }
01663
01664 if (remove->length() > 0)
01665 _removeObjects(remove, 1);
01666 log_debug("end\n");
01667 return changedContainers;
01668 }
01669
01670 Ref<Storage::ChangedContainers> SQLStorage::_purgeEmptyContainers(Ref<ChangedContainersStr> changedContainersStr)
01671 {
01672 log_debug("start upnp: %s; ui: %s\n", changedContainersStr->upnp->c_str(), changedContainersStr->ui->c_str());
01673 Ref<ChangedContainers> changedContainers(new ChangedContainers());
01674 if (! string_ok(changedContainersStr->upnp) && ! string_ok(changedContainersStr->ui))
01675 return changedContainers;
01676
01677 Ref<StringBuffer> bufSelUI(new StringBuffer());
01678 *bufSelUI << "SELECT " << TQD('a',"id")
01679 << ", COUNT(" << TQD('b',"parent_id")
01680 << ")," << TQD('a',"parent_id") << ',' << TQD('a',"flags")
01681 << " FROM " << TQ(CDS_OBJECT_TABLE) << ' ' << TQ('a')
01682 << " LEFT JOIN " << TQ(CDS_OBJECT_TABLE) << ' ' << TQ('b')
01683 << " ON " << TQD('a',"id") << '=' << TQD('b',"parent_id")
01684 << " WHERE " << TQD('a',"object_type") << '=' << quote(1)
01685 << " AND " << TQD('a',"id") << " IN (";
01686 int bufSelLen = bufSelUI->length();
01687 String strSel2 = _(") GROUP BY a.id");
01688
01689 Ref<StringBuffer> bufSelUpnp(new StringBuffer());
01690 *bufSelUpnp << bufSelUI;
01691
01692 Ref<StringBuffer> bufDel(new StringBuffer());
01693
01694 Ref<SQLResult> res;
01695 Ref<SQLRow> row;
01696
01697 *bufSelUI << changedContainersStr->ui;
01698 *bufSelUpnp << changedContainersStr->upnp;
01699
01700 bool again;
01701 int count = 0;
01702 do
01703 {
01704 again = false;
01705
01706 if (bufSelUpnp->length() > bufSelLen)
01707 {
01708 bufSelUpnp->setCharAt(bufSelLen, ' ');
01709 *bufSelUpnp << strSel2;
01710 log_debug("upnp-sql: %s\n", bufSelUpnp->c_str());
01711 res = select(bufSelUpnp);
01712 bufSelUpnp->setLength(bufSelLen);
01713 if (res == nil)
01714 throw _Exception(_("db error"));
01715 while ((row = res->nextRow()) != nil)
01716 {
01717 int flags = row->col(3).toInt();
01718 if (flags & OBJECT_FLAG_PERSISTENT_CONTAINER)
01719 changedContainers->upnp->append(row->col(0).toInt());
01720 else if (row->col(1) == "0")
01721 {
01722 *bufDel << ',' << row->col_c_str(0);
01723 *bufSelUI << ',' << row->col_c_str(2);
01724 }
01725 else
01726 {
01727 *bufSelUpnp << ',' << row->col_c_str(0);
01728 }
01729 }
01730 }
01731
01732 if (bufSelUI->length() > bufSelLen)
01733 {
01734 bufSelUI->setCharAt(bufSelLen, ' ');
01735 *bufSelUI << strSel2;
01736 log_debug("ui-sql: %s\n", bufSelUI->c_str());
01737 res = select(bufSelUI);
01738 bufSelUI->setLength(bufSelLen);
01739 if (res == nil)
01740 throw _Exception(_("db error"));
01741 while ((row = res->nextRow()) != nil)
01742 {
01743 int flags = row->col(3).toInt();
01744 if (flags & OBJECT_FLAG_PERSISTENT_CONTAINER)
01745 {
01746 changedContainers->ui->append(row->col(0).toInt());
01747 changedContainers->upnp->append(row->col(0).toInt());
01748 }
01749 else if (row->col(1) == "0")
01750 {
01751 *bufDel << ',' << row->col_c_str(0);
01752 *bufSelUI << ',' << row->col_c_str(2);
01753 }
01754 else
01755 {
01756 *bufSelUI << ',' << row->col_c_str(0);
01757 }
01758 }
01759 }
01760
01761
01762 if (bufDel->length() > 0)
01763 {
01764 _removeObjects(bufDel, 1);
01765 bufDel->clear();
01766 if (bufSelUI->length() > bufSelLen || bufSelUpnp->length() > bufSelLen)
01767 again = true;
01768 }
01769 if (count++ >= MAX_REMOVE_RECURSION)
01770 throw _Exception(_("there seems to be an infinite loop..."));
01771 }
01772 while (again);
01773
01774 if (bufSelUI->length() > bufSelLen)
01775 {
01776 changedContainers->ui->addCSV(bufSelUI->toString(bufSelLen + 1));
01777 changedContainers->upnp->addCSV(bufSelUI->toString(bufSelLen + 1));
01778 }
01779 if (bufSelUpnp->length() > bufSelLen)
01780 {
01781 changedContainers->upnp->addCSV(bufSelUpnp->toString(bufSelLen + 1));
01782 }
01783 log_debug("end; changedContainers (upnp): %s\n", changedContainers->upnp->toCSV().c_str());
01784 log_debug("end; changedContainers (ui): %s\n", changedContainers->ui->toCSV().c_str());
01785
01786
01787 if (cacheOn())
01788 cache->clear();
01789
01790
01791 return changedContainers;
01792 }
01793
01794 String SQLStorage::getInternalSetting(String key)
01795 {
01796 Ref<StringBuffer> q(new StringBuffer());
01797 *q << "SELECT " << TQ("value") << " FROM " << TQ(INTERNAL_SETTINGS_TABLE) << " WHERE " << TQ("key") << '='
01798 << quote(key) << " LIMIT 1";
01799 Ref<SQLResult> res = select(q);
01800 if (res == nil)
01801 return nil;
01802 Ref<SQLRow> row = res->nextRow();
01803 if (row == nil)
01804 return nil;
01805 return row->col(0);
01806 }
01807
01808
01809
01810
01811
01812 void SQLStorage::updateAutoscanPersistentList(scan_mode_t scanmode, Ref<AutoscanList> list)
01813 {
01814
01815 log_debug("setting persistent autoscans untouched - scanmode: %s;\n", AutoscanDirectory::mapScanmode(scanmode).c_str());
01816 Ref<StringBuffer> q(new StringBuffer());
01817 *q << "UPDATE " << TQ(AUTOSCAN_TABLE)
01818 << " SET " << TQ("touched") << '=' << mapBool(false)
01819 << " WHERE "
01820 << TQ("persistent") << '=' << mapBool(true)
01821 << " AND " << TQ("scan_mode") << '='
01822 << quote(AutoscanDirectory::mapScanmode(scanmode));
01823 exec(q);
01824
01825 int listSize = list->size();
01826 log_debug("updating/adding persistent autoscans (count: %d)\n", listSize);
01827 for (int i = 0; i < listSize; i++)
01828 {
01829 log_debug("getting ad %d from list..\n", i);
01830 Ref<AutoscanDirectory> ad = list->get(i);
01831 if (ad == nil)
01832 continue;
01833
01834
01835 assert(ad->persistent());
01836
01837 assert(ad->getScanMode() == scanmode);
01838
01839 String location = ad->getLocation();
01840 if (! string_ok(location))
01841 throw _Exception(_("AutoscanDirectoy with illegal location given to SQLStorage::updateAutoscanPersistentList"));
01842
01843 q->clear();
01844 *q << "SELECT " << TQ("id") << " FROM " << TQ(AUTOSCAN_TABLE)
01845 << " WHERE ";
01846 int objectID = findObjectIDByPath(location + '/');
01847 log_debug("objectID = %d\n", objectID);
01848 if (objectID == INVALID_OBJECT_ID)
01849 *q << TQ("location") << '=' << quote(location);
01850 else
01851 *q << TQ("obj_id") << '=' << quote(objectID);
01852 *q << " LIMIT 1";
01853 Ref<SQLResult> res = select(q);
01854 if (res == nil)
01855 throw _StorageException(nil, _("query error while selecting from autoscan list"));
01856 Ref<SQLRow> row;
01857 if ((row = res->nextRow()) != nil)
01858 {
01859 ad->setStorageID(row->col(0).toInt());
01860 updateAutoscanDirectory(ad);
01861 }
01862 else
01863 addAutoscanDirectory(ad);
01864 }
01865
01866 q->clear();
01867 *q << "DELETE FROM " << TQ(AUTOSCAN_TABLE)
01868 << " WHERE " << TQ("touched") << '=' << mapBool(false)
01869 << " AND " << TQ("scan_mode") << '='
01870 << quote(AutoscanDirectory::mapScanmode(scanmode));
01871 exec(q);
01872 }
01873
01874 Ref<AutoscanList> SQLStorage::getAutoscanList(scan_mode_t scanmode)
01875 {
01876 #define FLD(field) << TQD('a',field) <<
01877 Ref<StringBuffer> q(new StringBuffer());
01878 *q << "SELECT " FLD("id") ',' FLD("obj_id") ',' FLD("scan_level") ','
01879 FLD("scan_mode") ',' FLD("recursive") ',' FLD("hidden") ','
01880 FLD("interval") ',' FLD("last_modified") ',' FLD("persistent") ','
01881 FLD("location") ',' << TQD('t',"location")
01882 << " FROM " << TQ(AUTOSCAN_TABLE) << ' ' << TQ('a')
01883 << " LEFT JOIN " << TQ(CDS_OBJECT_TABLE) << ' ' << TQ('t')
01884 << " ON " FLD("obj_id") '=' << TQD('t',"id")
01885 << " WHERE " FLD("scan_mode") '=' << quote(AutoscanDirectory::mapScanmode(scanmode));
01886 Ref<SQLResult> res = select(q);
01887 if (res == nil)
01888 throw _StorageException(nil, _("query error while fetching autoscan list"));
01889 Ref<AutoscanList> ret(new AutoscanList());
01890 Ref<SQLRow> row;
01891 while((row = res->nextRow()) != nil)
01892 {
01893 Ref<AutoscanDirectory> dir = _fillAutoscanDirectory(row);
01894 if (dir == nil)
01895 removeAutoscanDirectory(row->col(0).toInt());
01896 else
01897 ret->add(dir);
01898 }
01899 return ret;
01900 }
01901
01902 Ref<AutoscanDirectory> SQLStorage::getAutoscanDirectory(int objectID)
01903 {
01904 #define FLD(field) << TQD('a',field) <<
01905 Ref<StringBuffer> q(new StringBuffer());
01906 *q << "SELECT " FLD("id") ',' FLD("obj_id") ',' FLD("scan_level") ','
01907 FLD("scan_mode") ',' FLD("recursive") ',' FLD("hidden") ','
01908 FLD("interval") ',' FLD("last_modified") ',' FLD("persistent") ','
01909 FLD("location") ',' << TQD('t',"location")
01910 << " FROM " << TQ(AUTOSCAN_TABLE) << ' ' << TQ('a')
01911 << " LEFT JOIN " << TQ(CDS_OBJECT_TABLE) << ' ' << TQ('t')
01912 << " ON " FLD("obj_id") '=' << TQD('t',"id")
01913 << " WHERE " << TQD('t',"id") << '=' << quote(objectID);
01914 Ref<SQLResult> res = select(q);
01915 if (res == nil)
01916 throw _StorageException(nil, _("query error while fetching autoscan"));
01917 Ref<AutoscanList> ret(new AutoscanList());
01918 Ref<SQLRow> row = res->nextRow();
01919 if (row == nil)
01920 return nil;
01921 else
01922 return _fillAutoscanDirectory(row);
01923 }
01924
01925 Ref<AutoscanDirectory> SQLStorage::_fillAutoscanDirectory(Ref<SQLRow> row)
01926 {
01927 int objectID = INVALID_OBJECT_ID;
01928 String objectIDstr = row->col(1);
01929 if (string_ok(objectIDstr))
01930 objectID = objectIDstr.toInt();
01931 int storageID = row->col(0).toInt();
01932
01933 String location;
01934 if (objectID == INVALID_OBJECT_ID)
01935 location = row->col(9);
01936 else
01937 {
01938 char prefix;
01939 location = stripLocationPrefix(&prefix, row->col(10));
01940 if (prefix != LOC_DIR_PREFIX)
01941 return nil;
01942 }
01943
01944 scan_level_t level = AutoscanDirectory::remapScanlevel(row->col(2));
01945 scan_mode_t mode = AutoscanDirectory::remapScanmode(row->col(3));
01946 bool recursive = remapBool(row->col(4));
01947 bool hidden = remapBool(row->col(5));
01948 bool persistent = remapBool(row->col(8));
01949 int interval = 0;
01950 if (mode == TimedScanMode)
01951 interval = row->col(6).toInt();
01952 time_t last_modified = row->col(7).toLong();
01953
01954
01955
01956 Ref<AutoscanDirectory> dir(new AutoscanDirectory(location, mode, level, recursive, persistent, INVALID_SCAN_ID, interval, hidden));
01957 dir->setObjectID(objectID);
01958 dir->setStorageID(storageID);
01959 dir->setCurrentLMT(last_modified);
01960 dir->updateLMT();
01961 return dir;
01962 }
01963
01964 void SQLStorage::addAutoscanDirectory(Ref<AutoscanDirectory> adir)
01965 {
01966 if (adir == nil)
01967 throw _Exception(_("addAutoscanDirectory called with adir==nil"));
01968 if (adir->getStorageID() >= 0)
01969 throw _Exception(_("tried to add autoscan directory with a storage id set"));
01970 int objectID;
01971 if (adir->getLocation() == FS_ROOT_DIRECTORY)
01972 objectID = CDS_ID_FS_ROOT;
01973 else
01974 objectID = findObjectIDByPath(adir->getLocation() + DIR_SEPARATOR);
01975 if (! adir->persistent() && objectID < 0)
01976 throw _Exception(_("tried to add non-persistent autoscan directory with an illegal objectID or location"));
01977
01978 Ref<IntArray> pathIds = _checkOverlappingAutoscans(adir);
01979
01980 _autoscanChangePersistentFlag(objectID, true);
01981
01982 Ref<StringBuffer> q(new StringBuffer());
01983 *q << "INSERT INTO " << TQ(AUTOSCAN_TABLE)
01984 << " (" << TQ("obj_id") << ','
01985 << TQ("scan_level") << ','
01986 << TQ("scan_mode") << ','
01987 << TQ("recursive") << ','
01988 << TQ("hidden") << ','
01989 << TQ("interval") << ','
01990 << TQ("last_modified") << ','
01991 << TQ("persistent") << ','
01992 << TQ("location") << ','
01993 << TQ("path_ids")
01994 << ") VALUES ("
01995 << (objectID >= 0 ? quote(objectID) : _(SQL_NULL)) << ','
01996 << quote(AutoscanDirectory::mapScanlevel(adir->getScanLevel())) << ','
01997 << quote(AutoscanDirectory::mapScanmode(adir->getScanMode())) << ','
01998 << mapBool(adir->getRecursive()) << ','
01999 << mapBool(adir->getHidden()) << ','
02000 << quote(adir->getInterval()) << ','
02001 << quote(adir->getPreviousLMT()) << ','
02002 << mapBool(adir->persistent()) << ','
02003 << (objectID >= 0 ? _(SQL_NULL) : quote(adir->getLocation())) << ','
02004 << (pathIds == nil ? _(SQL_NULL) : quote(_(",") + pathIds->toCSV() + ','))
02005 << ')';
02006 adir->setStorageID(exec(q, true));
02007 }
02008
02009 void SQLStorage::updateAutoscanDirectory(Ref<AutoscanDirectory> adir)
02010 {
02011 log_debug("id: %d, obj_id: %d\n", adir->getStorageID(), adir->getObjectID());
02012
02013 if (adir == nil)
02014 throw _Exception(_("updateAutoscanDirectory called with adir==nil"));
02015
02016 Ref<IntArray> pathIds = _checkOverlappingAutoscans(adir);
02017
02018 int objectID = adir->getObjectID();
02019 int objectIDold = _getAutoscanObjectID(adir->getStorageID());
02020 if (objectIDold != objectID)
02021 {
02022 _autoscanChangePersistentFlag(objectIDold, false);
02023 _autoscanChangePersistentFlag(objectID, true);
02024 }
02025 Ref<StringBuffer> q(new StringBuffer());
02026 *q << "UPDATE " << TQ(AUTOSCAN_TABLE)
02027 << " SET " << TQ("obj_id") << '=' << (objectID >= 0 ? quote(objectID) : _(SQL_NULL))
02028 << ',' << TQ("scan_level") << '='
02029 << quote(AutoscanDirectory::mapScanlevel(adir->getScanLevel()))
02030 << ',' << TQ("scan_mode") << '='
02031 << quote(AutoscanDirectory::mapScanmode(adir->getScanMode()))
02032 << ',' << TQ("recursive") << '=' << mapBool(adir->getRecursive())
02033 << ',' << TQ("hidden") << '=' << mapBool(adir->getHidden())
02034 << ',' << TQ("interval") << '=' << quote(adir->getInterval());
02035 if (adir->getPreviousLMT() > 0)
02036 *q << ',' << TQ("last_modified") << '=' << quote(adir->getPreviousLMT());
02037 *q << ',' << TQ("persistent") << '=' << mapBool(adir->persistent())
02038 << ',' << TQ("location") << '=' << (objectID >= 0 ? _(SQL_NULL) : quote(adir->getLocation()))
02039 << ',' << TQ("path_ids") << '=' << (pathIds == nil ? _(SQL_NULL) : quote(_(",") + pathIds->toCSV() + ','))
02040 << ',' << TQ("touched") << '=' << mapBool(true)
02041 << " WHERE " << TQ("id") << '=' << quote(adir->getStorageID());
02042 exec(q);
02043 }
02044
02045 void SQLStorage::removeAutoscanDirectoryByObjectID(int objectID)
02046 {
02047 if (objectID == INVALID_OBJECT_ID)
02048 return;
02049 Ref<StringBuffer> q(new StringBuffer());
02050 *q << "DELETE FROM " << TQ(AUTOSCAN_TABLE)
02051 << " WHERE " << TQ("obj_id") << '=' << quote(objectID);
02052 exec(q);
02053
02054 _autoscanChangePersistentFlag(objectID, false);
02055 }
02056
02057 void SQLStorage::removeAutoscanDirectory(int autoscanID)
02058 {
02059 if (autoscanID == INVALID_OBJECT_ID)
02060 return;
02061 int objectID = _getAutoscanObjectID(autoscanID);
02062 Ref<StringBuffer> q(new StringBuffer());
02063 *q << "DELETE FROM " << TQ(AUTOSCAN_TABLE)
02064 << " WHERE " << TQ("id") << '=' << quote(autoscanID);
02065 exec(q);
02066 if (objectID != INVALID_OBJECT_ID)
02067 _autoscanChangePersistentFlag(objectID, false);
02068 }
02069
02070 int SQLStorage::getAutoscanDirectoryType(int objectID)
02071 {
02072 return _getAutoscanDirectoryInfo(objectID, _("persistent"));
02073 }
02074
02075 int SQLStorage::isAutoscanDirectoryRecursive(int objectID)
02076 {
02077 return _getAutoscanDirectoryInfo(objectID, _("recursive"));
02078 }
02079
02080 int SQLStorage::_getAutoscanDirectoryInfo(int objectID, String field)
02081 {
02082 if (objectID == INVALID_OBJECT_ID)
02083 return 0;
02084 Ref<StringBuffer> q(new StringBuffer());
02085 *q << "SELECT " << TQ(field) << " FROM " << TQ(AUTOSCAN_TABLE)
02086 << " WHERE " << TQ("obj_id") << '=' << quote(objectID);
02087 Ref<SQLResult> res = select(q);
02088 Ref<SQLRow> row;
02089 if (res == nil || (row = res->nextRow()) == nil)
02090 return 0;
02091 if (! remapBool(row->col(0)))
02092 return 1;
02093 else
02094 return 2;
02095 }
02096
02097 int SQLStorage::_getAutoscanObjectID(int autoscanID)
02098 {
02099 Ref<StringBuffer> q(new StringBuffer());
02100 *q << "SELECT " << TQ("obj_id") << " FROM " << TQ(AUTOSCAN_TABLE)
02101 << " WHERE " << TQ("id") << '=' << quote(autoscanID)
02102 << " LIMIT 1";
02103 Ref<SQLResult> res = select(q);
02104 if (res == nil)
02105 throw _StorageException(nil, _("error while doing select on "));
02106 Ref<SQLRow> row;
02107 if ((row = res->nextRow()) != nil && string_ok(row->col(0)))
02108 return row->col(0).toInt();
02109 return INVALID_OBJECT_ID;
02110 }
02111
02112 void SQLStorage::_autoscanChangePersistentFlag(int objectID, bool persistent)
02113 {
02114 if (objectID == INVALID_OBJECT_ID && objectID == INVALID_OBJECT_ID_2)
02115 return;
02116 Ref<StringBuffer> q(new StringBuffer());
02117 *q << "UPDATE " << TQ(CDS_OBJECT_TABLE)
02118 << " SET " << TQ("flags") << " = (" << TQ("flags")
02119 << (persistent ? _(" | ") : _(" & ~"))
02120 << OBJECT_FLAG_PERSISTENT_CONTAINER
02121 << ") WHERE " << TQ("id") << '=' << quote(objectID);
02122 exec(q);
02123 }
02124
02125 void SQLStorage::autoscanUpdateLM(Ref<AutoscanDirectory> adir)
02126 {
02127
02128
02129
02130
02131
02132
02133
02134
02135
02136 log_debug("id: %d; last_modified: %d\n", adir->getStorageID(), adir->getPreviousLMT());
02137 Ref<StringBuffer> q(new StringBuffer());
02138 *q << "UPDATE " << TQ(AUTOSCAN_TABLE)
02139 << " SET " << TQ("last_modified") << '=' << quote(adir->getPreviousLMT())
02140 << " WHERE " << TQ("id") << '=' << quote(adir->getStorageID());
02141 exec(q);
02142 }
02143
02144 int SQLStorage::isAutoscanChild(int objectID)
02145 {
02146 Ref<IntArray> pathIDs = getPathIDs(objectID);
02147 if (pathIDs == nil)
02148 return INVALID_OBJECT_ID;
02149 for (int i = 0; i < pathIDs->size(); i++)
02150 {
02151 int recursive = isAutoscanDirectoryRecursive(pathIDs->get(i));
02152 if (recursive == 2)
02153 return pathIDs->get(i);
02154 }
02155 return INVALID_OBJECT_ID;
02156 }
02157
02158 void SQLStorage::checkOverlappingAutoscans(Ref<AutoscanDirectory> adir)
02159 {
02160 _checkOverlappingAutoscans(adir);
02161 }
02162
02163 Ref<IntArray> SQLStorage::_checkOverlappingAutoscans(Ref<AutoscanDirectory> adir)
02164 {
02165 if (adir == nil)
02166 throw _Exception(_("_checkOverlappingAutoscans called with adir==nil"));
02167 int checkObjectID = adir->getObjectID();
02168 if (checkObjectID == INVALID_OBJECT_ID)
02169 return nil;
02170 int storageID = adir->getStorageID();
02171
02172 Ref<SQLResult> res;
02173 Ref<SQLRow> row;
02174
02175 Ref<StringBuffer> q(new StringBuffer());
02176
02177 *q << "SELECT " << TQ("id")
02178 << " FROM " << TQ(AUTOSCAN_TABLE)
02179 << " WHERE " << TQ("obj_id") << " = "
02180 << quote(checkObjectID);
02181 if (storageID >= 0)
02182 *q << " AND " << TQ("id") << " != " << quote(storageID);
02183
02184 res = select(q);
02185 if (res == nil)
02186 throw _Exception(_("SQL error"));
02187
02188 if ((row = res->nextRow()) != nil)
02189 {
02190 Ref<CdsObject> obj = loadObject(checkObjectID);
02191 if (obj == nil)
02192 throw _Exception(_("Referenced object (by Autoscan) not found."));
02193 log_error("There is already an Autoscan set on %s\n", obj->getLocation().c_str());
02194 throw _Exception(_("There is already an Autoscan set on ") + obj->getLocation());
02195 }
02196
02197 if (adir->getRecursive())
02198 {
02199 q->clear();
02200 *q << "SELECT " << TQ("obj_id")
02201 << " FROM " << TQ(AUTOSCAN_TABLE)
02202 << " WHERE " << TQ("path_ids") << " LIKE "
02203 << quote(_("%,") + checkObjectID + ",%");
02204 if (storageID >= 0)
02205 *q << " AND " << TQ("id") << " != " << quote(storageID);
02206 *q << " LIMIT 1";
02207
02208 log_debug("------------ %s\n", q->c_str());
02209
02210 res = select(q);
02211 if (res == nil)
02212 throw _Exception(_("SQL error"));
02213 if ((row = res->nextRow()) != nil)
02214 {
02215 int objectID = row->col(0).toInt();
02216 log_debug("-------------- %d\n", objectID);
02217 Ref<CdsObject> obj = loadObject(objectID);
02218 if (obj == nil)
02219 throw _Exception(_("Referenced object (by Autoscan) not found."));
02220 log_error("Overlapping Autoscans are not allowed. There is already an Autoscan set on %s\n", obj->getLocation().c_str());
02221 throw _Exception(_("Overlapping Autoscans are not allowed. There is already an Autoscan set on ") + obj->getLocation());
02222 }
02223 }
02224
02225 Ref<IntArray> pathIDs = getPathIDs(checkObjectID);
02226 if (pathIDs == nil)
02227 throw _Exception(_("getPathIDs returned nil"));
02228 q->clear();
02229 *q << "SELECT " << TQ("obj_id")
02230 << " FROM " << TQ(AUTOSCAN_TABLE)
02231 << " WHERE " << TQ("obj_id") << " IN ("
02232 << pathIDs->toCSV()
02233 << ") AND " << TQ("recursive") << '=' << mapBool(true);
02234 if (storageID >= 0)
02235 *q << " AND " << TQ("id") << " != " << quote(storageID);
02236 *q << " LIMIT 1";
02237
02238 res = select(q);
02239 if (res == nil)
02240 throw _Exception(_("SQL error"));
02241 if ((row = res->nextRow()) == nil)
02242 return pathIDs;
02243 else
02244 {
02245 int objectID = row->col(0).toInt();
02246 Ref<CdsObject> obj = loadObject(objectID);
02247 if (obj == nil)
02248 throw _Exception(_("Referenced object (by Autoscan) not found."));
02249 log_error("Overlapping Autoscans are not allowed. There is already a recursive Autoscan set on %s\n", obj->getLocation().c_str());
02250 throw _Exception(_("Overlapping Autoscans are not allowed. There is already a recursive Autoscan set on ") + obj->getLocation());
02251 }
02252 }
02253
02254 Ref<IntArray> SQLStorage::getPathIDs(int objectID)
02255 {
02256 flushInsertBuffer();
02257
02258 if (objectID == INVALID_OBJECT_ID)
02259 return nil;
02260 Ref<IntArray> pathIDs(new IntArray());
02261 Ref<StringBuffer> q(new StringBuffer());
02262 *q << "SELECT " << TQ("parent_id") << " FROM " << TQ(CDS_OBJECT_TABLE) << " WHERE ";
02263 *q << TQ("id") << '=';
02264 int selBufLen = q->length();
02265 Ref<SQLResult> res;
02266 Ref<SQLRow> row;
02267 while (objectID != CDS_ID_ROOT)
02268 {
02269 pathIDs->append(objectID);
02270 q->setLength(selBufLen);
02271 *q << quote(objectID) << " LIMIT 1";
02272 res = select(q);
02273 if (res == nil || (row = res->nextRow()) == nil)
02274 break;
02275 objectID = row->col(0).toInt();
02276 }
02277 return pathIDs;
02278 }
02279
02280 String SQLStorage::getFsRootName()
02281 {
02282 if (string_ok(fsRootName))
02283 return fsRootName;
02284 setFsRootName();
02285 return fsRootName;
02286 }
02287
02288 void SQLStorage::setFsRootName(String rootName)
02289 {
02290 if (string_ok(rootName))
02291 {
02292 fsRootName = rootName;
02293 }
02294 else
02295 {
02296 Ref<CdsObject> fsRootObj = loadObject(CDS_ID_FS_ROOT);
02297 fsRootName = fsRootObj->getTitle();
02298 }
02299 }
02300
02301 int SQLStorage::getNextID()
02302 {
02303 if (lastID < CDS_ID_FS_ROOT)
02304 throw _Exception(_("SQLStorage::getNextID() called, but lastID hasn't been loaded correctly yet"));
02305 AUTOLOCK(nextIDMutex);
02306 return ++lastID;
02307 }
02308
02309 void SQLStorage::loadLastID()
02310 {
02311
02312
02313 Ref<StringBuffer> qb(new StringBuffer());
02314 *qb << "SELECT MAX(" << TQ("id") << ')'
02315 << " FROM " << TQ(CDS_OBJECT_TABLE);
02316 Ref<SQLResult> res = select(qb);
02317 if (res == nil)
02318 throw _Exception(_("could not load lastID (res==nil)"));
02319
02320 Ref<SQLRow> row = res->nextRow();
02321 if (row == nil)
02322 throw _Exception(_("could not load lastID (row==nil)"));
02323
02324 lastID = row->col(0).toInt();
02325 if (lastID < CDS_ID_FS_ROOT)
02326 throw _Exception(_("could not load correct lastID (db not initialized?)"));
02327 }
02328
02329 void SQLStorage::addObjectToCache(Ref<CdsObject> object, bool dontLock)
02330 {
02331 if (cacheOn() && object != nil)
02332 {
02333 AUTOLOCK_DEFINE_ONLY();
02334 if (! dontLock)
02335 AUTOLOCK_NO_DEFINE(cache->getMutex());
02336 Ref<CacheObject> cObj = cache->getObjectDefinitely(object->getID());
02337 if (cache->flushed())
02338 flushInsertBuffer();
02339 cObj->setObject(object);
02340 cache->checkLocation(cObj);
02341 }
02342 }
02343
02344 void SQLStorage::addToInsertBuffer(Ref<StringBuffer> query)
02345 {
02346 assert(doInsertBuffering());
02347
02348 _addToInsertBuffer(query);
02349
02350 AUTOLOCK(mutex);
02351 insertBufferEmpty = false;
02352 insertBufferStatementCount++;
02353 insertBufferByteCount += query->length();
02354
02355 if (insertBufferByteCount > 102400)
02356 flushInsertBuffer(true);
02357 }
02358
02359 void SQLStorage::flushInsertBuffer(bool dontLock)
02360 {
02361 if (! doInsertBuffering())
02362 return;
02363
02364 AUTOLOCK_DEFINE_ONLY();
02365 if (! dontLock)
02366 AUTOLOCK_NO_DEFINE(mutex);
02367 if (insertBufferEmpty)
02368 return;
02369 _flushInsertBuffer();
02370 log_debug("flushing insert buffer (%d statements)\n", insertBufferStatementCount);
02371 insertBufferEmpty = true;
02372 insertBufferStatementCount = 0;
02373 insertBufferByteCount = 0;
02374 }
02375
02376 void SQLStorage::clearFlagInDB(int flag)
02377 {
02378 Ref<StringBuffer> qb(new StringBuffer(256));
02379 *qb << "UPDATE "
02380 << TQ(CDS_OBJECT_TABLE)
02381 << " SET "
02382 << TQ("flags")
02383 << " = ("
02384 << TQ("flags")
02385 << "&~" << flag
02386 << ") WHERE "
02387 << TQ("flags")
02388 << "&" << flag;
02389 exec(qb);
02390 }