├── .gitignore ├── LICENSE ├── README.md ├── build-stats.groovy ├── build.xml ├── client-extjs ├── .gitignore ├── Readme.md ├── app.js ├── app.json ├── app │ ├── Application.js │ ├── Node.js │ ├── Readme.md │ ├── model │ │ ├── Base.js │ │ └── Readme.md │ ├── store │ │ ├── ArchiveOptions.js │ │ ├── ConflictActions.js │ │ ├── EpisodeOrder.js │ │ ├── FileAgeFilters.js │ │ ├── FileSizeFilters.js │ │ ├── Languages.js │ │ ├── LogLevels.js │ │ ├── MediaLabels.js │ │ ├── MovieDatabases.js │ │ ├── Readme.md │ │ ├── RenameActions.js │ │ ├── ScriptSources.js │ │ ├── SeriesDatabases.js │ │ └── VideoLengthFilters.js │ └── view │ │ ├── main │ │ ├── Main.js │ │ ├── MainController.js │ │ └── MainModel.js │ │ ├── navigation │ │ ├── Navigation.js │ │ ├── NavigationController.js │ │ └── NavigationModel.js │ │ ├── task │ │ ├── Task.js │ │ ├── TaskController.js │ │ └── TaskModel.js │ │ ├── tasklogcat │ │ ├── TaskLogCat.js │ │ ├── TaskLogCatController.js │ │ └── TaskLogCatModel.js │ │ └── taskmanager │ │ ├── TaskManager.js │ │ ├── TaskManagerController.js │ │ └── TaskManagerModel.js ├── build.xml ├── index.html ├── resources │ ├── images │ │ ├── clear.png │ │ ├── configure.png │ │ ├── environment.png │ │ ├── error.png │ │ ├── favicon.png │ │ ├── generic.png │ │ ├── help.png │ │ ├── info.png │ │ ├── license.png │ │ ├── mediainfo.png │ │ ├── ok.png │ │ ├── preferences.png │ │ ├── purchase.png │ │ ├── revert.png │ │ ├── run.png │ │ ├── schedule.png │ │ ├── select.png │ │ ├── settings.png │ │ ├── stop.png │ │ └── user.png │ └── main.css └── workspace.json ├── ivy.xml ├── makefile ├── package.properties ├── package ├── generic │ ├── start │ └── task ├── qnap │ ├── .gitignore │ ├── icons │ │ ├── filebot-node.png │ │ ├── filebot-node_100.png │ │ ├── filebot-node_80.png │ │ └── filebot-node_gray.png │ ├── package_routines │ ├── qpkg.cfg │ └── shared │ │ ├── filebot-node-service.sh │ │ ├── start │ │ └── task ├── synology-dsm7 │ ├── conf │ │ ├── privilege │ │ └── resource │ ├── scripts │ │ ├── postinst │ │ ├── postuninst │ │ ├── postupgrade │ │ ├── preinst │ │ ├── preuninst │ │ ├── preupgrade │ │ └── start-stop-status │ └── target │ │ ├── bin │ │ ├── filebot-node-start │ │ └── filebot-node-task │ │ └── client │ │ ├── FileBot.NodeClient.js │ │ ├── auth │ │ ├── config │ │ ├── environment.cgi │ │ ├── execute.cgi │ │ ├── folders.cgi │ │ ├── help │ │ └── enu │ │ │ └── filebot_node_index.html │ │ ├── helptoc.conf │ │ ├── images │ │ ├── filebot_node_16.png │ │ ├── filebot_node_24.png │ │ ├── filebot_node_256.png │ │ ├── filebot_node_32.png │ │ ├── filebot_node_48.png │ │ ├── filebot_node_64.png │ │ └── filebot_node_72.png │ │ ├── kill.cgi │ │ ├── output.cgi │ │ ├── proxy_pass.cgi │ │ ├── schedule.cgi │ │ ├── state.cgi │ │ ├── task.cgi │ │ ├── tasks.cgi │ │ ├── test.cgi │ │ ├── texts │ │ └── enu │ │ │ └── strings │ │ └── version.cgi └── synology │ ├── conf │ ├── privilege │ └── resource │ ├── scripts │ ├── postinst │ ├── postuninst │ ├── postupgrade │ ├── preinst │ ├── preuninst │ ├── preupgrade │ └── start-stop-status │ └── target │ ├── bin │ ├── filebot-node-start │ └── filebot-node-task │ └── client │ ├── auth │ ├── config │ ├── environment.cgi │ ├── execute.cgi │ ├── folders.cgi │ ├── images │ ├── filebot_node_16.png │ ├── filebot_node_24.png │ ├── filebot_node_256.png │ ├── filebot_node_32.png │ ├── filebot_node_48.png │ ├── filebot_node_64.png │ └── filebot_node_72.png │ ├── kill.cgi │ ├── output.cgi │ ├── proxy_pass.cgi │ ├── schedule.cgi │ ├── state.cgi │ ├── task.cgi │ ├── tasks.cgi │ ├── test.cgi │ └── version.cgi ├── server-nodejs ├── README.md ├── app.js ├── data │ └── .gitignore ├── package-lock.json ├── package.json ├── start.sh └── task.sh ├── syno-dsm6.json └── syno.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### OSX ### 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | 31 | ### Windows ### 32 | # Windows image file caches 33 | Thumbs.db 34 | ehthumbs.db 35 | 36 | # Folder config file 37 | Desktop.ini 38 | 39 | # Recycle Bin used on file shares 40 | $RECYCLE.BIN/ 41 | 42 | # Windows Installer files 43 | *.cab 44 | *.msi 45 | *.msm 46 | *.msp 47 | 48 | # Windows shortcuts 49 | *.lnk 50 | 51 | 52 | ### Linux ### 53 | *~ 54 | 55 | # KDE directory preferences 56 | .directory 57 | 58 | # Linux trash folder which might appear on any partition or disk 59 | .Trash-* 60 | 61 | 62 | ### ExtJs ### 63 | .architect 64 | bootstrap.json 65 | build/ 66 | ext/ 67 | 68 | 69 | ### Node ### 70 | # Logs 71 | logs 72 | *.log 73 | 74 | # Runtime data 75 | pids 76 | *.pid 77 | *.seed 78 | 79 | # Directory for instrumented libs generated by jscoverage/JSCover 80 | lib-cov 81 | 82 | # Coverage directory used by tools like istanbul 83 | coverage 84 | 85 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 86 | .grunt 87 | 88 | # node-waf configuration 89 | .lock-wscript 90 | 91 | # Compiled binary addons (http://nodejs.org/api/addons.html) 92 | build/Release 93 | 94 | # Dependency directory 95 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 96 | node_modules 97 | 98 | # More Excludes 99 | *.sublime-project 100 | *.sublime-workspace 101 | *.properties 102 | *.key 103 | .idea/ 104 | .sencha/ 105 | overrides/ 106 | packages/ 107 | log/ 108 | dist/ 109 | build/ 110 | release/ 111 | notes/ 112 | lib/ 113 | 114 | *.variables 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FileBot Node 2 | [![Github Releases](https://img.shields.io/github/downloads/filebot/filebot-node/total.svg)](https://github.com/filebot/filebot-node/releases) 3 | [![GitHub release](https://img.shields.io/github/release/filebot/filebot-node.svg)](https://www.filebot.net/linux/syno.html) 4 | 5 | ## Introduction 6 | FileBot Node is a server-side Node.js application that allows you to make `filebot` calls via a straight-forward ExtJS web application. 7 | 8 | ![FileBot Node](http://i.imgur.com/HkQkh2h.png) 9 | 10 | ## User Manual 11 | FileBot Node is available as Synology package via the [FileBot Package Source](https://www.filebot.net/forums/viewtopic.php?t=1802) and as generic Linux package for all other devices. Please refer to the [How To](https://www.filebot.net/forums/viewtopic.php?t=2733) manual if you need help getting started. 12 | 13 | ## Installation 14 | Add the following __Package Source__ to Synology DSM ► Package Center ► Settings ► Package Sources: 15 | 16 | https://get.filebot.net/syno/ 17 | 18 | FileBot Node will work on any Linux device that can run `filebot` and `node` but some tinkering may be required. You will need to [download](https://github.com/filebot/filebot-node/releases) and unpack the `tar` package and start the node service yourself. See `start.sh` for details. 19 | 20 | A Docker image is available [here](https://hub.docker.com/r/rednoah/filebot/). 21 | 22 | ## Notes 23 | * Node.js is required for the server-side process 24 | * System authentication is implemented for Synology DSM and QNAP NAS 25 | 26 | ## Discussion 27 | Please visit the [FileBot Forums](https://www.filebot.net/forums/viewforum.php?f=13) if you need help with setting things up. 28 | -------------------------------------------------------------------------------- /build-stats.groovy: -------------------------------------------------------------------------------- 1 | import groovy.json.* 2 | 3 | // get download count from GitHub API 4 | def json = new JsonSlurper().parse(new URL('https://api.github.com/repos/filebot/filebot-node/releases')) 5 | 6 | def stats = [ 7 | download_count: json.assets.download_count.flatten().sum() ?: 0 8 | ] 9 | 10 | println stats 11 | 12 | // export stats to ant build 13 | stats.each{ p, v -> project.setProperty(p, v as String) } 14 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /client-extjs/.gitignore: -------------------------------------------------------------------------------- 1 | .architect 2 | .sencha 3 | bootstrap.js 4 | bootstrap.json 5 | bootstrap.jsonp 6 | bootstrap.css 7 | build/ 8 | ext/ 9 | -------------------------------------------------------------------------------- /client-extjs/Readme.md: -------------------------------------------------------------------------------- 1 | # FileBot 2 | 3 | This folder is primarily a container for the top-level pieces of the application. 4 | While you can remove some files and folders that this application does not use, 5 | be sure to read below before deciding what can be deleted and what needs to be 6 | kept in source control. 7 | 8 | The following files are all needed to build and load the application. 9 | 10 | - `"app.json"` - The application descriptor which controls how the application is 11 | built and loaded. 12 | - `"app.js"` - The file that launches the application. This is primarily used to 13 | launch an instance of the `MyApp.Application` class. 14 | - `"index.html"` - The default web page for this application. This can be customized 15 | in `"app.json"`. 16 | - `"build.xml"` - The entry point for Sencha Cmd to access the generated build 17 | script. This file is a place where you can hook into these processes and tune 18 | them. See the comments in that file for more information. 19 | - `".sencha"` - This (typically hidden) folder contains the generated build scripts 20 | and configuration files for the application. This folder is required in order to 21 | build the application but its content should not need to be edited in most cases. 22 | The content of this folder is updated by "sencha app upgrade". 23 | 24 | These files can be ignored from source control as they are regenerated by the build 25 | process. 26 | 27 | - `"build"` - This folder contain the output of the build. The generated CSS file, 28 | consolidated resources and concatenated JavaScript file are all stored in this 29 | folder. 30 | - `"bootstrap.*"` - These files are generated by the build and watch commands to 31 | enable the application to load in "development mode". 32 | 33 | # Other Folders 34 | 35 | ## FileBot/app 36 | 37 | This folder contains the JavaScript files for the application. 38 | 39 | ## FileBot/resources 40 | 41 | This folder contains static resources (typically an `"images"` folder as well). 42 | 43 | ## FileBot/overrides 44 | 45 | This folder contains override classes. All overrides in this folder will be 46 | automatically included in application builds if the target class of the override 47 | is loaded. 48 | 49 | ## FileBot/sass 50 | 51 | This folder contains the styling for the application's views. See FileBot/sass/Readme.md 52 | for details. 53 | -------------------------------------------------------------------------------- /client-extjs/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated and updated by Sencha Cmd. You can edit this file as 3 | * needed for your application, but these edits will have to be merged by 4 | * Sencha Cmd when upgrading. 5 | */ 6 | Ext.application({ 7 | name: 'FileBot', 8 | 9 | extend: 'FileBot.Application', 10 | 11 | autoCreateViewport: 'FileBot.view.main.Main' 12 | 13 | //------------------------------------------------------------------------- 14 | // Most customizations should be made to FileBot.Application. If you need to 15 | // customize this file, doing so below this section reduces the likelihood 16 | // of merge conflicts when upgrading to new versions of Sencha Cmd. 17 | //------------------------------------------------------------------------- 18 | }); 19 | -------------------------------------------------------------------------------- /client-extjs/app.json: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * The toolkit to use. Select either "classic" or "modern". 4 | */ 5 | "toolkit": "classic", 6 | 7 | /** 8 | * The application's namespace. 9 | */ 10 | "name": "FileBot", 11 | 12 | /** 13 | * The version of the application. 14 | */ 15 | "version": "0.0.0", 16 | 17 | /** 18 | * The ports that the client application use for talking to the node server 19 | */ 20 | "server": { 21 | "endpoint": "", 22 | "refresh": 1500, 23 | "form": { 24 | "input": "", 25 | "output": "" 26 | }, 27 | "url": { 28 | "help": "https://www.filebot.net/node.html", 29 | "license_purchase": "https://www.filebot.net/purchase.html#filebot-node", 30 | "osdb_register": "https://www.opensubtitles.com/en/users/sign_in" 31 | } 32 | }, 33 | 34 | /** 35 | * The relative path to the appliaction's markup file (html, jsp, asp, etc.) 36 | */ 37 | "indexHtmlPath": "index.html", 38 | 39 | /** 40 | * Comma-separated string with the paths of directories or files to search. Any classes 41 | * declared in these locations will be available in your class "requires" or in calls 42 | * to "Ext.require". The "app.dir" variable below is expanded to the path where the 43 | * application resides (the same folder in which this file is located). 44 | */ 45 | "classpath": "${app.dir}/app", 46 | 47 | "overrides": "${app.dir}/overrides", 48 | 49 | /** 50 | * The Sencha Framework for this application: "ext" or "touch". 51 | */ 52 | "framework": "ext", 53 | 54 | /** 55 | * The name of the theme for this application. 56 | */ 57 | "theme": "ext-theme-crisp", 58 | 59 | /** 60 | * The list of required packages (with optional versions; default is "latest"). 61 | * 62 | * For example, 63 | * 64 | * "requires": [ 65 | * "sencha-charts" 66 | * ] 67 | */ 68 | "requires": [ 69 | ], 70 | 71 | /** 72 | * List of all JavaScript assets in the right execution order. 73 | * 74 | * Each item is an object with the following format: 75 | * 76 | * { 77 | * // Path to file. If the file is local this must be a relative path from 78 | * // this app.json file. 79 | * // 80 | * "path": "path/to/script.js", // REQUIRED 81 | * 82 | * // Set to true on one file to indicate that it should become the container 83 | * // for the concatenated classes. 84 | * // 85 | * "bundle": false, // OPTIONAL 86 | * 87 | * // Set to true to include this file in the concatenated classes. 88 | * // 89 | * "includeInBundle": false, // OPTIONAL 90 | * 91 | * // Specify as true if this file is remote and should not be copied into the 92 | * // build folder. Defaults to false for a local file which will be copied. 93 | * // 94 | * "remote": false, // OPTIONAL 95 | * 96 | * // If not specified, this file will only be loaded once, and cached inside 97 | * // localStorage until this value is changed. You can specify: 98 | * // 99 | * // - "delta" to enable over-the-air delta update for this file 100 | * // - "full" means full update will be made when this file changes 101 | * // 102 | * "update": "", // OPTIONAL 103 | * 104 | * // A value of true indicates that is a development mode only dependency. 105 | * // These files will not be copied into the build directory or referenced 106 | * // in the generate app.json manifest for the micro loader. 107 | * // 108 | * "bootstrap": false // OPTIONAL 109 | * } 110 | * 111 | */ 112 | "js": [ 113 | { 114 | "path": "${framework.dir}/build/ext-all-rtl-debug.js" 115 | }, 116 | { 117 | "path": "app.js", 118 | "bundle": true 119 | } 120 | ], 121 | 122 | /** 123 | * List of all CSS assets in the right inclusion order. 124 | * 125 | * Each item is an object with the following format: 126 | * 127 | * { 128 | * // Path to file. If the file is local this must be a relative path from 129 | * // this app.json file. 130 | * // 131 | * "path": "path/to/stylesheet.css", // REQUIRED 132 | * 133 | * // Specify as true if this file is remote and should not be copied into the 134 | * // build folder. Defaults to false for a local file which will be copied. 135 | * // 136 | * "remote": false, // OPTIONAL 137 | * 138 | * // If not specified, this file will only be loaded once, and cached inside 139 | * // localStorage until this value is changed. You can specify: 140 | * // 141 | * // - "delta" to enable over-the-air delta update for this file 142 | * // - "full" means full update will be made when this file changes 143 | * // 144 | * "update": "" // OPTIONAL 145 | * } 146 | */ 147 | "css": [ 148 | { 149 | "path": "bootstrap.css", 150 | "bootstrap": true 151 | }, { 152 | "path": "resources/main.css" 153 | } 154 | ], 155 | 156 | /** 157 | * This option is used to configure the dynamic loader. At present these options 158 | * are supported. 159 | */ 160 | "loader": { 161 | // This property controls how the loader manages caching for requests: 162 | // 163 | // - true: allows requests to receive cached responses 164 | // - false: disable cached responses by adding a random "cache buster" 165 | // - other: a string (such as the build.timestamp shown here) to allow 166 | // requests to be cached for this build. 167 | // 168 | "cache": "${build.timestamp}", 169 | 170 | // When "cache" is not true, this value is the request parameter used 171 | // to control caching. 172 | // 173 | "cacheParam": "_dc" 174 | }, 175 | 176 | /** 177 | * override objects for setting build environment specific 178 | * settings. 179 | */ 180 | "production": { 181 | }, 182 | 183 | "testing": { 184 | 185 | }, 186 | 187 | "development": { 188 | "server": { 189 | "endpoint": "http://localhost:5452/", 190 | "refresh": 10000, 191 | "authentication": "none" 192 | } 193 | }, 194 | 195 | /** 196 | * Controls the output structure of bootstrap artifacts. May be specified by a string: 197 | * 198 | * "bootstrap": "${app.dir}" 199 | * 200 | * to adjust the base path for all bootstrap objects, or expanded into object form: 201 | * 202 | * "bootstrap": { 203 | * "base": "${app.dir}, 204 | * "manifest": "bootstrap.json", 205 | * "microloader": "bootstrap.js", 206 | * "css": "bootstrap.css" 207 | * } 208 | * 209 | * You can optionally exclude entries from the manifest. If you use ext-*.js, 210 | * be sure to exclude "loadOrder" like so: 211 | * 212 | * "bootstrap": { 213 | * "manifest": { 214 | * "path": "bootstrap.json", 215 | * "exclude": "loadOrder" 216 | * } 217 | * } 218 | * 219 | */ 220 | "bootstrap": { 221 | "base": "${app.dir}", 222 | "manifest": "bootstrap.json", 223 | "microloader": "bootstrap.js", 224 | "css": "bootstrap.css" 225 | }, 226 | 227 | /** 228 | * Controls the output directory for build resources. May be set with 229 | * either a string: 230 | * 231 | * "${workspace.build.dir}/${build.environment}/${app.name}" 232 | * 233 | * or an object containing values for various types of 234 | * build artifacts: 235 | * 236 | * { 237 | * "base": "${workspace.build.dir}/${build.environment}/${app.name}", 238 | * "page": { 239 | * "path": "../index.html", 240 | * "enable": false 241 | * }, 242 | * "css": "${app.output.resources}/${app.name}-all.css", 243 | * "js": "app.js", 244 | * "microloader": { 245 | * "path": "microloader.js", 246 | * "embed": true, 247 | * "enable": true 248 | * }, 249 | * "manifest": { 250 | * "path": "app.json", 251 | * "embed": false, 252 | * "enable": "${app.output.microloader.enable}" 253 | * }, 254 | * "resources": "resources", 255 | * "slicer": { 256 | * "path": "${app.output.resources}/images", 257 | * "enable": false 258 | * } 259 | * } 260 | * 261 | */ 262 | "output": { 263 | "base": "${workspace.build.dir}/${build.environment}/${app.name}" 264 | }, 265 | 266 | /** 267 | * Used to automatically generate cache.manifest (HTML 5 application cache manifest) file when you build 268 | */ 269 | "appCache": { 270 | /** 271 | * List of items in the CACHE MANIFEST section 272 | */ 273 | "cache": [ 274 | "index.html" 275 | ], 276 | /** 277 | * List of items in the NETWORK section 278 | */ 279 | "network": [ 280 | "*" 281 | ], 282 | /** 283 | * List of items in the FALLBACK section 284 | */ 285 | "fallback": [] 286 | }, 287 | 288 | /** 289 | * Extra resources to be copied along when build 290 | */ 291 | "resources": [ 292 | "resources/images" 293 | ], 294 | 295 | /** 296 | * File / directory name matchers to ignore when copying to the builds, must be valid regular expressions 297 | */ 298 | "ignore": [ 299 | "(^|/)CVS(/?$|/.*?$)" 300 | ], 301 | 302 | /** 303 | * Directory path to store all previous production builds. Note that the content generated inside this directory 304 | * must be kept intact for proper generation of deltas between updates 305 | */ 306 | "archivePath": "archive", 307 | 308 | /** 309 | * Uniquely generated id for this application, used as prefix for localStorage keys. 310 | * Normally you should never change this value. 311 | */ 312 | "id": "a4b8099b-58df-4356-a78d-31a3a4a5f100" 313 | } -------------------------------------------------------------------------------- /client-extjs/app/Application.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The main application class. An instance of this class is created by app.js when it calls 3 | * Ext.application(). This is the ideal place to handle application launch and initialization 4 | * details. 5 | */ 6 | Ext.define('FileBot.Application', { 7 | extend: 'Ext.app.Application', 8 | 9 | name: 'FileBot', 10 | 11 | stores: [ 12 | 13 | ], 14 | views: [ 15 | 16 | ], 17 | 18 | launch: function() { 19 | FileBot.Node.init() 20 | } 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /client-extjs/app/Node.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.Node', { 2 | singleton: true, 3 | 4 | init: function() { 5 | FileBot.getApplication().on('auth', function(options) { 6 | // init CSRF token for DSM 6.2.4 7 | if (options.auth == 'SYNO') { 8 | this.init_syno() 9 | } else { 10 | this.init_generic() 11 | } 12 | }, this) 13 | 14 | // request auth config 15 | this.requestAuth() 16 | }, 17 | 18 | getServerEndpoint: function(path) { 19 | return Ext.manifest.server.endpoint + path 20 | }, 21 | 22 | getPostEndpoint: function(path, parameters) { 23 | const query = Ext.Object.toQueryString(parameters) 24 | if (query) { 25 | return this.getServerEndpoint(path) + '?' + query 26 | } else { 27 | return this.getServerEndpoint(path) 28 | } 29 | }, 30 | 31 | openEndpoint: function(path, parameters) { 32 | const url = new URL(this.getPostEndpoint(path, parameters), window.location) 33 | window.open(url.href, '_blank') 34 | }, 35 | 36 | requestAuth: function() { 37 | this.dispatchRequest('auth', {}) 38 | }, 39 | 40 | requestState: function(parameters) { 41 | this.dispatchRequest('state', parameters) 42 | }, 43 | 44 | requestEnvironment: function(parameters) { 45 | this.dispatchRequest('environment', parameters) 46 | }, 47 | 48 | requestExecute: function (parameters) { 49 | this.dispatchRequest('execute', parameters) 50 | }, 51 | 52 | requestSchedule: function (parameters) { 53 | this.dispatchRequest('schedule', parameters) 54 | }, 55 | 56 | requestKill: function(parameters) { 57 | this.dispatchRequest('kill', parameters) 58 | }, 59 | 60 | fetchLog: function(parameters, responseHandler) { 61 | this.fetchResource('output', parameters, responseHandler) 62 | }, 63 | 64 | requestVersion: function () { 65 | this.dispatchRequest('version', {}) 66 | }, 67 | 68 | dispatchRequest: function(path, parameters) { 69 | Ext.Ajax.request({ 70 | method: 'GET', 71 | url: this.getServerEndpoint(path), 72 | params: parameters, 73 | useDefaultXhrHeader: false, 74 | cors: true, 75 | disableCaching: true, 76 | success: function (response) { 77 | // broadcast response as application event 78 | const data = Ext.JSON.decode(response.responseText).data 79 | FileBot.getApplication().fireEvent(path, data) 80 | }, 81 | failure: function (response) { 82 | Ext.MessageBox.show({ 83 | title: 'Error', 84 | msg: response.responseText ? response.responseText : Ext.encode(response), 85 | buttons: Ext.MessageBox.OK, 86 | icon: Ext.MessageBox.ERROR 87 | }) 88 | }, 89 | scope: this 90 | }) 91 | }, 92 | 93 | fetchResource: function(path, parameters, responseHandler) { 94 | Ext.Ajax.request({ 95 | method: 'GET', 96 | url: this.getServerEndpoint(path), 97 | params: parameters, 98 | useDefaultXhrHeader: false, 99 | withCredentials: false, 100 | cors: true, 101 | disableCaching: false, 102 | success: responseHandler, 103 | failure: responseHandler 104 | }) 105 | }, 106 | 107 | getDataProxy: function(path) { 108 | return new Ext.data.proxy.Ajax({ 109 | method: 'GET', 110 | url: FileBot.Node.getServerEndpoint(path), 111 | useDefaultXhrHeader: false, 112 | cors: true, 113 | noCache: false, 114 | reader: { 115 | type: 'json', 116 | rootProperty: 'data' 117 | } 118 | }) 119 | }, 120 | 121 | 122 | 123 | init_generic: function() { 124 | // run startup code 125 | FileBot.getApplication().fireEvent('init') 126 | 127 | // restore state 128 | this.requestState({}) 129 | 130 | // tell user to call scheduled tasks via curl 131 | FileBot.getApplication().on('schedule', function(request) { 132 | const id = request.id 133 | const command = request.command 134 | 135 | const url = new URL(this.getPostEndpoint("task", {id: id}), window.location).href 136 | const curl = request.curl + ' "' + url + '"\n' 137 | 138 | Ext.create('Ext.window.MessageBox', { 139 | // set closeAction to 'destroy' if this instance is not 140 | // intended to be reused by the application 141 | closeAction: 'destroy' 142 | }).show({ 143 | title: 'Prepared Task', 144 | msg: 'Prepared Task ' + id + ' can be called via ' + command + '
or Link or cURL.
', 145 | buttons: Ext.MessageBox.OK, 146 | icon: Ext.MessageBox.INFO 147 | }).removeCls('x-unselectable') // HACK TO FIX UNSELECTABLE TEXT 148 | }, this) 149 | 150 | // display filebot version output after successful initialization 151 | const version = new Ext.util.DelayedTask(function() { 152 | // start fetching task data 153 | this.requestVersion() 154 | }, this) 155 | version.delay(250) 156 | }, 157 | 158 | 159 | 160 | init_syno: function() { 161 | // DSM 7 proxy_pass.cgi 162 | this.getServerEndpoint = function(path) { 163 | return Ext.manifest.server.endpoint + path + '.cgi' 164 | } 165 | 166 | // init CSRF token for DSM 6.2.4 167 | Ext.Ajax.request({ 168 | method: 'GET', 169 | url: '/webman/login.cgi', 170 | disableCaching: false, 171 | success: function (response) { 172 | const json = Ext.decode(response.responseText) 173 | const token = json['SynoToken'] 174 | 175 | // add CSRF token to all subsequent requests 176 | if (token) { 177 | Ext.Ajax.setDefaultHeaders({ 178 | 'X-SYNO-TOKEN': token 179 | }) 180 | this.getPostEndpoint = function(path, parameters) { 181 | parameters['SynoToken'] = token 182 | return this.getServerEndpoint(path) + '?' + Ext.Object.toQueryString(parameters) 183 | } 184 | } 185 | 186 | // run normal init code after login.cgi has been called 187 | this.init_generic() 188 | }, 189 | failure: function (response) { 190 | Ext.MessageBox.show({ 191 | title: 'Login Error', 192 | msg: response.responseText ? response.responseText : Ext.encode(response), 193 | buttons: Ext.MessageBox.OK, 194 | icon: Ext.MessageBox.ERROR 195 | }) 196 | }, 197 | scope: this 198 | }) 199 | 200 | // Task Scheduler Web API doesn't accept requests from localhost so we have to do it from the browser 201 | FileBot.getApplication().on('schedule', function(request) { 202 | const name = 'FileBot Task ' + request.id 203 | const command = request.command 204 | 205 | // Syno Web API rejects requests from localhost, so we have to send the request from the client 206 | Ext.Ajax.request({ 207 | method: 'POST', 208 | url: '/webapi/_______________________________________________________entry.cgi', 209 | params: { 210 | name: JSON.stringify(name), 211 | real_owner: JSON.stringify('admin'), 212 | owner: JSON.stringify('admin'), 213 | enable: true, 214 | schedule: JSON.stringify({"date_type":0,"week_day":"0,1,2,3,4,5,6","hour":4,"minute":0,"repeat_hour":0,"repeat_min":0,"last_work_hour":0,"repeat_min_store_config":[1,5,10,15,20,30],"repeat_hour_store_config":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}), 215 | extra: JSON.stringify({"notify_enable":false,"script":command,"notify_mail":"","notify_if_error":false}), 216 | type: JSON.stringify('script'), 217 | api: 'SYNO.Core.TaskScheduler', 218 | method: 'create', 219 | version: 3 220 | }, 221 | success: function (response) { 222 | // e.g. {"error":{"code":104},"success":false} 223 | const json = Ext.JSON.decode(response.responseText) 224 | if (json.success) { 225 | Ext.create('Ext.window.MessageBox', { 226 | // set closeAction to 'destroy' if this instance is not 227 | // intended to be reused by the application 228 | closeAction: 'destroy' 229 | }).show({ 230 | title: 'Synology Task Scheduler', 231 | msg: '' + name + ' has been added to the Synology Task Scheduler. Please use Control Panel ➔ Task Scheduler to modify or delete this task.', 232 | buttons: Ext.MessageBox.OK, 233 | icon: Ext.MessageBox.INFO 234 | }) 235 | } 236 | }, 237 | scope: this 238 | }) 239 | }, this) 240 | } 241 | 242 | }); 243 | -------------------------------------------------------------------------------- /client-extjs/app/Readme.md: -------------------------------------------------------------------------------- 1 | # ./controller 2 | 3 | This folder contains the application's global controllers. ViewControllers are located 4 | alongside their respective view class in `"./view"`. These controllers are used for routing 5 | and other activities that span all views. 6 | 7 | # ./model 8 | 9 | This folder contains the application's (data) Model classes. 10 | 11 | # ./view 12 | 13 | This folder contains the views as well as ViewModels and ViewControllers depending on the 14 | application's architecture. Pure MVC applications may not have ViewModels, for example. For 15 | MVCVM applications or MVC applications that use ViewControllers, the following directory 16 | structure is recommended: 17 | 18 | ./view/ 19 | foo/ # Some meaningful grouping of one or more views 20 | Foo.js # The view class 21 | FooController.js # The controller for Foo (a ViewController) 22 | FooModel.js # The ViewModel for Foo 23 | 24 | This structure helps keep these closely related classes together and easily identifiable in 25 | most tabbed IDE's or text editors. 26 | 27 | # ./store 28 | 29 | This folder contains any number of store instances or types that can then be reused in the 30 | application. 31 | -------------------------------------------------------------------------------- /client-extjs/app/model/Base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This model is the base for all other models in this application. 3 | */ 4 | Ext.define('FileBot.model.Base', { 5 | extend: 'Ext.data.Model', 6 | 7 | schema: { 8 | namespace: 'FileBot.model' 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /client-extjs/app/model/Readme.md: -------------------------------------------------------------------------------- 1 | This folder contains the Models for this application. 2 | -------------------------------------------------------------------------------- /client-extjs/app/store/ArchiveOptions.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.ArchiveOptions', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.archive-options', 4 | storeId: 'archive-options', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [0, 'skip', 'ignore archives'], 12 | [1, 'extract-keep', 'extract and keep archives'], 13 | [2, 'extract-delete', 'extract and delete archives'] 14 | ] 15 | }); 16 | -------------------------------------------------------------------------------- /client-extjs/app/store/ConflictActions.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.ConflictActions', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.conflict-actions', 4 | storeId: 'conflict-actions', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [0, 'skip', 'skip existing files'], 12 | [1, 'replace', 'overwrite existing files'], 13 | [2, 'auto', 'overwrite if new file is better'], 14 | [3, 'index', 'keep both and index new file'], 15 | [4, 'fail', 'fail if files already exist'] 16 | ] 17 | }); 18 | -------------------------------------------------------------------------------- /client-extjs/app/store/EpisodeOrder.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.EpisodeOrders', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.episode-orders', 4 | storeId: 'episode-orders', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [0, 'Airdate', 'Airdate'], 12 | [1, 'DVD', 'DVD'], 13 | [2, 'Absolute', 'Absolute'], 14 | [3, 'Digital', 'Digital'], 15 | [4, 'Story', 'Story Arc'], 16 | [5, 'Production', 'Production'], 17 | [6, 'Date', 'Date and Title'] 18 | ] 19 | }); 20 | -------------------------------------------------------------------------------- /client-extjs/app/store/FileAgeFilters.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.FileAgeFilters', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.fileage-filters', 4 | storeId: 'fileage-filters', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [1, '', 'default'], 12 | [2, '0.125', '3 hours'], 13 | [3, '0.5', '12 hours'], 14 | [4, '1', '24 hours'], 15 | [5, '3', '3 days'], 16 | [6, '7', '7 days'] 17 | ] 18 | }); 19 | -------------------------------------------------------------------------------- /client-extjs/app/store/FileSizeFilters.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.FileSizeFilters', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.filesize-filters', 4 | storeId: 'filesize-filters', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [1, '', 'default'], 12 | [2, ''+0, '0 bytes'], 13 | [3, ''+100*1000*1000, '100 MB'], 14 | [4, ''+500*1000*1000, '500 MB'], 15 | [5, ''+2*1000*1000*1000, '2 GB'], 16 | [6, ''+5*1000*1000*1000, '5 GB'] 17 | ] 18 | }); 19 | -------------------------------------------------------------------------------- /client-extjs/app/store/Languages.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.Languages', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.languages', 4 | storeId: 'languages', 5 | 6 | fields: [ 7 | 'id', 'iso_639_1', 'iso_639_3', 'iso_639_2B', 'label' 8 | ], 9 | 10 | data: [ 11 | [ 1, 'sq', 'sqi', 'alb', 'Albanian'], 12 | [ 2, 'ar', 'ara', 'ara', 'Arabic'], 13 | [ 3, 'hy', 'hye', 'arm', 'Armenian'], 14 | [ 4, 'pb', 'pob', 'pob', 'Brazilian'], 15 | [ 5, 'bg', 'bul', 'bul', 'Bulgarian'], 16 | [ 6, 'ca', 'cat', 'cat', 'Catalan'], 17 | [ 7, 'zh', 'zho', 'chi', 'Chinese'], 18 | [ 8, 'hr', 'hrv', 'hrv', 'Croatian'], 19 | [ 9, 'cs', 'ces', 'cze', 'Czech'], 20 | [10, 'da', 'dan', 'dan', 'Danish'], 21 | [11, 'nl', 'nld', 'dut', 'Dutch'], 22 | [12, 'en', 'eng', 'eng', 'English'], 23 | [13, 'et', 'est', 'est', 'Estonian'], 24 | [14, 'fi', 'fin', 'fin', 'Finnish'], 25 | [15, 'fr', 'fra', 'fre', 'French'], 26 | [16, 'de', 'deu', 'ger', 'German'], 27 | [17, 'el', 'ell', 'gre', 'Greek'], 28 | [18, 'he', 'heb', 'heb', 'Hebrew'], 29 | [19, 'hi', 'hin', 'hin', 'Hindi'], 30 | [20, 'hu', 'hun', 'hun', 'Hungarian'], 31 | [21, 'id', 'ind', 'ind', 'Indonesian'], 32 | [22, 'it', 'ita', 'ita', 'Italian'], 33 | [23, 'ja', 'jpn', 'jpn', 'Japanese'], 34 | [24, 'ko', 'kor', 'kor', 'Korean'], 35 | [25, 'lv', 'lav', 'lav', 'Latvian'], 36 | [26, 'lt', 'lit', 'lit', 'Lithuanian'], 37 | [27, 'mk', 'mkd', 'mac', 'Macedonian'], 38 | [28, 'ms', 'msa', 'may', 'Malay'], 39 | [29, 'no', 'nor', 'nor', 'Norwegian'], 40 | [30, 'fa', 'fas', 'per', 'Persian'], 41 | [31, 'pl', 'pol', 'pol', 'Polish'], 42 | [32, 'pt', 'por', 'por', 'Portuguese'], 43 | [33, 'ro', 'ron', 'rum', 'Romanian'], 44 | [34, 'ru', 'rus', 'rus', 'Russian'], 45 | [35, 'sr', 'srp', 'srp', 'Serbian'], 46 | [36, 'sk', 'slk', 'slo', 'Slovak'], 47 | [37, 'sl', 'slv', 'slv', 'Slovenian'], 48 | [38, 'es', 'spa', 'spa', 'Spanish'], 49 | [39, 'sv', 'swe', 'swe', 'Swedish'], 50 | [40, 'th', 'tha', 'tha', 'Thai'], 51 | [41, 'tr', 'tur', 'tur', 'Turkish'], 52 | [42, 'vi', 'vie', 'vie', 'Vietnamese'] 53 | ] 54 | }); 55 | -------------------------------------------------------------------------------- /client-extjs/app/store/LogLevels.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.LogLevels', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.log-levels', 4 | storeId: 'log-levels', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [0, 'info', 'only results and errors'], 12 | [1, 'fine', 'all important messages'], 13 | [2, 'all', 'everything'] 14 | ] 15 | }); 16 | -------------------------------------------------------------------------------- /client-extjs/app/store/MediaLabels.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.MediaLabels', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.media-labels', 4 | storeId: 'media-labels', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [0, '', 'Automatic'], 12 | [1, 'Movie', 'Movies'], 13 | [2, 'TV', 'TV Series'], 14 | [3, 'Anime', 'Anime'], 15 | [4, 'Music', 'Music'], 16 | [5, 'other', 'Files'] 17 | ] 18 | }); 19 | -------------------------------------------------------------------------------- /client-extjs/app/store/MovieDatabases.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.MovieDatabases', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.movie-databases', 4 | storeId: 'movie-databases', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [1, '', 'default'], 12 | [2, 'TheMovieDB', 'TheMovieDB'], 13 | [3, 'OMDb', 'OMDb'] 14 | ] 15 | }); 16 | -------------------------------------------------------------------------------- /client-extjs/app/store/Readme.md: -------------------------------------------------------------------------------- 1 | This folder contains store instances (identified by storeId) and store types 2 | (with "store.foo" aliases). 3 | -------------------------------------------------------------------------------- /client-extjs/app/store/RenameActions.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.RenameActions', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.rename-actions', 4 | storeId: 'rename-actions', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [1, 'move', 'move and rename'], 12 | [2, 'copy', 'copy'], 13 | [3, 'hardlink', 'hardlink'], 14 | [4, 'symlink', 'symlink'], 15 | [5, 'clone', 'reflink'], 16 | [6, 'keeplink', 'move and symlink back'], 17 | [7, 'duplicate', 'hardlink or copy'] 18 | ] 19 | }); 20 | -------------------------------------------------------------------------------- /client-extjs/app/store/ScriptSources.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.ScriptSources', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.script-sources', 4 | storeId: 'script-sources', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [0, 'fn', 'stable'], 12 | [1, 'dev', 'latest'] 13 | ] 14 | }); 15 | -------------------------------------------------------------------------------- /client-extjs/app/store/SeriesDatabases.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.SeriesDatabases', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.series-databases', 4 | storeId: 'series-databases', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [1, '', 'default'], 12 | [2, 'TheMovieDB::TV', 'TheMovieDB'], 13 | [3, 'AniDB', 'AniDB'], 14 | [4, 'TheTVDB', 'TheTVDB'], 15 | [5, 'TVmaze', 'TVmaze'] 16 | ] 17 | }); 18 | -------------------------------------------------------------------------------- /client-extjs/app/store/VideoLengthFilters.js: -------------------------------------------------------------------------------- 1 | Ext.define('FileBot.store.VideoLengthFilters', { 2 | extend: 'Ext.data.ArrayStore', 3 | alias: 'store.videolength-filters', 4 | storeId: 'videolength-filters', 5 | 6 | fields: [ 7 | 'id', 'value', 'label' 8 | ], 9 | 10 | data: [ 11 | [1, '', 'default'], 12 | [2, ''+0, '0 seconds'], 13 | [3, ''+5*60*1000, '5 minutes'], 14 | [4, ''+15*60*1000, '15 minutes'], 15 | [5, ''+30*60*1000, '30 minutes'], 16 | [6, ''+60*60*1000, '60 minutes'], 17 | [7, ''+90*60*1000, '90 minutes'] 18 | ] 19 | }); 20 | -------------------------------------------------------------------------------- /client-extjs/app/view/main/Main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the main view for the application. It is specified in app.js as the 3 | * "autoCreateViewport" property. That setting automatically applies the "viewport" 4 | * plugin to promote that instance of this class to the body element. 5 | * 6 | * TODO - Replace this content of this view to suite the needs of your application. 7 | */ 8 | Ext.define('FileBot.view.main.Main', { 9 | extend: 'Ext.container.Container', 10 | requires: [ 11 | 'FileBot.view.main.MainController', 12 | 'FileBot.view.main.MainModel', 13 | 'FileBot.view.navigation.Navigation', 14 | 'FileBot.view.task.Task' 15 | ], 16 | 17 | xtype: 'app-main', 18 | 19 | controller: 'main', 20 | viewModel: { 21 | type: 'main' 22 | }, 23 | 24 | layout: { 25 | type: 'border' 26 | }, 27 | 28 | items: [{ 29 | region: 'center', 30 | xtype: 'section-task' 31 | }] 32 | }); 33 | -------------------------------------------------------------------------------- /client-extjs/app/view/main/MainController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the main view for the application. It is specified in app.js as the 3 | * "autoCreateViewport" property. That setting automatically applies the "viewport" 4 | * plugin to promote that instance of this class to the body element. 5 | * 6 | * TODO - Replace this content of this view to suite the needs of your application. 7 | */ 8 | Ext.define('FileBot.view.main.MainController', { 9 | extend: 'Ext.app.ViewController', 10 | 11 | requires: [ 12 | 13 | ], 14 | 15 | alias: 'controller.main', 16 | 17 | /** 18 | * Called when the view is created 19 | */ 20 | init: function() { 21 | if (Ext.util.LocalStorage.supported) { 22 | Ext.state.Manager.setProvider(new Ext.state.LocalStorageProvider()) 23 | } else { 24 | Ext.state.Manager.setProvider(new Ext.state.CookieProvider()) 25 | } 26 | } 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /client-extjs/app/view/main/MainModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the view model for the Main view of the application. 3 | */ 4 | Ext.define('FileBot.view.main.MainModel', { 5 | extend: 'Ext.app.ViewModel', 6 | 7 | alias: 'viewmodel.main', 8 | 9 | data: { 10 | name: 'FileBot' 11 | } 12 | 13 | //TODO - add data, formulas and/or methods to support your view 14 | }); 15 | -------------------------------------------------------------------------------- /client-extjs/app/view/navigation/Navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/25/15. 3 | */ 4 | Ext.define('FileBot.view.navigation.Navigation', { 5 | extend: 'Ext.tab.Panel', 6 | xtype: 'navigation-tabs', 7 | 8 | // ui: 'navigation', // NOT WORKING 9 | 10 | tabBar: { 11 | layout: { 12 | pack: 'center' 13 | } 14 | }, 15 | 16 | defaults: { 17 | iconAlign: 'top', 18 | bodyPadding: 15 19 | } 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /client-extjs/app/view/navigation/NavigationController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/25/15. 3 | */ 4 | Ext.define('FileBot.view.navigation.NavigationController', { 5 | extend: 'Ext.app.ViewController', 6 | alias: 'controller.navigation', 7 | 8 | /** 9 | * Called when the view is created 10 | */ 11 | init: function() { 12 | 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /client-extjs/app/view/navigation/NavigationModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/25/15. 3 | */ 4 | Ext.define('FileBot.view.navigation.NavigationModel', { 5 | extend: 'Ext.app.ViewModel', 6 | alias: 'viewmodel.navigation', 7 | 8 | stores: { 9 | /* 10 | A declaration of Ext.data.Store configurations that are first processed as binds to produce an effective 11 | store configuration. For example: 12 | 13 | users: { 14 | model: 'Navigation', 15 | autoLoad: true 16 | } 17 | */ 18 | }, 19 | 20 | data: { 21 | /* This object holds the arbitrary data that populates the ViewModel and is then available for binding. */ 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /client-extjs/app/view/task/Task.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the main view for the application. It is specified in app.js as the 3 | * "autoCreateViewport" property. That setting automatically applies the "viewport" 4 | * plugin to promote that instance of this class to the body element. 5 | * 6 | * TODO - Replace this content of this view to suite the needs of your application. 7 | */ 8 | Ext.define('FileBot.view.task.Task', { 9 | extend: 'Ext.container.Container', 10 | requires: [ 11 | 'FileBot.Node', 12 | 'FileBot.view.task.TaskController', 13 | 'FileBot.view.task.TaskModel', 14 | 'FileBot.view.taskmanager.TaskManager', 15 | 'FileBot.view.tasklogcat.TaskLogCat', 16 | 'FileBot.store.RenameActions', 17 | 'FileBot.store.MediaLabels', 18 | 'FileBot.store.EpisodeOrders', 19 | 'FileBot.store.LogLevels', 20 | 'FileBot.store.ArchiveOptions', 21 | 'FileBot.store.VideoLengthFilters', 22 | 'FileBot.store.FileSizeFilters', 23 | 'FileBot.store.FileAgeFilters', 24 | 'FileBot.store.Languages', 25 | 'FileBot.store.ConflictActions', 26 | 'FileBot.store.ScriptSources', 27 | 'FileBot.store.MovieDatabases', 28 | 'FileBot.store.SeriesDatabases' 29 | ], 30 | 31 | xtype: 'section-task', 32 | 33 | controller: 'task', 34 | viewModel: { 35 | type: 'task' 36 | }, 37 | 38 | listeners: { 39 | afterrender: 'restoreState' 40 | }, 41 | 42 | defaults: { 43 | collapsible: true, 44 | split: true, 45 | scrollable: true, 46 | floatable: false, 47 | bodyPadding: 10 48 | }, 49 | 50 | layout: 'border', 51 | 52 | items: [{ 53 | region: 'center', 54 | xtype: 'form', 55 | title: 'Organize Files', 56 | headerPosition: 'left', 57 | bodyPadding: 20, 58 | collapsible: false, 59 | 60 | defaults: { 61 | xtype: 'fieldset', 62 | collapsible: true, 63 | collapsed: true 64 | }, 65 | items: [{ 66 | title: 'Organize Files', 67 | collapsible: false, 68 | collapsed: false, 69 | 70 | defaults: { 71 | allowBlank: false, 72 | forceSelection: true, 73 | queryMode: 'local' 74 | }, 75 | items: [{ 76 | xtype: 'hidden', 77 | name: 'fn', 78 | value: 'amc', 79 | hidden: true 80 | }, { 81 | xtype: 'combobox', 82 | name: 'input', 83 | fieldLabel: 'Input Folder', 84 | emptyText: '/path/to/input', 85 | value: Ext.manifest.server.form.input, 86 | bind: { 87 | store: '{folders}' 88 | }, 89 | displayField: 'path', 90 | valueField: 'path', 91 | minChars: 0, // forcing the query to run every time by setting minChars to 0 92 | queryCaching: true, 93 | queryParam: 'q', 94 | queryMode: 'remote', 95 | forceSelection: false, 96 | editable: true, 97 | anchor: '100%' 98 | }, { 99 | xtype: 'combobox', 100 | name: 'label', 101 | fieldLabel: 'Input Type', 102 | displayField: 'label', 103 | valueField: 'value', 104 | value: '', 105 | store: { 106 | type: 'media-labels' 107 | }, 108 | editable: false, 109 | minWidth: 280 110 | }, { 111 | xtype: 'checkboxfield', 112 | name: 'strict', 113 | fieldLabel: 'Strict Mode', 114 | boxLabel: 'use strict mode', 115 | inputValue: 'no', 116 | checked: false 117 | }, { 118 | xtype: 'combobox', 119 | name: 'action', 120 | fieldLabel: 'Action', 121 | displayField: 'label', 122 | valueField: 'value', 123 | value: 'duplicate', 124 | store: { 125 | type: 'rename-actions' 126 | }, 127 | editable: true, 128 | minWidth: 280 129 | }, { 130 | xtype: 'combobox', 131 | name: 'output', 132 | fieldLabel: 'Output Folder', 133 | emptyText: '/path/to/output', 134 | value: Ext.manifest.server.form.output, 135 | bind: { 136 | store: '{folders}' 137 | }, 138 | displayField: 'path', 139 | valueField: 'path', 140 | minChars: 0, // forcing the query to run every time by setting minChars to 0 141 | queryCaching: true, 142 | queryParam: 'q', 143 | queryMode: 'remote', 144 | forceSelection: false, 145 | editable: true, 146 | anchor: '100%' 147 | }, { 148 | xtype: 'checkboxfield', 149 | name: 'artwork', 150 | fieldLabel: 'Artwork', 151 | boxLabel: 'fetch artwork and generate .nfo files', 152 | checked: false 153 | }, { 154 | xtype: 'checkboxfield', 155 | name: 'clean', 156 | fieldLabel: 'Clean', 157 | boxLabel: 'delete left behind clutter files', 158 | checked: false 159 | }, { 160 | xtype: 'combobox', 161 | name: 'order', 162 | fieldLabel: 'Episode Order', 163 | displayField: 'label', 164 | valueField: 'value', 165 | value: 'Airdate', 166 | store: { 167 | type: 'episode-orders' 168 | }, 169 | editable: false, 170 | minWidth: 280 171 | }, { 172 | xtype: 'combobox', 173 | name: 'subtitles', 174 | fieldLabel: 'Subtitles', 175 | displayField: 'label', 176 | valueField: 'iso_639_3', 177 | store: { 178 | type: 'languages' 179 | }, 180 | emptyText: 'subtitle language', 181 | forceSelection: false, 182 | editable: true, 183 | allowBlank: true, 184 | minWidth: 320 185 | }, { 186 | xtype: 'combobox', 187 | name: 'lang', 188 | fieldLabel: 'Language', 189 | displayField: 'label', 190 | valueField: 'iso_639_1', 191 | value: 'en', 192 | store: { 193 | type: 'languages' 194 | }, 195 | emptyText: 'language', 196 | editable: false, 197 | minWidth: 320 198 | }] 199 | }, { 200 | title: 'Media Server', 201 | collapsible: false, 202 | collapsed: false, 203 | 204 | id: 'media-server-options', 205 | hidden: true, 206 | 207 | items: [{ 208 | xtype: 'checkboxfield', 209 | name: 'thumbnail', 210 | fieldLabel: 'Thumbnails', 211 | boxLabel: 'generate thumbnails', 212 | inputValue: 'on', 213 | uncheckedValue: 'no', 214 | checked: false 215 | }, { 216 | xtype: 'checkboxfield', 217 | name: 'refresh', 218 | fieldLabel: 'Re-index', 219 | boxLabel: 'refresh file services', 220 | inputValue: 'on', 221 | uncheckedValue: 'no', 222 | checked: false 223 | }] 224 | }, { 225 | title: 'File Options', 226 | defaults: { 227 | allowBlank: true, 228 | forceSelection: true, 229 | queryMode: 'local' 230 | }, 231 | items: [{ 232 | xtype: 'combobox', 233 | name: 'conflict', 234 | fieldLabel: 'Conflict', 235 | labelStyle: 'white-space: nowrap; width: 120px;', 236 | displayField: 'label', 237 | valueField: 'value', 238 | value: 'auto', 239 | store: { 240 | type: 'conflict-actions' 241 | }, 242 | editable: true, 243 | minWidth: 320 244 | }, { 245 | xtype: 'combobox', 246 | name: 'archives', 247 | fieldLabel: 'Archives', 248 | labelStyle: 'white-space: nowrap; width: 120px;', 249 | displayField: 'label', 250 | valueField: 'value', 251 | value: 'skip', 252 | store: { 253 | type: 'archive-options' 254 | }, 255 | editable: false, 256 | minWidth: 320 257 | }, { 258 | xtype: 'checkboxfield', 259 | name: 'music', 260 | fieldLabel: 'Music', 261 | labelStyle: 'white-space: nowrap; width: 120px;', 262 | boxLabel: 'skip music files', 263 | inputValue: 'no', 264 | checked: false 265 | }, { 266 | xtype: 'checkboxfield', 267 | name: 'unsorted', 268 | fieldLabel: 'Unsorted Files', 269 | labelStyle: 'white-space: nowrap; width: 120px;', 270 | boxLabel: 'skip unsorted files', 271 | inputValue: 'no', 272 | checked: false 273 | }, { 274 | xtype: 'textfield', 275 | name: 'ignore', 276 | fieldLabel: 'Ignore Rules', 277 | labelStyle: 'white-space: nowrap; width: 120px;', 278 | emptyText: 'games|books', 279 | anchor: '100%' 280 | }, { 281 | xtype: 'combobox', 282 | name: 'minLengthMS', 283 | fieldLabel: 'Video Duration', 284 | labelStyle: 'white-space: nowrap; width: 120px;', 285 | displayField: 'label', 286 | valueField: 'value', 287 | value: '', 288 | store: { 289 | type: 'videolength-filters' 290 | }, 291 | editable: true 292 | }, { 293 | xtype: 'combobox', 294 | name: 'minFileSize', 295 | fieldLabel: 'File Size', 296 | labelStyle: 'white-space: nowrap; width: 120px;', 297 | displayField: 'label', 298 | valueField: 'value', 299 | value: '', 300 | store: { 301 | type: 'filesize-filters' 302 | }, 303 | editable: true 304 | }, { 305 | xtype: 'combobox', 306 | name: 'minFileAge', 307 | fieldLabel: 'File Age', 308 | labelStyle: 'white-space: nowrap; width: 120px;', 309 | displayField: 'label', 310 | valueField: 'value', 311 | value: '', 312 | store: { 313 | type: 'fileage-filters' 314 | }, 315 | editable: true 316 | }, { 317 | xtype: 'checkboxfield', 318 | name: 'excludeLink', 319 | fieldLabel: 'Exclude Link', 320 | labelStyle: 'white-space: nowrap; width: 120px;', 321 | boxLabel: 'skip superfluous links', 322 | checked: false 323 | }, { 324 | xtype: 'textfield', 325 | name: 'excludeList', 326 | fieldLabel: 'Exclude List', 327 | labelStyle: 'white-space: nowrap; width: 120px;', 328 | emptyText: 'exclude file that keeps track of processed files', 329 | value: '.excludes', 330 | anchor: '100%' 331 | }] 332 | }, { 333 | title: 'Match Options', 334 | defaults: { 335 | allowBlank: true, 336 | xtype: 'textfield', 337 | anchor: '100%' 338 | }, 339 | items: [{ 340 | xtype: 'textfield', 341 | name: 'query', 342 | fieldLabel: 'Query Expression', 343 | labelStyle: 'white-space: nowrap; width: 120px;', 344 | emptyText: '70327', 345 | allowBlank: true, 346 | anchor: '100%' 347 | }, { 348 | xtype: 'textfield', 349 | name: 'filter', 350 | fieldLabel: 'Match Filter', 351 | labelStyle: 'white-space: nowrap; width: 120px;', 352 | emptyText: 'age < 7', 353 | allowBlank: true, 354 | anchor: '100%' 355 | }, { 356 | xtype: 'textfield', 357 | name: 'mapper', 358 | fieldLabel: 'Match Mapper', 359 | labelStyle: 'white-space: nowrap; width: 120px;', 360 | emptyText: 'order.absolute.episode', 361 | allowBlank: true, 362 | anchor: '100%' 363 | }] 364 | }, { 365 | title: 'Format Options', 366 | defaults: { 367 | allowBlank: true, 368 | xtype: 'textfield', 369 | anchor: '100%' 370 | }, 371 | items: [{ 372 | fieldLabel: 'Movie Format', 373 | labelStyle: 'white-space: nowrap; width: 120px;', 374 | name: 'movieFormat', 375 | emptyText: '{ plex.id }' 376 | }, { 377 | fieldLabel: 'Series Format', 378 | labelStyle: 'white-space: nowrap; width: 120px;', 379 | name: 'seriesFormat', 380 | emptyText: '{ plex.id }' 381 | }, { 382 | fieldLabel: 'Anime Format', 383 | labelStyle: 'white-space: nowrap; width: 120px;', 384 | name: 'animeFormat', 385 | emptyText: 'Anime/{ ~plex.id }' 386 | }, { 387 | fieldLabel: 'Music Format', 388 | labelStyle: 'white-space: nowrap; width: 120px;', 389 | name: 'musicFormat', 390 | emptyText: '{ plex.id }' 391 | }, { 392 | fieldLabel: 'Unsorted Format', 393 | labelStyle: 'white-space: nowrap; width: 120px;', 394 | name: 'unsortedFormat', 395 | emptyText: 'Unsorted/{ relativeFile }' 396 | }] 397 | }, { 398 | title: 'Post Processing Options', 399 | defaults: { 400 | allowBlank: true, 401 | xtype: 'textfield', 402 | anchor: '100%' 403 | }, 404 | items: [{ 405 | xtype: 'checkboxfield', 406 | name: 'import', 407 | fieldLabel: 'Import Extras', 408 | boxLabel: 'copy companion files along from the original folder to the destination folder', 409 | checked: false 410 | }, { 411 | xtype: 'checkboxfield', 412 | name: 'metadata', 413 | fieldLabel: 'Export Xattr', 414 | boxLabel: 'copy xattr metadata into hidden .xattr folders', 415 | checked: false 416 | }, { 417 | xtype: 'checkboxfield', 418 | name: 'chmod', 419 | fieldLabel: 'Set Permissions', 420 | boxLabel: 'set permissions to all-readable / user-writable (rw-r--r--)', 421 | checked: false 422 | }, { 423 | name: 'apply', 424 | fieldLabel: 'Run Script', 425 | emptyText: '/path/to/apply.groovy' 426 | }, { 427 | name: 'exec', 428 | fieldLabel: 'Run Command', 429 | emptyText: 'stat {quote f}' 430 | }, { 431 | name: 'plex', 432 | fieldLabel: 'Plex', 433 | emptyText: 'host:token' 434 | }, { 435 | name: 'kodi', 436 | fieldLabel: 'Kodi', 437 | emptyText: 'host' 438 | }, { 439 | name: 'emby', 440 | fieldLabel: 'Emby', 441 | emptyText: 'host:apikey' 442 | }, { 443 | name: 'jellyfin', 444 | fieldLabel: 'Jellyfin', 445 | emptyText: 'host:apikey' 446 | }, { 447 | name: 'pushover', 448 | fieldLabel: 'Pushover', 449 | emptyText: 'userkey:apikey' 450 | }, { 451 | name: 'pushbullet', 452 | fieldLabel: 'PushBullet', 453 | emptyText: 'apikey' 454 | }, { 455 | name: 'discord', 456 | fieldLabel: 'Discord', 457 | emptyText: 'webhook' 458 | }, { 459 | xtype: 'combobox', 460 | name: 'report', 461 | fieldLabel: 'Report Folder', 462 | emptyText: '.reports', 463 | value: Ext.manifest.server.form.output, 464 | bind: { 465 | store: '{folders}' 466 | }, 467 | displayField: 'path', 468 | valueField: 'path', 469 | minChars: 0, // forcing the query to run every time by setting minChars to 0 470 | queryCaching: true, 471 | queryParam: 'q', 472 | queryMode: 'remote', 473 | forceSelection: false, 474 | editable: true, 475 | anchor: '100%' 476 | }] 477 | }, { 478 | title: 'Database Options', 479 | defaults: { 480 | allowBlank: true, 481 | forceSelection: true, 482 | queryMode: 'local' 483 | }, 484 | items: [{ 485 | xtype: 'combobox', 486 | name: 'movieDB', 487 | fieldLabel: 'Movie Database', 488 | labelStyle: 'white-space: nowrap; width: 120px;', 489 | displayField: 'label', 490 | valueField: 'value', 491 | value: '', 492 | store: { 493 | type: 'movie-databases' 494 | }, 495 | editable: false, 496 | minWidth: 320 497 | }, { 498 | xtype: 'combobox', 499 | name: 'seriesDB', 500 | fieldLabel: 'Series Database', 501 | labelStyle: 'white-space: nowrap; width: 120px;', 502 | displayField: 'label', 503 | valueField: 'value', 504 | value: '', 505 | store: { 506 | type: 'series-databases' 507 | }, 508 | editable: false, 509 | minWidth: 320 510 | }, { 511 | xtype: 'combobox', 512 | name: 'animeDB', 513 | fieldLabel: 'Anime Database', 514 | labelStyle: 'white-space: nowrap; width: 120px;', 515 | displayField: 'label', 516 | valueField: 'value', 517 | value: '', 518 | store: { 519 | type: 'series-databases' 520 | }, 521 | editable: false, 522 | minWidth: 320 523 | }] 524 | }, { 525 | title: 'Developer Options', 526 | defaults: { 527 | allowBlank: true, 528 | forceSelection: true, 529 | queryMode: 'local' 530 | }, 531 | items: [{ 532 | xtype: 'combobox', 533 | name: 'channel', 534 | fieldLabel: 'Script Channel', 535 | displayField: 'label', 536 | valueField: 'value', 537 | value: 'fn', 538 | store: { 539 | type: 'script-sources' 540 | }, 541 | editable: false 542 | }, { 543 | xtype: 'combobox', 544 | name: 'log', 545 | fieldLabel: 'Log Level', 546 | displayField: 'label', 547 | valueField: 'value', 548 | value: 'all', 549 | store: { 550 | type: 'log-levels' 551 | }, 552 | editable: false 553 | }, { 554 | xtype: 'checkboxfield', 555 | name: 'probe', 556 | fieldLabel: 'Media Parser', 557 | boxLabel: 'disable media parser', 558 | inputValue: 'no', 559 | checked: false 560 | }, { 561 | xtype: 'checkboxfield', 562 | name: 'index', 563 | fieldLabel: 'Media Index', 564 | boxLabel: 'disable media index', 565 | inputValue: 'no', 566 | checked: false 567 | }] 568 | }], 569 | 570 | buttons: [{ 571 | xtype: 'button', 572 | scale: 'small', 573 | iconCls: 'configure-btn', 574 | text: 'Tools', 575 | menu: new Ext.menu.Menu({ 576 | items: [ 577 | // these will render as dropdown menu items when the arrow is clicked: 578 | { text: 'License', handler: 'onLicense', iconCls: 'license-item' }, 579 | { xtype: 'menuseparator' }, 580 | { text: 'Clear Cache', handler: 'onClear', iconCls: 'clear-item' }, 581 | { text: 'System Info', handler: 'onInfo', iconCls: 'sysinfo-item' }, 582 | { text: 'System Properties', handler: 'onSettings', iconCls: 'settings-item' }, 583 | { text: 'Environment', handler: 'onEnvironment', iconCls: 'environment-item' }, 584 | { text: 'Help', handler: 'onHelp', iconCls: 'help-item' }, 585 | { xtype: 'menuseparator' }, 586 | { text: 'MediaInfo', handler: 'onMediaInfo', iconCls: 'mediainfo-item' }, 587 | { text: 'OpenSubtitles', handler: 'onConfigure', iconCls: 'configure-item' } 588 | ] 589 | }), 590 | width: 80, 591 | style: 'left: 6em !important' // align this button to the left 592 | }, { 593 | xtype: 'splitbutton', 594 | formBind: true, 595 | scale: 'small', 596 | iconCls: 'run-btn', 597 | text: 'Execute', 598 | // handle a click on the button itself 599 | handler: 'onExecute', 600 | menu: new Ext.menu.Menu({ 601 | items: [ 602 | // these will render as dropdown menu items when the arrow is clicked: 603 | {text: 'Dry Run', handler: 'onTest', iconCls: 'dryrun-item'}, 604 | {text: 'Schedule', handler: 'onSchedule', iconCls: 'schedule-item'}, 605 | {text: 'Revert', handler: 'onRevert', iconCls: 'revert-item' }, 606 | ] 607 | }), 608 | width: 110, 609 | style:'margin-right: 3em' 610 | }] 611 | }, { 612 | xtype: 'container', 613 | region: 'south', 614 | frame: true, 615 | layout: 'border', 616 | height: 200, 617 | scrollable: false, 618 | 619 | items: [{ 620 | region: 'west', 621 | xtype: 'taskmanager', 622 | headerPosition: 'left', 623 | collapsible: true, 624 | floatable: false, 625 | scrollable: 'vertical', 626 | width: 325 627 | }, { 628 | region: 'center', 629 | xtype: 'tasklogcat', 630 | collapsible: false, 631 | floatable: false, 632 | scrollable: true 633 | }] 634 | }] 635 | }); 636 | -------------------------------------------------------------------------------- /client-extjs/app/view/task/TaskController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the main view for the application. It is specified in app.js as the 3 | * "autoCreateViewport" property. That setting automatically applies the "viewport" 4 | * plugin to promote that instance of this class to the body element. 5 | * 6 | * TODO - Replace this content of this view to suite the needs of your application. 7 | */ 8 | Ext.define('FileBot.view.task.TaskController', { 9 | extend: 'Ext.app.ViewController', 10 | requires: [ 11 | 'FileBot.Node' 12 | ], 13 | 14 | alias: 'controller.task', 15 | 16 | /** 17 | * Called when the view is created 18 | */ 19 | init: function() { 20 | FileBot.getApplication().on('init', function() { 21 | // start fetching folder data 22 | this.getViewModel().getStore('folders').setProxy(FileBot.Node.getDataProxy('folders')) 23 | }, this) 24 | 25 | FileBot.getApplication().on('auth', function(options) { 26 | var fields = this.getView().down('#media-server-options') 27 | if (options.auth == 'SYNO' || options.auth == 'QNAP') { 28 | // enable media server options by default 29 | fields.query('checkbox').forEach(function(checkbox) { 30 | checkbox.setValue(true) 31 | }) 32 | fields.show() 33 | } else { 34 | // remove media server options from the form entirely 35 | fields.destroy() 36 | } 37 | }, this) 38 | 39 | FileBot.getApplication().on('state', function(json) { 40 | // restore form fields 41 | if (json) { 42 | var form = this.getForm() 43 | form.setValues(Ext.decode(json)) 44 | form.isValid() 45 | } 46 | }, this) 47 | 48 | // show current environment 49 | FileBot.getApplication().on('environment', this.showEnvironmentForm, this) 50 | }, 51 | 52 | restoreState: function() { 53 | var values = Ext.state.Manager.get('formOrganizeFiles') 54 | var form = this.getForm() 55 | 56 | // restore form values 57 | if (values) { 58 | form.setValues(values) 59 | } 60 | 61 | // mark invalid fields on init 62 | form.isValid() 63 | }, 64 | 65 | saveState: function() { 66 | var values = this.getForm().getValues() 67 | Ext.state.Manager.set('formOrganizeFiles', values) 68 | FileBot.Node.requestState({'store': Ext.encode(values)}) 69 | return values 70 | }, 71 | 72 | onExecute: function() { 73 | var parameters = this.getExecuteParameters() 74 | if (parameters) { 75 | FileBot.Node.requestExecute(parameters) 76 | } 77 | }, 78 | 79 | onTest: function() { 80 | var parameters = this.getExecuteParameters() 81 | if (parameters) { 82 | // force --action test and then execute normally 83 | parameters.action = 'TEST' 84 | FileBot.Node.requestExecute(parameters) 85 | } 86 | }, 87 | 88 | onSchedule: function() { 89 | var parameters = this.getExecuteParameters() 90 | if (parameters) { 91 | FileBot.Node.requestSchedule(parameters) 92 | } 93 | }, 94 | 95 | onMediaInfo: function() { 96 | var parameters = this.getExecuteParameters() 97 | if (parameters) { 98 | parameters.fn = 'mediainfo' 99 | FileBot.Node.requestExecute(parameters) 100 | } 101 | }, 102 | 103 | onLicense: function() { 104 | Ext.create('Ext.window.Window', { 105 | id: 'licenseWindow', 106 | items: [{ 107 | xtype: 'form', 108 | id: 'licenseForm', 109 | items: [{ 110 | xtype: 'hidden', 111 | name: 'fn', 112 | value: 'license' 113 | }, { 114 | xtype: 'textareafield', 115 | width: 540, 116 | height: 360, 117 | id: 'licenseTextArea', 118 | name: 'license', 119 | fieldCls: 'license', 120 | allowBlank: false, 121 | emptyText: '-----BEGIN PGP SIGNED MESSAGE-----\n\n\n\n\n\n\n\n\n-----BEGIN PGP SIGNATURE-----\n\n\n\n\n\n\n\n\n\n-----END PGP SIGNATURE-----' 122 | }], 123 | buttons: [ 124 | { 125 | xtype: 'filefield', 126 | width: 75, 127 | buttonOnly: true, 128 | accept: '.psm', 129 | buttonConfig: { 130 | text: 'Select', 131 | iconCls: 'select-btn' 132 | }, 133 | listeners: { 134 | change: function(evt) { 135 | var file = evt.fileInputEl.dom.files[0] 136 | var reader = new FileReader() 137 | reader.onload = function(evt) { 138 | Ext.getCmp('licenseTextArea').setValue(evt.target.result) 139 | } 140 | reader.readAsText(file) 141 | } 142 | } 143 | }, 144 | { xtype: 'tbfill' }, 145 | { text:'Purchase', iconCls: 'purchase-btn', handler: function(btn) { 146 | window.open(Ext.manifest.server.url.license_purchase, '_blank') 147 | }}, 148 | { text:'Activate', iconCls: 'license-btn', formBind: true, handler: function(btn) { 149 | var form = Ext.getCmp('licenseForm').getForm() 150 | if (form.isValid()) { 151 | FileBot.Node.requestExecute(form.getValues()) 152 | Ext.getCmp('licenseWindow').destroy() 153 | } 154 | }} 155 | ], 156 | }], 157 | title: 'Activate License', 158 | bodyPadding: 10, 159 | scrollable: false, 160 | resizable: false, 161 | closable: true 162 | }).show() 163 | }, 164 | 165 | onConfigure: function() { 166 | Ext.create('Ext.window.Window', { 167 | id: 'osdbWindow', 168 | items: [{ 169 | xtype: 'form', 170 | id: 'osdbForm', 171 | items: [{ 172 | xtype: 'hidden', 173 | name: 'fn', 174 | value: 'configure' 175 | }, { 176 | xtype: 'textfield', 177 | allowBlank: false, 178 | fieldLabel: 'Username', 179 | name: 'osdbUser', 180 | emptyText: 'username' 181 | }, { 182 | xtype: 'textfield', 183 | allowBlank: false, 184 | fieldLabel: 'Password', 185 | name: 'osdbPwd', 186 | emptyText: 'password', 187 | inputType: 'password' 188 | }], 189 | buttons: [ 190 | { text:'Register', handler: function(btn) { 191 | window.open(Ext.manifest.server.url.osdb_register, '_blank') 192 | }}, 193 | { text:'Login', formBind: true, handler: function(btn) { 194 | var form = Ext.getCmp('osdbForm').getForm() 195 | if (form.isValid()) { 196 | FileBot.Node.requestExecute(form.getValues()) 197 | Ext.getCmp('osdbWindow').destroy() 198 | } 199 | }} 200 | ], 201 | }], 202 | title: 'OpenSubtitles', 203 | bodyPadding: 10, 204 | scrollable: false, 205 | resizable: false, 206 | closable: true 207 | }).show() 208 | }, 209 | 210 | onSettings: function() { 211 | Ext.create('Ext.window.Window', { 212 | id: 'settingsWindow', 213 | items: [{ 214 | xtype: 'form', 215 | id: 'settingsForm', 216 | items: [{ 217 | xtype: 'hidden', 218 | name: 'fn', 219 | value: 'properties' 220 | }, { 221 | xtype: 'textfield', 222 | allowBlank: false, 223 | fieldLabel: 'Name', 224 | name: 'name', 225 | emptyText: 'net.filebot.xattr.store' 226 | }, { 227 | xtype: 'textfield', 228 | allowBlank: true, 229 | fieldLabel: 'Value', 230 | name: 'value', 231 | emptyText: '.xattr' 232 | }], 233 | buttons: [ 234 | { text:'Set', formBind: true, handler: function(btn) { 235 | var form = Ext.getCmp('settingsForm').getForm() 236 | if (form.isValid()) { 237 | FileBot.Node.requestExecute(form.getValues()) 238 | Ext.getCmp('settingsWindow').destroy() 239 | } 240 | }} 241 | ], 242 | }], 243 | title: 'System Properties', 244 | bodyPadding: 10, 245 | scrollable: false, 246 | resizable: false, 247 | closable: true 248 | }).show() 249 | }, 250 | 251 | onRevert: function() { 252 | var parameters = {'fn':'revert'} 253 | FileBot.Node.requestExecute(parameters) 254 | }, 255 | 256 | onInfo: function() { 257 | var parameters = {'fn':'sysinfo'} 258 | FileBot.Node.requestExecute(parameters) 259 | }, 260 | 261 | onClear: function() { 262 | var parameters = {'fn':'clear'} 263 | FileBot.Node.requestExecute(parameters) 264 | }, 265 | 266 | onEnvironment: function() { 267 | var parameters = {} 268 | FileBot.Node.requestEnvironment(parameters) 269 | }, 270 | 271 | onHelp: function() { 272 | window.open(Ext.manifest.server.url.help, '_blank') 273 | }, 274 | 275 | getExecuteParameters: function() { 276 | var form = this.getForm() 277 | if (form.isValid()) { 278 | return this.saveState() 279 | } 280 | return null 281 | }, 282 | 283 | getForm: function() { 284 | return this.getView().down('form').getForm() 285 | }, 286 | 287 | showEnvironmentForm: function(data) { 288 | // show status message 289 | if (data.message != null) { 290 | Ext.create('Ext.window.MessageBox', { 291 | // set closeAction to 'destroy' if this instance is not 292 | // intended to be reused by the application 293 | closeAction: 'destroy' 294 | }).show({ 295 | title: 'Environment', 296 | msg: data.message, 297 | buttons: Ext.MessageBox.OK, 298 | icon: Ext.MessageBox.INFO 299 | }) 300 | } 301 | 302 | // show environment input form 303 | if (data.environment != null) { 304 | Ext.create('Ext.window.Window', { 305 | id: 'environmentWindow', 306 | items: [{ 307 | xtype: 'form', 308 | id: 'environmentForm', 309 | items: [{ 310 | xtype: 'textareafield', 311 | width: 540, 312 | height: 360, 313 | id: 'environmentTextArea', 314 | name: 'environment', 315 | fieldCls: 'environment', 316 | allowBlank: true, 317 | value: data.environment, 318 | emptyText: 'export JAVA_OPTS=-Xmx512m' 319 | }], 320 | buttons: [ 321 | { text:'Set Environment', formBind: true, handler: function(btn) { 322 | var environment = Ext.getCmp('environmentForm').getForm().getValues()['environment'] 323 | FileBot.Node.requestEnvironment({'environment': environment}) 324 | Ext.getCmp('environmentWindow').destroy() 325 | }} 326 | ], 327 | }], 328 | title: 'Environment', 329 | bodyPadding: 10, 330 | scrollable: false, 331 | resizable: false, 332 | closable: true 333 | }).show() 334 | } 335 | } 336 | 337 | }); 338 | -------------------------------------------------------------------------------- /client-extjs/app/view/task/TaskModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the view model for the Main view of the application. 3 | */ 4 | Ext.define('FileBot.view.task.TaskModel', { 5 | extend: 'Ext.app.ViewModel', 6 | 7 | alias: 'viewmodel.task', 8 | 9 | stores: { 10 | folders: { 11 | storeId: 'folders-store', 12 | autoLoad: false, 13 | pageSize: 0, 14 | remoteFilter: false, 15 | remoteSort: false, 16 | 17 | fields: [ 18 | { name: 'path', type: 'string' } 19 | ] 20 | } 21 | }, 22 | 23 | data: { 24 | name: 'FileBot' 25 | } 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /client-extjs/app/view/tasklogcat/TaskLogCat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/29/15. 3 | */ 4 | Ext.define('FileBot.view.tasklogcat.TaskLogCat', { 5 | extend: 'Ext.panel.Panel', 6 | requires: [ 7 | 'FileBot.view.tasklogcat.TaskLogCatController' 8 | ], 9 | xtype: 'tasklogcat', 10 | 11 | viewModel: { 12 | type: 'tasklogcat' 13 | }, 14 | controller: 'tasklogcat', 15 | 16 | layout: { 17 | type: 'hbox', 18 | align: 'stretch' 19 | }, 20 | frame: false, 21 | autoScroll: true, 22 | autoWidth: true, 23 | scrollable: true, 24 | focusable: false, 25 | editable: false, 26 | flex: 1, 27 | border: 0, 28 | 29 | items: [{ 30 | xtype: 'textarea', 31 | id: 'logcatviewer', 32 | fieldCls: 'logcatviewer', 33 | emptyText: '$', 34 | scrollable: false, 35 | focusable: false, 36 | editable: false, 37 | flex: 1, 38 | border: 0 39 | }] 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /client-extjs/app/view/tasklogcat/TaskLogCatController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/29/15. 3 | */ 4 | Ext.define('FileBot.view.tasklogcat.TaskLogCatController', { 5 | extend: 'Ext.app.ViewController', 6 | requires: [ 7 | 'FileBot.view.tasklogcat.TaskLogCatModel', 8 | 'Ext.util.TaskManager', 9 | 'FileBot.Node' 10 | ], 11 | alias: 'controller.tasklogcat', 12 | 13 | // task that is corrently locked on for log viewing 14 | task: null, 15 | 16 | // run this task repeatedly until process has finished producing output 17 | refreshJob: null, 18 | 19 | init: function() { 20 | this.refreshJob = Ext.util.TaskManager.newTask({ 21 | run: this.refresh, 22 | interval: Ext.manifest.server.refresh, 23 | scope: this 24 | }) 25 | 26 | // watch log of the newly selected task 27 | FileBot.getApplication().on('selectTask', function(record) { 28 | // stop existing refresh job if any 29 | this.refreshJob.stop() 30 | 31 | this.task = record 32 | this.refresh() 33 | 34 | // if task has not completed yet keep watching for new output 35 | if (this.task.status == '') { 36 | this.refreshJob.start() 37 | } 38 | }, this) 39 | 40 | FileBot.getApplication().on('version', function(message) { 41 | var val = ['$ filebot -version', message].join('\n') 42 | var cmp = Ext.getCmp('logcatviewer') 43 | cmp.setValue(val) 44 | }, this) 45 | }, 46 | 47 | refresh: function() { 48 | // fetch new log and update textarea 49 | FileBot.Node.fetchLog(this.task, function(response) { 50 | var val = response.responseText 51 | var cmp = Ext.getCmp('logcatviewer') 52 | 53 | // detect blocked requests 54 | if (!val) { 55 | console.log('Invalid Response', response) 56 | val = 'Invalid Response' 57 | + '\n└─ url: ' + JSON.stringify(response.request.requestOptions.url) 58 | + '\n└─ status: ' + JSON.stringify(response.status) 59 | + '\n└─ response: ' + JSON.stringify(response.responseText) 60 | + '\n\nPlease disable your Ad Blocker. Check Inspect ➔ Console / Network for details.' 61 | } 62 | 63 | if (val != cmp.getValue()) { 64 | cmp.setValue(val) 65 | 66 | // stop checking for updates once the task is done 67 | if (val.match(/\[Process (?:completed|error|killed)\]/g)) { 68 | this.refreshJob.stop() 69 | } 70 | } 71 | }.bind(this)) 72 | } 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /client-extjs/app/view/tasklogcat/TaskLogCatModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/29/15. 3 | */ 4 | Ext.define('FileBot.view.tasklogcat.TaskLogCatModel', { 5 | extend: 'Ext.app.ViewModel', 6 | alias: 'viewmodel.tasklogcat', 7 | 8 | stores: { 9 | /* 10 | A declaration of Ext.data.Store configurations that are first processed as binds to produce an effective 11 | store configuration. For example: 12 | 13 | users: { 14 | model: 'TaskLogCat', 15 | autoLoad: true 16 | } 17 | */ 18 | }, 19 | 20 | data: { 21 | /* This object holds the arbitrary data that populates the ViewModel and is then available for binding. */ 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /client-extjs/app/view/taskmanager/TaskManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/25/15. 3 | */ 4 | Ext.define('FileBot.view.taskmanager.TaskManager', { 5 | extend: 'Ext.grid.Panel', 6 | requires: [ 7 | 'FileBot.view.taskmanager.TaskManagerController', 8 | 'FileBot.view.taskmanager.TaskManagerModel', 9 | 'FileBot.Node' 10 | ], 11 | viewModel: { 12 | type: 'taskmanager' 13 | }, 14 | controller: 'taskmanager', 15 | xtype: 'taskmanager', 16 | 17 | bind: '{tasks}', 18 | 19 | tools: [{ 20 | type: 'print', 21 | callback: function() { 22 | FileBot.Node.openEndpoint('output', {}) 23 | } 24 | }], 25 | 26 | listeners: { 27 | select: function(view, record) { 28 | // broadcast event 29 | FileBot.getApplication().fireEvent('selectTask', record.data) 30 | } 31 | }, 32 | 33 | columns: [ 34 | { 35 | text: 'Date', 36 | dataIndex: 'date', 37 | width: 120, 38 | renderer: function(val) { 39 | var t = new Date(val) 40 | var date = Ext.util.Format.date(t, 'd M') 41 | var time = Ext.util.Format.date(t, 'H:i:s') 42 | return ''+date+''+''+time+'' 43 | } 44 | }, { 45 | text: 'Status', 46 | dataIndex: 'status', 47 | width: 100, 48 | align: 'left', 49 | renderer: function(val) { 50 | if (val == '') 51 | return 'Running' 52 | if (val == '0') 53 | return 'Complete' 54 | if (val == '100') 55 | return 'Complete' // NOOP 56 | if (val == '137') 57 | return 'Cancelled' 58 | if (val == '1000') 59 | return 'Scheduled' 60 | else 61 | return 'Failure' 62 | } 63 | }, { 64 | menuDisabled: true, 65 | sortable: false, 66 | xtype: 'actioncolumn', 67 | focusable: false, 68 | width: 50, 69 | align: 'center', 70 | items: [{ 71 | getClass: function(v, meta, rec) { 72 | var val = rec.get('status') 73 | if (val == '') 74 | return 'cancel-col' 75 | if (val == '0') 76 | return 'ok-col' 77 | if (val == '100') 78 | return 'ok-col' // NOOP 79 | if (val == '1000') 80 | return 'schedule-col' 81 | else 82 | return 'fail-col' 83 | }, 84 | getTip: function(v, meta, rec) { 85 | var val = rec.get('status') 86 | if (val == '') 87 | return 'Cancel' 88 | if (val == '0') 89 | return 'Success' 90 | if (val == '100') 91 | return 'No Operation' 92 | if (val == '137') 93 | return 'Cancelled' 94 | if (val == '1000') 95 | return 'Execute Task' 96 | else 97 | return 'Error (' + val + ')' 98 | }, 99 | handler: function(grid, rowIndex, colIndex) { 100 | var rec = grid.getStore().getAt(rowIndex) 101 | var val = rec.get('status') 102 | if (val == '') { 103 | FileBot.Node.requestKill({id: rec.get('id')}) 104 | } 105 | else if (val == '1000') { 106 | FileBot.Node.openEndpoint("task", {id: rec.get('id')}) 107 | } 108 | } 109 | }] 110 | }], 111 | 112 | title: 'Tasks', 113 | sortableColumns: false, 114 | enableColumnHide: false, 115 | enableColumnMove: false, 116 | 117 | viewConfig: { 118 | enableTextSelection: false, 119 | deferEmptyText: false, 120 | loadMask: false 121 | } 122 | }); 123 | -------------------------------------------------------------------------------- /client-extjs/app/view/taskmanager/TaskManagerController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/25/15. 3 | */ 4 | Ext.define('FileBot.view.taskmanager.TaskManagerController', { 5 | extend: 'Ext.app.ViewController', 6 | requires: [ 7 | 'FileBot.view.taskmanager.TaskManagerModel', 8 | 'Ext.util.TaskManager' 9 | ], 10 | alias: 'controller.taskmanager', 11 | 12 | /** 13 | * Called when the view is created 14 | */ 15 | init: function() { 16 | const store = this.getViewModel().getStore('tasks') 17 | 18 | FileBot.getApplication().on('init', function() { 19 | // start fetching task data 20 | store.setProxy(FileBot.Node.getDataProxy('tasks')) 21 | // refresh task state every few seconds 22 | Ext.util.TaskManager.start({ 23 | run: store.reload, 24 | interval: Ext.manifest.server.refresh, 25 | scope: store 26 | }) 27 | }, this) 28 | 29 | // immediately refresh data when new tasks are executed 30 | FileBot.getApplication().on('execute', function() { 31 | // auto-select first row after new rows have been loaded and rendered 32 | this.selectFirstRowOnUpdate = true 33 | store.reload() 34 | }, this) 35 | 36 | // same for filebot --license calls 37 | FileBot.getApplication().on('license', function() { 38 | // auto-select first row after new rows have been loaded and rendered 39 | this.selectFirstRowOnUpdate = true 40 | store.reload() 41 | }, this) 42 | 43 | // auto-select newly added tasks (on 'add' event doesn't work for grid) 44 | store.on('datachanged', this.updateFirstRowSelection, this) 45 | }, 46 | 47 | 48 | selectFirstRowOnUpdate: false, 49 | updateFirstRowSelection: function() { 50 | if (this.selectFirstRowOnUpdate) { 51 | this.selectFirstRowOnUpdate = false 52 | this.getView().getSelectionModel().select(0) 53 | } 54 | } 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /client-extjs/app/view/taskmanager/TaskManagerModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by reinhard on 4/25/15. 3 | */ 4 | Ext.define('FileBot.view.taskmanager.TaskManagerModel', { 5 | extend: 'Ext.app.ViewModel', 6 | requires: [ 7 | 'FileBot.Node' 8 | ], 9 | alias: 'viewmodel.taskmanager', 10 | 11 | stores: { 12 | tasks: { 13 | storeId: 'tasks-store', 14 | autoLoad: false, 15 | pageSize: 0, 16 | remoteFilter: false, 17 | remoteSort: false, 18 | 19 | fields: [ 20 | { name: 'id', type: 'string' }, 21 | { name: 'date', type: 'int' }, 22 | { name: 'status', type: 'string' } 23 | ], 24 | 25 | sorters: [{ 26 | property: 'date', 27 | direction: 'DESC' 28 | }] 29 | } 30 | }, 31 | 32 | data: { 33 | /* This object holds the arbitrary data that populates the ViewModel and is then available for binding. */ 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /client-extjs/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /client-extjs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | FileBot Node Client 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client-extjs/resources/images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/clear.png -------------------------------------------------------------------------------- /client-extjs/resources/images/configure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/configure.png -------------------------------------------------------------------------------- /client-extjs/resources/images/environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/environment.png -------------------------------------------------------------------------------- /client-extjs/resources/images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/error.png -------------------------------------------------------------------------------- /client-extjs/resources/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/favicon.png -------------------------------------------------------------------------------- /client-extjs/resources/images/generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/generic.png -------------------------------------------------------------------------------- /client-extjs/resources/images/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/help.png -------------------------------------------------------------------------------- /client-extjs/resources/images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/info.png -------------------------------------------------------------------------------- /client-extjs/resources/images/license.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/license.png -------------------------------------------------------------------------------- /client-extjs/resources/images/mediainfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/mediainfo.png -------------------------------------------------------------------------------- /client-extjs/resources/images/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/ok.png -------------------------------------------------------------------------------- /client-extjs/resources/images/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/preferences.png -------------------------------------------------------------------------------- /client-extjs/resources/images/purchase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/purchase.png -------------------------------------------------------------------------------- /client-extjs/resources/images/revert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/revert.png -------------------------------------------------------------------------------- /client-extjs/resources/images/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/run.png -------------------------------------------------------------------------------- /client-extjs/resources/images/schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/schedule.png -------------------------------------------------------------------------------- /client-extjs/resources/images/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/select.png -------------------------------------------------------------------------------- /client-extjs/resources/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/settings.png -------------------------------------------------------------------------------- /client-extjs/resources/images/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/stop.png -------------------------------------------------------------------------------- /client-extjs/resources/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/client-extjs/resources/images/user.png -------------------------------------------------------------------------------- /client-extjs/resources/main.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Source+Code+Pro'); 2 | 3 | 4 | .ok-col { 5 | background-image: url('images/ok.png'); 6 | background-size: contain; 7 | } 8 | 9 | .fail-col { 10 | background-image: url('images/error.png'); 11 | background-size: contain; 12 | } 13 | 14 | .cancel-col { 15 | background-image: url('images/stop.png'); 16 | background-size: contain; 17 | } 18 | 19 | .schedule-col { 20 | background-image: url('images/schedule.png'); 21 | background-size: contain; 22 | } 23 | 24 | .schedule-col:hover { 25 | background-image: url('images/run.png'); 26 | background-size: contain; 27 | } 28 | 29 | .run-btn { 30 | background-image: url('images/run.png'); 31 | background-size: contain; 32 | } 33 | 34 | .configure-btn { 35 | background-image: url('images/configure.png'); 36 | background-size: contain; 37 | } 38 | 39 | .purchase-btn { 40 | background-image: url('images/purchase.png'); 41 | background-size: contain; 42 | } 43 | 44 | .license-btn { 45 | background-image: url('images/license.png'); 46 | background-size: contain; 47 | } 48 | 49 | .select-btn { 50 | background-image: url('images/select.png'); 51 | background-size: contain; 52 | } 53 | 54 | .license-item { 55 | background-image: url('images/license.png'); 56 | background-size: contain; 57 | } 58 | 59 | .configure-item { 60 | background-image: url('images/user.png'); 61 | background-size: contain; 62 | } 63 | 64 | .environment-item { 65 | background-image: url('images/environment.png'); 66 | background-size: contain; 67 | } 68 | 69 | .sysinfo-item { 70 | background-image: url('images/info.png'); 71 | background-size: contain; 72 | } 73 | 74 | .mediainfo-item { 75 | background-image: url('images/mediainfo.png'); 76 | background-size: contain; 77 | } 78 | 79 | .settings-item { 80 | background-image: url('images/settings.png'); 81 | background-size: contain; 82 | } 83 | 84 | .clear-item { 85 | background-image: url('images/clear.png'); 86 | background-size: contain; 87 | } 88 | 89 | .revert-item { 90 | background-image: url('images/revert.png'); 91 | background-size: contain; 92 | } 93 | 94 | .dryrun-item { 95 | background-image: url('images/generic.png'); 96 | background-size: contain; 97 | } 98 | 99 | .schedule-item { 100 | background-image: url('images/schedule.png'); 101 | background-size: contain; 102 | } 103 | 104 | .help-item { 105 | background-image: url('images/help.png'); 106 | background-size: contain; 107 | } 108 | 109 | textarea.license { 110 | font-family: 'Source Code Pro', monospace !important; 111 | cursor: text !important; 112 | } 113 | 114 | textarea.environment { 115 | font-family: 'Source Code Pro', monospace !important; 116 | cursor: text !important; 117 | } 118 | 119 | textarea.logcatviewer { 120 | font-family: 'Source Code Pro', monospace !important; 121 | font-size: 8pt !important; 122 | background: #300A24 !important; 123 | color: #FFFFFF !important; 124 | cursor: text !important; 125 | line-height: 1.2em !important; 126 | white-space: pre !important; 127 | word-wrap: normal !important; 128 | } 129 | 130 | span.crontab { 131 | user-select: none; 132 | } 133 | 134 | span.crontab code { 135 | font-family: 'Source Code Pro', monospace !important; 136 | background: #300A24 !important; 137 | color: #FFFFFF !important; 138 | cursor: text !important; 139 | user-select: text !important; 140 | padding: 0px 1px !important; 141 | } 142 | 143 | /* 144 | * FIX: ExtJS 6.5.0 Fieldset legend are not visible in Safari 11 145 | * https://www.sencha.com/forum/showthread.php?423768-ExtJS-6-5-0-Fieldset-legend-are-not-visible-in-Safari-11 146 | */ 147 | .x-fieldset { 148 | overflow: visible !important; 149 | } 150 | -------------------------------------------------------------------------------- /client-extjs/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * An object containing key value pair framework descriptors. 4 | * 5 | * The value can be a string or an object containing at least one of "dir" or "pkg", 6 | * where "dir" can be a filesystem path to the framework sources and "pkg" can be a 7 | * package name. For example: 8 | * 9 | * "frameworks": { 10 | * 11 | * "ext-x": "/absolute/path/to/ext", 12 | * "ext-y": { 13 | * "source": "../relative/path/to/ext", 14 | * "path": "ext" 15 | * }, 16 | * "ext-z": { 17 | * "package": "ext@n.n.n", 18 | * "path": "ext-n.n.n" 19 | * }, 20 | * "touch": "touch" 21 | * } 22 | * 23 | */ 24 | "frameworks": { 25 | "ext": { 26 | "path":"ext", 27 | "version":"6.2.0.981" 28 | } 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | }, 37 | 38 | /** 39 | * This is the folder for build outputs in the workspace. 40 | */ 41 | "build": { 42 | "dir": "${workspace.dir}/build" 43 | }, 44 | 45 | /** 46 | * These configs determine where packages are generated and extracted to (when downloaded). 47 | */ 48 | "packages": { 49 | /** 50 | * This folder contains all local packages. 51 | * If a comma-separated string is used as value the first path will be used as the path to generate new packages. 52 | */ 53 | "dir": "${workspace.dir}/packages/local,${workspace.dir}/packages", 54 | 55 | /** 56 | * This folder contains all extracted (remote) packages. 57 | */ 58 | "extract": "${workspace.dir}/packages/remote" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | ANT := ant -lib lib 2 | 3 | build-production: 4 | $(ANT) clean build 5 | 6 | publish: clean build-production 7 | $(ANT) tar spk syno-repo spk-dsm6 syno-repo-dsm6 qpkg checksum 8 | 9 | run-client: 10 | docker run --rm -it -v "${PWD}/client-extjs:/src" -p 1841:1841 rednoah/sencha-build app watch 11 | 12 | run-server: 13 | docker run --rm -it -v "${PWD}/server-nodejs:/server-nodejs" -v "${PWD}/dist:/dist" --workdir /server-nodejs -p 5452:5452 --entrypoint /server-nodejs/start.sh rednoah/filebot:node 14 | 15 | npm-install: 16 | docker run --rm -it -v "${PWD}/server-nodejs:/server-nodejs" --workdir /server-nodejs node:latest npm install 17 | 18 | sencha-app-upgrade: 19 | docker run --rm -it -v "${PWD}/client-extjs:/src" rednoah/sencha-build app upgrade 20 | 21 | sencha-show-props: 22 | docker run --rm -it -v "${PWD}/client-extjs:/src" rednoah/sencha-build diag show-props 23 | 24 | sencha-bash: 25 | docker run --rm -it -v "${PWD}/client-extjs:/src" --entrypoint /bin/bash rednoah/sencha-build 26 | 27 | resolve: npm-install 28 | -rm -rvf lib 29 | $(ANT) resolve 30 | 31 | spk: 32 | $(ANT) clean build spk 33 | 34 | qpkg: 35 | $(ANT) clean build qpkg 36 | 37 | clean: 38 | rm -rvf build dist release 39 | 40 | reset: 41 | git reset --hard 42 | git pull 43 | git --no-pager log -1 44 | -------------------------------------------------------------------------------- /package.properties: -------------------------------------------------------------------------------- 1 | # NOTE: package name and package version are used in external projects and automated build systems 2 | package: filebot-node 3 | version: 0.4.8 4 | 5 | title: FileBot Node 6 | description: FileBot Node allows you to execute, monitor and schedule filebot amc script commands via an easy-to-use web interface. FileBot Node requires Node.js and FileBot. 7 | -------------------------------------------------------------------------------- /package/generic/start: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | echo " 5 | -------------------- Run $0 (PID: $$) -------------------- $(date) 6 | " 7 | 8 | 9 | export FILEBOT_NODE_HOST="0.0.0.0" # bind to all interfaces 10 | 11 | export FILEBOT_NODE_AUTH="BASIC" 12 | export FILEBOT_NODE_AUTH_USER="$USER" # default username is your username 13 | export FILEBOT_NODE_AUTH_PASS="$USER" # default password is your username -> PLEASE DO CHANGE !!! 14 | 15 | export FILEBOT_NODE_HTTP="YES" 16 | export FILEBOT_NODE_HTTP_PORT="5452" 17 | 18 | export FILEBOT_NODE_HTTPS="NO" 19 | export FILEBOT_NODE_HTTPS_PORT="5453" 20 | export FILEBOT_NODE_HTTPS_KEY="/path/to/server.key" 21 | export FILEBOT_NODE_HTTPS_CRT="/path/to/server.crt" 22 | 23 | export FILEBOT_NODE_DATA="$(dirname $0)/data" 24 | export FILEBOT_TASK_CMD="$(dirname $0)/task" 25 | 26 | export FILEBOT_CMD="filebot" 27 | export FILEBOT_CMD_CWD="$PWD" 28 | export FILEBOT_CMD_UID=$(id -u $USER) 29 | export FILEBOT_CMD_GID=$(id -g $USER) 30 | 31 | export FILEBOT_NODE_CLIENT="client" 32 | 33 | 34 | # import user environment 35 | if [ -f "$FILEBOT_NODE_DATA/environment.sh" ]; then 36 | . "$FILEBOT_NODE_DATA/environment.sh" 37 | fi 38 | 39 | 40 | exec node "server/app.js" 41 | -------------------------------------------------------------------------------- /package/generic/task: -------------------------------------------------------------------------------- 1 | #!/bin/bash -u 2 | 3 | export FILEBOT_NODE_DATA="$(dirname $0)/data" 4 | export FILEBOT_NODE_TASK="$1" 5 | 6 | 7 | # sanity check 8 | if [ ! -f "$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" ]; then 9 | echo "$0: Task $FILEBOT_NODE_TASK does not exist" 10 | exit 1 11 | fi 12 | 13 | 14 | # import user environment 15 | if [ -f "$FILEBOT_NODE_DATA/environment.sh" ]; then 16 | . "$FILEBOT_NODE_DATA/environment.sh" 17 | fi 18 | 19 | 20 | # execute filebot task and record output 21 | filebot "@$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" 2>&1 | tee -a "$FILEBOT_NODE_DATA/log/$FILEBOT_NODE_TASK.log" 22 | 23 | 24 | # get filebot exit code (and not tee exit code) 25 | STATUS="${PIPESTATUS[0]}" 26 | 27 | # treat ExitCode.NOOP as ExitCode.SUCCESS 28 | if [ $STATUS -eq 100 ]; then 29 | exit 0 30 | fi 31 | 32 | exit $STATUS 33 | -------------------------------------------------------------------------------- /package/qnap/.gitignore: -------------------------------------------------------------------------------- 1 | certificate 2 | private_key 3 | -------------------------------------------------------------------------------- /package/qnap/icons/filebot-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/qnap/icons/filebot-node.png -------------------------------------------------------------------------------- /package/qnap/icons/filebot-node_100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/qnap/icons/filebot-node_100.png -------------------------------------------------------------------------------- /package/qnap/icons/filebot-node_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/qnap/icons/filebot-node_80.png -------------------------------------------------------------------------------- /package/qnap/icons/filebot-node_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/qnap/icons/filebot-node_gray.png -------------------------------------------------------------------------------- /package/qnap/package_routines: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Define any package specific operations that shall be performed when 3 | # the package is installed. 4 | ###################################################################### 5 | 6 | pkg_post_install(){ 7 | FILEBOT_NODE_DATA="$SYS_QPKG_DIR/data" 8 | 9 | # prepare application data folder with read permissions for all users 10 | mkdir "$FILEBOT_NODE_DATA" 11 | 12 | # prepare log file permissions 13 | touch "$FILEBOT_NODE_DATA/filebot.log" 14 | chmod 666 "$FILEBOT_NODE_DATA/filebot.log" 15 | 16 | # set -Xmx to 0.8 of physical memory (on low-memory devices) 17 | if [ ! -f "$FILEBOT_NODE_DATA/environment.sh" ]; then 18 | JAVA_OPTS=$(awk '/MemTotal:/ { xmx = ($2*0.8)/1024; if (xmx < 1024) { printf "-Xmx%dm", xmx }; exit}' /proc/meminfo) 19 | echo "export JAVA_OPTS=$JAVA_OPTS" > "$FILEBOT_NODE_DATA/environment.sh" 20 | fi 21 | 22 | # chown data folder 23 | chown -R admin.administrators "$FILEBOT_NODE_DATA" 24 | } 25 | -------------------------------------------------------------------------------- /package/qnap/qpkg.cfg: -------------------------------------------------------------------------------- 1 | QPKG_NAME="filebot-node" 2 | QPKG_DISPLAY_NAME="FileBot Node" 3 | QPKG_VER="0.0.0" 4 | QPKG_AUTHOR="FileBot" 5 | QPKG_SUMMARY="FileBot Node allows you to execute filebot via QNAP QTS" 6 | QPKG_LICENSE="GPLv3" 7 | QPKG_RC_NUM="453" 8 | QPKG_SERVICE_PROGRAM="filebot-node-service.sh" 9 | QTS_MINI_VERSION="4.2.0" 10 | 11 | 12 | QPKG_PROXY_PATH="/filebot-node/" 13 | QPKG_WEBUI="/" 14 | QPKG_WEB_PORT="5452" 15 | QPKG_USE_PROXY="1" 16 | QPKG_DESKTOP_APP="1" 17 | QPKG_DESKTOP_APP_WIN_WIDTH="1000" 18 | QPKG_DESKTOP_APP_WIN_HEIGHT="600" 19 | -------------------------------------------------------------------------------- /package/qnap/shared/filebot-node-service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export QPKG_CONF="/etc/config/qpkg.conf" 3 | export QPKG_NAME="filebot-node" 4 | export QPKG_ROOT=$(/sbin/getcfg $QPKG_NAME Install_Path -f $QPKG_CONF) 5 | export QPKG_DEFAULT_VOLUME=$(/sbin/getcfg SHARE_DEF defVolMP -f /etc/config/def_share.info) 6 | 7 | 8 | case "$1" in 9 | start) 10 | ENABLED=$(/sbin/getcfg $QPKG_NAME Enable -u -d FALSE -f $QPKG_CONF) 11 | if [ "$ENABLED" != "TRUE" ]; then 12 | echo "$QPKG_NAME is disabled." 13 | exit 1 14 | fi 15 | 16 | # create /opt/filebot-node symlink 17 | ln -sf "$QPKG_ROOT" "/opt/$QPKG_NAME" 18 | 19 | # start service 20 | "$QPKG_ROOT/start" >> "$QPKG_ROOT/$QPKG_NAME.log" 2>&1 & 21 | exit $? 22 | ;; 23 | 24 | stop) 25 | rm "/opt/$QPKG_NAME" 26 | kill "$("$0" status)" >> "$QPKG_ROOT/$QPKG_NAME.log" 2>&1 27 | exit $? 28 | ;; 29 | 30 | status) 31 | curl -fs 'http://127.0.0.1:5452/status' | grep -oE '"pid":[0-9]+' | grep -oE '[0-9]+' 32 | exit $? 33 | ;; 34 | 35 | restart) 36 | $0 stop 37 | $0 start 38 | exit $? 39 | ;; 40 | 41 | *) 42 | echo "Usage: $0 {start|stop|status|restart}" 43 | exit 1 44 | ;; 45 | esac 46 | -------------------------------------------------------------------------------- /package/qnap/shared/start: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | echo " 5 | -------------------- Run $0 (PID: $$) -------------------- $(date) 6 | " 7 | 8 | 9 | export FILEBOT_NODE_HOST="127.0.0.1" # bind to local apache reverse proxy 10 | export FILEBOT_NODE_AUTH="QNAP" 11 | 12 | export FILEBOT_NODE_HTTP="YES" 13 | export FILEBOT_NODE_HTTP_PORT="5452" 14 | 15 | export USER="admin" # set admin as filebot user 16 | 17 | export FILEBOT_NODE_DATA="/opt/filebot-node/data" 18 | export FILEBOT_TASK_CMD="/opt/filebot-node/task" 19 | 20 | export FILEBOT_CMD="filebot" 21 | export FILEBOT_CMD_CWD="/share" 22 | export FILEBOT_CMD_UID=$(id -u $USER) 23 | export FILEBOT_CMD_GID=$(cat /etc/group | grep 'administrators' | cut -d: -f3) # cannot use `id -u $USER` because the result is 100:users but we need 101:administrators because users don't have execute permissions 24 | 25 | export FILEBOT_NODE_CLIENT="client" 26 | 27 | 28 | # set working dir 29 | cd "$QPKG_ROOT" 30 | 31 | 32 | # import user environment 33 | . "$FILEBOT_NODE_DATA/environment.sh" 34 | 35 | 36 | # sanity check 37 | if [ -z "$FILEBOT_CMD_UID" ]; then 38 | echo "id -u $USER must not be empty" 39 | exit 1 40 | fi 41 | 42 | 43 | # nodejsv8 package comes with broken permissions, so we need to fix them here first 44 | find /usr/local/bin -type l -name node -exec chmod +x {} + 45 | 46 | # check $PATH 47 | NODE="$(which node)" 48 | 49 | # check QNAP packages 50 | if [ ! -x "$NODE" ]; then 51 | NODE="$(find "$QPKG_DEFAULT_VOLUME"/.qpkg/{QDMS,nodejs*} -maxdepth 3 -type f -name node 2> /dev/null | head -n1)" 52 | echo "NODE=$NODE" 53 | fi 54 | 55 | 56 | # node: command not found 57 | if [ ! -x "$NODE" ]; then 58 | WARN="[node: command not found] Please install 'Node.js' or 'Media Streaming Add-on' in the App Center." 59 | # print warning to console log 60 | echo "$WARN" 61 | # send to QNAP notification center 62 | /sbin/log_tool -t2 -uSystem -p127.0.0.1 -mlocalhost -a "[FileBot Node] $WARN" 63 | # node: command not found 64 | exit 127 65 | fi 66 | 67 | 68 | exec "$NODE" "server/app.js" 69 | -------------------------------------------------------------------------------- /package/qnap/shared/task: -------------------------------------------------------------------------------- 1 | #!/bin/bash -u 2 | 3 | export USER=$(whoami) # make sure that $USER is set correctly 4 | 5 | export FILEBOT_NODE_DATA="/opt/filebot-node/data" 6 | export FILEBOT_NODE_TASK="$1" 7 | 8 | 9 | # sanity check 10 | if [ ! -f "$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" ]; then 11 | echo "$0: Task $FILEBOT_NODE_TASK does not exist" 12 | exit 1 13 | fi 14 | 15 | 16 | # import user environment 17 | . "$FILEBOT_NODE_DATA/environment.sh" 18 | 19 | 20 | # execute filebot task and record output 21 | filebot "@$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" 2>&1 | tee -a "$FILEBOT_NODE_DATA/log/$FILEBOT_NODE_TASK.log" 22 | 23 | 24 | # get filebot exit code (and not tee exit code) 25 | STATUS="${PIPESTATUS[0]}" 26 | 27 | # treat ExitCode.NOOP as ExitCode.SUCCESS 28 | if [ $STATUS -eq 100 ]; then 29 | exit 0 30 | fi 31 | 32 | exit $STATUS 33 | -------------------------------------------------------------------------------- /package/synology-dsm7/conf/privilege: -------------------------------------------------------------------------------- 1 | { 2 | "defaults":{ 3 | "run-as": "package" 4 | }, 5 | "username": "FileBot", 6 | "groupname": "FileBot" 7 | } 8 | -------------------------------------------------------------------------------- /package/synology-dsm7/conf/resource: -------------------------------------------------------------------------------- 1 | { 2 | "usr-local-linker": { 3 | "bin": ["bin/filebot-node-task"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /package/synology-dsm7/scripts/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | FILEBOT_NODE_DATA="/var/packages/filebot-node/var" 3 | 4 | # prepare log file permissions 5 | touch "$FILEBOT_NODE_DATA/filebot.log" 6 | chmod 666 "$FILEBOT_NODE_DATA/filebot.log" 7 | 8 | # set -Xmx to 0.8 of physical memory (on low-memory devices) 9 | if [ ! -f "$FILEBOT_NODE_DATA/environment.sh" ]; then 10 | JAVA_OPTS=$(awk '/MemTotal:/ { xmx = ($2*0.8)/1024; if (xmx < 1024) { printf "-Xmx%dm", xmx }; exit}' /proc/meminfo) 11 | echo "export JAVA_OPTS=$JAVA_OPTS" > "$FILEBOT_NODE_DATA/environment.sh" 12 | fi 13 | 14 | # print notification 15 | { 16 | cat << EOF 17 |

NOTE

18 |

FileBot Node cannot access your files unless you explicitly grant Read/Write permissions.

19 |
    20 |
  1. Open Control Panel and select Shared Folder
  2. 21 |
  3. Select the share that contains your files and click Edit
  4. 22 |
  5. Select Permissions
  6. 23 |
  7. Change Local users to System internal user
  8. 24 |
  9. Check Read/Write for FileBot and click Save
  10. 25 |
26 | EOF 27 | } >> "$SYNOPKG_TEMP_LOGFILE" 28 | 29 | # return successfully 30 | exit 0 31 | -------------------------------------------------------------------------------- /package/synology-dsm7/scripts/postuninst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | FILEBOT_NODE_DATA="/var/packages/filebot-node/var" 3 | 4 | # purge user data files 5 | if [ "$SYNOPKG_PKG_STATUS" == "UNINSTALL" ]; then 6 | rm -rv "$FILEBOT_NODE_DATA"/* 7 | fi 8 | 9 | # return successfully 10 | exit 0 11 | -------------------------------------------------------------------------------- /package/synology-dsm7/scripts/postupgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exit 0 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/scripts/preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | # require node 5 | if ! which node; then 6 | echo '

FileBot Node requires Node.js. Please install Node.js first.

' >> "$SYNOPKG_TEMP_LOGFILE" 7 | exit 1 8 | fi 9 | 10 | 11 | # require filebot 12 | if ! which filebot; then 13 | echo '

FileBot Node requires FileBot. Please install FileBot first.

' >> "$SYNOPKG_TEMP_LOGFILE" 14 | exit 1 15 | fi 16 | 17 | 18 | # nginx reverse proxy configuration installed by FileBot Node pre-DSM 6.2.4 (as default root user) 19 | # can no longer be uninstalled by FileBot Node after DSM 6.2.4 (as new default system user) 20 | if [ -f '/usr/local/etc/nginx/conf.d/dsm.filebot-node.conf' ]; then 21 | { 22 | echo '

Please use sudo to remove dsm.filebot-node.conf manually before installing FileBot Node:

' 23 | echo '

sudo rm -v /usr/local/etc/nginx/conf.d/dsm.filebot-node.conf

' 24 | echo '

* The nginx reverse proxy configuration file installed by FileBot Node pre-DSM 6.2.4 (as default root user) can no longer be uninstalled by FileBot Node after DSM 6.2.4 (as new default system user) because the system user does not have root permissions.

' 25 | } >> "$SYNOPKG_TEMP_LOGFILE" 26 | exit 1 27 | fi 28 | 29 | 30 | # return successfully 31 | exit 0 32 | -------------------------------------------------------------------------------- /package/synology-dsm7/scripts/preuninst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exit 0 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/scripts/preupgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exit 0 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/scripts/start-stop-status: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | case "$1" in 5 | start) 6 | "$SYNOPKG_PKGDEST/bin/filebot-node-start" >> "$SYNOPKG_PKGVAR/filebot-node.log" 2>&1 & 7 | exit $? 8 | ;; 9 | 10 | stop) 11 | killall -u "FileBot" -- "node" >> "$SYNOPKG_PKGVAR/filebot-node.log" 2>&1 12 | exit $? 13 | ;; 14 | 15 | status) 16 | ps -u "FileBot" | grep "node" 17 | exit $? 18 | ;; 19 | esac 20 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/bin/filebot-node-start: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | echo " 5 | -------------------- Run $0 (PID: $$) -------------------- $(date) 6 | " 7 | 8 | 9 | export FILEBOT_NODE_HOST="127.0.0.1" # bind to local host 10 | export FILEBOT_NODE_AUTH="SYNO" 11 | 12 | export FILEBOT_NODE_HTTP="YES" 13 | export FILEBOT_NODE_HTTP_PORT="5452" 14 | 15 | export FILEBOT_NODE_DATA="/var/packages/filebot-node/var" 16 | 17 | export FILEBOT_TASK_CMD="filebot-node-task" 18 | 19 | export FILEBOT_CMD="filebot" 20 | export FILEBOT_CMD_CWD="$SYNOPKG_PKGDEST_VOL" 21 | 22 | export FILEBOT_CMD_UID=$(id -u) 23 | export FILEBOT_CMD_GID=$(id -g) 24 | 25 | # set working dir 26 | cd "/var/packages/filebot-node/target" 27 | 28 | 29 | # import user environment 30 | . "$FILEBOT_NODE_DATA/environment.sh" 31 | 32 | 33 | exec node "server/app.js" 34 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/bin/filebot-node-task: -------------------------------------------------------------------------------- 1 | #!/bin/bash -u 2 | 3 | export FILEBOT_NODE_DATA="/var/packages/filebot-node/var" 4 | export FILEBOT_NODE_TASK="$1" 5 | 6 | 7 | # sanity check 8 | if [ ! -f "$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" ]; then 9 | echo "$0: Task $FILEBOT_NODE_TASK does not exist" 10 | exit 1 11 | fi 12 | 13 | 14 | # import user environment 15 | . "$FILEBOT_NODE_DATA/environment.sh" 16 | 17 | 18 | # execute filebot task and record output 19 | filebot "@$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" 2>&1 | tee -a "$FILEBOT_NODE_DATA/log/$FILEBOT_NODE_TASK.log" 20 | 21 | 22 | # get filebot exit code (and not tee exit code) 23 | STATUS="${PIPESTATUS[0]}" 24 | 25 | # treat ExitCode.NOOP as ExitCode.SUCCESS 26 | if [ $STATUS -eq 100 ]; then 27 | exit 0 28 | fi 29 | 30 | exit $STATUS 31 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/FileBot.NodeClient.js: -------------------------------------------------------------------------------- 1 | // Namespace definition 2 | Ext.ns("FileBot.NodeClient"); 3 | 4 | // Application definition 5 | Ext.define("FileBot.NodeClient.AppInstance", { 6 | extend: "SYNO.SDS.AppInstance", 7 | appWindowName: "FileBot.NodeClient.AppWindow" 8 | }); 9 | 10 | // Window definition 11 | Ext.define("FileBot.NodeClient.AppWindow", { 12 | extend: "SYNO.SDS.AppWindow", 13 | 14 | constructor: function(config) { 15 | this.appInstance = config.appInstance; 16 | 17 | config = Ext.apply({ 18 | resizable: true, 19 | maximizable: true, 20 | minimizable: true, 21 | width: 980, 22 | height: 580, 23 | minWidth: 830, 24 | minHeight: 510, 25 | items: [{ 26 | xtype: 'box', 27 | autoEl: { 28 | tag: 'iframe', 29 | src: '/webman/3rdparty/filebot-node/index.html', 30 | width: '100%', 31 | height: '100%', 32 | frameborder: '0' 33 | } 34 | }], 35 | tools: [{ 36 | id: 'fullscreen', 37 | qtip: 'Open in New Tab', 38 | handler: function(event, element, panel) { 39 | window.open('/webman/3rdparty/filebot-node/index.html', '_blank') 40 | } 41 | }, { 42 | id: 'help', 43 | qtip: 'Open Help', 44 | handler: function(event, element, panel) { 45 | window.open('https://www.filebot.net/node.html', '_blank') 46 | } 47 | }] 48 | }, config); 49 | 50 | this.callParent([config]); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/auth: -------------------------------------------------------------------------------- 1 | {"success":true,"data":{"auth":"SYNO"}} -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/config: -------------------------------------------------------------------------------- 1 | { 2 | "FileBot.NodeClient.js": { 3 | "FileBot.NodeClient.AppInstance": { 4 | "type": "app", 5 | "title": "app:name", 6 | "desc": "app:description", 7 | "icon": "images/filebot_node_{0}.png", 8 | "texts": "texts", 9 | "allowMultiInstance": false, 10 | "allUsers": true, 11 | "appWindow": "FileBot.NodeClient.AppWindow", 12 | "depend": ["FileBot.NodeClient.AppWindow"] 13 | }, 14 | "FileBot.NodeClient.AppWindow": { 15 | "type": "lib", 16 | "title": "app:name", 17 | "icon": "images/filebot_node_{0}.png", 18 | "texts": "texts" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/environment.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/execute.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/folders.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/help/enu/filebot_node_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | FileBot Node for Synology NAS 13 | 14 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/helptoc.conf: -------------------------------------------------------------------------------- 1 | { 2 | "app": "FileBot.NodeClient.AppInstance", 3 | "title": "app:name", 4 | "content": "filebot_node_index.html", 5 | "helpset": "help", 6 | "stringset": "texts" 7 | } 8 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/images/filebot_node_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology-dsm7/target/client/images/filebot_node_16.png -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/images/filebot_node_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology-dsm7/target/client/images/filebot_node_24.png -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/images/filebot_node_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology-dsm7/target/client/images/filebot_node_256.png -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/images/filebot_node_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology-dsm7/target/client/images/filebot_node_32.png -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/images/filebot_node_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology-dsm7/target/client/images/filebot_node_48.png -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/images/filebot_node_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology-dsm7/target/client/images/filebot_node_64.png -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/images/filebot_node_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology-dsm7/target/client/images/filebot_node_72.png -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/kill.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/output.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/proxy_pass.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PROXY_PASS='http://127.0.0.1:5452' 4 | PROXY_FILE="$(basename "$SCRIPT_NAME" '.cgi')" 5 | 6 | exec -c -a 'proxy_pass.cgi' \ 7 | curl \ 8 | --include \ 9 | --silent \ 10 | --no-buffer \ 11 | --header "If-Modified-Since: $HTTP_IF_MODIFIED_SINCE" \ 12 | --header "X-Syno-Token: $HTTP_X_SYNO_TOKEN" \ 13 | --header "X-Real-IP: $REMOTE_ADDR" \ 14 | --cookie "$HTTP_COOKIE" \ 15 | "$PROXY_PASS/$PROXY_FILE?$QUERY_STRING" 16 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/schedule.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/state.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/task.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/tasks.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/test.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo 'Status: 200 OK' 4 | echo 'Content-Type: text/plain; charset=UTF-8' 5 | echo '' 6 | 7 | if /usr/syno/synoman/webman/authenticate.cgi; then 8 | echo '---------- printenv ----------' 9 | printenv 10 | echo '---------- id ----------' 11 | id 12 | echo '---------- node ----------' 13 | node -v 2>&1 14 | echo '---------- java ----------' 15 | java -version 2>&1 16 | echo '---------- filebot ----------' 17 | filebot -script fn:sysinfo 2>&1 18 | echo '---------- filebot-node ----------' 19 | cat /var/packages/filebot-node/var/filebot-node.log 20 | else 21 | echo $? 22 | fi 23 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/texts/enu/strings: -------------------------------------------------------------------------------- 1 | [app] 2 | name = "FileBot Node" 3 | description = "FileBot Node allows you to execute, monitor and schedule filebot commands." 4 | -------------------------------------------------------------------------------- /package/synology-dsm7/target/client/version.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/conf/privilege: -------------------------------------------------------------------------------- 1 | { 2 | "defaults":{ 3 | "run-as": "system" 4 | }, 5 | "username": "FileBot", 6 | "groupname": "FileBot" 7 | } 8 | -------------------------------------------------------------------------------- /package/synology/conf/resource: -------------------------------------------------------------------------------- 1 | { 2 | "usr-local-linker": { 3 | "bin": ["bin/filebot-node-task"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /package/synology/scripts/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | FILEBOT_NODE_DATA="/var/packages/filebot-node/target/data" 3 | 4 | # prepare application data folder with read / write permissions for all users 5 | mkdir "$FILEBOT_NODE_DATA" 6 | 7 | # prepare log file permissions 8 | touch "$FILEBOT_NODE_DATA/filebot.log" 9 | chmod 666 "$FILEBOT_NODE_DATA/filebot.log" 10 | 11 | # set -Xmx to 0.8 of physical memory (on low-memory devices) 12 | if [ ! -f "$FILEBOT_NODE_DATA/environment.sh" ]; then 13 | JAVA_OPTS=$(awk '/MemTotal:/ { xmx = ($2*0.8)/1024; if (xmx < 1024) { printf "-Xmx%dm", xmx }; exit}' /proc/meminfo) 14 | echo "export JAVA_OPTS=$JAVA_OPTS" > "$FILEBOT_NODE_DATA/environment.sh" 15 | fi 16 | 17 | # print notification 18 | { 19 | cat << EOF 20 |

NOTE

21 |

FileBot Node cannot access your files unless you explicitly grant Read/Write permissions.

22 |
    23 |
  1. Open Control Panel and select Shared Folder
  2. 24 |
  3. Select the share that contains your files and click Edit
  4. 25 |
  5. Select Permissions
  6. 26 |
  7. Change Local users to System internal user
  8. 27 |
  9. Check Read/Write for FileBot and click Save
  10. 28 |
29 | EOF 30 | } >> "$SYNOPKG_TEMP_LOGFILE" 31 | 32 | # return successfully 33 | exit 0 34 | -------------------------------------------------------------------------------- /package/synology/scripts/postuninst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exit 0 3 | -------------------------------------------------------------------------------- /package/synology/scripts/postupgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # import data files 4 | cd "$SYNOPKG_PKGDEST" && tar -xvzf "/tmp/$SYNOPKG_PKGNAME.data.tgz" && rm "/tmp/$SYNOPKG_PKGNAME.data.tgz" 5 | 6 | # return successfully 7 | exit 0 8 | -------------------------------------------------------------------------------- /package/synology/scripts/preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | # require node 5 | if ! which node; then 6 | echo '

