diff --git a/public/digikam4.db b/public/digikam4.db deleted file mode 100644 index c0f753f..0000000 Binary files a/public/digikam4.db and /dev/null differ diff --git a/public/test_photos/Australie/IMG_0700.JPG b/public/test_photos/Australie/IMG_0700.JPG new file mode 100644 index 0000000..732094f Binary files /dev/null and b/public/test_photos/Australie/IMG_0700.JPG differ diff --git a/public/test_photos/Australie/IMG_0706.JPG b/public/test_photos/Australie/IMG_0706.JPG new file mode 100644 index 0000000..7dcfe0d Binary files /dev/null and b/public/test_photos/Australie/IMG_0706.JPG differ diff --git a/public/test_photos/Australie/IMG_0711.JPG b/public/test_photos/Australie/IMG_0711.JPG new file mode 100644 index 0000000..76ec0d1 Binary files /dev/null and b/public/test_photos/Australie/IMG_0711.JPG differ diff --git a/public/test_photos_db/digikam4.db b/public/test_photos_db/digikam4.db new file mode 100644 index 0000000..3114481 Binary files /dev/null and b/public/test_photos_db/digikam4.db differ diff --git a/public/test_photos_db/thumbnails-digikam.db b/public/test_photos_db/thumbnails-digikam.db new file mode 100644 index 0000000..8cb280a Binary files /dev/null and b/public/test_photos_db/thumbnails-digikam.db differ diff --git a/src/database.js b/src/database.js index da02f41..aa2ddc1 100644 --- a/src/database.js +++ b/src/database.js @@ -3,9 +3,24 @@ import React from 'react'; export const DBTypeEnum = { ALASQL_SQLITE: 1, ALASQL_INDEXEDDB: 2, - ALASQL_NATIVE: 3 + ALASQL_NATIVE: 3, + SQLJS_SQLITE: 4 }; +export const DBSourceEnum = { + ATTACHFILE: 1, + CREATE: 2 +} + +export function alasql_async_queries(alasql_object, queries) { + var p = Promise.resolve(null); + for (let i = 0; i < queries.length; i++) { + p = p.then(() => { return alasql_object.promise(queries[i]); }); + } + + return p; +} + export class DB { state = { db_type: false, @@ -27,12 +42,7 @@ export class DB { this.state.db_type === DBTypeEnum.ALASQL_INDEXEDDB || this.state.db_type === DBTypeEnum.ALASQL_NATIVE) { var self = this; - var p = Promise.resolve(null); - for (let i = 0; i < s.length; i++) { - p = p.then(() => { return self.state.db_object.promise(s[i]); }); - } - - return p; + return alasql_async_queries(self.state.db_object, s); } throw new Error("Unsupported database type for async operations: " + this.state.db_type); } @@ -47,6 +57,12 @@ export class DB { } return this.state.db_object(s[s.length - 1]); } + if (this.state.db_type === DBTypeEnum.SQLJS_SQLITE) { + for (var i = 0; i < (s.length - 1); i++) { + this.state.db_object.exec(s[i]); + } + return this.state.db_object.exec(s[s.length - 1]); + } throw new Error("Unsupported database type for sync operations: " + this.state.db_type); } @@ -91,6 +107,67 @@ export class DB { resolve(); }); }); + } else if (this.state.db_type === DBTypeEnum.SQLJS_SQLITE && db_type === DBTypeEnum.ALASQL_NATIVE) { + return new Promise(function (resolve, reject) { + var tables = self.queries_sync(["SELECT * FROM sqlite_master WHERE type='table';"])[0].values; + console.log(tables); + + // Get all table definition lines. + var table_definitions = []; + var table_names = []; + tables.forEach(elem => { + table_definitions.push(elem[4]); + table_names.push(elem[1]); + }); + + // We want to create all these tables in the AlaSQL database as well. However, that will require + // us to escape the table names because AlaSQL may break on colums with names equal to keywords + // (see https://github.com/agershun/alasql/issues/1155). + // Here we take a dirty shortcut: explicitly escape all occurrences of "value", "query" and "matrix". + var new_table_definitions = []; + table_definitions.forEach(elem => { + var newelem = elem + .replace(/value/g, "`value`") + .replace(/query/g, "`query`") + .replace(/matrix/g, "`matrix`"); + new_table_definitions.push(newelem); + }); + console.log(new_table_definitions); + + var asql = require('alasql'); + var queries = []; + queries.push("CREATE DATABASE IF NOT EXISTS " + db_name + ";"); + queries.push("USE " + db_name + ";"); + new_table_definitions.forEach(elem => { + queries.push(elem); // Creates the table + }); + alasql_async_queries(asql, queries) + .then(() => { + // Fill the tables by passing through a JS dictionary object. + table_names.forEach(elem => { + var res = self.queries_sync(["SELECT * FROM " + elem + ";"])[0]; + if (res) { + var structured = []; + res.values.forEach(vals => { + var row = {}; + var i = 0; + res.columns.forEach(column => { + row[column] = vals[i]; + i++; + }) + structured.push(row); + }); + asql("SELECT * INTO " + elem + " FROM ?", [structured]); + } + }); + }) + .then(() => { + self.state.db_type = db_type; + self.state.db_name = db_name; + self.state.db_object = asql; + resolve(); + }); + }); } throw new Error("Unsupported copy creation from db_type " + this.state.db_type + " to " + db_type); } @@ -113,6 +190,28 @@ function fetch_db_from_sqlite(filename, db_name) { }); } +function fetch_sqljs_db_from_sqlite(filename, db_name) { + return new Promise(function (resolve, reject) { + var initSqlJs = require('sql.js'); + initSqlJs({ locateFile: filename => `/sql.js/dist/${filename}` }) + .then(SQL => { + fetch(filename) + .then(res => { + if (!res.ok) { + throw new Error(res.status); + } + return res.arrayBuffer(); + }) + .then(data => { + var array = new Uint8Array(data); + var sqljs_db = new SQL.Database(array); + var imported_db = new DB(sqljs_db, db_name, DBTypeEnum.SQLJS_SQLITE); + resolve(imported_db); + }) + }); + }); +} + function create_indexed_db(name) { return new Promise(function (resolve, reject) { var asql = require('alasql'); @@ -141,11 +240,6 @@ function create_native_db(name) { }); } -export const DBSourceEnum = { - ATTACHFILE: 1, - CREATE: 2 -} - export class ProvideDB extends React.Component { state = { loading: true, @@ -180,6 +274,15 @@ export class ProvideDB extends React.Component { .then(() => { this.setState({ loading: false, done: true, db: db }) }); } else { this.setState({ loading: false, done: true, db: db }) }; }); + } else if (this.props.db_source_type === DBTypeEnum.SQLJS_SQLITE && this.props.db_source === DBSourceEnum.ATTACHFILE) { + fetch_sqljs_db_from_sqlite(this.props.db_file, this.props.db_source_name) + .then(db => { + if (this.props.db_target_type !== this.props.db_source_type) { + db.migrate_async(this.props.db_target_type, this.props.db_target_name) + .then(() => { this.setState({ loading: false, done: true, db: db }) }); + } else { this.setState({ loading: false, done: true, db: db }) }; + }) + .catch(error => this.setState({ loading: false, done: false, error })); } else { throw new Error("Unsupported ProvideDB configuration: from source " + this.props.db_source + " with source type " + this.props.db_source_type + " to target type " + this.props.db_target_type + "."); diff --git a/src/index.js b/src/index.js index 72b4b63..51f1c91 100644 --- a/src/index.js +++ b/src/index.js @@ -6,21 +6,21 @@ import { Photo, PhotoView } from './media.js'; import './index.css'; -const URLLoading = ({url}) =>

Loading: {url}

; -const URLError = ({error}) =>

Failed to load URL resource: {error.message}

; -const URLFinished = ({url, data}) =>

{url} was loaded: {data.byteLength} bytes received.

; +const URLLoading = ({ url }) =>

Loading: {url}

; +const URLError = ({ error }) =>

Failed to load URL resource: {error.message}

; +const URLFinished = ({ url, data }) =>

{url} was loaded: {data.byteLength} bytes received.

; -const DBLoading = ({sqlite_file}) =>

Loading: {sqlite_file}

; -const DBError = ({error}) =>

Failed to load database: {error.message}

; -const DBFinished = ({sqlite_file, db}) =>

{sqlite_file} was loaded. Name: { db.state.db_name }.

; +const DBLoading = ({ sqlite_file }) =>

Loading: {sqlite_file}

; +const DBError = ({ error }) =>

Failed to load database: {error.message}

; +const DBFinished = ({ sqlite_file, db }) =>

{sqlite_file} was loaded. Name: {db.state.db_name}.

; -const TestFetch = ({url}) => ( +const TestFetch = ({ url }) => ( {({ loading, error, done, data }) => ( <> - { loading && } - { error && } - { done && } + {loading && } + {error && } + {done && } )} @@ -33,44 +33,46 @@ export class PhotoFromDB extends React.Component { } componentDidMount() { - this.props.database.queries_async(["SELECT * FROM Images;"]) - .then(res => { - var photo = new Photo(res[0]); - this.setState({ done: true, photo: photo }); - }); + this.props.database.queries_async(["SELECT Images.name, Albums.relativePath FROM Images INNER JOIN Albums ON Images.album=Albums.id;"]) + .then(res => { + var photo = new Photo; + var imagepath = process.env.PUBLIC_URL + "/test_photos" + res[0]["relativePath"] + "/" + res[0]["name"]; + photo.state.path = imagepath; + this.setState({ done: true, photo: photo }); + }); } render() { return ( <> - { this.state.done && } + {this.state.done && } ); } } -const TestDBFetch = ({sqlite_file}) => ( - +const TestDBFetch = ({ sqlite_file }) => ( + {({ loading, error, done, db }) => ( <> - { loading && } - { error && } - { done && } - { done && } - { done && } + {loading && } + {error && } + {done && } + {done && } + {done && } )} ) -const TestDBPlayground = ({db_name}) => ( +const TestDBPlayground = ({ db_name }) => ( + db_source_name={db_name} db_target_name={db_name} db_source={DBSourceEnum.CREATE}> {({ loading, error, done, db }) => ( <> - { error && } - { done && } + {error && } + {done && } )} @@ -78,12 +80,12 @@ const TestDBPlayground = ({db_name}) => ( ReactDOM.render( <> -

Test file fetching:

- -

IndexedDB playground:

- -

Test DB fetching:

- +

Test file fetching:

+ +

IndexedDB playground:

+ +

Test DB fetching:

+ , document.getElementById('root') ); diff --git a/src/media.js b/src/media.js index 678ce28..90406d5 100644 --- a/src/media.js +++ b/src/media.js @@ -5,11 +5,7 @@ export class Media {} export class Photo extends Media { state = { path: false, - } - - constructor(entry) { - super(); - this.state.path = entry.ImagePath; + thumbnailpath: false } }