diff --git a/src/getAlbums.js b/src/getAlbums.js index 8239400..70042c6 100644 --- a/src/getAlbums.js +++ b/src/getAlbums.js @@ -1,12 +1,11 @@ import fetch from "node-fetch"; import readline from "readline"; -import { exit } from "process"; import { writeFileSync } from "fs"; import { secretsFile, secrets, getToken } from "./common.js"; if (!secrets.refreshToken) { console.error("No refresh token found. Please run 'npm run token' first."); - exit(); + process.exit(1); } const rl = readline.createInterface({ diff --git a/src/sync.js b/src/sync.js index f38b241..0899377 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1,26 +1,80 @@ import fetch from "node-fetch"; -import { createWriteStream } from "fs"; +import fs from "fs"; +import path from "path"; import { secrets, getToken } from "./common.js"; if (!secrets.refreshToken) { console.error("No refresh token found. Please run 'npm run token' first."); + process.exit(1); } else if (!secrets.album || !secrets.album.id || !secrets.album.count || !secrets.album.name) { console.error("No valid album found in secrets. Please run 'npm run albums' first."); + process.exit(1); } -// TODO: Check for argument for folder -// TODO: Create folder and check permissions -const path = "./album"; +const args = process.argv.slice(2); +const argsConfig = { + path: args.length > 0 ? args[0] : "./album", + downloadVideo: false, //args.includes("--video") not implemented, + downloadRaw: args.includes("--raw"), +} +argsConfig.path = path.resolve(argsConfig.path); + +if (!fs.existsSync(argsConfig.path)) { + try { + fs.mkdirSync(argsConfig.path); + } catch (e) { + console.error("Could not create album directory: ", e); + process.exit(1); + } +} else { + try { + fs.accessSync(argsConfig.path, fs.constants.W_OK); + } catch { + console.error("Album directory is not writable: ", argsConfig.path); + process.exit(1); + } +} let tokenDeath = 0; let token = ""; +const filenameCounter = {}; + +function shouldDownload(mimeType) { + switch (true) { + case mimeType.startsWith("video/"): return argsConfig.downloadVideo; + case mimeType === "image/raw": return argsConfig.downloadRaw; + case mimeType === "image/jpeg": return true; + case mimeType === "image/png": return true; + default: return false; + } +} async function syncImage(imageObject) { - // TODO: Test if file exists - if (imageObject.mimeType !== "image/jpeg" && imageObject.mimeType !== "image/png") return; + if (!shouldDownload(imageObject.mimeType)) return; + + /* + Problem: Google Photo Albums can have multiple images with the same filename. + We want to preserve the original filename, but we also want to make sure that + each image is only downloaded when necessary. + + Solution: We use a counter to keep track of how many times we've downloaded + the same filename. This is only dependend on the sync but not accross syncs. + This want work when the creation time of the images change because Google sorts + images by that. And we can't sort them ourself because "orderBy" needs a + "dateFilter" and that is exclusive with the "albumId"... + */ + + const filename = filenameCounter[imageObject.filename] ? `${imageObject.filename.substring(0, imageObject.filename.lastIndexOf("."))}_${filenameCounter[imageObject.filename] + 1}.${imageObject.filename.substring(imageObject.filename.lastIndexOf(".") + 1)}` : imageObject.filename; + filenameCounter[imageObject.filename] = filenameCounter[imageObject.filename] ? filenameCounter[imageObject.filename] + 1 : 1; + + const filepath = path.resolve(argsConfig.path, filename); + if (fs.existsSync(filepath)) { + console.log(`Skipping ${filename} (already exists)`); + return + }; const response = await fetch(`${imageObject.baseUrl}=w${imageObject.mediaMetadata.width}-h${imageObject.mediaMetadata.height}`); - const fileStream = createWriteStream(`${path}/${imageObject.filename}`); + const fileStream = fs.createWriteStream(filepath); await new Promise((resolve, reject) => { response.body.pipe(fileStream); response.body.on("error", reject);