2019-03-23 22:40:56 +01:00
/ *
* Copyright ( C ) 2019 Philipp Haefelfinger ( http : //www.haefelfinger.ch/). All Rights Reserved.
* This application is licensed under GPLv2 . See the LICENSE file in the root directory of the project .
* /
2019-04-02 21:30:39 +02:00
package datastore
2019-03-21 22:37:16 +01:00
2019-03-12 23:44:05 +01:00
import (
2019-03-15 00:35:49 +01:00
"database/sql"
2019-03-12 23:44:05 +01:00
"errors"
2019-03-15 22:50:48 +01:00
"fmt"
2019-03-15 00:35:49 +01:00
_ "github.com/mattn/go-sqlite3"
2019-03-15 22:50:48 +01:00
"github.com/sirupsen/logrus"
2019-03-12 23:44:05 +01:00
"time"
)
2019-03-15 22:50:48 +01:00
var ErrorRecordNotFound = errors . New ( "Record not found" )
2019-04-02 23:26:25 +02:00
type CategoryData struct {
CategoryId int
PiwigoId int
PiwigoParentId int
Name string
Key string
}
func ( cat * CategoryData ) String ( ) string {
return fmt . Sprintf ( "CategoryData{CategoryId:%d, PiwigoId:%d, PiwigoParentId:%d, Name:%s, Key:%s}" , cat . CategoryId , cat . PiwigoId , cat . PiwigoParentId , cat . Name , cat . Key )
}
2019-03-12 23:44:05 +01:00
type ImageMetaData struct {
2019-04-06 23:01:53 +02:00
ImageId int
PiwigoId int
FullImagePath string
Filename string
Md5Sum string
LastChange time . Time
CategoryPath string
CategoryPiwigoId int
UploadRequired bool
DeleteRequired bool
2019-03-12 23:44:05 +01:00
}
2019-03-15 22:50:48 +01:00
func ( img * ImageMetaData ) String ( ) string {
2019-04-06 23:01:53 +02:00
return fmt . Sprintf ( "ImageMetaData{ImageId:%d, PiwigoId:%d, CategoryPiwigoId:%d, RelPath:%s, File:%s, Md5:%s, Change:%sS, catpath:%s, UploadRequired: %t, DeleteRequired: %t}" , img . ImageId , img . PiwigoId , img . CategoryPiwigoId , img . FullImagePath , img . Filename , img . Md5Sum , img . LastChange . String ( ) , img . CategoryPath , img . UploadRequired , img . DeleteRequired )
2019-03-12 23:44:05 +01:00
}
2019-04-02 23:26:25 +02:00
type CategoryProvider interface {
SaveCategory ( category CategoryData ) error
2019-04-06 23:38:39 +02:00
GetCategoryByPiwigoId ( piwigoId int ) ( CategoryData , error )
2019-04-02 23:26:25 +02:00
GetCategoryByKey ( key string ) ( CategoryData , error )
2019-04-06 22:47:56 +02:00
GetCategoriesToCreate ( ) ( [ ] CategoryData , error )
2019-04-02 23:26:25 +02:00
}
2019-03-15 22:50:48 +01:00
type ImageMetadataProvider interface {
2019-03-20 23:15:41 +01:00
ImageMetadata ( fullImagePath string ) ( ImageMetaData , error )
2019-03-20 00:14:10 +01:00
ImageMetadataToUpload ( ) ( [ ] ImageMetaData , error )
2019-03-23 23:21:35 +01:00
ImageMetadataToDelete ( ) ( [ ] ImageMetaData , error )
2019-03-23 23:55:55 +01:00
ImageMetadataAll ( ) ( [ ] ImageMetaData , error )
2019-03-12 23:44:05 +01:00
SaveImageMetadata ( m ImageMetaData ) error
2019-03-19 23:07:55 +01:00
SavePiwigoIdAndUpdateUploadFlag ( md5Sum string , piwigoId int ) error
2019-03-24 23:44:16 +01:00
DeleteMarkedImages ( ) error
2019-03-12 23:44:05 +01:00
}
2019-04-02 21:30:39 +02:00
type LocalDataStore struct {
2019-03-12 23:44:05 +01:00
connectionString string
}
2019-04-02 21:30:39 +02:00
func NewLocalDataStore ( ) * LocalDataStore {
return & LocalDataStore { }
}
func ( d * LocalDataStore ) Initialize ( connectionString string ) error {
2019-03-12 23:44:05 +01:00
if connectionString == "" {
return errors . New ( "connection string could not be empty." )
}
d . connectionString = connectionString
2019-03-15 00:35:49 +01:00
db , err := d . openDatabase ( )
if err != nil {
return err
}
defer db . Close ( )
err = d . createTablesIfNeeded ( db )
return err
2019-03-12 23:44:05 +01:00
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) ImageMetadata ( fullImagePath string ) ( ImageMetaData , error ) {
2019-03-20 23:15:41 +01:00
logrus . Tracef ( "Query image metadata for file %s" , fullImagePath )
2019-03-15 22:50:48 +01:00
img := ImageMetaData { }
2019-03-15 00:35:49 +01:00
db , err := d . openDatabase ( )
if err != nil {
2019-03-15 22:50:48 +01:00
return img , err
2019-03-15 00:35:49 +01:00
}
defer db . Close ( )
2019-04-06 23:01:53 +02:00
stmt , err := db . Prepare ( "SELECT imageId, piwigoId, fullImagePath, fileName, md5sum, lastChanged, categoryPath, categoryPiwigoId, uploadRequired, deleteRequired FROM image WHERE fullImagePath = ?" )
2019-03-15 00:35:49 +01:00
if err != nil {
2019-03-15 22:50:48 +01:00
return img , err
2019-03-15 00:35:49 +01:00
}
2019-03-20 23:15:41 +01:00
rows , err := stmt . Query ( fullImagePath )
2019-03-15 00:35:49 +01:00
if err != nil {
2019-03-15 22:50:48 +01:00
return img , err
2019-03-15 00:35:49 +01:00
}
2019-03-15 22:50:48 +01:00
defer rows . Close ( )
2019-03-15 00:35:49 +01:00
2019-03-15 22:50:48 +01:00
if rows . Next ( ) {
2019-04-06 23:23:43 +02:00
err = readImageMetadataFromRow ( rows , & img )
2019-03-15 22:50:48 +01:00
if err != nil {
return img , err
}
} else {
return img , ErrorRecordNotFound
}
err = rows . Err ( )
return img , err
2019-03-12 23:44:05 +01:00
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) ImageMetadataAll ( ) ( [ ] ImageMetaData , error ) {
2019-03-23 23:55:55 +01:00
logrus . Tracef ( "Query all image metadata that represent files on the disk" )
db , err := d . openDatabase ( )
if err != nil {
return nil , err
}
defer db . Close ( )
2019-04-06 23:01:53 +02:00
rows , err := db . Query ( "SELECT imageId, piwigoId, fullImagePath, fileName, md5sum, lastChanged, categoryPath, categoryPiwigoId, uploadRequired, deleteRequired FROM image" )
2019-03-23 23:55:55 +01:00
if err != nil {
return nil , err
}
defer rows . Close ( )
images := [ ] ImageMetaData { }
for rows . Next ( ) {
img := & ImageMetaData { }
2019-04-06 23:23:43 +02:00
err = readImageMetadataFromRow ( rows , img )
2019-03-23 23:55:55 +01:00
if err != nil {
return nil , err
}
images = append ( images , * img )
}
err = rows . Err ( )
return images , err
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) ImageMetadataToDelete ( ) ( [ ] ImageMetaData , error ) {
2019-03-23 23:21:35 +01:00
logrus . Tracef ( "Query all image metadata that represent files queued to delete" )
db , err := d . openDatabase ( )
if err != nil {
return nil , err
}
defer db . Close ( )
2019-04-06 23:01:53 +02:00
rows , err := db . Query ( "SELECT imageId, piwigoId, fullImagePath, fileName, md5sum, lastChanged, categoryPath, categoryPiwigoId, uploadRequired, deleteRequired FROM image WHERE deleteRequired = 1" )
2019-03-23 23:21:35 +01:00
if err != nil {
return nil , err
}
defer rows . Close ( )
images := [ ] ImageMetaData { }
for rows . Next ( ) {
img := & ImageMetaData { }
2019-04-06 23:23:43 +02:00
err = readImageMetadataFromRow ( rows , img )
2019-03-23 23:21:35 +01:00
if err != nil {
return nil , err
}
images = append ( images , * img )
}
err = rows . Err ( )
return images , err
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) ImageMetadataToUpload ( ) ( [ ] ImageMetaData , error ) {
2019-03-17 23:36:40 +01:00
logrus . Tracef ( "Query all image metadata that represent files queued to upload" )
db , err := d . openDatabase ( )
if err != nil {
return nil , err
}
defer db . Close ( )
2019-04-06 23:01:53 +02:00
rows , err := db . Query ( "SELECT imageId, piwigoId, fullImagePath, fileName, md5sum, lastChanged, categoryPath, categoryPiwigoId, uploadRequired, deleteRequired FROM image WHERE uploadRequired = 1 and deleteRequired = 0 order by fullImagePath asc" )
2019-03-17 23:36:40 +01:00
if err != nil {
return nil , err
}
defer rows . Close ( )
2019-03-20 00:14:10 +01:00
images := [ ] ImageMetaData { }
2019-03-17 23:36:40 +01:00
for rows . Next ( ) {
img := & ImageMetaData { }
2019-04-06 23:23:43 +02:00
err = readImageMetadataFromRow ( rows , img )
2019-03-17 23:36:40 +01:00
if err != nil {
return nil , err
}
2019-03-20 00:14:10 +01:00
images = append ( images , * img )
2019-03-17 23:36:40 +01:00
}
err = rows . Err ( )
return images , err
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) SaveImageMetadata ( img ImageMetaData ) error {
2019-03-17 22:59:18 +01:00
logrus . Tracef ( "Saving imagemetadata: %s" , img . String ( ) )
2019-03-15 00:35:49 +01:00
db , err := d . openDatabase ( )
if err != nil {
return err
}
defer db . Close ( )
tx , err := db . Begin ( )
if err != nil {
return err
}
2019-03-15 22:50:48 +01:00
if img . ImageId <= 0 {
err = d . insertImageMetaData ( tx , img )
2019-03-15 00:35:49 +01:00
} else {
2019-03-15 22:50:48 +01:00
err = d . updateImageMetaData ( tx , img )
2019-03-15 00:35:49 +01:00
}
2019-03-15 22:50:48 +01:00
if err != nil {
2019-03-20 23:15:41 +01:00
logrus . Errorf ( "Rolling back transaction for metadata of %s" , img . FullImagePath )
2019-03-15 22:50:48 +01:00
errTx := tx . Rollback ( )
if errTx != nil {
2019-03-20 23:15:41 +01:00
logrus . Errorf ( "Rollback of transaction for metadata of %s failed!" , img . FullImagePath )
2019-03-15 22:50:48 +01:00
}
return err
}
2019-03-24 23:44:16 +01:00
logrus . Tracef ( "Committing metadata for image %s" , img . String ( ) )
2019-03-15 22:50:48 +01:00
return tx . Commit ( )
2019-03-15 00:35:49 +01:00
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) SavePiwigoIdAndUpdateUploadFlag ( md5Sum string , piwigoId int ) error {
2019-03-19 23:07:55 +01:00
logrus . Tracef ( "Saving piwigo id %d for file with md5sum %s" , piwigoId , md5Sum )
db , err := d . openDatabase ( )
if err != nil {
return err
}
defer db . Close ( )
tx , err := db . Begin ( )
if err != nil {
return err
}
2019-03-20 00:31:09 +01:00
uploadRequired := 1
if piwigoId > 0 {
uploadRequired = 0
}
stmt , err := tx . Prepare ( "UPDATE image SET piwigoId = ?, uploadRequired = ? WHERE md5sum = ?" )
2019-03-19 23:07:55 +01:00
if err != nil {
return err
}
2019-03-20 00:31:09 +01:00
_ , err = stmt . Exec ( piwigoId , uploadRequired , md5Sum )
2019-03-19 23:07:55 +01:00
if err != nil {
logrus . Errorf ( "Rolling back transaction for piwigo id update of file %s" , md5Sum )
errTx := tx . Rollback ( )
if errTx != nil {
logrus . Errorf ( "Rollback of transaction for piwigo id update of file %s failed!" , md5Sum )
}
return err
}
2019-03-24 23:44:16 +01:00
logrus . Tracef ( "Committing piwigo id %d for file with md5sum %s" , piwigoId , md5Sum )
return tx . Commit ( )
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) DeleteMarkedImages ( ) error {
2019-03-24 23:44:16 +01:00
logrus . Trace ( "Deleting marked records from database..." )
db , err := d . openDatabase ( )
if err != nil {
return err
}
defer db . Close ( )
tx , err := db . Begin ( )
if err != nil {
return err
}
_ , err = tx . Exec ( "DELETE FROM image WHERE deleteRequired = 1" )
if err != nil {
logrus . Errorf ( "Rolling back transaction of deleting marked images" )
errTx := tx . Rollback ( )
if errTx != nil {
logrus . Errorf ( "Rollback of transaction for piwigo delete failed!" )
}
return err
}
logrus . Tracef ( "Committing deleted images from database" )
2019-03-19 23:07:55 +01:00
return tx . Commit ( )
}
2019-04-06 23:07:11 +02:00
func ( d * LocalDataStore ) SaveCategory ( category CategoryData ) error {
2019-04-06 23:23:43 +02:00
logrus . Tracef ( "Saving category: %s" , category . String ( ) )
db , err := d . openDatabase ( )
if err != nil {
return err
}
defer db . Close ( )
tx , err := db . Begin ( )
if err != nil {
return err
}
if category . CategoryId <= 0 {
err = d . insertCategoryData ( tx , category )
} else {
err = d . updateCategoryData ( tx , category )
}
if err != nil {
logrus . Errorf ( "Rolling back transaction for category of %s" , category . Key )
errTx := tx . Rollback ( )
if errTx != nil {
logrus . Errorf ( "Rollback of transaction for category of %s failed!" , category . Key )
}
return err
}
logrus . Tracef ( "Committing category for image %s" , category . String ( ) )
return tx . Commit ( )
2019-04-06 23:07:11 +02:00
}
2019-04-06 23:38:39 +02:00
func ( d * LocalDataStore ) GetCategoryByPiwigoId ( piwigoId int ) ( CategoryData , error ) {
logrus . Tracef ( "Query category by piwigoid %d" , piwigoId )
cat := CategoryData { }
db , err := d . openDatabase ( )
if err != nil {
return cat , err
}
defer db . Close ( )
stmt , err := db . Prepare ( "SELECT categoryId, piwigoId, piwigoParentId, name, key FROM category WHERE piwigoId = ?" )
if err != nil {
return cat , err
}
rows , err := stmt . Query ( piwigoId )
if err != nil {
return cat , err
}
defer rows . Close ( )
if rows . Next ( ) {
err = readCategoryFromRow ( rows , & cat )
if err != nil {
return cat , err
}
} else {
return cat , ErrorRecordNotFound
}
err = rows . Err ( )
return cat , err
2019-04-06 23:07:11 +02:00
}
func ( d * LocalDataStore ) GetCategoryByKey ( key string ) ( CategoryData , error ) {
2019-04-06 23:23:43 +02:00
logrus . Tracef ( "Query category %s" , key )
cat := CategoryData { }
2019-04-06 23:07:11 +02:00
2019-04-06 23:23:43 +02:00
db , err := d . openDatabase ( )
if err != nil {
return cat , err
}
defer db . Close ( )
2019-04-06 23:07:11 +02:00
2019-04-06 23:23:43 +02:00
stmt , err := db . Prepare ( "SELECT categoryId, piwigoId, piwigoParentId, name, key FROM category WHERE key = ?" )
2019-03-15 00:35:49 +01:00
if err != nil {
2019-04-06 23:23:43 +02:00
return cat , err
2019-03-15 00:35:49 +01:00
}
2019-04-06 23:23:43 +02:00
rows , err := stmt . Query ( key )
if err != nil {
return cat , err
}
defer rows . Close ( )
if rows . Next ( ) {
err = readCategoryFromRow ( rows , & cat )
if err != nil {
return cat , err
}
} else {
return cat , ErrorRecordNotFound
}
err = rows . Err ( )
return cat , err
}
func ( d * LocalDataStore ) GetCategoriesToCreate ( ) ( [ ] CategoryData , error ) {
2019-04-06 23:38:39 +02:00
logrus . Trace ( "Query categories to create on piwigo" )
db , err := d . openDatabase ( )
if err != nil {
return nil , err
}
defer db . Close ( )
stmt , err := db . Prepare ( "SELECT categoryId, piwigoId, piwigoParentId, name, key FROM category WHERE piwigoId = 0 ORDER BY key" )
if err != nil {
return nil , err
}
rows , err := stmt . Query ( )
if err != nil {
return nil , err
}
defer rows . Close ( )
var categories [ ] CategoryData
for rows . Next ( ) {
cat := CategoryData { }
err = readCategoryFromRow ( rows , & cat )
if err != nil {
return nil , err
}
categories = append ( categories , cat )
}
err = rows . Err ( )
return categories , err
2019-03-15 00:35:49 +01:00
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) openDatabase ( ) ( * sql . DB , error ) {
2019-03-15 00:35:49 +01:00
db , err := sql . Open ( "sqlite3" , d . connectionString )
if err != nil {
2019-03-15 22:50:48 +01:00
logrus . Warnf ( "Could not open database %s" , d . connectionString )
2019-03-15 00:35:49 +01:00
return nil , err
}
db . SetMaxOpenConns ( 1 )
return db , err
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) createTablesIfNeeded ( db * sql . DB ) error {
2019-03-15 00:35:49 +01:00
_ , err := db . Exec ( "CREATE TABLE IF NOT EXISTS image (" +
2019-03-15 22:50:48 +01:00
"imageId INTEGER PRIMARY KEY," +
2019-03-15 00:35:49 +01:00
"piwigoId INTEGER NULL," +
2019-03-20 23:15:41 +01:00
"fullImagePath NVARCHAR(1000) NOT NULL," +
2019-03-15 00:35:49 +01:00
"fileName NVARCHAR(255) NOT NULL," +
"md5sum NVARCHAR(50) NOT NULL," +
"lastChanged DATETIME NOT NULL," +
"categoryPath NVARCHAR(1000) NOT NULL," +
2019-04-06 23:01:53 +02:00
"categoryPiwigoId INTEGER NULL," +
2019-03-23 22:54:31 +01:00
"uploadRequired BIT NOT NULL," +
"deleteRequired BIT NOT NULL" +
2019-03-15 00:35:49 +01:00
");" )
2019-03-15 22:50:48 +01:00
if err != nil {
return err
}
2019-03-20 23:15:41 +01:00
_ , err = db . Exec ( "CREATE UNIQUE INDEX IF NOT EXISTS UX_ImageFullImagePath ON image (fullImagePath);" )
2019-04-06 23:01:53 +02:00
if err != nil {
return err
}
_ , err = db . Exec ( "CREATE TABLE IF NOT EXISTS category (" +
2019-04-06 23:07:11 +02:00
"categoryId INTEGER PRIMARY KEY," +
"piwigoId INTEGER NULL," +
"piwigoParentId INTEGER NULL," +
"name NVARCHAR(255) NOT NULL," +
"key NVARCHAR(1000) NOT NULL" +
2019-04-06 23:01:53 +02:00
");" )
if err != nil {
return err
}
2019-04-06 23:07:11 +02:00
_ , err = db . Exec ( "CREATE UNIQUE INDEX IF NOT EXISTS UX_Category_Key ON category (key);" )
if err != nil {
return err
}
2019-04-07 22:01:58 +02:00
_ , err = db . Exec ( "CREATE UNIQUE INDEX IF NOT EXISTS UX_Category_PiwigoId ON category (piwigoId) WHERE piwigoId > 0;" )
2019-04-06 23:07:11 +02:00
if err != nil {
return err
}
logrus . Debug ( "Database successfully initialized" )
return nil
2019-03-15 22:50:48 +01:00
}
2019-04-06 23:23:43 +02:00
func readImageMetadataFromRow ( rows * sql . Rows , img * ImageMetaData ) error {
err := rows . Scan ( & img . ImageId , & img . PiwigoId , & img . FullImagePath , & img . Filename , & img . Md5Sum , & img . LastChange , & img . CategoryPath , & img . CategoryPiwigoId , & img . UploadRequired , & img . DeleteRequired )
return err
}
func ( d * LocalDataStore ) insertImageMetaData ( tx * sql . Tx , data ImageMetaData ) error {
stmt , err := tx . Prepare ( "INSERT INTO image (piwigoId, fullImagePath, fileName, md5sum, lastChanged, categoryPath, categoryPiwigoId, uploadRequired, deleteRequired) VALUES (?,?,?,?,?,?,?,?,?)" )
if err != nil {
return err
}
_ , err = stmt . Exec ( data . PiwigoId , data . FullImagePath , data . Filename , data . Md5Sum , data . LastChange , data . CategoryPath , data . CategoryPiwigoId , data . UploadRequired , data . DeleteRequired )
return err
}
2019-04-02 21:30:39 +02:00
func ( d * LocalDataStore ) updateImageMetaData ( tx * sql . Tx , data ImageMetaData ) error {
2019-04-06 23:01:53 +02:00
stmt , err := tx . Prepare ( "UPDATE image SET piwigoId = ?, fullImagePath = ?, fileName = ?, md5sum = ?, lastChanged = ?, categoryPath = ?, categoryPiwigoId = ?, uploadRequired = ?, deleteRequired = ? WHERE imageId = ?" )
2019-03-15 22:50:48 +01:00
if err != nil {
return err
}
2019-04-06 23:01:53 +02:00
_ , err = stmt . Exec ( data . PiwigoId , data . FullImagePath , data . Filename , data . Md5Sum , data . LastChange , data . CategoryPath , data . CategoryPiwigoId , data . UploadRequired , data . DeleteRequired , data . ImageId )
2019-03-15 00:35:49 +01:00
return err
2019-03-12 23:44:05 +01:00
}
2019-04-06 23:23:43 +02:00
func readCategoryFromRow ( rows * sql . Rows , cat * CategoryData ) error {
err := rows . Scan ( & cat . CategoryId , & cat . PiwigoId , & cat . PiwigoParentId , & cat . Name , & cat . Key )
return err
}
func ( d * LocalDataStore ) updateCategoryData ( tx * sql . Tx , data CategoryData ) error {
stmt , err := tx . Prepare ( "UPDATE category SET piwigoId = ?, piwigoParentId = ?, name = ?, key = ? WHERE categoryId = ?" )
if err != nil {
return err
}
_ , err = stmt . Exec ( data . PiwigoId , data . PiwigoParentId , data . Name , data . Key , data . CategoryId )
return err
}
func ( d * LocalDataStore ) insertCategoryData ( tx * sql . Tx , data CategoryData ) error {
stmt , err := tx . Prepare ( "INSERT INTO category (piwigoId, piwigoParentId, name, key) VALUES (?,?,?,?)" )
if err != nil {
return err
}
_ , err = stmt . Exec ( data . PiwigoId , data . PiwigoParentId , data . Name , data . Key )
return err
}