image sync

This commit is contained in:
azivner 2018-01-06 15:56:00 -05:00
parent 91cf090820
commit 784cd62df1
10 changed files with 201 additions and 56 deletions

59
package-lock.json generated
View File

@ -4167,6 +4167,34 @@
"es5-ext": "0.10.35" "es5-ext": "0.10.35"
} }
}, },
"exec-buffer": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz",
"integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==",
"requires": {
"execa": "0.7.0",
"p-finally": "1.0.0",
"pify": "3.0.0",
"rimraf": "2.6.2",
"tempfile": "2.0.0"
},
"dependencies": {
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
},
"tempfile": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz",
"integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=",
"requires": {
"temp-dir": "1.0.0",
"uuid": "3.1.0"
}
}
}
},
"exec-series": { "exec-series": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/exec-series/-/exec-series-1.0.3.tgz", "resolved": "https://registry.npmjs.org/exec-series/-/exec-series-1.0.3.tgz",
@ -4180,7 +4208,6 @@
"version": "0.7.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
"integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
"dev": true,
"requires": { "requires": {
"cross-spawn": "5.1.0", "cross-spawn": "5.1.0",
"get-stream": "3.0.0", "get-stream": "3.0.0",
@ -5461,6 +5488,16 @@
} }
} }
}, },
"imagemin-pngquant": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/imagemin-pngquant/-/imagemin-pngquant-5.0.1.tgz",
"integrity": "sha1-2KMp2lU6+iJrEc5i3r4Lfje0OeY=",
"requires": {
"exec-buffer": "3.2.0",
"is-png": "1.1.0",
"pngquant-bin": "3.1.1"
}
},
"import-lazy": { "import-lazy": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
@ -5826,6 +5863,11 @@
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
"dev": true "dev": true
}, },
"is-png": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-png/-/is-png-1.1.0.tgz",
"integrity": "sha1-1XSxK/J1wDUEVVcLDltXqwYgd84="
},
"is-posix-bracket": { "is-posix-bracket": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
@ -7826,6 +7868,16 @@
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.1.tgz", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.1.tgz",
"integrity": "sha512-ggXCTsqHRIsGMkHlCEhbHhUmNTA2r1lpkE0NL4Q9S8spkXbm4vE9TVmPso2AGYn90Gltdz8W5CyzhcIGg2Gejg==" "integrity": "sha512-ggXCTsqHRIsGMkHlCEhbHhUmNTA2r1lpkE0NL4Q9S8spkXbm4vE9TVmPso2AGYn90Gltdz8W5CyzhcIGg2Gejg=="
}, },
"pngquant-bin": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/pngquant-bin/-/pngquant-bin-3.1.1.tgz",
"integrity": "sha1-0STZinWpSH9AwWQLTb/Lsr1aH9E=",
"requires": {
"bin-build": "2.2.0",
"bin-wrapper": "3.0.2",
"logalot": "2.1.0"
}
},
"postcss": { "postcss": {
"version": "5.2.18", "version": "5.2.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz",
@ -10309,6 +10361,11 @@
} }
} }
}, },
"temp-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
"integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0="
},
"tempfile": { "tempfile": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz", "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz",

View File

@ -35,6 +35,7 @@
"html": "^1.0.0", "html": "^1.0.0",
"imagemin": "^5.3.1", "imagemin": "^5.3.1",
"imagemin-mozjpeg": "^7.0.0", "imagemin-mozjpeg": "^7.0.0",
"imagemin-pngquant": "^5.0.1",
"ini": "^1.3.4", "ini": "^1.3.4",
"jimp": "^0.2.28", "jimp": "^0.2.28",
"multer": "^1.3.0", "multer": "^1.3.0",

View File

@ -5,6 +5,7 @@ const router = express.Router();
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const auth = require('../../services/auth'); const auth = require('../../services/auth');
const utils = require('../../services/utils'); const utils = require('../../services/utils');
const sync_table = require('../../services/sync_table');
const multer = require('multer')(); const multer = require('multer')();
const imagemin = require('imagemin'); const imagemin = require('imagemin');
const imageminMozJpeg = require('imagemin-mozjpeg'); const imageminMozJpeg = require('imagemin-mozjpeg');
@ -24,6 +25,7 @@ router.get('/:imageId/:filename', auth.checkApiAuth, async (req, res, next) => {
}); });
router.post('/upload', auth.checkApiAuth, multer.single('upload'), async (req, res, next) => { router.post('/upload', auth.checkApiAuth, multer.single('upload'), async (req, res, next) => {
const sourceId = req.headers.source_id;
const file = req.file; const file = req.file;
const imageId = utils.newNoteId(); const imageId = utils.newNoteId();
@ -37,6 +39,7 @@ router.post('/upload', auth.checkApiAuth, multer.single('upload'), async (req, r
const resizedImage = await resize(file.buffer); const resizedImage = await resize(file.buffer);
const optimizedImage = await optimize(resizedImage); const optimizedImage = await optimize(resizedImage);
await sql.doInTransaction(async () => {
await sql.insert("images", { await sql.insert("images", {
image_id: imageId, image_id: imageId,
format: file.mimetype.substr(6), format: file.mimetype.substr(6),
@ -48,6 +51,9 @@ router.post('/upload', auth.checkApiAuth, multer.single('upload'), async (req, r
date_created: now date_created: now
}); });
await sync_table.addImageSync(imageId, sourceId);
});
res.send({ res.send({
uploaded: true, uploaded: true,
url: `/api/image/${imageId}/${file.originalname}` url: `/api/image/${imageId}/${file.originalname}`
@ -60,8 +66,6 @@ const MAX_BYTE_SIZE = 200000; // images should have under 100 KBs
async function resize(buffer) { async function resize(buffer) {
const image = await jimp.read(buffer); const image = await jimp.read(buffer);
console.log("Size: ", buffer.byteLength);
if (image.bitmap.width > image.bitmap.height && image.bitmap.width > MAX_SIZE) { if (image.bitmap.width > image.bitmap.height && image.bitmap.width > MAX_SIZE) {
image.resize(MAX_SIZE, jimp.AUTO); image.resize(MAX_SIZE, jimp.AUTO);
} }

View File

@ -122,6 +122,17 @@ router.get('/recent_notes/:noteTreeId', auth.checkApiAuth, async (req, res, next
res.send(await sql.getFirst("SELECT * FROM recent_notes WHERE note_tree_id = ?", [noteTreeId])); res.send(await sql.getFirst("SELECT * FROM recent_notes WHERE note_tree_id = ?", [noteTreeId]));
}); });
router.get('/images/:imageId', auth.checkApiAuth, async (req, res, next) => {
const imageId = req.params.imageId;
const entity = await sql.getFirst("SELECT * FROM images WHERE image_id = ?", [imageId]);
if (entity && entity.data !== null) {
entity.data = entity.data.toString('base64');
}
res.send(entity);
});
router.put('/notes', auth.checkApiAuth, async (req, res, next) => { router.put('/notes', auth.checkApiAuth, async (req, res, next) => {
await syncUpdate.updateNote(req.body.entity, req.body.sourceId); await syncUpdate.updateNote(req.body.entity, req.body.sourceId);
@ -158,4 +169,10 @@ router.put('/recent_notes', auth.checkApiAuth, async (req, res, next) => {
res.send({}); res.send({});
}); });
router.put('/images', auth.checkApiAuth, async (req, res, next) => {
await syncUpdate.updateImage(req.body.entity, req.body.sourceId);
res.send({});
});
module.exports = router; module.exports = router;

View File

@ -4,8 +4,11 @@ const sql = require('./sql');
const log = require('./log'); const log = require('./log');
const messaging = require('./messaging'); const messaging = require('./messaging');
const sync_mutex = require('./sync_mutex'); const sync_mutex = require('./sync_mutex');
const utils = require('./utils');
async function runCheck(query, errorText, errorList) { async function runCheck(query, errorText, errorList) {
utils.assertArguments(query, errorText, errorList);
const result = await sql.getFirstColumn(query); const result = await sql.getFirstColumn(query);
if (result.length > 0) { if (result.length > 0) {
@ -138,7 +141,7 @@ async function runAllChecks() {
WHERE WHERE
(SELECT COUNT(*) FROM notes_tree WHERE notes.note_id = notes_tree.note_id AND notes_tree.is_deleted = 0) = 0 (SELECT COUNT(*) FROM notes_tree WHERE notes.note_id = notes_tree.note_id AND notes_tree.is_deleted = 0) = 0
AND notes.is_deleted = 0 AND notes.is_deleted = 0
`,); `, 'No undeleted note trees for note IDs', errorList);
await runCheck(` await runCheck(`
SELECT SELECT

View File

@ -19,7 +19,8 @@ async function getHashes() {
const optionsQuestionMarks = Array(options.SYNCED_OPTIONS.length).fill('?').join(','); const optionsQuestionMarks = Array(options.SYNCED_OPTIONS.length).fill('?').join(',');
const hashes = { const hashes = {
notes: getHash(await sql.getAll(`SELECT notes: getHash(await sql.getAll(`
SELECT
note_id, note_id,
note_title, note_title,
note_text, note_text,
@ -29,7 +30,8 @@ async function getHashes() {
FROM notes FROM notes
ORDER BY note_id`)), ORDER BY note_id`)),
notes_tree: getHash(await sql.getAll(`SELECT notes_tree: getHash(await sql.getAll(`
SELECT
note_tree_id, note_tree_id,
note_id, note_id,
parent_note_id, parent_note_id,
@ -40,7 +42,8 @@ async function getHashes() {
FROM notes_tree FROM notes_tree
ORDER BY note_tree_id`)), ORDER BY note_tree_id`)),
notes_history: getHash(await sql.getAll(`SELECT notes_history: getHash(await sql.getAll(`
SELECT
note_history_id, note_history_id,
note_id, note_id,
note_title, note_title,
@ -50,7 +53,8 @@ async function getHashes() {
FROM notes_history FROM notes_history
ORDER BY note_history_id`)), ORDER BY note_history_id`)),
recent_notes: getHash(await sql.getAll(`SELECT recent_notes: getHash(await sql.getAll(`
SELECT
note_tree_id, note_tree_id,
note_path, note_path,
date_accessed, date_accessed,
@ -58,12 +62,27 @@ async function getHashes() {
FROM recent_notes FROM recent_notes
ORDER BY note_path`)), ORDER BY note_path`)),
options: getHash(await sql.getAll(`SELECT options: getHash(await sql.getAll(`
SELECT
opt_name, opt_name,
opt_value opt_value
FROM options FROM options
WHERE opt_name IN (${optionsQuestionMarks}) WHERE opt_name IN (${optionsQuestionMarks})
ORDER BY opt_name`, options.SYNCED_OPTIONS)) ORDER BY opt_name`, options.SYNCED_OPTIONS)),
// we don't include image data on purpose because they are quite large, checksum is good enough
// to represent the data anyway
images: getHash(await sql.getAll(`
SELECT
image_id,
format,
checksum,
name,
is_deleted,
date_modified,
date_created
FROM images
ORDER BY image_id`))
}; };
const elapseTimeMs = new Date().getTime() - startTime.getTime(); const elapseTimeMs = new Date().getTime() - startTime.getTime();

View File

@ -143,6 +143,9 @@ async function pullSync(syncContext) {
else if (sync.entity_name === 'recent_notes') { else if (sync.entity_name === 'recent_notes') {
await syncUpdate.updateRecentNotes(resp, syncContext.sourceId); await syncUpdate.updateRecentNotes(resp, syncContext.sourceId);
} }
else if (sync.entity_name === 'images') {
await syncUpdate.updateImage(resp, syncContext.sourceId);
}
else { else {
throw new Error(`Unrecognized entity type ${sync.entity_name} in sync #${sync.id}`); throw new Error(`Unrecognized entity type ${sync.entity_name} in sync #${sync.id}`);
} }
@ -214,6 +217,13 @@ async function pushEntity(sync, syncContext) {
else if (sync.entity_name === 'recent_notes') { else if (sync.entity_name === 'recent_notes') {
entity = await sql.getFirst('SELECT * FROM recent_notes WHERE note_tree_id = ?', [sync.entity_id]); entity = await sql.getFirst('SELECT * FROM recent_notes WHERE note_tree_id = ?', [sync.entity_id]);
} }
else if (sync.entity_name === 'images') {
entity = await sql.getFirst('SELECT * FROM images WHERE image_id = ?', [sync.entity_id]);
if (entity.data !== null) {
entity.data = entity.data.toString('base64');
}
}
else { else {
throw new Error(`Unrecognized entity type ${sync.entity_name} in sync #${sync.id}`); throw new Error(`Unrecognized entity type ${sync.entity_name} in sync #${sync.id}`);
} }

View File

@ -28,6 +28,10 @@ async function addRecentNoteSync(noteTreeId, sourceId) {
await addEntitySync("recent_notes", noteTreeId, sourceId); await addEntitySync("recent_notes", noteTreeId, sourceId);
} }
async function addImageSync(imageId, sourceId) {
await addEntitySync("images", imageId, sourceId);
}
async function addEntitySync(entityName, entityId, sourceId) { async function addEntitySync(entityName, entityId, sourceId) {
await sql.replace("sync", { await sql.replace("sync", {
entity_name: entityName, entity_name: entityName,
@ -78,6 +82,7 @@ async function fillAllSyncRows() {
await fillSyncRows("notes_tree", "note_tree_id"); await fillSyncRows("notes_tree", "note_tree_id");
await fillSyncRows("notes_history", "note_history_id"); await fillSyncRows("notes_history", "note_history_id");
await fillSyncRows("recent_notes", "note_tree_id"); await fillSyncRows("recent_notes", "note_tree_id");
await fillSyncRows("images", "image_id");
} }
module.exports = { module.exports = {
@ -87,6 +92,7 @@ module.exports = {
addNoteHistorySync, addNoteHistorySync,
addOptionsSync, addOptionsSync,
addRecentNoteSync, addRecentNoteSync,
addImageSync,
cleanupSyncRowsForMissingEntities, cleanupSyncRowsForMissingEntities,
fillAllSyncRows fillAllSyncRows
}; };

View File

@ -92,11 +92,30 @@ async function updateRecentNotes(entity, sourceId) {
} }
} }
async function updateImage(entity, sourceId) {
if (entity.data !== null) {
entity.data = Buffer.from(entity.data, 'base64');
}
const origImage = await sql.getFirst("SELECT * FROM images WHERE image_id = ?", [entity.image_id]);
if (!origImage || origImage.date_modified <= entity.date_modified) {
await sql.doInTransaction(async () => {
await sql.replace("images", entity);
await sync_table.addImageSync(entity.image_id, sourceId);
});
log.info("Update/sync image " + entity.image_id);
}
}
module.exports = { module.exports = {
updateNote, updateNote,
updateNoteTree, updateNoteTree,
updateNoteHistory, updateNoteHistory,
updateNoteReordering, updateNoteReordering,
updateOptions, updateOptions,
updateRecentNotes updateRecentNotes,
updateImage
}; };

View File

@ -79,6 +79,14 @@ function sanitizeSql(str) {
return str.replace(/'/g, "\\'"); return str.replace(/'/g, "\\'");
} }
function assertArguments() {
for (const i in arguments) {
if (!arguments[i]) {
throw new Error(`Argument idx#${i} should not be falsy: ${arguments[i]}`);
}
}
}
module.exports = { module.exports = {
randomSecureToken, randomSecureToken,
randomString, randomString,
@ -95,5 +103,6 @@ module.exports = {
hash, hash,
isEmptyOrWhitespace, isEmptyOrWhitespace,
getDateTimeForFile, getDateTimeForFile,
sanitizeSql sanitizeSql,
assertArguments
}; };