|
[{"id":"watch","type":"watch","z":"tab_downloads","name":"Watch Downloads","files":"D:\\\\Downloads","recursive":false,"x":150,"y":140,"wires":[["6b9199e0f36c15de"]]},{"id":"moveImage","type":"function","z":"tab_downloads","name":"Move files","func":"/**\n * File file types\n */\n\nconst file = msg.fileName // normalised with path\nconst ext = path.extname(file).toLowerCase()\nconst dir = path.dirname(file)\nlet targetDir = null\n\n// Extend this object with other file types as needed\nconst types = {\n images: [\n '.jpg','.jpeg','.png','.gif','.bmp','.webp','.tif','.tiff','.svg','.heic','.avif'\n ]\n}\n\nfor (const type in types) {\n // Add additional cases to match the file types to process\n switch (type) {\n case \"images\": {\n if (types.images.includes(ext)) {\n targetDir = path.join(dir,'images')\n } \n break\n }\n }\n\n}\n\nif (targetDir === null) {\n node.warn(`File extension ${ext} not recognized, no target directory set.`)\n return null\n}\n\n// Make sure target folder exists\nif (!fs.existsSync(targetDir)) {\n fs.mkdirSync(targetDir,{recursive:true})\n}\n\nconst dest = path.join(targetDir,path.basename(file))\ntry {\n fs.renameSync(file,dest)\n node.status({ fill: 'green', shape: 'dot', text: `MOVED: ${path.basename(file)}` })\n node.warn(`Moved file ${file} to ${dest}`)\n} catch(err){\n node.status({ fill: 'red', shape: 'dot', text: `FAILED: ${path.basename(file)}` })\n node.error(`Move failed. ${file} to ${dest}. ${err.message}`, msg)\n return\n}\n","outputs":0,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"fs","module":"fs"},{"var":"path","module":"path"}],"x":580,"y":140,"wires":[]},{"id":"injectHourly","type":"inject","z":"tab_downloads","name":"Run","props":[],"repeat":"","crontab":"","once":false,"onceDelay":"5","topic":"","x":130,"y":40,"wires":[["archiveOld"]]},{"id":"archiveOld","type":"function","z":"tab_downloads","name":"Archive files >6 months","func":"/**\n * Archive downloads >6m old\n * by moving to an \"archive\" sub-folder\n */\n\nconst downloadDir ='D:\\\\Downloads'\nconst archiveDir=path.join(downloadDir,'archive')\nlet out = {\n archived: [],\n failed: [],\n}\n\nif(!fs.existsSync(archiveDir)) {\n fs.mkdirSync(archiveDir,{recursive:true})\n}\n\nconst cutoff = Date.now() - 15811200000 // = 183*24*60*60*1000, 6 months\nconst dir = fs.readdirSync(downloadDir, { withFileTypes: true, recursive: false })\n\nfor(const item of dir){\n // Ignore sub-folders, links, etc\n if (!item.isFile()) continue\n\n const full = path.join(downloadDir, item.name)\n\n let stat\n try{\n stat = fs.statSync(full)\n } catch(e){\n node.warn(e.message)\n out.failed.push(item.name)\n continue // Couldn't stat the file so ignore\n }\n\n if(stat.mtimeMs < cutoff){\n try{\n fs.renameSync(full, path.join(archiveDir, item.name))\n out.archived.push(item.name)\n } catch(err){\n node.warn(err.message)\n out.failed.push(item.name)\n }\n }\n}\n\nnode.status({ fill: 'green', shape: 'dot', text: `ARCHIVED: ${out.archived.length}, FAILED: ${out.failed.length}` })\n\nreturn { payload: out}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"path","module":"node:path"},{"var":"fs","module":"node:fs"}],"x":330,"y":40,"wires":[["e6a2ccb53829bbe3"]],"info":"Archive downloads >6m old\r\nby moving to an \"archive\" sub-folder"},{"id":"6b9199e0f36c15de","type":"function","z":"tab_downloads","name":"Stable File Size Watcher","func":"/** \n * Taking input from a watch node, ignore changes for all folders,\n * files not in the watch root and all files ending in .crdownload or .tmp.\n * For files being processed, check file size, wait for x seconds, check again.\n * If file size is different, repeat. Only if file size is stable, output.\n */\n\n// Function to check if the file size is stable\nasync function isFileSizeStable(fname, delay) {\n let previousSize = -1\n while (true) {\n const stats = await fsPromises.stat(fname)\n const currentSize = stats.size\n if (currentSize === previousSize) {\n return true\n }\n previousSize = currentSize\n await new Promise(resolve => setTimeout(resolve, delay))\n }\n}\n\nmsg.rootDir = path.normalize(`${msg.topic}\\\\`)\nconst fullName = msg.fileName = path.normalize(msg.filename)\nconst fName = fullName.replace(msg.rootDir, '')\n\n// Check if the file is in the root directory\n// msg.dirName = path.dirname(msg.filename)\n// msg.path = fileName.split(/\\/|\\\\/)\n// msg.pathLength = fileName.split(/\\/|\\\\/).length\nconst isInRoot = msg.isInRoot = fName.split(/\\/|\\\\/).length === 1\n\n// Ignore changes for all folders and specific file extensions\nif (\n !isInRoot ||\n msg.event === 'remove' ||\n msg?.size === 0 ||\n msg.type === 'directory' ||\n fName.endsWith('.crdownload') ||\n fName.endsWith('.tmp')\n ) {\n return [null, msg]\n}\n\n// Wait for the file size to stabilize\nconst delay = 5000 // x seconds\ntry {\n await isFileSizeStable(fullName, delay)\n} catch (e) {\n node.error(`Error checking file size stability: ${e.message}`, msg)\n return [null, msg]\n}\n\n// Output the message once the file size is stable\nreturn [msg, null]","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"fsPromises","module":"node:fs/promises"},{"var":"path","module":"node:path"}],"x":370,"y":140,"wires":[["moveImage"],[]],"inputLabels":["Watch node input"],"outputLabels":["Files to process","Errors and ignores"]},{"id":"e6a2ccb53829bbe3","type":"debug","z":"tab_downloads","name":"debug 24","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"","statusType":"counter","x":495,"y":40,"wires":[],"l":false}] |