FileBot Node requires Node.js. Please install Node.js first.

' >> "$SYNOPKG_TEMP_LOGFILE" 7 | exit 1 8 | fi 9 | 10 | 11 | # require filebot 12 | if ! which filebot; then 13 | echo '

FileBot Node requires FileBot. Please install FileBot first.

' >> "$SYNOPKG_TEMP_LOGFILE" 14 | exit 1 15 | fi 16 | 17 | 18 | # nginx reverse proxy configuration installed by FileBot Node pre-DSM 6.2.4 (as default root user) 19 | # can no longer be uninstalled by FileBot Node after DSM 6.2.4 (as new default system user) 20 | if [ -f '/usr/local/etc/nginx/conf.d/dsm.filebot-node.conf' ]; then 21 | { 22 | echo '

Please use sudo to remove dsm.filebot-node.conf manually before installing FileBot Node:

' 23 | echo '

sudo rm -v /usr/local/etc/nginx/conf.d/dsm.filebot-node.conf

' 24 | echo '

* The nginx reverse proxy configuration file installed by FileBot Node pre-DSM 6.2.4 (as default root user) can no longer be uninstalled by FileBot Node after DSM 6.2.4 (as new default system user) because the system user does not have root permissions.

' 25 | } >> "$SYNOPKG_TEMP_LOGFILE" 26 | exit 1 27 | fi 28 | 29 | 30 | # return successfully 31 | exit 0 32 | -------------------------------------------------------------------------------- /package/synology/scripts/preuninst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exit 0 3 | -------------------------------------------------------------------------------- /package/synology/scripts/preupgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # store data files 4 | cd "$SYNOPKG_PKGDEST" && tar -cvzf "/tmp/$SYNOPKG_PKGNAME.data.tgz" "data/" 5 | 6 | # return successfully 7 | exit 0 8 | -------------------------------------------------------------------------------- /package/synology/scripts/start-stop-status: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | case "$1" in 5 | start) 6 | "$SYNOPKG_PKGDEST/bin/filebot-node-start" >> "$SYNOPKG_PKGDEST/filebot-node.log" 2>&1 & 7 | exit $? 8 | ;; 9 | 10 | stop) 11 | kill "$("$0" status)" >> "$SYNOPKG_PKGDEST/filebot-node.log" 2>&1 12 | exit $? 13 | ;; 14 | 15 | status) 16 | curl -fs 'http://127.0.0.1:5452/status' | grep -oE '"pid":[0-9]+' | grep -oE '[0-9]+' 17 | exit $? 18 | ;; 19 | esac 20 | -------------------------------------------------------------------------------- /package/synology/target/bin/filebot-node-start: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | echo " 5 | -------------------- Run $0 (PID: $$) -------------------- $(date) 6 | " 7 | 8 | 9 | export FILEBOT_NODE_HOST="127.0.0.1" # bind to local host 10 | export FILEBOT_NODE_AUTH="SYNO" 11 | 12 | export FILEBOT_NODE_HTTP="YES" 13 | export FILEBOT_NODE_HTTP_PORT="5452" 14 | 15 | export FILEBOT_NODE_DATA="/var/packages/filebot-node/target/data" 16 | 17 | export FILEBOT_TASK_CMD="filebot-node-task" 18 | 19 | export FILEBOT_CMD="filebot" 20 | export FILEBOT_CMD_CWD="$SYNOPKG_PKGDEST_VOL" 21 | 22 | export FILEBOT_CMD_UID=$(id -u FileBot) 23 | export FILEBOT_CMD_GID=$(id -g FileBot) 24 | 25 | # set working dir 26 | cd "/var/packages/filebot-node/target" 27 | 28 | 29 | # import user environment 30 | . "$FILEBOT_NODE_DATA/environment.sh" 31 | 32 | 33 | exec node "server/app.js" 34 | -------------------------------------------------------------------------------- /package/synology/target/bin/filebot-node-task: -------------------------------------------------------------------------------- 1 | #!/bin/bash -u 2 | 3 | export FILEBOT_NODE_DATA="/var/packages/filebot-node/target/data" 4 | export FILEBOT_NODE_TASK="$1" 5 | 6 | 7 | # sanity check 8 | if [ ! -f "$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" ]; then 9 | echo "$0: Task $FILEBOT_NODE_TASK does not exist" 10 | exit 1 11 | fi 12 | 13 | 14 | # import user environment 15 | . "$FILEBOT_NODE_DATA/environment.sh" 16 | 17 | 18 | # execute filebot task and record output 19 | filebot "@$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" 2>&1 | tee -a "$FILEBOT_NODE_DATA/log/$FILEBOT_NODE_TASK.log" 20 | 21 | 22 | # get filebot exit code (and not tee exit code) 23 | STATUS="${PIPESTATUS[0]}" 24 | 25 | # treat ExitCode.NOOP as ExitCode.SUCCESS 26 | if [ $STATUS -eq 100 ]; then 27 | exit 0 28 | fi 29 | 30 | exit $STATUS 31 | -------------------------------------------------------------------------------- /package/synology/target/client/auth: -------------------------------------------------------------------------------- 1 | {"success":true,"data":{"auth":"SYNO"}} -------------------------------------------------------------------------------- /package/synology/target/client/config: -------------------------------------------------------------------------------- 1 | { 2 | ".url": { 3 | "FileBot.NodeClient": { 4 | "type": "legacy", 5 | "title": "FileBot Node", 6 | "desc": "FileBot Node allows you to execute filebot calls via Synology DSM", 7 | "icon": "images/filebot_node_{0}.png", 8 | "url": "/webman/3rdparty/filebot-node/index.html", 9 | "allUsers": true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package/synology/target/client/environment.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/execute.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/folders.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/images/filebot_node_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology/target/client/images/filebot_node_16.png -------------------------------------------------------------------------------- /package/synology/target/client/images/filebot_node_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology/target/client/images/filebot_node_24.png -------------------------------------------------------------------------------- /package/synology/target/client/images/filebot_node_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology/target/client/images/filebot_node_256.png -------------------------------------------------------------------------------- /package/synology/target/client/images/filebot_node_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology/target/client/images/filebot_node_32.png -------------------------------------------------------------------------------- /package/synology/target/client/images/filebot_node_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology/target/client/images/filebot_node_48.png -------------------------------------------------------------------------------- /package/synology/target/client/images/filebot_node_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology/target/client/images/filebot_node_64.png -------------------------------------------------------------------------------- /package/synology/target/client/images/filebot_node_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filebot/filebot-node/f1ecfa0d9a16ec0598aeb141e04cf4dd87b0f6ae/package/synology/target/client/images/filebot_node_72.png -------------------------------------------------------------------------------- /package/synology/target/client/kill.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/output.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/proxy_pass.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PROXY_PASS='http://127.0.0.1:5452' 4 | PROXY_FILE="$(basename "$SCRIPT_NAME" '.cgi')" 5 | 6 | exec -c -a 'proxy_pass.cgi' \ 7 | curl \ 8 | --include \ 9 | --silent \ 10 | --no-buffer \ 11 | --header "If-Modified-Since: $HTTP_IF_MODIFIED_SINCE" \ 12 | --header "X-Syno-Token: $HTTP_X_SYNO_TOKEN" \ 13 | --header "X-Real-IP: $REMOTE_ADDR" \ 14 | --cookie "$HTTP_COOKIE" \ 15 | "$PROXY_PASS/$PROXY_FILE?$QUERY_STRING" 16 | -------------------------------------------------------------------------------- /package/synology/target/client/schedule.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/state.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/task.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/tasks.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /package/synology/target/client/test.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo 'Status: 200 OK' 4 | echo 'Content-Type: text/plain; charset=UTF-8' 5 | echo '' 6 | 7 | if /usr/syno/synoman/webman/authenticate.cgi; then 8 | echo '---------- printenv ----------' 9 | printenv 10 | echo '---------- id ----------' 11 | id 12 | echo '---------- node ----------' 13 | node -v 2>&1 14 | echo '---------- java ----------' 15 | java -version 2>&1 16 | echo '---------- filebot ----------' 17 | filebot -script fn:sysinfo 2>&1 18 | echo '---------- filebot-node ----------' 19 | cat /var/packages/filebot-node/target/data/filebot-node.log 20 | else 21 | echo $? 22 | fi 23 | -------------------------------------------------------------------------------- /package/synology/target/client/version.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . /usr/syno/synoman/webman/3rdparty/filebot-node/proxy_pass.cgi 3 | -------------------------------------------------------------------------------- /server-nodejs/README.md: -------------------------------------------------------------------------------- 1 | Run 2 | --- 3 | 4 | sh start.sh 5 | 6 | -------------------------------------------------------------------------------- /server-nodejs/app.js: -------------------------------------------------------------------------------- 1 | // PROCESS NAME 2 | process.title = 'filebot-node' 3 | 4 | // INCLUDES 5 | const http = require('http') 6 | const https = require('https') 7 | const url = require('url') 8 | const querystring = require('querystring') 9 | const child_process = require('child_process') 10 | const fs = require('fs') 11 | const path = require('path') 12 | const shellescape = require('shell-escape') 13 | const xmlParser = require('fast-xml-parser') 14 | const httpBasicAuth = require('basic-auth') 15 | 16 | // CONFIGURATION AND GLOBAL VARIABLES 17 | const DATA = process.env['FILEBOT_NODE_DATA'] 18 | const AUTH = process.env['FILEBOT_NODE_AUTH'] 19 | const CLIENT = process.env['FILEBOT_NODE_CLIENT'] 20 | const TASK_CMD = process.env['FILEBOT_TASK_CMD'] 21 | const FILEBOT_CMD = process.env['FILEBOT_CMD'] 22 | const FILEBOT_CMD_CWD = process.env['FILEBOT_CMD_CWD'] 23 | const FILEBOT_CMD_UID = parseInt(process.env['FILEBOT_CMD_UID'], 10) 24 | const FILEBOT_CMD_GID = parseInt(process.env['FILEBOT_CMD_GID'], 10) 25 | 26 | const PUBLIC_HTML = CLIENT ? '/' : '' 27 | const ROUTES = new RegExp('^/[a-z]+$') 28 | 29 | const MIME_TYPES = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.png': 'image/png', '.gif': 'image/gif', '.json': 'application/json', '.log': 'text/plain; charset=utf-8'} 30 | const SYSTEM_FILES = /^([.@#].+|bin|initrd|opt|sbin|var|dev|lib|lib32|lib64|config|proc|sys|var.defaults|etc|lost.found|root|tmp|etc.defaults|mnt|run|usr|home|homes|external|rpc|.+[.]backupbundle|NetBackup|\w+_tmp|tmp_\w+|new_\w+|CACHEDEV\w+_DATA|HD\w+_DATA|System.Volume.Information)$/ 31 | const DASHLINE = '------------------------------------------' 32 | const NEWLINE = '\n' 33 | const WRAP = '\n\n' 34 | const SIGKILL_EXIT_CODE = 137 35 | const SCHEDULED_TASK_CODE = 1000 36 | 37 | // INITIALIZERS 38 | const AUTH_CACHE = {} 39 | const ACTIVE_PROCESSES = {} 40 | const TASKS = [] 41 | 42 | // update task list via If-Last-Modified 43 | TASKS.lastModified = Date.now() 44 | 45 | const DATA_FOLDER = path.resolve(DATA) 46 | const LOG_FOLDER = path.resolve(DATA_FOLDER, 'log') 47 | const TASK_FOLDER = path.resolve(DATA_FOLDER, 'task') 48 | const TASK_INDEX = path.resolve(DATA_FOLDER, 'schedule.ids') 49 | const STATE_JSON = path.resolve(DATA_FOLDER, 'state.json') 50 | const ENVIRONMENT_SCRIPT = path.resolve(DATA_FOLDER, 'environment.sh') 51 | const FILEBOT_LOG = path.resolve(DATA_FOLDER, 'filebot.log') 52 | 53 | // create folder if necessary 54 | if (!fs.existsSync(DATA_FOLDER)) { 55 | fs.mkdirSync(DATA_FOLDER) 56 | } 57 | if (!fs.existsSync(TASK_FOLDER)) { 58 | fs.mkdirSync(TASK_FOLDER) 59 | } 60 | if (!fs.existsSync(LOG_FOLDER)) { 61 | fs.mkdirSync(LOG_FOLDER) 62 | fs.chownSync(LOG_FOLDER, FILEBOT_CMD_UID, FILEBOT_CMD_GID) // FILEBOT USER MUST BE ABLE TO WRITE LOGS 63 | } 64 | if (!fs.existsSync(FILEBOT_LOG)) { 65 | fs.writeFileSync(FILEBOT_LOG, '# Created on ' + (new Date().toString()) + NEWLINE) 66 | fs.chownSync(FILEBOT_LOG, FILEBOT_CMD_UID, FILEBOT_CMD_GID) // FILEBOT USER MUST BE ABLE TO WRITE LOGS 67 | } 68 | 69 | if (fs.existsSync(TASK_INDEX)) { 70 | fs.readFileSync(TASK_INDEX, {'encoding': 'UTF-8'}).split(/\n/).forEach(function(id) { 71 | if (id) { 72 | var stats = fs.statSync(getLogFile(id)) 73 | var mtime = new Date(stats.mtime).getTime() 74 | 75 | TASKS.push({ id: id, date: mtime, status: SCHEDULED_TASK_CODE }) 76 | } 77 | }) 78 | } 79 | 80 | 81 | /* ------------------------------------------ FileBot Command ------------------------------------------ */ 82 | 83 | 84 | function getLogFile(id) { 85 | return path.join(LOG_FOLDER, id + '.log') 86 | } 87 | 88 | function getCommand() { 89 | return FILEBOT_CMD 90 | } 91 | 92 | function getCommandArguments(options) { 93 | var args = [] 94 | if (options.fn == 'amc') { 95 | args.push('-script') 96 | args.push(options.channel == 'dev' ? 'dev:amc' : 'fn:amc') 97 | if (options.input) { 98 | args.push(options.input) 99 | } 100 | if (options.output) { 101 | args.push('--output') 102 | args.push(options.output) 103 | } 104 | if (options.action) { 105 | args.push('--action') 106 | args.push(options.action) 107 | } 108 | if (options.strict != 'no') { 109 | args.push('-non-strict') 110 | } 111 | if (options.order) { 112 | args.push('--order') 113 | args.push(options.order) 114 | } 115 | if (options.conflict) { 116 | args.push('--conflict') 117 | args.push(options.conflict) 118 | } 119 | if (options.query) { 120 | args.push('--q') 121 | args.push(options.query) 122 | } 123 | if (options.filter) { 124 | args.push('--filter') 125 | args.push(options.filter) 126 | } 127 | if (options.mapper) { 128 | args.push('--mapper') 129 | args.push(options.mapper) 130 | } 131 | if (options.lang) { 132 | args.push('--lang') 133 | args.push(options.lang) 134 | } 135 | args.push('--def') 136 | if (options.label) args.push('ut_label=' + options.label) 137 | if (options.music != 'no') args.push('music=y') 138 | if (options.unsorted != 'no') args.push('unsorted=y') 139 | if (options.excludeLink == 'on') args.push('excludeLink=y') 140 | if (options.artwork == 'on') args.push('artwork=y') 141 | if (options.subtitles) args.push('subtitles=' + options.subtitles) 142 | if (options.clean == 'on') args.push('clean=y') 143 | if (options.archives == 'skip') { 144 | args.push('skipExtract=y') 145 | } else if (options.archives == 'extract-delete') { 146 | args.push('deleteAfterExtract=y') 147 | } 148 | if (options.ignore) args.push('ignore=' + options.ignore) 149 | if (options.minLengthMS) args.push('minLengthMS=' + options.minLengthMS) 150 | if (options.minFileSize) args.push('minFileSize=' + options.minFileSize) 151 | if (options.minFileAge) args.push('minFileAge=' + options.minFileAge) 152 | if (options.exec) args.push('exec=' + options.exec) 153 | if (options.plex) args.push('plex=' + options.plex) 154 | if (options.kodi) args.push('kodi=' + options.kodi) 155 | if (options.emby) args.push('emby=' + options.emby) 156 | if (options.jellyfin) args.push('jellyfin=' + options.jellyfin) 157 | if (options.pushover) args.push('pushover=' + options.pushover) 158 | if (options.pushbullet) args.push('pushbullet=' + options.pushbullet) 159 | if (options.discord) args.push('discord=' + options.discord) 160 | if (options.report) args.push('storeReport=' + options.report) 161 | if (options.seriesFormat) args.push('seriesFormat=' + options.seriesFormat) 162 | if (options.animeFormat) args.push('animeFormat=' + options.animeFormat) 163 | if (options.movieFormat) args.push('movieFormat=' + options.movieFormat) 164 | if (options.musicFormat) args.push('musicFormat=' + options.musicFormat) 165 | if (options.movieDB) args.push('movieDB=' + options.movieDB) 166 | if (options.seriesDB) args.push('seriesDB=' + options.seriesDB) 167 | if (options.animeDB) args.push('animeDB=' + options.animeDB) 168 | if (options.unsortedFormat) args.push('unsortedFormat=' + options.unsortedFormat) 169 | if (options.excludeList) args.push('excludeList=' + options.excludeList) 170 | // --apply options 171 | var apply = ['--apply'] 172 | if (options.import == 'on') apply.push('import') 173 | if (options.metadata == 'on') apply.push('metadata') 174 | if (options.chmod == 'on') apply.push('chmod') 175 | if (options.thumbnail == 'on') apply.push('thumbnail') 176 | if (options.refresh == 'on') apply.push('refresh') 177 | if (options.apply) apply.push(options.apply) 178 | if (apply.length > 1) { 179 | args = args.concat(apply) 180 | } 181 | if (options.probe == 'no') args.push('-no-probe') 182 | if (options.index == 'no') args.push('-no-index') 183 | if (options.log) { 184 | args.push('--log') 185 | args.push(options.log) 186 | } 187 | // --def ut_* options for custom commands executed via curl 188 | var ut_options = ['--def'] 189 | if (options.ut_dir) ut_options.push('ut_dir=' + options.ut_dir) 190 | if (options.ut_file) ut_options.push('ut_file=' + options.ut_file) 191 | if (options.ut_label) ut_options.push('ut_label=' + options.ut_label) 192 | if (options.ut_title) ut_options.push('ut_title=' + options.ut_title) 193 | if (options.ut_kind) ut_options.push('ut_kind=' + options.ut_kind) 194 | if (options.ut_state) ut_options.push('ut_state=' + options.ut_state) 195 | if (ut_options.length > 1) { 196 | args = args.concat(ut_options) 197 | } 198 | } else if (options.fn == 'license' && options.license) { 199 | args.push('--license') 200 | args.push(options.license) 201 | } else if (options.fn == 'revert') { 202 | args.push('-revert') 203 | } else if (options.fn == 'sysinfo') { 204 | args.push('-script') 205 | args.push('fn:sysinfo') 206 | } else if (options.fn == 'clear') { 207 | args.push('-clear-cache') 208 | args.push('-clear-history') 209 | } else if (options.fn == 'configure') { 210 | args.push('-script') 211 | args.push('fn:configure') 212 | args.push('--def') 213 | args.push('osdbUser=' + options.osdbUser) 214 | args.push('osdbPwd=' + options.osdbPwd) 215 | } else if (options.fn == 'properties') { 216 | args.push('-script') 217 | args.push('fn:properties') 218 | args.push('--def') 219 | args.push(options.name + '=' + options.value) 220 | } else if (options.fn == 'mediainfo') { 221 | args.push('-script') 222 | args.push(options.channel == 'dev' ? 'dev:mediainfo' : 'fn:mediainfo') 223 | args.push(options.input) 224 | args.push('--mode') 225 | args.push('raw') 226 | } else { 227 | throw new Error('Illegal options: ' + JSON.stringify(options)) 228 | } 229 | 230 | // require --log-file because otherwise it will default to lock.log anyway 231 | args.push('--log-file') 232 | args.push(FILEBOT_LOG) 233 | 234 | return args 235 | } 236 | 237 | function getExitStatus(code) { 238 | var status = NEWLINE + DASHLINE + WRAP 239 | if (code == null) { 240 | status += '[Process killed]' 241 | } else if (code == 0 || code == 100) { 242 | status += '[Process completed]' 243 | } else if (code == -2 || code == 'ENOENT') { 244 | status += '[Process error]' 245 | status += WRAP + getCommand() + ': command not found' 246 | status += WRAP + '⚠️ FileBot is not installed. FileBot Node requires FileBot. Please install FileBot first and then try again.' 247 | } else { 248 | status += '[Process error]' 249 | status += WRAP + '🔺 Exit Code: ' + code 250 | // Whoopsies! --action TEST requires a valid license. 251 | if (code == 2) { 252 | status += WRAP + '💡 Please use an interactive terminal (i.e. SSH) to evaluate the filebot command-line tool.' 253 | status += WRAP + '💡 FileBot Node generates and executes filebot commands but cannot itself be used to evaluate the filebot command-line tool.' 254 | } 255 | // java: command not found 256 | if (code == 127) { 257 | status += WRAP + '💡 FileBot Node requires FileBot and Java. Please ensure that FileBot and Java are installed.' 258 | } 259 | } 260 | return status + WRAP 261 | } 262 | 263 | function spawnChildProcess(command, arguments) { 264 | // just use current time in millis as process id 265 | const id = 'R' + Date.now() 266 | const logFile = getLogFile(id) 267 | 268 | const pd = { id: id, date: Date.now(), status: null } 269 | 270 | // each log contains the original command (as JSON) in the first line 271 | fs.writeFileSync(logFile, shellescape([command].concat(arguments)) + WRAP + DASHLINE + WRAP) 272 | fs.chownSync(logFile, FILEBOT_CMD_UID, FILEBOT_CMD_GID) 273 | 274 | const out = fs.openSync(logFile, 'a') 275 | const child = child_process.spawn(command, arguments, { 276 | stdio: ['ignore', out, out], 277 | env: process.env, 278 | cwd: FILEBOT_CMD_CWD, 279 | uid: FILEBOT_CMD_UID, 280 | gid: FILEBOT_CMD_GID, 281 | // new process group leader so we can kill the entire group with kill -pid 282 | detached: true 283 | } 284 | ) 285 | 286 | child.on('error', function(error) { 287 | console.log(command, error) 288 | }) 289 | child.on('close', function(code) { 290 | // remove process object reference 291 | delete ACTIVE_PROCESSES[id] 292 | // store exit code 293 | pd.status = code != null ? code : SIGKILL_EXIT_CODE 294 | TASKS.lastModified = Date.now() 295 | 296 | // add status message 297 | fs.writeSync(out, getExitStatus(code)) 298 | fs.closeSync(out) 299 | }) 300 | 301 | ACTIVE_PROCESSES[id] = child 302 | TASKS.push(pd) 303 | TASKS.lastModified = Date.now() 304 | 305 | return pd 306 | } 307 | 308 | 309 | /* ------------------------------------------ Routes ------------------------------------------ */ 310 | 311 | 312 | function version() { 313 | const child = child_process.spawnSync(getCommand(), ['-version'], { 314 | stdio: ['ignore', 'pipe', 'pipe'], 315 | encoding: 'UTF-8', 316 | env: process.env, 317 | cwd: FILEBOT_CMD_CWD, 318 | uid: FILEBOT_CMD_UID, 319 | gid: FILEBOT_CMD_GID 320 | } 321 | ) 322 | if (child.error && child.error.code) { 323 | return getExitStatus(child.error.code) 324 | } 325 | return [child.stdout, child.stderr].join(WRAP).trim() 326 | } 327 | 328 | function state(options) { 329 | // PUT STATE 330 | if (options.store) { 331 | fs.writeFileSync(STATE_JSON, options.store) 332 | } 333 | 334 | // GET STATE 335 | else if (fs.existsSync(STATE_JSON)) { 336 | return fs.readFileSync(STATE_JSON, {'encoding': 'UTF-8'}) 337 | } 338 | 339 | return null 340 | } 341 | 342 | function environment(options) { 343 | // PUT ENV 344 | if (options.environment != null) { 345 | fs.writeFileSync(ENVIRONMENT_SCRIPT, options.environment) 346 | return { environment: null, message: "The environment has been set. Please restart the FileBot Node process to reload the environment." } 347 | } 348 | 349 | // GET ENV 350 | var environment = "" 351 | if (fs.existsSync(ENVIRONMENT_SCRIPT)) { 352 | environment = fs.readFileSync(ENVIRONMENT_SCRIPT, {'encoding': 'UTF-8'}) 353 | } 354 | return { environment: environment, message: null } 355 | } 356 | 357 | function task(request, response, options) { 358 | var id = options.id 359 | 360 | response.setHeader('Access-Control-Allow-Origin', '*') 361 | response.setHeader('Cache-Control', 'private, max-age=0, no-cache, must-revalidate') 362 | response.setHeader('Connection', 'Keep-Alive') 363 | 364 | // disable response caching to display response stream in real time 365 | response.setHeader('Content-Type', 'text/plain; charset=UTF-8') 366 | response.setHeader('X-Content-Type-Options', 'nosniff') 367 | 368 | // enable HTTP 1.1 Trailer (use curl --raw /task to see Exit-Code trailer value) 369 | response.setHeader('Transfer-Encoding', 'chunked') 370 | response.setHeader('Trailer', 'Exit-Code') 371 | 372 | // try to avoid socket timeout 373 | response.setTimeout(3 * 24 * 60 * 60 * 1000) 374 | 375 | // flush headers 376 | response.write(TASK_CMD + " " + id + WRAP + DASHLINE + WRAP) 377 | 378 | var child = child_process.spawn(TASK_CMD, [id], { 379 | stdio: ['ignore', 'pipe', 'pipe'], 380 | encoding: 'UTF-8', 381 | env: process.env, 382 | cwd: FILEBOT_CMD_CWD, 383 | uid: FILEBOT_CMD_UID, 384 | gid: FILEBOT_CMD_GID 385 | } 386 | ) 387 | 388 | child.stdout.pipe(response, {end: false}) 389 | child.stderr.pipe(response, {end: false}) 390 | 391 | child.on('close', function(code) { 392 | response.write(getExitStatus(code)) 393 | response.addTrailers({ "Exit-Code": code }) 394 | response.end() 395 | }) 396 | } 397 | 398 | function command(request, response) { 399 | response.setHeader('Access-Control-Allow-Origin', '*') 400 | response.setHeader('Cache-Control', 'private, max-age=0, no-cache, must-revalidate') 401 | response.setHeader('Connection', 'Keep-Alive') 402 | 403 | // disable response caching to display response stream in real time 404 | response.setHeader('Content-Type', 'text/plain; charset=UTF-8') 405 | response.setHeader('X-Content-Type-Options', 'nosniff') 406 | 407 | // enable HTTP 1.1 Trailer (use curl --raw /task to see Exit-Code trailer value) 408 | response.setHeader('Transfer-Encoding', 'chunked') 409 | response.setHeader('Trailer', 'Exit-Code') 410 | 411 | // try to avoid socket timeout 412 | response.setTimeout(3 * 24 * 60 * 60 * 1000) 413 | 414 | // read post body 415 | var body = '' 416 | request.on('data', function(data) { 417 | body += data; 418 | }) 419 | request.on('end', function() { 420 | try { 421 | // read argument list from HTTP POST body 422 | const args = [] 423 | 424 | if (request.headers['content-type'] == 'application/json') { 425 | // argument list as JSON array 426 | JSON.parse(body).forEach(function(argument) { args.push(argument.toString()) }) 427 | } else { 428 | // argument list as line-by-line plain text 429 | body.split(/[\r\n]+/g).forEach(function(line) { if (line.length > 0) args.push(line) }) 430 | } 431 | 432 | // require --log-file because otherwise it will default to lock.log anyway 433 | args.push('--log-file', FILEBOT_LOG) 434 | 435 | response.write(DASHLINE + WRAP + getCommand() + NEWLINE + args.join(NEWLINE) + WRAP + DASHLINE + WRAP) 436 | 437 | var child = child_process.spawn(getCommand(), args, { 438 | stdio: ['ignore', 'pipe', 'pipe'], 439 | encoding: 'UTF-8', 440 | env: process.env, 441 | cwd: FILEBOT_CMD_CWD, 442 | uid: FILEBOT_CMD_UID, 443 | gid: FILEBOT_CMD_GID 444 | } 445 | ) 446 | 447 | child.stdout.pipe(response, {end: false}) 448 | child.stderr.pipe(response, {end: false}) 449 | 450 | child.on('close', function(code) { 451 | response.write(getExitStatus(code)) 452 | response.addTrailers({ "Exit-Code": code }) 453 | response.end() 454 | }) 455 | } catch(e) { 456 | return error(response, e) 457 | } 458 | }) 459 | } 460 | 461 | function execute(options) { 462 | var pd = spawnChildProcess(getCommand(), getCommandArguments(options)) 463 | return pd 464 | } 465 | 466 | function kill(options) { 467 | var id = options.id 468 | var child = ACTIVE_PROCESSES[id] 469 | 470 | if (child) { 471 | // remove process object reference 472 | delete ACTIVE_PROCESSES[id] 473 | 474 | // if pid is less than -1, then sig is sent to every process in the process group whose ID is -pid 475 | process.kill(-child.pid) 476 | 477 | return {id: id, status: SIGKILL_EXIT_CODE} 478 | } else { 479 | throw new Error('No such process') 480 | } 481 | } 482 | 483 | function listFolders(options) { 484 | var folder = options.q 485 | var file = null 486 | 487 | folder = folder && folder[0] == '/' ? folder : FILEBOT_CMD_CWD 488 | while(!fs.existsSync(folder)) { 489 | file = path.basename(folder) 490 | folder = path.dirname(folder) 491 | } 492 | 493 | var folders = [] 494 | if (folder && fs.lstatSync(folder).isDirectory()) { 495 | fs.readdirSync(folder).forEach(function(s) { 496 | if (!SYSTEM_FILES.test(s) && (file == null || s.indexOf(file) == 0)) { 497 | var f = path.resolve(folder, s) 498 | if (fs.existsSync(f) && fs.statSync(f).isDirectory()) { 499 | folders.push({path: f}) 500 | } 501 | } 502 | } 503 | ) 504 | } 505 | return folders 506 | } 507 | 508 | function listLogs() { 509 | return fs.readdirSync(LOG_FOLDER).map(function(s) { 510 | return s.substr(0, s.lastIndexOf('.')) 511 | }) 512 | } 513 | 514 | function status() { 515 | return { 516 | pid: process.pid, 517 | node: process.version, 518 | uptime: process.uptime().toFixed(0), 519 | date: new Date().toUTCString() 520 | } 521 | } 522 | 523 | 524 | /* ------------------------------------------ Main ------------------------------------------ */ 525 | 526 | 527 | function handleRequest(request, response) { 528 | const requestParameters = url.parse(request.url) 529 | const requestPath = requestParameters.pathname 530 | 531 | // serve static resources 532 | if (!ROUTES.test(requestPath)) { 533 | return html(request, response, requestPath) 534 | } 535 | 536 | // check if service is running 537 | if ('/status' == requestPath) { 538 | return ok(response, status()) 539 | } 540 | 541 | // require user authentication for all handlers below 542 | const options = querystring.parse(requestParameters.query) 543 | const user = auth(request, response, options) 544 | 545 | if ('/auth' == requestPath) { 546 | if (user === undefined) { 547 | return unauthorized(response, true) 548 | } else { 549 | return ok(response, {'auth': AUTH, 'user': user}) 550 | } 551 | } 552 | 553 | // AUTHENTICATION REQUIRED BEYOND THIS POINT 554 | if (!user) { 555 | return unauthorized(response) 556 | } 557 | 558 | if ('/version' == requestPath) { 559 | return ok(response, version()) 560 | } 561 | 562 | if ('/tasks' == requestPath) { 563 | if (modifiedSince(request, TASKS.lastModified)) { 564 | return ok(response, TASKS, TASKS.lastModified) 565 | } else { 566 | return notModified(response) 567 | } 568 | } 569 | 570 | if ('/folders' == requestPath) { 571 | return ok(response, listFolders(options)) 572 | } 573 | 574 | if ('/output' == requestPath) { 575 | const id = options.id 576 | if (id) { 577 | return file(request, response, getLogFile(id), MIME_TYPES['.log'], false, false) 578 | } else { 579 | return file(request, response, FILEBOT_LOG, MIME_TYPES['.log'], true, true) 580 | } 581 | } 582 | 583 | if ('/execute' == requestPath) { 584 | return ok(response, execute(options)) 585 | } 586 | 587 | if ('/schedule' == requestPath) { 588 | return schedule(request, response, options) 589 | } 590 | 591 | if ('/task' == requestPath) { 592 | return task(request, response, options) 593 | } 594 | 595 | if ('/command' == requestPath) { 596 | return command(request, response) 597 | } 598 | 599 | if ('/kill' == requestPath) { 600 | return ok(response, kill(options)) 601 | } 602 | 603 | if ('/state' == requestPath) { 604 | return ok(response, state(options)) 605 | } 606 | 607 | if ('/environment' == requestPath) { 608 | return ok(response, environment(options)) 609 | } 610 | 611 | return error(response, 'BAD ROUTE') 612 | } 613 | 614 | 615 | function html(request, response, requestPath) { 616 | if (PUBLIC_HTML && requestPath.indexOf(PUBLIC_HTML) == 0) { 617 | const requestedFile = requestPath == PUBLIC_HTML ? 'index.html' : requestPath.substring(PUBLIC_HTML.length) 618 | const ext = path.extname(requestedFile) 619 | const contentType = MIME_TYPES[ext] 620 | if (contentType) { 621 | return file(request, response, path.resolve(CLIENT, requestedFile), contentType, true, false) // resolve against CLIENT folder 622 | } 623 | } 624 | return notFound(response) 625 | } 626 | 627 | 628 | /* ------------------------------------------ HTTP Response ------------------------------------------ */ 629 | 630 | 631 | function modifiedSince(request, lastModified) { 632 | var header = request.headers['if-modified-since'] 633 | if (header) { 634 | var lastModifiedInSeconds = Math.floor(lastModified / 1000) 635 | var ifModifiedSinceInSeconds = Date.parse(header) / 1000 // UTC STRING IS ONLY IN SECONDS PRECISION !!! 636 | return lastModifiedInSeconds > ifModifiedSinceInSeconds 637 | } 638 | return true // assume modified by default 639 | } 640 | 641 | function ok(response, data, lastModified) { 642 | var result = {success: true, data: data} 643 | 644 | response.statusCode = 200 645 | response.setHeader('Content-Type', 'application/json') 646 | response.setHeader('Access-Control-Allow-Origin', '*') 647 | if (lastModified > 0) { 648 | response.setHeader('Cache-Control', 'Cache-Control: private, max-age=0, no-cache, must-revalidate') 649 | response.setHeader('Last-Modified', new Date(lastModified).toUTCString()) 650 | } 651 | response.end(JSON.stringify(result)) 652 | } 653 | 654 | function file(request, response, file, contentType, cacheable, attachment) { 655 | fs.stat(file, function(err, stats) { 656 | if (err) { 657 | return notFound(response) 658 | } 659 | if (modifiedSince(request, stats.mtime.getTime())) { 660 | var readStream = fs.createReadStream(file) 661 | readStream.on('open', function() { 662 | response.statusCode = 200 663 | response.setHeader('Content-Type', contentType) 664 | response.setHeader('Content-Length', stats.size) 665 | if (attachment) response.setHeader('Content-Disposition', 'attachment; filename="' + path.basename(file) +'"') 666 | if (!cacheable) response.setHeader('Cache-Control', 'Cache-Control: private, max-age=0, no-cache, must-revalidate') 667 | response.setHeader('Last-Modified', stats.mtime.toUTCString()) 668 | response.setHeader('Access-Control-Allow-Origin', '*') 669 | readStream.pipe(response) // response.end() is called automatically 670 | }) 671 | readStream.on('error', function(err) { 672 | return error(response, err) 673 | }) 674 | } else { 675 | return notModified(response) 676 | } 677 | }) 678 | } 679 | 680 | function notModified(response) { 681 | response.statusCode = 304 682 | response.setHeader('Access-Control-Allow-Origin', '*') 683 | response.end() 684 | } 685 | 686 | function notFound(response) { 687 | response.statusCode = 404 688 | response.setHeader('Access-Control-Allow-Origin', '*') 689 | response.end('Not Found') 690 | } 691 | 692 | function unauthorized(response, authenticate) { 693 | response.statusCode = 401 694 | if (authenticate) { 695 | response.setHeader('WWW-Authenticate', 'Basic realm="filebot-node"') 696 | } 697 | response.setHeader('Access-Control-Allow-Origin', '*') 698 | response.end('Unauthorized') 699 | } 700 | 701 | function error(response, exception) { 702 | const result = {success: false, error: exception.toString()} 703 | response.statusCode = 400 704 | response.setHeader('Content-Type', 'application/json') 705 | response.setHeader('Access-Control-Allow-Origin', '*') 706 | response.end(JSON.stringify(result)) 707 | } 708 | 709 | 710 | /* ------------------------------------------ Authentication ------------------------------------------ */ 711 | 712 | 713 | function auth(request, response, options) { 714 | switch (AUTH) { 715 | case 'SYNO': 716 | return auth_syno(request, response, options) 717 | case 'QNAP': 718 | return auth_qnap(request, response) 719 | case 'BASIC': 720 | return auth_basic_env(request, response) 721 | case 'NONE': 722 | return 'NONE' 723 | default: 724 | return null 725 | } 726 | } 727 | 728 | function auth_header(request, options) { 729 | try { 730 | switch (AUTH) { 731 | case 'SYNO': 732 | if (options.SynoToken) { 733 | return { 734 | 'cookie': request.headers['cookie'].match(/\b(id=[^;]+)/)[1], 735 | 'X-Syno-Token': options.SynoToken 736 | } 737 | } else { 738 | return { 739 | 'cookie': request.headers['cookie'].match(/\b(id=[^;]+)/)[1], 740 | 'X-Syno-Token': request.headers['x-syno-token'] 741 | } 742 | } 743 | 744 | case 'QNAP': 745 | return { 746 | 'cookie': request.headers['cookie'].match(/\b(NAS_SID=[^;]+)/)[1] 747 | } 748 | } 749 | } catch(e) { 750 | // ignore invalid cookies 751 | } 752 | return null 753 | } 754 | 755 | function auth_basic_env(request, response) { 756 | const user = httpBasicAuth(request) 757 | 758 | if (user == undefined) { 759 | return undefined // REQUEST AUTH 760 | } 761 | 762 | if (user && user.name == process.env['FILEBOT_NODE_AUTH_USER'] && user.pass == process.env['FILEBOT_NODE_AUTH_PASS']) { 763 | return user.name // AUTH OK 764 | } 765 | 766 | return null // REQUEST FAIL 767 | } 768 | 769 | function auth_syno(request, response, options) { 770 | const auth = auth_header(request, options) 771 | if (!auth) { 772 | return null 773 | } 774 | 775 | const key = JSON.stringify(auth) 776 | const user = AUTH_CACHE[key] 777 | if (user) { 778 | return user 779 | } 780 | 781 | // X-Real-IP header is set by nginx server 782 | var remoteAddress = request.headers['x-real-ip'] 783 | if (!remoteAddress) { 784 | // DSM 7 does not allow nginx reverse_proxy configuration, so we may be serving requests directly 785 | remoteAddress = request.connection.remoteAddress 786 | } 787 | 788 | // authenticate.cgi requires these and some other environment variables for authentication 789 | const cmd = '/usr/syno/synoman/webman/authenticate.cgi' 790 | const env = { 791 | 'HTTP_COOKIE': auth['cookie'], 792 | 'REMOTE_ADDR': remoteAddress, 793 | 'HTTP_X_SYNO_TOKEN': auth['X-Syno-Token'] 794 | } 795 | 796 | console.log({ 'cmd': cmd, 'env': env }) 797 | 798 | // call authenticate.cgi and capture output 799 | const pd = child_process.spawnSync(cmd , [], { 800 | stdio: ['ignore', 'pipe', 'inherit'], 801 | encoding: 'UTF-8', 802 | env: env 803 | } 804 | ) 805 | 806 | if (pd.status == 0) { 807 | const value = pd.stdout.trim() 808 | AUTH_CACHE[key] = value 809 | 810 | console.log({ 'auth': auth, 'user': value }) 811 | return value 812 | } 813 | 814 | console.log({ 'status': pd.status, 'stdout': pd.stdout, 'stderr': pd.stderr }) 815 | return null 816 | } 817 | 818 | function auth_qnap(request, response) { 819 | const auth = auth_header(request) 820 | if (!auth) { 821 | return null 822 | } 823 | 824 | const key = JSON.stringify(auth) 825 | const user = AUTH_CACHE[key] 826 | if (user) { 827 | return user 828 | } 829 | 830 | // authLogin.cgi requires QUERY_STRING sid= 831 | const sid = auth.cookie.split(/=/).pop() 832 | 833 | const cmd = '/home/httpd/cgi-bin/authLogin.cgi' 834 | const env = { 835 | 'QUERY_STRING': "sid=" + sid 836 | } 837 | 838 | console.log({ 'cmd': cmd, 'env': env }) 839 | 840 | const pd = child_process.spawnSync(cmd, [], { 841 | stdio: ['ignore', 'pipe', 'inherit'], 842 | encoding: 'UTF-8', 843 | env: env 844 | } 845 | ) 846 | 847 | if (pd.status == 0) { 848 | const cgiResponse = pd.stdout.trim() 849 | const xmlStartIndex = cgiResponse.indexOf(' 0) { 852 | const xmlResponse = cgiResponse.substring(xmlStartIndex) 853 | const dom = xmlParser.parse(xmlResponse) 854 | 855 | if (dom && dom.QDocRoot && dom.QDocRoot.authPassed == "1") { 856 | const value = dom.QDocRoot.user 857 | AUTH_CACHE[key] = value 858 | 859 | console.log({ 'auth': auth, 'user': value }) 860 | return value 861 | } 862 | } 863 | } 864 | 865 | console.log({ 'status': pd.status, 'stdout': pd.stdout, 'stderr': pd.stderr }) 866 | return null 867 | } 868 | 869 | 870 | /* ------------------------------------------ Scheduled Task ------------------------------------------ */ 871 | 872 | 873 | function schedule(request, response, options) { 874 | const command = prepareScheduledTask(options) 875 | const id = command.split(/\s/).pop() 876 | 877 | const curl = prepareCurlCommand(request, options) 878 | 879 | const clientSideRequest = { id: id, command: command, curl: curl } 880 | return ok(response, clientSideRequest) 881 | } 882 | 883 | 884 | function prepareCurlCommand(request, options) { 885 | switch (AUTH) { 886 | case 'SYNO': 887 | const syno = auth_header(request, options) 888 | if (syno['X-Syno-Token']) { 889 | return 'curl --header "X-Syno-Token: '+ syno['X-Syno-Token'] +'" --cookie "' + syno.cookie + '"' 890 | } else { 891 | return 'curl --cookie "' + syno.cookie + '"' 892 | } 893 | case 'QNAP': 894 | const qnap = auth_header(request, options) 895 | return 'curl --cookie "' + qnap.cookie + '"' 896 | case 'BASIC': 897 | const user = httpBasicAuth(request) 898 | return 'curl --user "' + user.name + ':' + user.pass + '"' 899 | case 'NONE': 900 | return 'curl' 901 | } 902 | return null 903 | } 904 | 905 | 906 | function prepareScheduledTask(options) { 907 | const id = fs.readdirSync(TASK_FOLDER).length 908 | const logFile = getLogFile(id) 909 | 910 | const command = TASK_CMD + ' ' + id 911 | const args = getCommandArguments(options) 912 | 913 | // each log contains the original command (as JSON) in the first line 914 | fs.writeFileSync(logFile, command + ' # ' + shellescape([getCommand()].concat(args)) + WRAP + DASHLINE + WRAP) 915 | fs.chownSync(logFile, FILEBOT_CMD_UID, FILEBOT_CMD_GID) 916 | fs.chmodSync(logFile, 0o666) 917 | 918 | const argsFile = path.resolve(TASK_FOLDER, id + '.args') 919 | fs.writeFileSync(argsFile, args.join(NEWLINE)) 920 | 921 | // update scheduled tasks index 922 | fs.appendFileSync(TASK_INDEX, id + NEWLINE) 923 | TASKS.push({ id: id, date: Date.now(), status: SCHEDULED_TASK_CODE }) 924 | TASKS.lastModified = Date.now() 925 | 926 | return command 927 | } 928 | 929 | 930 | /* ------------------------------------------ Start Server ------------------------------------------ */ 931 | 932 | 933 | function server(request, response) { 934 | // catch and ignore exceptions in production 935 | try { 936 | return handleRequest(request, response) 937 | } catch(e) { 938 | return error(response, e) 939 | } 940 | } 941 | 942 | // LOGGING 943 | console.log("ENVIRONMENT", process.env) 944 | console.log("STATUS", status()) 945 | console.log("USER", { UID: FILEBOT_CMD_UID,GID: FILEBOT_CMD_GID }) 946 | 947 | // HTTP 948 | if ('YES' == process.env['FILEBOT_NODE_HTTP']) { 949 | var host = process.env['FILEBOT_NODE_HOST'] 950 | var port = process.env['FILEBOT_NODE_HTTP_PORT'] 951 | http.createServer(server).listen(port, host) 952 | console.log(process.title + ' listening at http://' + host + ':' + port + PUBLIC_HTML) 953 | } 954 | 955 | // HTTPS 956 | if ('YES' == process.env['FILEBOT_NODE_HTTPS']) { 957 | var host = process.env['FILEBOT_NODE_HOST'] 958 | var port = process.env['FILEBOT_NODE_HTTPS_PORT'] 959 | var options = { 960 | key: fs.readFileSync(process.env['FILEBOT_NODE_HTTPS_KEY']), 961 | cert: fs.readFileSync(process.env['FILEBOT_NODE_HTTPS_CRT']) 962 | } 963 | https.createServer(options, server).listen(port, host) 964 | console.log(process.title + ' listening at https://' + host + ':' + port + PUBLIC_HTML) 965 | } 966 | -------------------------------------------------------------------------------- /server-nodejs/data/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.ids 3 | *.json 4 | *.args 5 | *.sh 6 | -------------------------------------------------------------------------------- /server-nodejs/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filebot-node-server", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "filebot-node-server", 9 | "version": "0.0.0", 10 | "license": "GPL-3.0", 11 | "dependencies": { 12 | "basic-auth": "^2.0.1", 13 | "fast-xml-parser": "^3.19.0", 14 | "shell-escape": "^0.2.0" 15 | } 16 | }, 17 | "node_modules/basic-auth": { 18 | "version": "2.0.1", 19 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 20 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 21 | "dependencies": { 22 | "safe-buffer": "5.1.2" 23 | }, 24 | "engines": { 25 | "node": ">= 0.8" 26 | } 27 | }, 28 | "node_modules/fast-xml-parser": { 29 | "version": "3.19.0", 30 | "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz", 31 | "integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==", 32 | "bin": { 33 | "xml2js": "cli.js" 34 | }, 35 | "funding": { 36 | "type": "paypal", 37 | "url": "https://paypal.me/naturalintelligence" 38 | } 39 | }, 40 | "node_modules/safe-buffer": { 41 | "version": "5.1.2", 42 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 43 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 44 | }, 45 | "node_modules/shell-escape": { 46 | "version": "0.2.0", 47 | "resolved": "https://registry.npmjs.org/shell-escape/-/shell-escape-0.2.0.tgz", 48 | "integrity": "sha1-aP0CXrBJC09WegJ/C/IkgLX4QTM=" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filebot-node-server", 3 | "version": "0.0.0", 4 | "description": "FileBot Node Server", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "sh start.sh" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/filebot/filebot-node.git" 12 | }, 13 | "keywords": [ 14 | "filebot", 15 | "synology", 16 | "dsm" 17 | ], 18 | "author": "Reinhard Pointner", 19 | "license": "GPL-3.0", 20 | "homepage": "https://github.com/filebot/filebot-node", 21 | "bugs": { 22 | "url": "https://github.com/filebot/filebot-node/issues" 23 | }, 24 | "dependencies": { 25 | "basic-auth": "^2.0.1", 26 | "fast-xml-parser": "^3.19.0", 27 | "shell-escape": "^0.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server-nodejs/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | echo " 5 | -------------------- Run $0 (PID: $$) -------------------- $(date) 6 | " 7 | 8 | 9 | export FILEBOT_NODE_HOST='0.0.0.0' # use 0.0.0.0 to allow external connections 10 | export FILEBOT_NODE_AUTH='NONE' 11 | 12 | export FILEBOT_NODE_HTTP='YES' 13 | export FILEBOT_NODE_HTTP_PORT='5452' 14 | 15 | export FILEBOT_NODE_DATA="$PWD/data" 16 | export FILEBOT_TASK_CMD="$PWD/task.sh" 17 | 18 | export FILEBOT_CMD='filebot' 19 | export FILEBOT_CMD_CWD="$PWD" 20 | export FILEBOT_CMD_UID=$(id -u $USER) 21 | export FILEBOT_CMD_GID=$(id -g $USER) 22 | 23 | export FILEBOT_NODE_CLIENT='../dist/generic/client' 24 | 25 | 26 | # force headless mode 27 | export FILEBOT_OPTS='-Djava.awt.headless=true' 28 | 29 | 30 | # import user environment 31 | if [ -f "$FILEBOT_NODE_DATA/environment.sh" ]; then 32 | . "$FILEBOT_NODE_DATA/environment.sh" 33 | fi 34 | 35 | 36 | node 'app.js' 37 | -------------------------------------------------------------------------------- /server-nodejs/task.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -u 2 | 3 | export FILEBOT_NODE_DATA="$PWD/data" 4 | export FILEBOT_NODE_TASK="$1" 5 | 6 | 7 | # sanity check 8 | if [ ! -f "$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" ]; then 9 | echo "$0: Task $FILEBOT_NODE_TASK does not exist" 10 | exit 1 11 | fi 12 | 13 | 14 | # import user environment 15 | if [ -f "$FILEBOT_NODE_DATA/environment.sh" ]; then 16 | . "$FILEBOT_NODE_DATA/environment.sh" 17 | fi 18 | 19 | 20 | # execute filebot task and record output 21 | filebot "@$FILEBOT_NODE_DATA/task/$FILEBOT_NODE_TASK.args" 2>&1 | tee -a "$FILEBOT_NODE_DATA/log/$FILEBOT_NODE_TASK.log" 22 | 23 | 24 | # get filebot exit code (and not tee exit code) 25 | STATUS="${PIPESTATUS[0]}" 26 | 27 | # treat ExitCode.NOOP as ExitCode.SUCCESS 28 | if [ $STATUS -eq 100 ]; then 29 | exit 0 30 | fi 31 | 32 | exit $STATUS 33 | -------------------------------------------------------------------------------- /syno-dsm6.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "package": "filebot-node", 5 | "version": "0.4.8", 6 | "dname": "FileBot Node", 7 | "desc": "FileBot Node allows you to execute and schedule filebot calls via Synology DSM. FileBot Node requires Node.js and FileBot.", 8 | "maintainer": "FileBot", 9 | "maintainer_url": "https://www.filebot.net/", 10 | "distributor": "FileBot", 11 | "distributor_url": "https://www.filebot.net/", 12 | "start": true, 13 | "qstart": true, 14 | "qinst": true, 15 | "qupgrade": true, 16 | "thumbnail": [ 17 | "https://www.filebot.net/syno/filebot-node-thumbnail.png" 18 | ], 19 | "snapshot": [ 20 | "https://www.filebot.net/syno/filebot-node-snapshot.png" 21 | ], 22 | "link": "https://github.com/filebot/filebot-node/releases/download/0.4.8/filebot-node_0.4.8-dsm6.spk", 23 | "download_count": "80213", 24 | "md5": "19916099ba88ca3496a2ef8af62c59a7", 25 | "sha256": "2bcd1d8ee5bf90486d6a2d05f483afb6895e32414ae91b2e2b67f8c4e40e0cd2", 26 | "size": 665600 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /syno.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "package": "filebot-node", 5 | "version": "0.4.8", 6 | "dname": "FileBot Node", 7 | "desc": "FileBot Node allows you to execute and schedule filebot calls via Synology DSM. FileBot Node requires Node.js and FileBot.", 8 | "maintainer": "FileBot", 9 | "maintainer_url": "https://www.filebot.net/", 10 | "distributor": "FileBot", 11 | "distributor_url": "https://www.filebot.net/", 12 | "start": true, 13 | "qstart": true, 14 | "qinst": true, 15 | "qupgrade": true, 16 | "thumbnail": [ 17 | "https://www.filebot.net/syno/filebot-node-thumbnail.png" 18 | ], 19 | "snapshot": [ 20 | "https://www.filebot.net/syno/filebot-node-snapshot.png" 21 | ], 22 | "link": "https://github.com/filebot/filebot-node/releases/download/0.4.8/filebot-node_0.4.8.spk", 23 | "download_count": "80213", 24 | "md5": "38ca8328d512b176e87061ed5fa52ec9", 25 | "sha256": "e528971996216a96b3a77f72122a12b123f72df60e30ebedc90d2b0f7e936fc9", 26 | "size": 665600 27 | } 28 | ] 29 | } --------------------------------------------------------------------------------