2018-09-25 22:55:14 +03:00
# include "storage.h"
# include <QVariant>
# include <assert.h>
# include <encryption.h>
2019-06-23 18:46:41 +03:00
# include <iostream>
2018-09-25 22:55:14 +03:00
# ifdef TARGET_WIN
# include <WinSock2.h>
# endif
enum class DbProperty
{
2018-12-21 23:11:26 +02:00
Version = 1 ,
InternalCipher = 2
2018-09-25 22:55:14 +03:00
} ;
# define CURRENT_DBVERSION "0"
// Function to make encryption. Input is raw content blob, output is blob with IV, original length and encrypted data.
2018-12-21 23:11:26 +02:00
/*static void EncryptBlob(const QByteArray& input, QByteArray& output)
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
TwofishCipher cipher ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Generate 16 bytes IV
QByteArray iv ( 16 , 0 ) ;
IV : : Generate ( iv ) ;
cipher . setIV ( iv ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Make padding
int padded = ( input . length ( ) % cipher . blocksize ( ) ) ? ( cipher . blocksize ( ) - input . length ( ) % cipher . blocksize ( ) ) : 0 ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Prepare output buffer
output . resize ( iv . length ( ) + 4 + input . length ( ) + padded ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Copy IV
memcpy ( output . data ( ) , iv . data ( ) , iv . length ( ) ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Copy original length in network byte order
uint32_t lengthNbo = htonl ( input . length ( ) ) ;
memcpy ( output . data ( ) + iv . length ( ) , & lengthNbo , 4 ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Encrypt
cipher . encrypt ( input , 0 , output , iv . length ( ) + 4 ) ;
} */
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
/*static void DecryptBlob(const QByteArray& input, QByteArray& output)
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
TwofishCipher cipher ;
assert ( input . length ( ) > = cipher . blocksize ( ) + 4 ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Read IV
QByteArray iv ( input . data ( ) , cipher . blocksize ( ) ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Read original length
uint32_t lengthNbo ;
memcpy ( & lengthNbo , input . data ( ) + iv . length ( ) , 4 ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Prepare output buffer
output . resize ( input . length ( ) - iv . length ( ) - 4 ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Set IV
cipher . setIV ( iv ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Decrypt data
cipher . decrypt ( input , iv . length ( ) + 4 , output , 0 ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Truncate to original size
output . truncate ( ntohl ( lengthNbo ) ) ;
} */
2018-09-25 22:55:14 +03:00
Storage : : Storage ( )
{
}
Storage : : ~ Storage ( )
{
2018-12-21 23:11:26 +02:00
close ( ) ;
2018-09-25 22:55:14 +03:00
}
QString Storage : : path ( )
{
2018-12-21 23:11:26 +02:00
return mPath ;
2018-09-25 22:55:14 +03:00
}
void Storage : : setPath ( const QString & path )
{
2018-12-21 23:11:26 +02:00
mPath = path ;
2018-09-25 22:55:14 +03:00
}
QString Storage : : key ( )
{
2018-12-21 23:11:26 +02:00
return mKey ;
2018-09-25 22:55:14 +03:00
}
void Storage : : setKey ( const QString & key )
{
2018-12-21 23:11:26 +02:00
mKey = key ;
2018-09-25 22:55:14 +03:00
}
SQLite : : Database & Storage : : database ( )
{
2018-12-21 23:11:26 +02:00
return * mDatabase ;
2018-09-25 22:55:14 +03:00
}
bool Storage : : create ( )
{
2018-12-21 23:11:26 +02:00
assert ( ! mPath . isEmpty ( ) ) ;
try
{
mDatabase = QSharedPointer < SQLite : : Database > ( new SQLite : : Database ( mPath . toStdString ( ) . c_str ( ) , SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE ) ) ;
}
catch ( std : : exception & e )
{
return false ;
}
2018-09-25 22:55:14 +03:00
# ifdef USE_ENCRYPTED_DB
2018-12-21 23:11:26 +02:00
try
{
std : : string keyQuery = " pragma key=' " + mKey . toStdString ( ) + " ' " ;
mDatabase - > exec ( keyQuery . c_str ( ) ) ;
mDatabase - > exec ( " pragma locking_mode=EXCLUSIVE " ) ;
mDatabase - > exec ( " pragma journal_mode=MEMORY " ) ;
mDatabase - > exec ( " pragma temp_store=MEMORY " ) ;
}
catch ( std : : exception & e )
{
2019-06-23 18:46:41 +03:00
std : : cout < < e . what ( ) < < std : : endl ;
2018-12-21 23:11:26 +02:00
return false ;
}
2018-09-25 22:55:14 +03:00
# endif
2018-12-21 23:11:26 +02:00
try
{
2019-06-23 18:46:41 +03:00
// Synctime & timestamp are always milliseconds from the start of UNIX epoch.
2018-12-21 23:11:26 +02:00
// Timeline
2021-06-05 18:13:42 +03:00
mDatabase - > exec ( " CREATE TABLE timeline ( "
" id INTEGER PRIMARY KEY, "
" removed INTEGER, "
" taskid INTEGER, "
" starttime TEXT, "
" endtime TEXT, "
" timestamp INTEGER) " ) ;
2018-12-21 23:11:26 +02:00
mDatabase - > exec ( " CREATE INDEX timeline_taskid_index ON timeline(taskid ASC) " ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Tasks tree
2021-06-05 18:13:42 +03:00
mDatabase - > exec ( " CREATE TABLE task ( "
" type INTEGER, "
" removed INTEGER, "
" id INTEGER PRIMARY KEY, "
" parentid INTEGER, "
" orderid INTEGER, "
" title TEXT, "
" html TEXT, "
" flags INTEGER, "
" timestamp INTEGER) " ) ;
2018-12-21 23:11:26 +02:00
mDatabase - > exec ( " CREATE INDEX task_parentid_index ON task(parentid ASC) " ) ;
2018-09-25 22:55:14 +03:00
2019-06-23 18:46:41 +03:00
// Tasks history
2021-06-05 18:13:42 +03:00
mDatabase - > exec ( " CREATE TABLE history_task ( "
" removed_old INTEGER, "
" removed_new INTEGER, "
" timestamp_old INTEGER, "
" timestamp_new INTEGER, "
" id INTEGER PRIMARY KEY, "
" parent_id_old INTEGER, "
" parent_id_new INTEGER, "
" order_id_old INTEGER, "
" order_id_new INTEGER, "
" diff_title TEXT, "
" diff_html TEXT, "
" flags_old INTEGER, "
" flags_new INTEGER) " ) ;
2019-06-23 18:46:41 +03:00
2018-12-21 23:11:26 +02:00
// Attachments
2021-06-05 18:13:42 +03:00
mDatabase - > exec ( " CREATE TABLE file ( "
" id INTEGER PRIMARY KEY, "
" removed INTEGER, "
" taskid INTEGER, "
" filename TEXT, "
" content BLOB, "
" orderid INTEGER, "
" timestamp INTEGER) " ) ;
mDatabase - > exec ( " CREATE TABLE history_file ( "
" id INTEGER PRIMARY KEY, "
" removed_old INTEGER, "
" removed_new INTEGER, "
" taskid_old INTEGER, "
" taskid_new INTEGER, "
" filename_old TEXT, "
" filename_new TEXT, "
" content BLOB, "
" order_id_old INTEGER, "
" order_id_new INTEGER, "
" timestamp_old INTEGER, "
" timestamp_new INTEGER) " ) ;
2019-06-23 18:46:41 +03:00
2018-12-21 23:11:26 +02:00
mDatabase - > exec ( " CREATE INDEX file_taskid_index ON file(taskid ASC) " ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Sync status
2019-06-23 18:46:41 +03:00
mDatabase - > exec ( " CREATE TABLE syncs (timestamp INT, status TEXT) " ) ;
2018-12-21 23:11:26 +02:00
}
catch ( std : : exception & e )
{
2019-06-23 18:46:41 +03:00
std : : cerr < < e . what ( ) < < std : : endl ;
2018-12-21 23:11:26 +02:00
return false ;
}
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
return true ;
2018-09-25 22:55:14 +03:00
}
bool Storage : : open ( )
{
2018-12-21 23:11:26 +02:00
assert ( ! mPath . isEmpty ( ) ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
try
{
mDatabase = QSharedPointer < SQLite : : Database > ( new SQLite : : Database ( mPath . toStdString ( ) . c_str ( ) , SQLITE_OPEN_READWRITE ) ) ;
mDatabase - > exec ( " pragma key=' " + mKey . toStdString ( ) + " ' " ) ;
SQLite : : Statement q ( * mDatabase , " select count(*) from sqlite_master " ) ;
if ( ! q . executeStep ( ) )
return false ;
mDatabase - > exec ( " pragma locking_mode=EXCLUSIVE " ) ;
mDatabase - > exec ( " pragma journal_mode=MEMORY " ) ;
mDatabase - > exec ( " pragma temp_store=MEMORY " ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
}
catch ( std : : exception & e )
{
2019-07-28 20:49:34 +03:00
std : : cerr < < e . what ( ) < < std : : endl ;
2018-12-21 23:11:26 +02:00
return false ;
}
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
loadTaskTree ( ) ;
return true ;
2018-09-25 22:55:14 +03:00
}
void Storage : : close ( )
{
2018-12-21 23:11:26 +02:00
if ( mDatabase )
{
mDatabase . clear ( ) ;
}
2018-09-25 22:55:14 +03:00
}
bool Storage : : hasTable ( const QString & tablename )
{
2018-12-21 23:11:26 +02:00
SQLite : : Statement tableQuery ( * mDatabase , " SELECT name FROM sqlite_master WHERE type='table' AND name=:tablename " ) ;
tableQuery . bind ( " :tablename " , tablename . toStdString ( ) ) ;
return tableQuery . executeStep ( ) ;
2018-09-25 22:55:14 +03:00
}
bool Storage : : upgrade ( )
{
2018-12-21 23:11:26 +02:00
int currentVersion = 0 ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Check if properties table is here
if ( ! mDatabase )
return false ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
if ( ! hasTable ( " properties " ) )
{
// If not - create it and consider database version 0
SQLite : : Statement createQuery ( * mDatabase , " create table properties(type INTEGER, value TEXT) " ) ;
if ( ! createQuery . exec ( ) )
return false ;
// Insert version 0
SQLite : : Statement insertVersionQuery ( * mDatabase , " insert into properties(type, value) values(:proptype, :proptext) " ) ;
insertVersionQuery . bind ( " :proptype " , ( int ) DbProperty : : Version ) ;
insertVersionQuery . bind ( " :proptext " , " 0 " ) ;
}
else
{
// If yes - get database version from 'dbversion' key
SQLite : : Statement versionQuery ( * mDatabase , " select value from properties where type=:proptype " ) ;
versionQuery . bind ( " :proptype " , ( int ) DbProperty : : Version ) ;
if ( ! versionQuery . executeStep ( ) )
return false ;
QString dbVersionText = versionQuery . getColumn ( 0 ) . getText ( " " ) ;
if ( dbVersionText . isEmpty ( ) )
return false ;
bool ok = false ;
currentVersion = dbVersionText . toInt ( & ok ) ;
if ( ! ok )
return false ;
}
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Depending on obtained version upgrade database
switch ( currentVersion )
{
case 0 :
if ( ! upgradeFromVersion0 ( ) )
return false ;
break ;
}
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Update version number after upgrade
SQLite : : Statement updateVersion ( * mDatabase , " update table properties set value=:proptext where type=:proptype " ) ;
updateVersion . bind ( " :proptext " , CURRENT_DBVERSION ) ;
updateVersion . bind ( " :proptype " , ( int ) DbProperty : : Version ) ;
updateVersion . exec ( ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
return true ;
2018-09-25 22:55:14 +03:00
}
bool Storage : : upgradeFromVersion0 ( )
{
2018-12-21 23:11:26 +02:00
/*
2018-09-25 22:55:14 +03:00
// Upgrade tasks table
SQLite : : Statement addEncryptedTitle ( * mDatabase , " alter table task add column title_encrypted BLOB " ) ;
if ( ! addEncryptedTitle . exec ( ) )
return false ;
SQLite : : Statement addEncryptedContent ( * mDatabase , " alter table task add column content_encrypted BLOB " ) ;
if ( ! addEncryptedContent . exec ( ) )
return false ;
SQLite : : Statement addEncryptedAttachments ( * mDatabase , " alter table attachment add column content_encrypted BLOB " ) ;
if ( ! addEncryptedAttachments . exec ( ) )
return false ;
// Database scheme is upgraded here, convert the data
SQLite : : Statement tasks ( * mDatabase , " select id, title, html from task " ) ;
while ( tasks . executeStep ( ) )
{
// TODO
}
// Iterate all notes and attachments and make them Twofish encrypted
mTaskToUpgrade . resize ( topOfTaskTree ( ) . size ( ) ) ;
std : : copy ( topOfTaskTree ( ) . begin ( ) , topOfTaskTree ( ) . end ( ) , mTaskToUpgrade ) ;
for ( PTask task : mTaskToUpgrade )
{
encryptTaskContent ( task ) ;
//encryptTaskAttachment(task);
// Add children to list
int tl = mTaskToUpgrade . length ( ) ;
mTaskToUpgrade . resize ( mTaskToUpgrade . size ( ) + task - > children ( ) . size ( ) ) ;
std : : copy ( task - > children ( ) . begin ( ) , task - > children ( ) . end ( ) , mTaskToUpgrade . begin ( ) + tl ) ;
}
*/
2018-12-21 23:11:26 +02:00
return true ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
bool Storage : : encryptTask ( PTask /*task*/ )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
return false ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
bool Storage : : encryptTaskContent ( PTask /*task*/ )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
return false ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
bool Storage : : encryptTaskAttachment ( PAttachment /*attachment*/ )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
return false ;
2018-09-25 22:55:14 +03:00
}
PTask Storage : : createTask ( int index )
{
2018-12-21 23:11:26 +02:00
if ( ! mDatabase )
return PTask ( ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
SQLite : : Statement insertQuery ( * mDatabase , " insert into task(id, title, html, parentid, orderid, type, flags) values(NULL, :title, :html, NULL, :orderid, 0, 0) " ) ;
insertQuery . bind ( " :title " , " " ) ;
insertQuery . bind ( " :html " , " " ) ;
insertQuery . bind ( " :orderid " , index ) ;
if ( insertQuery . exec ( ) )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
PTask result ( new Task ( ) ) ;
2021-06-05 18:13:42 +03:00
result - > setId ( database ( ) . getLastInsertRowid ( ) )
. setIndex ( index ) ;
2018-12-21 23:11:26 +02:00
mTaskModelIdMap [ result - > modelId ( ) ] = result ;
mTaskIdMap [ result - > id ( ) ] = result ;
if ( index > mTopTasks . size ( ) )
mTopTasks . push_back ( result ) ;
else
{
mTopTasks . insert ( mTopTasks . begin ( ) + index , result ) ;
// Assign new indexes for top tasks
for ( int i = 0 ; i < mTopTasks . size ( ) ; i + + )
{
2021-06-05 18:13:42 +03:00
mTopTasks [ i ] - > setIndex ( i )
. save ( ) ;
2018-12-21 23:11:26 +02:00
}
}
return result ;
2018-09-25 22:55:14 +03:00
}
2018-12-21 23:11:26 +02:00
else
return PTask ( ) ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
PTask Storage : : createTask ( const PTask & parent , int index )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
if ( ! parent )
return createTask ( index ) ;
if ( ! mDatabase )
return PTask ( ) ;
SQLite : : Statement insertQuery ( database ( ) , " insert into task(id, title, html, parentid, orderid, type, flags) values(NULL, :title, :html, :parentid, :orderid, 0, 0) " ) ;
insertQuery . bind ( " :title " , " " ) ;
insertQuery . bind ( " :html " , " " ) ;
insertQuery . bind ( " :parentid " , ( sqlite3_int64 ) parent - > id ( ) ) ;
insertQuery . bind ( " :orderid " , index ) ;
if ( insertQuery . exec ( ) )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
PTask result ( new Task ( ) ) ;
result - > setId ( database ( ) . getLastInsertRowid ( ) ) ;
result - > setIndex ( index ) ;
result - > setParent ( parent ) ;
if ( index > parent - > children ( ) . size ( ) )
parent - > children ( ) . push_back ( result ) ;
else
{
parent - > children ( ) . insert ( parent - > children ( ) . begin ( ) + index , result ) ;
for ( int i = 0 ; i < parent - > children ( ) . size ( ) ; i + + )
{
2021-06-05 18:13:42 +03:00
parent - > children ( ) [ i ] - > setIndex ( i )
. save ( ) ;
2018-12-21 23:11:26 +02:00
}
}
mTaskModelIdMap [ result - > modelId ( ) ] = result ;
mTaskIdMap [ result - > id ( ) ] = result ;
return result ;
2018-09-25 22:55:14 +03:00
}
2018-12-21 23:11:26 +02:00
return PTask ( ) ;
2018-09-25 22:55:14 +03:00
}
/*
PTask Storage : : loadTask ( Task : : Id id , PTask parent )
{
PTask result ( new Task ( ) ) ;
result - > setId ( id ) ;
result - > load ( ) ;
result - > setParent ( parent ) ;
mTaskIdMap [ result - > modelId ( ) ] = result ;
return result ;
}
*/
void Storage : : saveTask ( PTask task , Depth depth )
{
2018-12-21 23:11:26 +02:00
if ( depth = = depthSingleTask )
saveSingleTask ( task ) ;
else
{
saveSingleTask ( task ) ;
TaskArray & children = task - > children ( ) ;
foreach ( PTask child , children )
saveTask ( child , depthRecursive ) ;
}
2018-09-25 22:55:14 +03:00
}
bool Storage : : moveTask ( PTask task , PTask newParent , int indexToInsert )
{
2018-12-21 23:11:26 +02:00
bool result = false ;
task - > setParent ( newParent ) ;
if ( newParent )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
SQLite : : Statement updateParent ( database ( ) , " update task set parentid=:parentid where id=:id " ) ;
updateParent . bind ( " :parentid " , ( sqlite3_int64 ) newParent - > id ( ) ) ;
updateParent . bind ( " :id " , ( sqlite3_int64 ) task - > id ( ) ) ;
result = updateParent . exec ( ) ;
// Insert into children list
TaskArray & children = newParent - > children ( ) ;
if ( indexToInsert > = children . size ( ) )
children . push_back ( task ) ;
else
children . insert ( indexToInsert , task ) ;
for ( int i = 0 ; i < children . size ( ) ; i + + )
{
children [ i ] - > setIndex ( i ) ;
children [ i ] - > save ( ) ;
}
2018-09-25 22:55:14 +03:00
}
else
{
2018-12-21 23:11:26 +02:00
SQLite : : Statement updateToRoot ( database ( ) , " update task set parentid = NULL where id=:id " ) ;
updateToRoot . bind ( " :id " , ( sqlite3_int64 ) task - > id ( ) ) ;
result = updateToRoot . exec ( ) ;
if ( result )
task - > setParentId ( 0 ) ;
// Insert into root list
if ( topOfTaskTree ( ) . size ( ) > indexToInsert )
topOfTaskTree ( ) . insert ( indexToInsert , task ) ;
else
topOfTaskTree ( ) . push_back ( task ) ;
for ( int i = 0 ; i < topOfTaskTree ( ) . size ( ) ; i + + )
{
2021-06-05 18:13:42 +03:00
topOfTaskTree ( ) [ i ] - > setIndex ( i )
. save ( ) ;
2018-12-21 23:11:26 +02:00
}
2018-09-25 22:55:14 +03:00
}
2018-12-21 23:11:26 +02:00
if ( result )
{
task - > save ( ) ;
}
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
return result ;
2018-09-25 22:55:14 +03:00
}
bool Storage : : isOpened ( )
{
2018-12-21 23:11:26 +02:00
return mDatabase ! = nullptr ;
2018-09-25 22:55:14 +03:00
}
void Storage : : save ( )
{
2018-12-21 23:11:26 +02:00
foreach ( PTask task , mTopTasks )
saveTask ( task , depthRecursive ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
database ( ) . exec ( " delete from task where removed = 1 " ) ;
database ( ) . exec ( " delete from timeline where removed = 1 " ) ;
database ( ) . exec ( " delete from file where removed = 1 " ) ;
//database().exec("delete from change where removed = 1");
2018-09-25 22:55:14 +03:00
}
void Storage : : saveSingleTask ( PTask task )
{
2018-12-21 23:11:26 +02:00
task - > save ( ) ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
void Storage : : loadTaskRecord ( Task & t , SQLite : : Statement & q )
{
t . mId = q . getColumn ( 0 ) . getInt64 ( ) ;
t . mTitle = q . getColumn ( 1 ) . getText ( ) ;
t . mIndex = q . getColumn ( 2 ) . getInt ( ) ;
t . mFlags = q . getColumn ( 3 ) . getInt ( ) ;
t . mAttachmentCount = q . getColumn ( 4 ) . getInt ( ) ;
}
2018-09-25 22:55:14 +03:00
void Storage : : loadTaskTree ( )
{
2018-12-21 23:11:26 +02:00
mTopTasks . clear ( ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
SQLite : : Statement q ( database ( ) , " select id, title, orderid, flags, (select count(*) from file where (file.taskid = task.id) and ((file.removed is null) or (file.removed = 0))) from task where (parentid is null) and ((removed != 1) or (removed is null)) order by orderid " ) ;
int currentIndex = 0 ;
while ( q . executeStep ( ) )
{
PTask t ( new Task ( ) ) ;
2021-06-05 18:13:42 +03:00
loadTaskRecord ( * t , q ) ;
2018-12-21 23:11:26 +02:00
t - > setIndex ( currentIndex + + ) ;
mTaskModelIdMap [ t - > modelId ( ) ] = t ;
mTaskIdMap [ t - > id ( ) ] = t ;
mTopTasks . push_back ( t ) ;
}
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
foreach ( PTask task , mTopTasks )
loadTaskChildren ( task ) ;
2018-09-25 22:55:14 +03:00
}
void Storage : : loadTaskChildren ( PTask task )
{
2018-12-21 23:11:26 +02:00
SQLite : : Statement q ( database ( ) , " select id, title, orderid, flags, (select count(*) from file where (file.taskid = task.id) and ((file.removed is null) or (file.removed = 0))) from task where (task.parentid = :parentid) and ((task.removed != 1) or (task.removed is null)) order by task.orderid " ) ;
q . bind ( " :parentid " , ( sqlite3_int64 ) task - > id ( ) ) ;
int currentIndex = 0 ;
while ( q . executeStep ( ) )
{
PTask t ( new Task ( ) ) ;
2021-06-05 18:13:42 +03:00
loadTaskRecord ( * t , q ) ;
t - > setIndex ( currentIndex + + )
. setParent ( task , false ) ;
2018-12-21 23:11:26 +02:00
loadTaskChildren ( t ) ;
mTaskModelIdMap [ t - > modelId ( ) ] = t ;
mTaskIdMap [ t - > id ( ) ] = t ;
task - > children ( ) . push_back ( t ) ;
}
2018-09-25 22:55:14 +03:00
}
PTask Storage : : findTaskByModelId ( Task : : ModelId id )
{
2018-12-21 23:11:26 +02:00
auto taskIter = mTaskModelIdMap . find ( id ) ;
if ( taskIter ! = mTaskModelIdMap . end ( ) )
return * taskIter ;
else
return PTask ( ) ;
2018-09-25 22:55:14 +03:00
}
PTask Storage : : findTaskById ( Task : : Id id )
{
2018-12-21 23:11:26 +02:00
auto taskIter = mTaskIdMap . find ( id ) ;
if ( taskIter ! = mTaskIdMap . end ( ) )
return * taskIter ;
else
return PTask ( ) ;
2018-09-25 22:55:14 +03:00
}
int Storage : : findTaskIndexInParent ( PTask task )
{
2018-12-21 23:11:26 +02:00
if ( task - > parent ( ) )
return task - > parent ( ) - > children ( ) . indexOf ( task ) ;
else
return topOfTaskTree ( ) . indexOf ( task ) ;
2018-09-25 22:55:14 +03:00
}
TaskArray & Storage : : topOfTaskTree ( )
{
2018-12-21 23:11:26 +02:00
return mTopTasks ;
2018-09-25 22:55:14 +03:00
}
void Storage : : loadAttachments ( PTask task , AttachmentArray & output )
{
2018-12-21 23:11:26 +02:00
SQLite : : Statement q ( database ( ) , " select id, filename, orderid from file where (taskid = :taskid) and ((removed != 1) or (removed is null)) order by orderid " ) ;
q . bind ( " :taskid " , ( sqlite3_int64 ) task - > id ( ) ) ;
while ( q . executeStep ( ) )
{
PAttachment att ( new Attachment ( ) ) ;
2021-06-05 18:13:42 +03:00
att - > setId ( q . getColumn ( 0 ) . getInt64 ( ) )
. setFilename ( q . getColumn ( 1 ) . getText ( ) )
. setTaskId ( task - > id ( ) )
. setIndex ( q . getColumn ( 2 ) . getInt ( ) ) ;
2018-12-21 23:11:26 +02:00
output . push_back ( att ) ;
}
2018-09-25 22:55:14 +03:00
}
void Storage : : deleteAttachment ( PAttachment att )
{
2018-12-21 23:11:26 +02:00
SQLite : : Statement q ( database ( ) , " update file set removed=1 where id=:id " ) ;
q . bind ( " :id " , ( sqlite3_int64 ) att - > id ( ) ) ;
q . exec ( ) ;
2018-09-25 22:55:14 +03:00
}
void Storage : : undeleteAttachment ( PAttachment att )
{
2018-12-21 23:11:26 +02:00
SQLite : : Statement q ( database ( ) , " update file set removed=0 where id=:id " ) ;
q . bind ( " :id " , ( sqlite3_int64 ) att - > id ( ) ) ;
q . exec ( ) ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
Id Storage : : saveMetadata ( const Attachment & a )
{
if ( a . mId )
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , " update file set filename = :filename, orderid = :orderid where id = :id " ) ;
q . bind ( " :filename " , a . mFilename . toStdString ( ) . c_str ( ) ) ;
q . bind ( " :orderid " , a . mIndex ) ;
q . bind ( " :id " , ( sqlite3_int64 ) a . mId ) ;
if ( q . exec ( ) )
return a . mId ;
}
else
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , " insert into file (filename, taskid, orderid, removed) values(:filename, :taskid, :orderid, 0) " ) ;
q . bind ( " :filename " , a . mFilename . toStdString ( ) . c_str ( ) ) ;
q . bind ( " :taskid " , ( sqlite3_int64 ) a . mTaskId ) ;
q . bind ( " :orderid " , a . mIndex ) ;
if ( q . exec ( ) )
{
return Storage : : instance ( ) . database ( ) . getLastInsertRowid ( ) ;
}
}
return Id ( 0 ) ;
}
void Storage : : saveContent ( const Attachment & a , const QByteArray & content )
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , " update file set content = :content where id = :id " ) ;
q . bind ( " :content " , content . data ( ) , content . size ( ) ) ;
q . bind ( " :id " , ( sqlite3_int64 ) a . mId ) ;
if ( q . exec ( ) )
;
}
QByteArray Storage : : loadContent ( const Attachment & a )
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , " select content from file where id = :id " ) ;
q . bind ( " :id " , ( sqlite3_int64 ) a . mId ) ;
if ( q . executeStep ( ) )
return QByteArray ( ( const char * ) q . getColumn ( 0 ) . getBlob ( ) , q . getColumn ( 0 ) . size ( ) ) ;
else
return QByteArray ( ) ;
}
Id Storage : : saveTimeRecord ( const TimeRecord & r )
{
if ( ! r . id ( ) )
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , " insert into timeline(id, starttime, endtime, taskid, removed) values (NULL, :starttime, :endtime, :taskid, :removed) " ) ;
q . bind ( " :starttime " , helper : : chrono : : timeToStr ( r . startTime ( ) ) ) ;
q . bind ( " :endtime " , helper : : chrono : : timeToStr ( r . endTime ( ) ) ) ;
q . bind ( " :taskid " , static_cast < sqlite3_int64 > ( r . taskId ( ) ) ) ;
q . bind ( " :removed " , 0 ) ;
if ( q . exec ( ) )
return Storage : : instance ( ) . database ( ) . getLastInsertRowid ( ) ;
}
else
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) ,
" update timeline set starttime = :starttime, endtime = :endtime, taskid = :taskid, removed = 0 where id = :id " ) ;
q . bind ( " :starttime " , helper : : chrono : : timeToStr ( r . startTime ( ) ) ) ;
q . bind ( " :endtime " , helper : : chrono : : timeToStr ( r . endTime ( ) ) ) ;
q . bind ( " :taskid " , static_cast < sqlite3_int64 > ( r . taskId ( ) ) ) ;
q . bind ( " :id " , static_cast < sqlite3_int64 > ( r . id ( ) ) ) ;
if ( q . exec ( ) )
return r . id ( ) ;
}
return ( Id ) 0 ;
}
void Storage : : deleteTimeRecord ( const TimeRecord & r )
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , " update timeline set removed = 1 where id = :id " ) ;
q . bind ( " :id " , static_cast < sqlite3_int64 > ( r . id ( ) ) ) ;
q . exec ( ) ;
}
void Storage : : loadTimeLine ( TimeLine & l )
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , " select id, starttime, endtime from timeline where (taskid = :taskid) and ((removed is null) or (removed != 1)) order by id asc " ) ;
q . bind ( " :taskid " , static_cast < sqlite3_int64 > ( l . taskId ( ) ) ) ;
while ( q . executeStep ( ) )
{
time_t start = helper : : chrono : : strToTime ( q . getColumn ( 1 ) . getText ( ) ) ;
time_t stop = helper : : chrono : : strToTime ( q . getColumn ( 2 ) . getText ( ) ) ;
TimeRecord tr ;
tr . setId ( q . getColumn ( 0 ) . getInt64 ( ) )
. setStartTime ( start )
. setEndTime ( stop )
. setTaskId ( l . taskId ( ) ) ;
l . data ( ) . push_back ( tr ) ;
}
// Sort time intervals
l . sortData ( ) ;
// Find current total time length
l . mTotalTime = l . findTotalTime ( ) ;
}
void Storage : : saveTimeLime ( const TimeLine & l )
{
}
2018-09-25 22:55:14 +03:00
Storage & Storage : : instance ( )
{
2018-12-21 23:11:26 +02:00
static Storage _instance ;
return _instance ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
void Storage : : deleteTask ( const PTask & task , DeleteOption option )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
if ( option ! = DeleteOption_FromParent )
{
// Remove from hash
removeTask ( task ) ;
}
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
TaskArray & siblings = task - > parent ( ) ? task - > parent ( ) - > children ( ) : mTopTasks ;
siblings . removeOne ( task ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Remove from database
if ( option = = DeleteOption_Total )
{
SQLite : : Statement q ( database ( ) , " update task set removed = 1 where id = :id " ) ;
q . bind ( " :id " , ( sqlite3_int64 ) task - > id ( ) ) ;
q . exec ( ) ;
}
2018-09-25 22:55:14 +03:00
#if 0
2018-12-21 23:11:26 +02:00
int taskIndex = - 1 ;
// Update indexes
for ( int i = 0 ; i < siblings . size ( ) ; i + + )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
if ( siblings [ i ] = = task )
taskIndex = i ;
Task & t = * siblings [ i ] ;
if ( t . index ( ) > task - > index ( ) )
{
t . setIndex ( t . index ( ) - 1 ) ;
t . save ( ) ;
}
2018-09-25 22:55:14 +03:00
}
2018-12-21 23:11:26 +02:00
// Remove from tree
if ( taskIndex > = 0 )
siblings . remove ( taskIndex ) ;
2018-09-25 22:55:14 +03:00
# endif
}
2021-06-05 18:13:42 +03:00
void Storage : : undeleteTask ( const PTask & task )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
SQLite : : Statement q ( database ( ) , " update task set removed = 0 where id = :id " ) ;
q . bind ( " :id " , ( sqlite3_int64 ) task - > id ( ) ) ;
q . exec ( ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
// Find place where to insert task
TaskArray & siblings = task - > parent ( ) ? task - > parent ( ) - > children ( ) : mTopTasks ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
TaskArray : : iterator taskIter = std : : lower_bound ( siblings . begin ( ) , siblings . end ( ) , task - > index ( ) , [ ] ( const PTask & t , int index ) { return t - > index ( ) < index ; } ) ;
if ( taskIter ! = siblings . end ( ) )
siblings . insert ( taskIter , task ) ;
else
siblings . append ( task ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
mTaskModelIdMap [ task - > modelId ( ) ] = task ;
mTaskIdMap [ task - > id ( ) ] = task ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
void Storage : : removeTask ( const PTask & task )
2018-09-25 22:55:14 +03:00
{
2018-12-21 23:11:26 +02:00
auto taskModelIter = mTaskModelIdMap . find ( task - > modelId ( ) ) ;
if ( taskModelIter ! = mTaskModelIdMap . end ( ) )
mTaskModelIdMap . erase ( taskModelIter ) ;
2018-09-25 22:55:14 +03:00
2018-12-21 23:11:26 +02:00
auto taskIter = mTaskIdMap . find ( task - > id ( ) ) ;
if ( taskIter ! = mTaskIdMap . end ( ) )
mTaskIdMap . erase ( taskIter ) ;
2018-09-25 22:55:14 +03:00
}
2021-06-05 18:13:42 +03:00
void Storage : : loadTaskContent ( Task & task )
{
SQLite : : Statement htmlQuery ( Storage : : instance ( ) . database ( ) , " select html from task where id = :id " ) ;
htmlQuery . bind ( " :id " , ( sqlite3_int64 ) task . mId ) ;
if ( htmlQuery . executeStep ( ) )
{
task . mHtml = htmlQuery . getColumn ( 0 ) . getText ( ) ;
task . mHtmlLoaded = true ;
task . mHtmlModified = false ;
}
if ( ! task . mTimeLine )
{
task . mTimeLine = PTimeLine ( new TimeLine ( ) ) ;
task . mTimeLine - > setTaskId ( task . mId ) ;
task . mTimeLine - > load ( ) ;
}
}
void Storage : : saveTask ( const Task & task , SaveOptions options )
{
const Task & t = task ;
if ( ! t . mTitleModified & & ! t . mHtmlModified & & ! t . mIndexModified & & ! t . mParentModified & & options = = Save_Automatic )
return ;
const char * queryText = nullptr ;
// Minimize changes to DB
if ( t . mTitleModified & & t . mHtmlModified )
queryText = " update task set parentid = :parentid, flags = :flags, title = :title, html = :html, orderid = :orderid where id = :id " ;
else
if ( t . mTitleModified )
queryText = " update task set parentid = :parentid, flags = :flags, title = :title, orderid = :orderid where id = :id " ;
else
if ( t . mHtmlModified )
queryText = " update task set parentid = :parentid, flags = :flags, html = :html, orderid = :orderid where id = :id " ;
else
queryText = " update task set parentid = :parentid, flags = :flags, orderid = :orderid where id = :id " ;
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , queryText ) ;
if ( t . mParent )
q . bind ( " :parentid " , ( sqlite3_int64 ) t . mParent - > id ( ) ) ;
else
q . bind ( " :parentid " ) ;
q . bind ( " :flags " , t . mFlags ) ;
if ( t . mTitleModified )
q . bind ( " :title " , t . mTitle . toStdString ( ) ) ;
if ( t . mHtmlModified )
q . bind ( " :html " , t . mHtml . toStdString ( ) ) ;
q . bind ( " :id " , ( sqlite3_int64 ) t . mId ) ;
q . bind ( " :orderid " , t . mIndex ) ;
q . exec ( ) ;
}
int Storage : : findAttachmentCountOnTask ( const Task & t )
{
SQLite : : Statement q ( Storage : : instance ( ) . database ( ) , " select count(*) from file where (taskid = :taskid) and ((removed = 0) or (removed is null)) " ) ;
q . bind ( " :taskid " , ( sqlite3_int64 ) t . id ( ) ) ;
if ( q . executeStep ( ) )
return q . getColumn ( 0 ) . getInt ( ) ;
else
return 0 ;
}