25 #include <QApplication>
30 #include <QDataStream>
31 #include <QMessageBox>
33 #include <QSqlDatabase>
92 buf.open(QIODevice::WriteOnly);
93 img.
save(&buf, format);
145 bool accessshared =
false;
157 if (url.
scheme() ==
"klf+sqlite") {
161 accessshared =
false;
166 tr(
"Database file <b>%1</b> does not exist.").arg(path));
176 tr(
"Unable to open library file \"%1\" (engine: \"%2\").\nError: %3")
182 qWarning(
"KLFLibDBEngine::openUrl: bad url scheme in URL\n\t%s",
195 klfDbgSt(
"fileName="<<fileName<<
", sresname="<<sresname<<
", srestitle="<<srestitle) ;
198 QString subrestitle = srestitle;
219 tr(
"Unable to create library file %1 (SQLITE database):\n"
221 .arg(path, db.
lastError().text()), QMessageBox::Ok);
227 subresname =
"table1";
229 subrestitle =
"Table 1";
233 subrestitle = subresname;
238 qWarning()<<
KLF_FUNC_NAME<<
"\" character is not allowed in SQLITE database tables (<-> library sub-resources).";
242 r = initFreshDatabase(db);
245 r = createFreshDataTable(db, subresname);
249 tr(
"Unable to initialize the SQLITE database file %1!").arg(url.
path()));
257 qWarning()<<
"Failed to create table named "<<subresname<<
"!";
266 KLFLibDBEngine::KLFLibDBEngine(
const QSqlDatabase& db,
bool autodisconnect,
267 const QUrl& url,
bool accessshared,
QObject *parent)
269 |FeatureSubResourceProps, parent)
277 readResourceProperty(-1);
282 for (k = 0; k < subres.size(); ++k)
283 readAvailColumns(subres[k]);
287 this, SLOT(resourcePropertyUpdate(
int)));
289 this, SLOT(subResourcePropertyUpdate(
const QString&,
int)));
307 bool KLFLibDBEngine::tableExists(
const QString& subResource)
const
309 return pDB.
tables().contains(dataTableName(subResource), Qt::CaseInsensitive);
315 return "t_"+subResource.
toLower();
318 QString KLFLibDBEngine::quotedDataTableName(
const QString& subResource)
320 QString dtname = dataTableName(subResource);
322 return '"' + dtname +
'"';
377 if ( propName.isEmpty() )
385 q.
prepare(
"DELETE FROM klf_properties WHERE name = ?");
389 qWarning()<<
"KLFLibDBEngine::setRes.Property("<<propId<<
","<<value<<
"): can't DELETE!\n\t"
396 q.
prepare(
"INSERT INTO klf_properties (name,value) VALUES (?,?)");
398 q.
bindValue(1, convertVariantToDBData(value));
400 qWarning()<<
"KLFLibDBEngine::setRes.Property("<<propId<<
","<<value<<
"): can't INSERT!\n\t"
406 dbPropertyNotifierInstance(pDB.connectionName())->notifyResourcePropertyChanged(propId);
412 void KLFLibDBEngine::resourcePropertyUpdate(
int propId)
414 readResourceProperty(propId);
417 void KLFLibDBEngine::subResourcePropertyUpdate(
const QString& subResource,
int propId)
422 void KLFLibDBEngine::readResourceProperty(
int propId)
427 QString sqlstr =
"SELECT name,value FROM klf_properties";
431 qWarning()<<
"Can't read un-registered resource property "<<propId<<
" !";
434 sqlstr +=
" WHERE name = ?";
439 q.
prepare(
"SELECT name,value FROM klf_properties");
450 klfDbg(
"Setting property `"<<propname<<
"' (id #"<<propId<<
") to "<<propvalue<<
"" ) ;
456 void KLFLibDBEngine::readDbMetaInfo()
459 q.
prepare(
"SELECT name,value FROM klf_dbmetainfo");
469 pDBVersion = version.
toInt();
473 void KLFLibDBEngine::readAvailColumns(
const QString& subResource)
478 for (k = 0; k < rec.
count(); ++k)
481 pDBAvailColumns[subResource] = columns;
493 for (k = 0; k < entryPropList.
size(); ++k) {
495 if (pDBAvailColumns[subResource].contains(col))
500 if (entryPropList.
size() == 0) {
503 if (wantIdFirst && (!cols.size() || cols[0] !=
"id") )
515 for (k = 0; k < rec.
count(); ++k) {
518 if (propId < 0 && propName !=
"id") {
519 klfDbg(
"Registering property "<<propName ) ;
532 for (k = 0; k < cols.size(); ++k) {
537 QVariant value = dbReadEntryPropertyValue(q.
value(k), propId);
547 klfDbg(
": missing or incorrect preview size set to "<<entry.
preview().
size() ) ;
561 q.
prepare(
QString(
"SELECT id FROM %1").arg(quotedDataTableName(subResource)));
565 qWarning(
"KLFLibDBEngine::allIds: Error fetching IDs!\n"
566 "SQL Error: %s", qPrintable(q.
lastError().text()));
571 idlist << q.
value(0).toInt();
581 q.
prepare(
QString(
"SELECT id FROM %1 WHERE id = ?").arg(quotedDataTableName(subResource)));
585 qWarning(
"KLFLibDBEngine::hasEntry: Error!\n"
586 "SQL Error: %s", qPrintable(q.
lastError().text()));
603 QStringList cols = columnNameList(subResource, wantedEntryProperties,
true);
607 << pDBAvailColumns[subResource];
612 quotedDataTableName(subResource)));
621 for (k = 0; k < idList.
size(); ++k) {
623 progr.doReportProgress(k);
629 qWarning(
"KLFLibDBEngine::entries: Error\n"
630 "SQL Error (?): %s", qPrintable(q.
lastError().text()));
634 klfDbg(
": id="<<idList[k]<<
" does not exist in DB." ) ;
641 e.
entry = readEntry(q, cols);
646 progr.doReportProgress(idList.
size());
655 return "'" + s +
"'";
680 return field+
" LIKE '"+val+
"' ESCAPE '\\' ";
691 *haspostsqlcondition =
false;
703 switch ( f & 0xFF ) {
704 case Qt::MatchExactly:
705 condition += field+
" = ?";
706 if ((f & Qt::CaseSensitive) == 0)
707 condition +=
" COLLATE NOCASE";
708 if (f & Qt::MatchFixedString)
713 condition +=
" OR "+field+
" IS NULL";
715 case Qt::MatchContains:
718 case Qt::MatchStartsWith:
721 case Qt::MatchEndsWith:
724 case Qt::MatchRegExp:
726 *haspostsqlcondition =
true;
727 *postsqlcondition = m;
730 case Qt::MatchWildcard:
731 if (f & Qt::CaseSensitive) {
732 condition += field+
" GLOB ? ";
734 condition +=
" lower("+field+
") GLOB lower(?) ";
739 qWarning()<<
KLF_FUNC_NAME<<
": unknown property match type flags: "<<f ;
747 qWarning()<<
KLF_FUNC_NAME<<
": condition list is empty for NOT match type!";
752 haspostsqlcondition, &postm) ;
753 if (*haspostsqlcondition) {
760 static const char *w_and =
" AND ";
761 static const char *w_or =
" OR ";
769 for (k = 0; k < clist.
size(); ++k) {
776 &thishaspostsql, &thispostm) ;
777 if (thishaspostsql) {
778 postconditionlist.
append(thispostm);
782 if (postconditionlist.
size()) {
783 *haspostsqlcondition =
true;
797 klfDbg(
"\t: subResource="<<subResource<<
"; query="<<query ) ;
806 sql =
QString(
"SELECT %1 FROM %2 ").
arg(cols.
join(
","), quotedDataTableName(subResource));
807 QVariantList placeholders;
808 bool haspostsqlcondition =
false;
812 sql +=
" WHERE "+wherecond;
815 if (haspostsqlcondition) {
817 klfDbg(
"You are using a feature that is not natively implemented in KLFLibDBEngine: falling back to "
818 "rudimentary and slow implementation!");
827 if (query.
limit != -1) {
831 klfDbg(
"Built query: SQL="<<sql<<
"; placeholders="<<placeholders) ;
837 for (k = 0; k < placeholders.size(); ++k)
844 <<
"\nSql was="<<sql<<
"; bound values="<<q.
boundValues();
850 cols = detectEntryColumns(q);
864 while (skipped < query.
skip && (ok = q.
next()))
866 klfDbg(
"skipped "<<skipped<<
" entries.") ;
871 while (ok && q.
next()) {
872 if (count % 10 == 0 && count < N) {
879 e.
entry = readEntry(q, cols);
891 klfDbg(
"got "<<count<<
" entries.") ;
897 klfDbg(
"\t: subResource="<<subResource<<
"; entryPropId="<<entryPropId ) ;
903 qWarning()<<
KLF_FUNC_NAME<<
": bad sub-resource: "<<subResource;
904 return QVariantList();
910 qWarning()<<
KLF_FUNC_NAME<<
": Invalid property ID "<<entryPropId;
911 return QVariantList();
914 if (!pDBAvailColumns[subResource].contains(pname)) {
915 qWarning()<<
KLF_FUNC_NAME<<
": property "<<pname<<
" is not available in tables for sub-res "<<subResource
916 <<
" (avail are "<<pDBAvailColumns[subResource]<<
")";
917 return QVariantList();
920 QString sql =
"SELECT DISTINCT "+pname+
" FROM "+quotedDataTableName(subResource);
928 <<
"\nSQL was: "<<sql;
929 return QVariantList();
934 list << dbReadEntryPropertyValue(q.
value(0), entryPropId);
935 klfDbg(
"adding value "<<list.last().toString()) ;
949 q.
prepare(
QString(
"SELECT * FROM %1 WHERE id = ?").arg(quotedDataTableName(subResource)));
954 qWarning(
"KLFLibDBEngine::entry: id=%d cannot be found!\n"
955 "SQL Error (?): %s",
id, qPrintable(q.
lastError().text()));
963 qWarning(
"KLFLibDBEngine::entry(): no entry available!\n"
964 "SQL=\"%s\" (?=%d)", qPrintable(q.
lastQuery()),
id);
969 KLFLibEntry e = readEntry(q, detectEntryColumns(q));
981 QStringList cols = columnNameList(subResource, wantedEntryProperties,
true);
984 q.
prepare(
QString(
"SELECT %1 FROM %2 ORDER BY id ASC").arg(cols.
join(
","), quotedDataTableName(subResource)));
995 cols = detectEntryColumns(q);
997 int count = q.
size();
1009 e.
entry = readEntry(q, cols);
1045 q.
prepare(
"SELECT pvalue FROM klf_subresprops WHERE lower(subresource) = lower(?) AND pid = ?");
1046 q.
addBindValue(QVariant::fromValue<QString>(subResource));
1050 qWarning()<<
"KLFLibDBEngine::subResourceProperty("<<subResource<<
","<<propId<<
"): SQL Error: "
1052 klfDbg(
"DB: "<<pDB.connectionName());
1067 return convertVariantFromDBData(q.
value(0));
1073 return tableExists(subRes);
1084 for (k = 0; k < allTables.size(); ++k) {
1085 if (allTables[k].startsWith(
"t_"))
1086 subreslist << allTables[k].mid(2);
1104 klfDbg(
": setting sub-resource property "<<propId<<
" to "<<value<<
" in sub-res "
1108 klfDbg(
"property already has the requested value "<<value<<
".");
1114 q.
prepare(
"DELETE FROM klf_subresprops WHERE lower(subresource) = lower(?) and pid = ?");
1119 qWarning()<<
"KLFLibDBEngine::setSubRes.Prop.("<<subResource<<
","<<propId<<
","<<value<<
"):"
1126 q.
prepare(
"INSERT INTO klf_subresprops (subresource,pid,pvalue) VALUES (?,?,?)");
1129 q.
bindValue(2, convertVariantToDBData(value));
1131 qWarning()<<
"KLFLibDBEngine::setSubRes.Prop.("<<subResource<<
","<<propId<<
","<<value<<
"):"
1137 dbPropertyNotifierInstance(pDB.connectionName())
1138 ->notifySubResourcePropertyChanged(subResource, propId);
1148 QVariant KLFLibDBEngine::dbMakeEntryPropertyValue(
const QVariant& entryval,
int propertyId)
1151 return QVariant::fromValue<QString>(entryval.
toString());
1153 return QVariant::fromValue<qulonglong>(entryval.
toDateTime().toTime_t());
1157 return QVariant::fromValue<QString>(entryval.
toString());
1159 return QVariant::fromValue<QString>(entryval.
toString());
1162 return QVariant::fromValue<qulonglong>( (((qulonglong)s.
width()) << 32) |
1163 (((qulonglong)s.
height()) & 0xFFFFFFFF) );
1166 return convertVariantToDBData(entryval);
1168 QVariant KLFLibDBEngine::dbReadEntryPropertyValue(
const QVariant& dbdata,
int propertyId)
1177 return QVariant::fromValue<QImage>(img);
1185 int w = (int)((val>>32) & 0xFFFFFFFF) ;
1186 int h = (int)(val & 0xFFFFFFFF) ;
1187 return QVariant::fromValue<QSize>(
QSize(w, h));
1190 return convertVariantFromDBData(dbdata);
1198 QVariant KLFLibDBEngine::convertVariantToDBData(
const QVariant& value)
const
1205 int t = value.
type();
1207 if (t == QVariant::Int || t == QVariant::UInt || t == QVariant::LongLong || t == QVariant::ULongLong ||
1208 t == QVariant::Double || t == QVariant::Bool)
1212 if (t == QVariant::String)
1213 return encaps(ts, value.
toString());
1216 if (t == QVariant::ByteArray)
1218 if (t == QVariant::DateTime)
1220 if (t == QVariant::Image)
1225 {
QDataStream stream(&valuedata, QIODevice::WriteOnly);
1226 stream.setVersion(QDataStream::Qt_4_4);
1228 return encaps(ts, valuedata);
1231 QVariant KLFLibDBEngine::encaps(
const char *ts,
const QString& data)
const
1233 return QVariant::fromValue<QString>(
QString(
"[")+ts+
"]"+data);
1242 return QVariant::fromValue<QByteArray>(edata);
1244 QVariant KLFLibDBEngine::convertVariantFromDBData(
const QVariant& dbdata)
const
1249 int t = dbdata.
type();
1250 if (t == QVariant::Int || t == QVariant::UInt || t == QVariant::LongLong || t == QVariant::ULongLong ||
1251 t == QVariant::Double || t == QVariant::Bool)
1254 if (t == QVariant::String)
1256 if (t == QVariant::ByteArray)
1259 qWarning()<<
"Unexpected DB data variant found: "<<dbdata;
1264 return decaps(sdata.
toUtf8());
1274 for (k = 1; k < data.
size() && data[k] !=
']'; ++k) ;
1275 if (k >= data.
size()) {
1276 qWarning()<<
"KLFLibDBEngine::decaps(QB.A.): bad data:"<<data;
1282 if (typenam ==
"bool") {
1284 return QVariant::fromValue<bool>(svaluedata[0] !=
'0' ||
1285 (svaluedata !=
"1" && (svaluedata[0].
toLower() ==
't' ||
1286 svaluedata[0].
toLower() ==
'y' ||
1287 svaluedata.
toInt() != 0)) );
1289 if (typenam ==
"QString")
1291 if (typenam ==
"QByteArray")
1292 return QVariant::fromValue<QByteArray>(valuedata);
1293 if (typenam ==
"QDateTime")
1295 if (typenam ==
"QImage") {
1298 return QVariant::fromValue<QImage>(img);
1305 stream.setVersion(QDataStream::Qt_4_4);
1310 bool KLFLibDBEngine::ensureDataTableColumnsExist(
const QString& subResource,
const QStringList& columnList)
1314 bool failed =
false;
1315 for (k = 0; k < columnList.size(); ++k) {
1316 if (columnList[k] ==
"*")
1321 sql.
prepare(
"ALTER TABLE "+quotedDataTableName(subResource)+
" ADD COLUMN "+columnList[k]+
" BLOB");
1322 bool r = sql.
exec();
1324 qWarning()<<
"KLFLibDBEngine::ensureDataTableColumnsExist("<<subResource<<
"): Can't add column "
1330 readAvailColumns(subResource);
1334 bool KLFLibDBEngine::ensureDataTableColumnsExist(
const QString& subResource)
1339 return ensureDataTableColumnsExist(subResource, propNameList);
1355 q.
prepare(
QString(
"DROP TABLE %1").arg(quotedDataTableName(subResource)));
1358 qWarning()<<
KLF_FUNC_NAME<<
"("<<subResource<<
"): SQL Error: "
1367 QString newDefaultSubResource;
1371 newDefaultSubResource =
QString();
1389 qWarning()<<
"KLFLibDBEngine::createSubResource: Sub-Resource "<<subResource<<
" already exists!";
1393 bool r = createFreshDataTable(pDB, subResource);
1398 title = subResource;
1418 klfDbg(
"subres="<<subres<<
"; entrylist="<<entrylist) ;
1420 if ( entrylist.
size() == 0 ) {
1425 klfDbg(
"can't modify data.") ;
1429 if ( !tableExists(subres) ) {
1430 qWarning()<<
KLF_FUNC_NAME<<
": Sub-Resource "<<subres<<
" does not exist.";
1438 for (k = 0; k < propids.
size(); ++k) {
1440 questionmarks <<
"?";
1445 ensureDataTableColumnsExist(subres);
1452 q.
prepare(
"INSERT INTO " + quotedDataTableName(subres) +
" (" + props.
join(
",") +
") "
1453 " VALUES (" + questionmarks.
join(
",") +
")");
1456 for (j = 0; j < entrylist.
size(); ++j) {
1458 progr.doReportProgress(j);
1460 for (k = 0; k < propids.
size(); ++k) {
1461 QVariant data = dbMakeEntryPropertyValue(entrylist[j].
property(propids[k]), propids[k]);
1463 klfDbg(
"Binding value "<<k<<
": "<<data ) ;
1469 qWarning()<<
"INSERT failed! SQL Error: "<<q.
lastError().text()<<
"\n\tSQL="<<q.
lastQuery();
1476 insertedIds << v_id.
toInt();
1482 progr.doReportProgress(entrylist.
size());
1497 if ( !tableExists(subResource) ) {
1498 qWarning()<<
KLF_FUNC_NAME<<
": Sub-Resource "<<subResource<<
" does not exist.";
1502 if ( properties.
size() != values.
size() ) {
1503 qWarning(
"KLFLibDBEngine::changeEntry(): properties' and values' sizes mismatch!");
1507 if ( idlist.
size() == 0 )
1510 klfDbg(
"KLFLibDBEngine::changeEntries: funcional tests passed; idlist="<<idlist<<
" props="
1511 <<properties<<
" vals="<<values ) ;
1513 ensureDataTableColumnsExist(subResource);
1518 for (k = 0; k < properties.
size(); ++k) {
1524 .arg(quotedDataTableName(subResource), updatepairs.
join(
",")));
1525 for (k = 0; k < properties.
size(); ++k) {
1526 q.
bindValue(k, dbMakeEntryPropertyValue(values[k], properties[k]));
1528 const int idBindValueNum = k;
1534 bool failed =
false;
1535 for (k = 0; k < idlist.
size(); ++k) {
1537 progr.doReportProgress(k);
1542 qWarning() <<
"SQL UPDATE Error: "<<q.
lastError().text()<<
"\nWith SQL="<<q.
lastQuery()
1548 progr.doReportProgress(idlist.
size());
1559 if (idlist.
size() == 0)
1564 if ( !tableExists(subResource) ) {
1565 qWarning()<<
KLF_FUNC_NAME<<
": Sub-Resource "<<subResource<<
" does not exist.";
1570 bool failed =
false;
1572 QString sql =
QString(
"DELETE FROM %1 WHERE id = ?").
arg(quotedDataTableName(subResource));
1574 klfDbg(
"sql is "<<sql<<
", idlist is "<<idlist) ;
1583 for (k = 0; k < idlist.
size(); ++k) {
1585 progr.doReportProgress(k);
1596 progr.doReportProgress(idlist.
size());
1606 if (!newPath.
host().isEmpty()) {
1607 qWarning()<<
"KLFLibDBEngine::saveTo("<<newPath<<
"): Expected empty host!";
1612 qWarning()<<
"KLFLibDBEngine::saveTo("<<newPath<<
"): Bad scheme!";
1617 bool KLFLibDBEngine::initFreshDatabase(
QSqlDatabase db)
1625 sql <<
"CREATE TABLE klf_properties (id INTEGER PRIMARY KEY, name TEXT, value BLOB)";
1626 sql <<
"INSERT INTO klf_properties (name, value) VALUES ('Title', 'New Resource')";
1627 sql <<
"INSERT INTO klf_properties (name, value) VALUES ('Locked', 'false')";
1628 sql <<
"CREATE TABLE klf_dbmetainfo (id INTEGER PRIMARY KEY, name TEXT, value BLOB)";
1629 sql <<
"INSERT INTO klf_dbmetainfo (name, value) VALUES ('klf_version', '" KLF_VERSION_STRING
"')";
1630 sql <<
"INSERT INTO klf_dbmetainfo (name, value) VALUES ('klf_dbversion', '"+
1632 sql <<
"CREATE TABLE klf_subresprops (id INTEGER PRIMARY KEY, pid INTEGER, subresource TEXT, pvalue BLOB)";
1635 for (k = 0; k < sql.size(); ++k) {
1637 query.prepare(sql[k]);
1638 bool r =
query.exec();
1639 if ( !r ||
query.lastError().isValid() ) {
1640 qWarning()<<
"KLFLibDBEngine::initFreshDatabase(): SQL Error: "<<
query.lastError().text()<<
"\n"
1651 qDebug(
"KLFLibDBEngine::createFreshDataTable(.., '%s')", qPrintable(subres));
1652 QString datatablename = dataTableName(subres);
1654 qWarning(
"KLFLibDBEngine::createFreshDataTable(..,%s): DB is not open!", qPrintable(subres));
1658 if ( db.
tables().contains(datatablename) ) {
1659 qWarning(
"KLFLibDBEngine::createFreshDataTable(..,%s): table %s exists!", qPrintable(subres),
1660 qPrintable(datatablename));
1663 QString qdtname = quotedDataTableName(subres);
1668 "CREATE TABLE "+qdtname+
" (id INTEGER PRIMARY KEY, Latex TEXT, DateTime TEXT, "
1669 " Preview BLOB, PreviewSize TEXT, Category TEXT, Tags TEXT, Style BLOB)");
1670 bool r =
query.exec();
1671 if ( !r ||
query.lastError().isValid() ) {
1672 qWarning()<<
"createFreshDataTable(): SQL Error: "<<
query.lastError().text()<<
"\n"
1673 <<
"SQL="<<
query.lastQuery();
1705 if (!pDBPropertyNotifiers.
contains(dbname))
1707 return pDBPropertyNotifiers[dbname];
1712 #define MAGIC_SQLITE_HEADER_LEN 16
1718 = { 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66,
1719 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00 };
1722 klfDbg(
"guessing scheme of "<<fileName) ;
1728 if ( ! f.
open(QIODevice::ReadOnly) ) {
1729 klfDbg(
"Yikes, can't read file "<<fileName);
1736 klfDbg(
"Nope-can't read header.");
1741 klfDbg(
"Yep, it's klf-sqlite!");
1745 klfDbg(
"Nope-bad header.");
1771 return tr(
"Local Library Database File");
1780 qWarning()<<
"KLFLibDBEngineFactory::schemeFunctions: Bad scheme: "<<scheme;
1801 QString defsubres = parameters[
"klfDefaultSubResource"].toString();
1802 QString defsubrestitle = parameters[
"klfDefaultSubResourceTitle"].toString();
1804 defsubres =
"entries";
1806 defsubrestitle =
tr(
"Default Table",
"[[default sub-resource title]]");
1811 if ( !parameters.
contains(
"Filename") ) {
1813 <<
"KLFLibLegacyEngineFactory::createResource: bad parameters. They do not contain `Filename': "
1819 defsubrestitle, parent);
1820 qWarning()<<
"KLFLibDBEngineFactory::createResource("<<scheme<<
","<<parameters<<
","<<parent<<
"):"