├── .babelrc.js ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── .sgcrc ├── .snyk ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── __mocks__ ├── filehound.js └── fs.js ├── __tests__ ├── __helpers__ │ └── _constants.ts ├── getters │ ├── allFilesWithCategory.ts │ ├── allMovies.ts │ ├── allTvSeries.ts │ ├── allTvSeriesNames.ts │ └── constants.ts └── methods │ ├── addNewPath.ts │ ├── createFromJSON.ts │ ├── filterMovies.ts │ ├── filterTvSeries.ts │ ├── removeOldFiles.ts │ ├── scan.ts │ ├── toJSON.ts │ └── toJSONObject.ts ├── config ├── release-rules.js └── release.config.js ├── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── MediaScan.ts ├── MediaScanTypes.ts ├── filters │ ├── filterBooleanProperty.ts │ ├── filterNumberProperty.ts │ ├── filterProperties.ts │ └── filterStringProperty.ts └── utils │ └── utils_functions.ts ├── tsconfig.jest.json ├── tsconfig.json └── tslint.json /.babelrc.js: -------------------------------------------------------------------------------- 1 | // var env = process.env.BABEL_ENV || process.env.NODE_ENV; // Maybe later : for minify stuff 2 | var presets = [ 3 | ["minify", { 4 | "mangle": { 5 | "keepClassName": true 6 | } 7 | }], 8 | ["@babel/env", { loose: true, "targets": { "node": 8 } } ], 9 | "@babel/preset-typescript" 10 | ]; 11 | var plugins = [ 12 | "@babel/plugin-proposal-class-properties", 13 | "@babel/proposal-object-rest-spread" 14 | ]; 15 | var ignore = []; 16 | var comments = false; 17 | 18 | module.exports = { 19 | presets: presets, 20 | plugins: plugins, 21 | ignore: ignore, 22 | comments: comments 23 | }; 24 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ['https://www.buymeacoffee.com/GPFR'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 4 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 5 | #patreon: # Replace with a single Patreon username 6 | #open_collective: # Replace with a single Open Collective username 7 | #ko_fi: # Replace with a single Ko-fi username 8 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | #liberapay: # Replace with a single Liberapay username 11 | #issuehunt: # Replace with a single IssueHunt username 12 | #otechie: # Replace with a single Otechie username 13 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # babel src to lib 61 | lib 62 | 63 | # intellij files 64 | *.iml 65 | .idea 66 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # uncompiled files 2 | src 3 | # jest unit test folders 4 | __mocks__ 5 | __tests__ -------------------------------------------------------------------------------- /.sgcrc: -------------------------------------------------------------------------------- 1 | { 2 | "scope": false, 3 | "body": true, 4 | "emoji": true, 5 | "lowercaseTypes": false, 6 | "initial-commit": { 7 | "isEnabled": true, 8 | "emoji": ":tada:", 9 | "message": "Initial commit" 10 | }, 11 | "types": [ 12 | { 13 | "emoji": ":wrench:", 14 | "type": "Chore:", 15 | "description": "Changes that affect the build system or external dependencies and moving files" 16 | }, 17 | { 18 | "emoji": ":construction_worker:", 19 | "type": "CI:", 20 | "description": "Changes to our CI configuration files and scripts" 21 | }, 22 | { 23 | "emoji": ":memo:", 24 | "type": "Docs:", 25 | "description": "Documentation only changes" 26 | }, 27 | { 28 | "emoji": ":sparkles:", 29 | "type": "Feat:", 30 | "description": "New feature" 31 | }, 32 | { 33 | "emoji": ":bug:", 34 | "type": "Fix:", 35 | "description": "Bug fix" 36 | }, 37 | { 38 | "emoji": ":zap:", 39 | "type": "Perf:", 40 | "description": "Code change that improves performance" 41 | }, 42 | { 43 | "emoji": ":hammer:", 44 | "type": "Refactor:", 45 | "description": "Code change that neither fixes a bug nor adds a feature" 46 | }, 47 | { 48 | "emoji": ":art:", 49 | "type": "Style:", 50 | "description": "Changes that do not affect the meaning of the code" 51 | }, 52 | { 53 | "emoji": ":white_check_mark:", 54 | "type": "Test:", 55 | "description": "Adding missing tests or correcting existing tests" 56 | }, 57 | { 58 | "emoji": ":boom:", 59 | "type": "Breaking:", 60 | "description": "Breaking Change" 61 | },{ 62 | "emoji": ":gem:", 63 | "type": "Upgrade:", 64 | "description": "Update dependencies" 65 | } 66 | ], 67 | "rules": { 68 | "max-char": 72, 69 | "min-char": 10, 70 | "end-with-dot": false 71 | } 72 | } -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.13.5 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | 'npm:extend:20180424': 7 | - filehound > file-js > proper-lockfile > extend: 8 | patched: '2019-01-03T01:32:40.892Z' 9 | SNYK-JS-LODASH-450202: 10 | - snyk > snyk-nodejs-lockfile-parser > lodash: 11 | patched: '2019-07-03T17:53:49.046Z' 12 | - lodash: 13 | patched: '2019-07-03T17:53:49.046Z' 14 | - filehound > lodash: 15 | patched: '2019-07-03T17:53:49.046Z' 16 | - snyk > @snyk/dep-graph > lodash: 17 | patched: '2019-07-03T17:53:49.046Z' 18 | - snyk > snyk-config > lodash: 19 | patched: '2019-07-03T17:53:49.046Z' 20 | - snyk > snyk-mvn-plugin > lodash: 21 | patched: '2019-07-03T17:53:49.046Z' 22 | - snyk > lodash: 23 | patched: '2019-07-03T17:53:49.046Z' 24 | - snyk > snyk-nuget-plugin > lodash: 25 | patched: '2019-07-03T17:53:49.046Z' 26 | - snyk > snyk-php-plugin > lodash: 27 | patched: '2019-07-03T17:53:49.046Z' 28 | - snyk > inquirer > lodash: 29 | patched: '2019-07-03T17:53:49.046Z' 30 | - snyk > snyk-go-plugin > graphlib > lodash: 31 | patched: '2019-07-03T17:53:49.046Z' 32 | - snyk > snyk-nodejs-lockfile-parser > graphlib > lodash: 33 | patched: '2019-07-03T17:53:49.046Z' 34 | - snyk > @snyk/dep-graph > graphlib > lodash: 35 | patched: '2019-07-03T17:53:49.046Z' 36 | - snyk > lodash: 37 | patched: '2019-07-24T09:53:29.991Z' 38 | - filehound > lodash: 39 | patched: '2019-07-24T09:53:29.991Z' 40 | - snyk > @snyk/dep-graph > lodash: 41 | patched: '2019-07-24T09:53:29.991Z' 42 | - snyk > snyk-config > lodash: 43 | patched: '2019-07-24T09:53:29.991Z' 44 | - snyk > snyk-nodejs-lockfile-parser > lodash: 45 | patched: '2019-07-24T09:53:29.991Z' 46 | - snyk > snyk-nuget-plugin > lodash: 47 | patched: '2019-07-24T09:53:29.991Z' 48 | - snyk > inquirer > lodash: 49 | patched: '2019-07-24T09:53:29.991Z' 50 | - snyk > snyk-go-plugin > graphlib > lodash: 51 | patched: '2019-07-24T09:53:29.991Z' 52 | - snyk > snyk-nodejs-lockfile-parser > graphlib > lodash: 53 | patched: '2019-07-24T09:53:29.991Z' 54 | - snyk > @snyk/dep-graph > graphlib > lodash: 55 | patched: '2019-07-24T09:53:29.991Z' 56 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | # keep the npm cache around to speed up installs 5 | cache: 6 | directories: 7 | - "$HOME/.npm" 8 | notifications: 9 | slack: 10 | on_success: change 11 | on_failure: always 12 | node_js: 13 | - 'node' 14 | - '10' 15 | - '8' 16 | os: 17 | - linux 18 | before_install: 19 | - npm install -g codecov 20 | - npm i -g travis-deploy-once 21 | - npm install -g greenkeeper-lockfile@latest 22 | install: 23 | - npm i -g npm@latest 24 | - npm install 25 | before_script: 26 | - npm prune 27 | - echo "Check if TypeScript files have valid syntax/types" 28 | - npm run type-check 29 | - echo "Check if TypeScript files have valid linting" 30 | - npm run lint 31 | - echo "GreenKeeper lockfile update" 32 | - greenkeeper-lockfile-update 33 | after_script: 34 | - greenkeeper-lockfile-upload 35 | after_success: 36 | - codecov 37 | - travis-deploy-once && npx -p node@8 npm run semantic-release 38 | branches: 39 | except: 40 | - /^v\d+\.\d+\.\d+$/ 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.1](https://github.com/jy95/mediaScan/compare/v2.0.0...v2.0.1) (2019-08-02) 2 | 3 | 4 | ### CI 5 | 6 | * auto update of lockfile ([a3065977d7cf103502068be9ccb11926396b6aa3](https://github.com/jy95/mediaScan/commit/a3065977d7cf103502068be9ccb11926396b6aa3)) 7 | * auto update of lockfile ([4912e459c461838e1a56cf7a1a1513d23899deeb](https://github.com/jy95/mediaScan/commit/4912e459c461838e1a56cf7a1a1513d23899deeb)) 8 | 9 | ### fix 10 | 11 | * .snyk to reduce vulnerabilities ([f3e5e47315e45bcd6c01b7d818e337e255d0a5cc](https://github.com/jy95/mediaScan/commit/f3e5e47315e45bcd6c01b7d818e337e255d0a5cc)) 12 | * .snyk, package.json & package-lock.json to reduce vulnerabilities ([42390ff4a13020408922aaf429ba8d9fae61b2a4](https://github.com/jy95/mediaScan/commit/42390ff4a13020408922aaf429ba8d9fae61b2a4)) 13 | * .snyk, package.json & package-lock.json to reduce vulnerabilities ([689062ec471b5a0991498bc929f47e33639b13cb](https://github.com/jy95/mediaScan/commit/689062ec471b5a0991498bc929f47e33639b13cb)) 14 | * configure Snyk protect to enable patches ([3c245bd370ba78980a35f180f8097939ed004805](https://github.com/jy95/mediaScan/commit/3c245bd370ba78980a35f180f8097939ed004805)) 15 | * package.json & package-lock.json to reduce vulnerabilities ([e8ef8d70d9a52f569e9c296e80ff2111f69e4fd9](https://github.com/jy95/mediaScan/commit/e8ef8d70d9a52f569e9c296e80ff2111f69e4fd9)) 16 | 17 | ### Fix 18 | 19 | * typings issues ([1d4282e6a37e140335b5e3b312a0ba5ea66ace00](https://github.com/jy95/mediaScan/commit/1d4282e6a37e140335b5e3b312a0ba5ea66ace00)) 20 | 21 | ### Upgrade 22 | 23 | * Update @semantic-release/changelog to version 3.0.0 ([af3ed183246d3379ef891690f56f79eca5a174de](https://github.com/jy95/mediaScan/commit/af3ed183246d3379ef891690f56f79eca5a174de)) 24 | * Update @semantic-release/git to version 6.0.0 ([f8125603b5e9fc9796dd13ce7bf466c33994f652](https://github.com/jy95/mediaScan/commit/f8125603b5e9fc9796dd13ce7bf466c33994f652)) 25 | * Update @semantic-release/git to version 7.0.0 ([eac31ac5449834458a6d439dd83807fffdecdfb4](https://github.com/jy95/mediaScan/commit/eac31ac5449834458a6d439dd83807fffdecdfb4)) 26 | * Update @types/jest to version 24.0.0 ([035ccd7b68148d5cc4bd361da6cfb1501d6ed679](https://github.com/jy95/mediaScan/commit/035ccd7b68148d5cc4bd361da6cfb1501d6ed679)) 27 | * Update @types/node to version 11.9.0 ([73579977d90aec53a547aa014c6f2cdb0459c804](https://github.com/jy95/mediaScan/commit/73579977d90aec53a547aa014c6f2cdb0459c804)) 28 | * Update @types/node to version 12.0.0 ([f7317713109b6390f7cf1c9448de2d0292e7bc22](https://github.com/jy95/mediaScan/commit/f7317713109b6390f7cf1c9448de2d0292e7bc22)) 29 | * Update babel-minify to version 0.5.0 ([922b85d806f936cb38a849887d4ea6841191dda2](https://github.com/jy95/mediaScan/commit/922b85d806f936cb38a849887d4ea6841191dda2)) 30 | * Update snyk to version 1.163.0 ([d859133271de8f42605c8bee4c171ef01e208b2f](https://github.com/jy95/mediaScan/commit/d859133271de8f42605c8bee4c171ef01e208b2f)) 31 | * Update snyk to version 2.0.0 ([5a95ba1e603961c6412b082e8e7d6a9904acba27](https://github.com/jy95/mediaScan/commit/5a95ba1e603961c6412b082e8e7d6a9904acba27)) 32 | * Update ts-jest to version 23.0.0 ([8f1bf4c0da85dac2e698861a0af2b529fb38402f](https://github.com/jy95/mediaScan/commit/8f1bf4c0da85dac2e698861a0af2b529fb38402f)) 33 | * Update ts-jest to version 23.10.1 ([3287411bfd9e8afec131fc4a803d6822b753303c](https://github.com/jy95/mediaScan/commit/3287411bfd9e8afec131fc4a803d6822b753303c)) 34 | * Update ts-jest to version 24.0.0 ([aa7a5dc8cb75dd27f5e0839d6c94d471e3a79cd5](https://github.com/jy95/mediaScan/commit/aa7a5dc8cb75dd27f5e0839d6c94d471e3a79cd5)) 35 | 36 | 37 | # [2.0.0](https://github.com/jy95/mediaScan/compare/v1.2.4...v2.0.0) (2018-05-20) 38 | 39 | 40 | ### Breaking 41 | 42 | * Drop Support for Node 6 (EOL) ([ef84bc3b0e86684f09df7abdb37ec6af578bdb0c](https://github.com/jy95/mediaScan/commit/ef84bc3b0e86684f09df7abdb37ec6af578bdb0c)) 43 | 44 | ### Chore 45 | 46 | * Fix typo ([8c652c0317a8de3ee13d5b31a9972086bb38251d](https://github.com/jy95/mediaScan/commit/8c652c0317a8de3ee13d5b31a9972086bb38251d)) 47 | 48 | ### Upgrade 49 | 50 | * Babel beta 46 ([7453218893063930a85eb531e381ad7c4bc24c7f](https://github.com/jy95/mediaScan/commit/7453218893063930a85eb531e381ad7c4bc24c7f)) 51 | * Lockfile ([b5bceeb7aedac1836729e9aa9cb9446bc75a9d88](https://github.com/jy95/mediaScan/commit/b5bceeb7aedac1836729e9aa9cb9446bc75a9d88)) 52 | * Semantic-release ([25fe356638a50b5345e8940fe2c5b403df49d004](https://github.com/jy95/mediaScan/commit/25fe356638a50b5345e8940fe2c5b403df49d004)) 53 | 54 | 55 | ## [1.2.4](https://github.com/jy95/mediaScan/compare/v1.2.3...v1.2.4) (2018-04-11) 56 | 57 | 58 | ### Docs 59 | 60 | * declaration only for ts files ([0183ce58749fe6a1f32b1a7730306e4994f56a3e](https://github.com/jy95/mediaScan/commit/0183ce58749fe6a1f32b1a7730306e4994f56a3e)) 61 | * Fix typo in tsconfig ([0ca2e1fe077d3491bd3289b0532d2213971d78e3](https://github.com/jy95/mediaScan/commit/0ca2e1fe077d3491bd3289b0532d2213971d78e3)) 62 | 63 | ### Perf 64 | 65 | * Replace eval by object function ([f1a30bbf6cd20fe0c0b850d818aa3337f12f9eaf](https://github.com/jy95/mediaScan/commit/f1a30bbf6cd20fe0c0b850d818aa3337f12f9eaf)) 66 | 67 | ### Style 68 | 69 | * Follows tslint code conventions - unit tests ([013286eb01b9997028f97d13367da1a85ba6f2e9](https://github.com/jy95/mediaScan/commit/013286eb01b9997028f97d13367da1a85ba6f2e9)) 70 | * Prettier all sources code ([e9b508e406e16e71e6409e829a33a1615acf6633](https://github.com/jy95/mediaScan/commit/e9b508e406e16e71e6409e829a33a1615acf6633)) 71 | * Tslint fix errors ([802b823de097a219e1aa4fc3c47144cb22f2f072](https://github.com/jy95/mediaScan/commit/802b823de097a219e1aa4fc3c47144cb22f2f072)) 72 | 73 | ### Test 74 | 75 | * Fix lost coverage (because of jest issue) ([90bd75ceefaadc2a71217974bb199ad98fbba551](https://github.com/jy95/mediaScan/commit/90bd75ceefaadc2a71217974bb199ad98fbba551)) 76 | * Fix ts-jest mix up with Ts config ([3a9cebd51ea05a047e19c27452052304284c5ce3](https://github.com/jy95/mediaScan/commit/3a9cebd51ea05a047e19c27452052304284c5ce3)) 77 | 78 | ### Upgrade 79 | 80 | * Update Babel to beta 44 ([2e4e16c14960eb65fc99a82afc41e07e2768d2fa](https://github.com/jy95/mediaScan/commit/2e4e16c14960eb65fc99a82afc41e07e2768d2fa)) 81 | * Update dependancies ([45e3835025dfdeebb18220ccc948687e84913046](https://github.com/jy95/mediaScan/commit/45e3835025dfdeebb18220ccc948687e84913046)) 82 | 83 | 84 | ## [1.2.3](https://github.com/jy95/mediaScan/compare/v1.2.2...v1.2.3) (2018-03-23) 85 | 86 | 87 | ### Refactor 88 | 89 | * Rewrite default parser integration ([6965bd15b6be2596ab0357cbeabbd4b336d8b010](https://github.com/jy95/mediaScan/commit/6965bd15b6be2596ab0357cbeabbd4b336d8b010)) 90 | 91 | ### Style 92 | 93 | * Follows Tslint code conventions ([43dae497e40a27581b59076c57b2caf4d0b67261](https://github.com/jy95/mediaScan/commit/43dae497e40a27581b59076c57b2caf4d0b67261)) 94 | 95 | 96 | ## [1.2.2](https://github.com/jy95/mediaScan/compare/v1.2.1...v1.2.2) (2018-03-21) 97 | 98 | 99 | ### Perf 100 | 101 | * Lodash FP version of removeOldFiles ([5029fd7faafb339fe64694a14a8bb2c0dccaaf03](https://github.com/jy95/mediaScan/commit/5029fd7faafb339fe64694a14a8bb2c0dccaaf03)) 102 | * Use Array.prototype.push.apply instead of concat ([047118524790e172df3bb6990403d9e0e24a3a0d](https://github.com/jy95/mediaScan/commit/047118524790e172df3bb6990403d9e0e24a3a0d)) 103 | 104 | ### Upgrade 105 | 106 | * Update semantic-release to version 15.1.3 ([da08546df874effc0e71d3d37433bd1fa7541991](https://github.com/jy95/mediaScan/commit/da08546df874effc0e71d3d37433bd1fa7541991)), closes [#21](https://github.com/jy95/mediaScan/issues/21) 107 | 108 | 109 | ## [1.2.1](https://github.com/jy95/mediaScan/compare/v1.2.0...v1.2.1) (2018-03-20) 110 | 111 | 112 | ### Perf 113 | 114 | * Use babel-minify@canary ([0a05799722e159499cd15e73f69155b7d234c8ea](https://github.com/jy95/mediaScan/commit/0a05799722e159499cd15e73f69155b7d234c8ea)) 115 | 116 | ### Refactor 117 | 118 | * Array.prototype.push.apply instead of lodash concat ([cc462c5f9504559d8cadc7a6e16bcc6c5ffe8a76](https://github.com/jy95/mediaScan/commit/cc462c5f9504559d8cadc7a6e16bcc6c5ffe8a76)) 119 | * less code version of removeOldFiles ([d6f0fe35d34083f548bef78560bcc3115b61fd36](https://github.com/jy95/mediaScan/commit/d6f0fe35d34083f548bef78560bcc3115b61fd36)) 120 | 121 | ### Upgrade 122 | 123 | * Update ALL 20/03/2018 ([9e9df23e26ba5463bf38e249e297e07517e01cf6](https://github.com/jy95/mediaScan/commit/9e9df23e26ba5463bf38e249e297e07517e01cf6)) 124 | * Update package-lock ([c604e484f5170efc16bc190977ba39cb8cb18e6e](https://github.com/jy95/mediaScan/commit/c604e484f5170efc16bc190977ba39cb8cb18e6e)) 125 | 126 | 127 | # [1.2.0](https://github.com/jy95/mediaScan/compare/v1.1.6...v1.2.0) (2018-03-16) 128 | 129 | 130 | ### CI 131 | 132 | * cache only npm and not node modules ([3dbf969d3922c1e70e28278cb8f1b52c1b2b6595](https://github.com/jy95/mediaScan/commit/3dbf969d3922c1e70e28278cb8f1b52c1b2b6595)) 133 | * Speed Up Build install step ([a909330eab2b977317c90e86ccf92c08cde8133c](https://github.com/jy95/mediaScan/commit/a909330eab2b977317c90e86ccf92c08cde8133c)) 134 | 135 | ### Feat 136 | 137 | * getter allTvSeriesNames ([907e10eee43106faf0a3a3dd96a61aae04e92708](https://github.com/jy95/mediaScan/commit/907e10eee43106faf0a3a3dd96a61aae04e92708)) 138 | 139 | ### Perf 140 | 141 | * quick return for filter functions ([5723838147a00398d44b11f7d3904ffedfb05926](https://github.com/jy95/mediaScan/commit/5723838147a00398d44b11f7d3904ffedfb05926)) 142 | * simplify toJSONObject series key ([aa1746e36c91bbfb0b5e3d9367f6e0b7ea089f3f](https://github.com/jy95/mediaScan/commit/aa1746e36c91bbfb0b5e3d9367f6e0b7ea089f3f)) 143 | 144 | ### Refactor 145 | 146 | * Use Lodash FP instead of transducers ([2ab89f038962e2c0ce8ec56acd2fff806e838671](https://github.com/jy95/mediaScan/commit/2ab89f038962e2c0ce8ec56acd2fff806e838671)) 147 | 148 | 149 | ## [1.1.6](https://github.com/jy95/mediaScan/compare/v1.1.5...v1.1.6) (2018-03-08) 150 | 151 | 152 | ### Perf 153 | 154 | * Transducers for mapProperties ([d9e5d84d4597fe5adb404ca8e4e3aca1ca130355](https://github.com/jy95/mediaScan/commit/d9e5d84d4597fe5adb404ca8e4e3aca1ca130355)) 155 | * Transducers version of removeOldFiles ([e40c721f7ed8ad7719eae94843556e882edbdb7c](https://github.com/jy95/mediaScan/commit/e40c721f7ed8ad7719eae94843556e882edbdb7c)) 156 | * Use transducers.js to make filter functions faster ([0049824a7c0985a6f1de3f5fc44419b542db1891](https://github.com/jy95/mediaScan/commit/0049824a7c0985a6f1de3f5fc44419b542db1891)) 157 | 158 | ### Test 159 | 160 | * Fix coverage issue in removeOldFiles ([b3351829fb91e92d8c6d4ce3fbb3ecffa875368b](https://github.com/jy95/mediaScan/commit/b3351829fb91e92d8c6d4ce3fbb3ecffa875368b)) 161 | 162 | 163 | ## [1.1.5](https://github.com/jy95/mediaScan/compare/v1.1.4...v1.1.5) (2018-03-07) 164 | 165 | 166 | ### Refactor 167 | 168 | * Trying to fix typings for package ([627571c90c003ec94d4dda0cab15dcbeec426a2c](https://github.com/jy95/mediaScan/commit/627571c90c003ec94d4dda0cab15dcbeec426a2c)) 169 | 170 | 171 | ## [1.1.4](https://github.com/jy95/mediaScan/compare/v1.1.3...v1.1.4) (2018-03-05) 172 | 173 | 174 | ### Chore 175 | 176 | * Update DevDependancies ([5a871e2be9b97d6000066f4eb9f3bcc45f098a5b](https://github.com/jy95/mediaScan/commit/5a871e2be9b97d6000066f4eb9f3bcc45f098a5b)) 177 | 178 | ### Docs 179 | 180 | * Add TypeScript types for this lib ([46ea8e4a38419f76590a9a2a7676b9c7b25fd665](https://github.com/jy95/mediaScan/commit/46ea8e4a38419f76590a9a2a7676b9c7b25fd665)) 181 | 182 | ### Refactor 183 | 184 | * Use implementation of toJSONObject for toJSON() ([a518fdf6639de1ccf4a1191bc2c7879ddde817c0](https://github.com/jy95/mediaScan/commit/a518fdf6639de1ccf4a1191bc2c7879ddde817c0)) 185 | 186 | 187 | ## [1.1.3](https://github.com/jy95/mediaScan/compare/v1.1.2...v1.1.3) (2018-02-25) 188 | 189 | 190 | ### Perf 191 | 192 | * addNewFiles rewritten with Lodash ([3edb2294ce78036c539faad70508c7cdb403e099](https://github.com/jy95/mediaScan/commit/3edb2294ce78036c539faad70508c7cdb403e099)) 193 | * removeOldFiles rewritten with Lodash ([95f784690110db972412ccee335eff0e0185fd1c](https://github.com/jy95/mediaScan/commit/95f784690110db972412ccee335eff0e0185fd1c)) 194 | 195 | ### Refactor 196 | 197 | * Drop forEach to for ... of ([b2c87c594edb16141fecf36704a0d5b80ba88ed3](https://github.com/jy95/mediaScan/commit/b2c87c594edb16141fecf36704a0d5b80ba88ed3)) 198 | 199 | 200 | ## [1.1.2](https://github.com/jy95/mediaScan/compare/v1.1.1...v1.1.2) (2018-02-25) 201 | 202 | 203 | ### Perf 204 | 205 | * Set specific babel settings ([068aa48b30f45f6057d56b774d5f604dd1f9f88b](https://github.com/jy95/mediaScan/commit/068aa48b30f45f6057d56b774d5f604dd1f9f88b)) 206 | 207 | ### Test 208 | 209 | * Fix typo in test name ([592f5e0b8ecdb9884ff14c7066ba358407dfb781](https://github.com/jy95/mediaScan/commit/592f5e0b8ecdb9884ff14c7066ba358407dfb781)) 210 | 211 | 212 | ## [1.1.1](https://github.com/jy95/mediaScan/compare/v1.1.0...v1.1.1) (2018-02-23) 213 | 214 | 215 | ### Refactor 216 | 217 | * filterMoviesByProperties rewritten ([a0a209b96a977e6a941b8a28370e88e5a9ce4ef3](https://github.com/jy95/mediaScan/commit/a0a209b96a977e6a941b8a28370e88e5a9ce4ef3)) 218 | * filterTvSeriesByProperties Rewritten ([f0af6d4c9603d471341cba7dd25c6ee358ad97f4](https://github.com/jy95/mediaScan/commit/f0af6d4c9603d471341cba7dd25c6ee358ad97f4)) 219 | 220 | ### Test 221 | 222 | * Expect direction ([bc738e49a088ddbf18e8469b44519b8f1d087d51](https://github.com/jy95/mediaScan/commit/bc738e49a088ddbf18e8469b44519b8f1d087d51)) 223 | 224 | 225 | # [1.1.0](https://github.com/jy95/mediaScan/compare/v1.0.1...v1.1.0) (2018-02-22) 226 | 227 | 228 | ### Docs 229 | 230 | * Add Greenkeeper badge ([afb7bb20b983a55483d9ce5cf2a0dc595322d5ad](https://github.com/jy95/mediaScan/commit/afb7bb20b983a55483d9ce5cf2a0dc595322d5ad)) 231 | 232 | ### Feat 233 | 234 | * toJSONObject method ([f625e4a6e46e1430cb0a359fd445ecc9ee1d7969](https://github.com/jy95/mediaScan/commit/f625e4a6e46e1430cb0a359fd445ecc9ee1d7969)) 235 | 236 | ### Perf 237 | 238 | * Rewrite filter search Map in ES6 ([5fb3b4328a1e47120afd171a0cfba524cfed7155](https://github.com/jy95/mediaScan/commit/5fb3b4328a1e47120afd171a0cfba524cfed7155)) 239 | 240 | 241 | ## [1.0.1](https://github.com/jy95/mediaScan/compare/v1.0.0...v1.0.1) (2018-02-22) 242 | 243 | 244 | ### Docs 245 | 246 | * add README.md ([964a48fc164bcd23dc0c9c571469452e5fb2f69b](https://github.com/jy95/mediaScan/commit/964a48fc164bcd23dc0c9c571469452e5fb2f69b)) 247 | 248 | ### Perf 249 | 250 | * Rewritten filter functions ([9ac0a50ea8da2aafa83b18a699cd10d9968079db](https://github.com/jy95/mediaScan/commit/9ac0a50ea8da2aafa83b18a699cd10d9968079db)) 251 | 252 | ### Style 253 | 254 | * change greenkeeper messages with emoji ([9671e75211f271107fb784f18a2451e1a63ca921](https://github.com/jy95/mediaScan/commit/9671e75211f271107fb784f18a2451e1a63ca921)) 255 | 256 | 257 | # 1.0.0 (2018-02-21) 258 | 259 | 260 | ### CI 261 | 262 | * Set up Codecov ([ad9d8ede49a1f54592b1dc2117dd978d2505e9ad](https://github.com/jy95/mediaScan/commit/ad9d8ede49a1f54592b1dc2117dd978d2505e9ad)) 263 | 264 | ### Fix 265 | 266 | * @babel/runtime -prod / @babel/plugin-transform-runtime -dev ([22fa8b5240d6140c169f30116d8a12d624b85db1](https://github.com/jy95/mediaScan/commit/22fa8b5240d6140c169f30116d8a12d624b85db1)) 267 | * Fix Babel setup with TypeScript ([f6c31f71f16a6ce4fee1e90b0d3b803b9a0e41cb](https://github.com/jy95/mediaScan/commit/f6c31f71f16a6ce4fee1e90b0d3b803b9a0e41cb)) 268 | * Fix Build for Node 6 ([aff1d7480da6b50e4311ef527918097031670e9d](https://github.com/jy95/mediaScan/commit/aff1d7480da6b50e4311ef527918097031670e9d)) 269 | * Objects.entries not available in Node 6 - Real Fix ([08bb6a3cdc4222512911c9624b2769aeb18bcbdb](https://github.com/jy95/mediaScan/commit/08bb6a3cdc4222512911c9624b2769aeb18bcbdb)) 270 | 271 | ### Perf 272 | 273 | * Add Babel Support (TODO) ([535f81cf9eb4cefc5c8622c7753882cee8ef9a08](https://github.com/jy95/mediaScan/commit/535f81cf9eb4cefc5c8622c7753882cee8ef9a08)) 274 | * Rewrite lib in Typescript ([21c03ea65fc33c6408b515c2102e34c9aa9f572d](https://github.com/jy95/mediaScan/commit/21c03ea65fc33c6408b515c2102e34c9aa9f572d)) 275 | 276 | ### Refactor 277 | 278 | * Scan method ([f299cedc2fa861686bea506c9f1e3e0d879933a6](https://github.com/jy95/mediaScan/commit/f299cedc2fa861686bea506c9f1e3e0d879933a6)) 279 | 280 | ### Test 281 | 282 | * Fix tests ([eef74a80cc1fa1c332fe7bb3360e505c7701d2b5](https://github.com/jy95/mediaScan/commit/eef74a80cc1fa1c332fe7bb3360e505c7701d2b5)) 283 | * Rewrite test in proper way ([e49ec418d32b3094ab0ed9b45ea36b0d4a7ccb5d](https://github.com/jy95/mediaScan/commit/e49ec418d32b3094ab0ed9b45ea36b0d4a7ccb5d)) 284 | * Rewritten tests in Jest ([e4f7a1abf129384df78689095c5588b900e39bd2](https://github.com/jy95/mediaScan/commit/e4f7a1abf129384df78689095c5588b900e39bd2)) 285 | * Trying to fix addNewPath issue ([3e67528bf6d02a1628ea74fdeb3e6898535d434d](https://github.com/jy95/mediaScan/commit/3e67528bf6d02a1628ea74fdeb3e6898535d434d)) 286 | * Use toThrowError for rejected promises ([546f5f2aabb4ccdc41390bf315056873609cc292](https://github.com/jy95/mediaScan/commit/546f5f2aabb4ccdc41390bf315056873609cc292)) 287 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jacques.yakoub+github@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 jy95 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mediascan [![Build Status](https://img.shields.io/travis/jy95/mediaScan.svg)](https://travis-ci.org/jy95/mediaScan) [![codecov](https://codecov.io/gh/jy95/mediaScan/branch/master/graph/badge.svg)](https://codecov.io/gh/jy95/mediaScan) [![Dependency Status](https://img.shields.io/david/jy95/mediaScan.svg)](https://david-dm.org/jy95/mediaScan) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Greenkeeper badge](https://badges.greenkeeper.io/jy95/mediaScan.svg)](https://greenkeeper.io/) [![Join the chat at https://gitter.im/mediaScan/Lobby](https://badges.gitter.im/mediaScan/Lobby.svg)](https://gitter.im/mediaScan/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | 3 | > A scanner for media files that follows a user-provided naming convention 4 | 5 | ## What to do with this library ? 6 | 7 | A lot of things : 8 | * Basic listing purposes : 9 | * [List found movies](https://github.com/jy95/mediaScan/wiki/List-found-movies) 10 | * [List each found tv serie](https://github.com/jy95/mediaScan/wiki/List-each-found-tv-serie) 11 | * [Detect the category of each file](https://github.com/jy95/mediaScan/wiki/Detect-the-category-of-each-file) 12 | * Filtering purposes : 13 | * [Filter movies based on search parameters](https://github.com/jy95/mediaScan/wiki/Filter-movies-by-parameters) 14 | * [Filter tv-shown based on search parameters](https://github.com/jy95/mediaScan/wiki/Filter-tv-series-by-parameters) 15 | * Miscellaneous purposes 16 | * [Create custom playlist(s)](https://github.com/jy95/mediaScan/wiki/Create-custom-playlist(s)) 17 | * ... 18 | 19 | Don't hesitate to suggest new features : it is always worthy :) 20 | 21 | ## FAQ 22 | 23 | ### Which naming convention can I use with this lib ? 24 | 25 | **ANYTHING**. All You have to do is to implement a parser function : 26 | A function that takes a single string argument `fullPathFile` (the full path to the file) that returns an object that minimal contains a `title` string property. 27 | For example : 28 | 29 | ```js 30 | const ptt = require("parse-torrent-title"); 31 | const information = ptt.parse("Game.of.Thrones.S01E01.720p.HDTV.x264-CTU"); 32 | 33 | console.log(information.title); // Game of Thrones 34 | console.log(information.season); // 1 35 | console.log(information.episode); // 1 36 | console.log(information.resolution); // 720p 37 | console.log(information.codec); // x264 38 | console.log(information.source); // HDTV 39 | console.log(information.group); // CTU 40 | ``` 41 | 42 | This lib was tested with these parsers that follows torrent naming conventions (see their readme for more info) : 43 | 44 | * [parse-torrent-title](https://www.npmjs.com/package/parse-torrent-title) (the default in this lib) 45 | * [torrent-name-parser](https://www.npmjs.com/package/torrent-name-parser) 46 | * [torrent-name-parse](https://www.npmjs.com/package/torrent-name-parse) 47 | 48 | ### How the library detects the category of a media file ? 49 | 50 | The default implementation determines it is a tv-show if there is `season` and `episode` attributes can be found in the information provided by the parser. 51 | Here is a example if you want to implement one : 52 | ```ts 53 | // Default implementation to know which category is this file 54 | function defaultWhichCategoryFunction(object : MediaScanLib.TPN) : MediaScanLib.Category{ 55 | // workaround : const string enum aren't compiled correctly with Babel 56 | return (checkProperties(object, ['season', 'episode'])) 57 | ? 'TV_SERIES' as MediaScanLib.Category.TV_SERIES_TYPE : 'MOVIES' as MediaScanLib.Category.MOVIES_TYPE; 58 | } 59 | ``` 60 | 61 | ### Using custom parameters in the lib 62 | 63 | Check the [constructor](https://github.com/jy95/mediaScan/blob/master/src/MediaScan.ts#L38) for more detail - an illustration : 64 | 65 | ```js 66 | const MediaScan = require("mediascan"); 67 | let libInstance = new MediaScan({ 68 | defaultPath = process.cwd(), // Default path to explore , if paths is empty 69 | paths = [], // all the paths that will be explored 70 | allFilesWithCategory = new Map(), // the mapping between file and Category 71 | movies = new Set(), // Set (all the movies) 72 | series = new Map(), // > (all the tv-series episodes) 73 | }, { 74 | parser = nameParser, // the explained parser 75 | whichCategory = defaultWhichCategoryFunction, // the previously explained detection function 76 | }); 77 | ``` 78 | 79 | ## Installation 80 | 81 | For npm users : 82 | 83 | ```shell 84 | $ npm install --save mediascan 85 | ``` 86 | 87 | for Yarn : 88 | ```shell 89 | $ yarn add mediascan 90 | ``` 91 | 92 | ## Test 93 | 94 | ```shell 95 | npm test 96 | ``` 97 | 98 | # Types definitions 99 | 100 | If You want, You can have the types definitions used in this lib : 101 | 102 | ```shell 103 | npm install @types/mediascan 104 | ``` 105 | 106 | ## Contributing 107 | 108 | * If you're unsure if a feature would make a good addition, you can always [create an issue](https://github.com/jy95/mediaScan/issues/new) first. 109 | * We aim for 100% test coverage. Please write tests for any new functionality or changes. 110 | * Any API changes should be fully documented. 111 | * Make sure your code meets our linting standards. Run `npm run lint` to check your code. 112 | * Be mindful of others when making suggestions and/or code reviewing. -------------------------------------------------------------------------------- /__mocks__/filehound.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // need to manually mock these function as there is no solution : https://github.com/facebook/jest/issues/5589 4 | // here ; a short truncated copy of babel stuff of this class 5 | function _possibleConstructorReturn(self, call) { 6 | if (!self) { 7 | throw new ReferenceError( 8 | "this hasn't been initialised - super() hasn't been called" 9 | ); 10 | } 11 | return call && (typeof call === "object" || typeof call === "function") 12 | ? call 13 | : self; 14 | } 15 | 16 | var _createClass = (function() { 17 | function defineProperties(target, props) { 18 | for (var i = 0; i < props.length; i++) { 19 | var descriptor = props[i]; 20 | descriptor.enumerable = descriptor.enumerable || false; 21 | descriptor.configurable = true; 22 | if ("value" in descriptor) descriptor.writable = true; 23 | Object.defineProperty(target, descriptor.key, descriptor); 24 | } 25 | } 26 | 27 | return function(Constructor, protoProps, staticProps) { 28 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 29 | if (staticProps) defineProperties(Constructor, staticProps); 30 | return Constructor; 31 | }; 32 | })(); 33 | 34 | // truncated version of this stuff 35 | var FileHound = (function() { 36 | // temp files 37 | let result; 38 | 39 | function FileHound() { 40 | var _this = _possibleConstructorReturn( 41 | this, 42 | (FileHound.__proto__ || Object.getPrototypeOf(FileHound)).call(this) 43 | ); 44 | return _this; 45 | } 46 | 47 | _createClass( 48 | FileHound, 49 | [ 50 | { 51 | key: "paths", 52 | value: function paths() { 53 | return this; 54 | } 55 | }, 56 | { 57 | key: "ext", 58 | value: function ext() { 59 | return this; 60 | } 61 | }, 62 | { 63 | key: "find", 64 | value: function find() { 65 | if (result !== undefined) { 66 | return Promise.resolve(result); 67 | } 68 | // if nothing provided , throw error 69 | return Promise.reject("VAUDOU"); 70 | } 71 | } 72 | ], 73 | [ 74 | { 75 | key: "create", 76 | value: function create() { 77 | return new FileHound(); 78 | } 79 | }, 80 | { 81 | key: "__setResult", 82 | value: function(ExpectedResult) { 83 | result = ExpectedResult; 84 | } 85 | } 86 | ] 87 | ); 88 | 89 | return FileHound; 90 | })(); 91 | module.exports = FileHound; 92 | -------------------------------------------------------------------------------- /__mocks__/fs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const fs = jest.genMockFromModule("fs"); 3 | 4 | // temp files 5 | let mockPaths = []; 6 | 7 | function access(path, mode, callback) { 8 | if (!mockPaths.includes(path)) { 9 | callback(new Error("VAUDOU")); 10 | } 11 | callback(); 12 | } 13 | 14 | function __setMockPaths(pathArray) { 15 | mockPaths = pathArray; 16 | } 17 | 18 | fs.access = access; 19 | fs.__setMockPaths = __setMockPaths; 20 | 21 | module.exports = fs; 22 | -------------------------------------------------------------------------------- /__tests__/__helpers__/_constants.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import {join} from "path"; 3 | 4 | export const MediaScan = require("../../index.js"); 5 | 6 | export const folders = ["folder1", "folder2"]; 7 | 8 | export const files = [ 9 | join( 10 | folders[0], 11 | "The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi", 12 | ), 13 | join( 14 | folders[1], 15 | "The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi", 16 | ), 17 | join( 18 | folders[0], 19 | "Bad.Ass.2012.REMASTERED.TRUEFRENCH.DVDRiP.XviD" + 20 | "-www.zone-telechargement.ws.avi", 21 | ), 22 | ]; 23 | -------------------------------------------------------------------------------- /__tests__/getters/allFilesWithCategory.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 7 | 8 | describe("allFilesWithCategory", () => { 9 | 10 | beforeAll(() => { 11 | // Set up some mocked out file info before each test 12 | require("fs").__setMockPaths(folders); 13 | require("filehound").__setResult(files); 14 | }); 15 | 16 | /** @test {TorrentLibrary#allFilesWithCategory} */ 17 | test("Should correctly detect the category of each file", async () => { 18 | const libInstance = new MediaScan(); 19 | await expect(libInstance.addNewPath(...folders)).resolves; 20 | await expect(libInstance.scan()).resolves; 21 | expect(libInstance.allFilesWithCategory).toEqual(new Map([ 22 | [files[2], MediaScan.MOVIES_TYPE], 23 | [files[0], MediaScan.TV_SERIES_TYPE], 24 | [files[1], MediaScan.TV_SERIES_TYPE], 25 | ])); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /__tests__/getters/allMovies.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | const basename = require("path").basename; 8 | import {parse as nameParser} from "parse-torrent-title"; 9 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 10 | 11 | describe("allMovies", () => { 12 | 13 | beforeAll(() => { 14 | // Set up some mocked out file info before each test 15 | require("fs").__setMockPaths(folders); 16 | require("filehound").__setResult(files); 17 | }); 18 | 19 | // TESTS 20 | /** @test {MediaScan#allMovies} */ 21 | test("Returns the movies", async () => { 22 | const libInstance = new MediaScan(); 23 | await expect(libInstance.addNewPath(...folders).resolves); 24 | await expect(libInstance.scan().resolves); 25 | expect(libInstance.allMovies).toEqual( 26 | new Set([ 27 | Object.assign( 28 | nameParser(basename(files[2])), 29 | {filePath: files[2]}, 30 | ), 31 | ]), 32 | ); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /__tests__/getters/allTvSeries.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | const basename = require("path").basename; 8 | import {parse as nameParser} from "parse-torrent-title"; 9 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 10 | 11 | describe("allTvSeries", () => { 12 | 13 | beforeAll(() => { 14 | // Set up some mocked out file info before each test 15 | require("fs").__setMockPaths(folders); 16 | require("filehound").__setResult(files); 17 | }); 18 | 19 | // TESTS 20 | /** @test {TorrentLibrary#allTvSeries} */ 21 | test("Returns the tv-shows", async () => { 22 | const libInstance = new MediaScan(); 23 | await expect(libInstance.addNewPath(...folders)).resolves; 24 | await expect(libInstance.scan().resolves); 25 | expect(libInstance.allTvSeries).toEqual( 26 | new Map([ 27 | [nameParser(basename(files[0])).title, new Set([ 28 | Object.assign( 29 | nameParser(basename(files[0])), 30 | {filePath: files[0]}, 31 | ), 32 | Object.assign( 33 | nameParser(basename(files[1])), 34 | {filePath: files[1]}, 35 | ), 36 | ])], 37 | ]), 38 | ); 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /__tests__/getters/allTvSeriesNames.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 8 | 9 | describe("allTvSeriesNames", () => { 10 | 11 | beforeAll(() => { 12 | // Set up some mocked out file info before each test 13 | require("fs").__setMockPaths(folders); 14 | require("filehound").__setResult(files); 15 | }); 16 | 17 | test("Returns all the found tv series names", async () => { 18 | const libInstance = new MediaScan(); 19 | await expect(libInstance.addNewPath(...folders)).resolves; 20 | await expect(libInstance.scan().resolves); 21 | expect(libInstance.allTvSeriesNames).toEqual( 22 | ["The Blacklist"], 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /__tests__/getters/constants.ts: -------------------------------------------------------------------------------- 1 | import {MediaScan} from "../__helpers__/_constants"; 2 | 3 | const videosExtension = require("video-extensions"); 4 | 5 | describe("Constants", () => { 6 | /** @test {TorrentLibrary.MOVIES_TYPE} */ 7 | test("Constant MOVIES_TYPE", () => { 8 | expect(MediaScan.MOVIES_TYPE).toBe("MOVIES"); 9 | }); 10 | 11 | /** @test {TorrentLibrary.TV_SERIES_TYPE} */ 12 | test("Constant TV_SERIES", () => { 13 | expect(MediaScan.TV_SERIES_TYPE).toBe("TV_SERIES"); 14 | }); 15 | 16 | /** @test {TorrentLibrary.listVideosExtension} */ 17 | test("List of videos extension", () => { 18 | expect(MediaScan.listVideosExtension()).toBe(videosExtension); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /__tests__/methods/addNewPath.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | jest.mock("fs"); 3 | jest.mock("filehound"); 4 | 5 | // imports 6 | import * as path from "path"; 7 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 8 | 9 | describe("addNewPath", () => { 10 | beforeAll(() => { 11 | // Set up some mocked out file info before each test 12 | require("fs").__setMockPaths(folders); 13 | require("filehound").__setResult(files); 14 | }); 15 | 16 | // TESTS 17 | /** @test {MediaScan#addNewPath} */ 18 | test("missing parameter", async () => { 19 | const libInstance = new MediaScan(); 20 | const eventSpy = jest.spyOn(libInstance, "addNewPath"); 21 | await expect(libInstance.addNewPath()).rejects.toThrowError(); 22 | expect(eventSpy).toHaveBeenCalled(); 23 | expect(eventSpy).toHaveBeenCalledTimes(1); 24 | expect(libInstance.hasPathsProvidedByUser()).toBe(false); 25 | }); 26 | 27 | /** @test {MediaScan#addNewPath} */ 28 | test("Not an existent path", async () => { 29 | const libInstance = new MediaScan(); 30 | const eventSpy = jest.spyOn(libInstance, "addNewPath"); 31 | await expect(libInstance.addNewPath(path.join(__dirname, "wrongPath"))).rejects.toThrowError(); 32 | expect(eventSpy).toHaveBeenCalled(); 33 | expect(eventSpy).toHaveBeenCalledTimes(1); 34 | expect(libInstance.hasPathsProvidedByUser()).toBe(false); 35 | }); 36 | 37 | /** @test {MediaScan#addNewPath} */ 38 | test("existent paths", async () => { 39 | const libInstance = new MediaScan(); 40 | const data = await libInstance.addNewPath(...folders); 41 | await expect(data).resolves; 42 | expect(libInstance.hasPathsProvidedByUser()).toBe(true); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /__tests__/methods/createFromJSON.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 8 | 9 | describe("createFromJSON", () => { 10 | beforeAll(() => { 11 | // Set up some mocked out file info before each test 12 | require("fs").__setMockPaths(folders); 13 | require("filehound").__setResult(files); 14 | }); 15 | 16 | /** @test {MediaScan.createFromJSON} */ 17 | test("create a perfect copy of instance", async () => { 18 | const libInstance = new MediaScan(); 19 | await expect(libInstance.addNewPath(...folders)).resolves; 20 | await expect(libInstance.scan()).resolves; 21 | const jsonFromLib = JSON.parse(libInstance.toJSON()); 22 | const createdInstance = MediaScan.createFromJSON(jsonFromLib); 23 | expect(createdInstance.allFilesWithCategory).toEqual(libInstance.allFilesWithCategory); 24 | expect(createdInstance.allMovies).toEqual(libInstance.allMovies); 25 | expect(createdInstance.allTvSeries).toEqual(libInstance.allTvSeries); 26 | }); 27 | 28 | // dummy test for ES6 code coverage 29 | /** @test {MediaScan.createFromJSON} */ 30 | test("empty instance(s)", async () => { 31 | expect(MediaScan.createFromJSON({}, {})).toBeInstanceOf(MediaScan); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /__tests__/methods/filterMovies.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | import {parse as nameParser} from "parse-torrent-title"; 8 | import * as path from "path"; 9 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 10 | 11 | describe("filterMovies", () => { 12 | 13 | beforeAll(() => { 14 | // Set up some mocked out file info before each test 15 | require("fs").__setMockPaths(folders); 16 | require("filehound").__setResult(files); 17 | }); 18 | 19 | /** @test {MediaScan#filterMovies} */ 20 | test("Should work without parameters", async () => { 21 | const libInstance = new MediaScan(); 22 | const eventSpy = jest.spyOn(libInstance, "scan"); 23 | // whatever path that should exists 24 | await expect(libInstance.addNewPath(...folders)).resolves; 25 | await expect(libInstance.scan()).resolves; 26 | expect(eventSpy).toHaveBeenCalled(); 27 | expect(eventSpy).toHaveBeenCalledTimes(1); 28 | expect(libInstance.filterMovies()).toEqual( 29 | new Set([ 30 | Object.assign( 31 | nameParser(path.basename(files[2])), 32 | {filePath: files[2]}, 33 | ), 34 | ]), 35 | ); 36 | }); 37 | 38 | /** @test {MediaScan#filterMovies} */ 39 | test("default boolean parameters search", async () => { 40 | const libInstance = new MediaScan(); 41 | const eventSpy = jest.spyOn(libInstance, "scan"); 42 | // whatever path that should exists 43 | await expect(libInstance.addNewPath(...folders)).resolves; 44 | await expect(libInstance.scan()).resolves; 45 | expect(eventSpy).toHaveBeenCalled(); 46 | expect(eventSpy).toHaveBeenCalledTimes(1); 47 | 48 | // A simple filter that should returns the only movie that we have 49 | expect( 50 | libInstance.filterMovies({ 51 | remastered: true, 52 | }), 53 | ).toEqual( 54 | new Set([ 55 | Object.assign( 56 | nameParser(path.basename(files[2])), 57 | {filePath: files[2]}, 58 | ), 59 | ]), 60 | ); 61 | 62 | // A complex filter that should returns nothing 63 | expect(libInstance.filterMovies({ 64 | additionalProperties: [ 65 | {type: "boolean", name: "AnotherField", value: true}, 66 | ], 67 | convert: true, 68 | extended: true, 69 | hardcoded: true, 70 | proper: true, 71 | remastered: true, 72 | repack: true, 73 | retail: true, 74 | unrated: true, 75 | })).toEqual(new Set()); 76 | }); 77 | 78 | /** @test {MediaScan#filterMovies} */ 79 | test("default number parameters search", async () => { 80 | const libInstance = new MediaScan(); 81 | const eventSpy = jest.spyOn(libInstance, "scan"); 82 | // whatever path that should exists 83 | await expect(libInstance.addNewPath(...folders)).resolves; 84 | await expect(libInstance.scan()).resolves; 85 | expect(eventSpy).toHaveBeenCalled(); 86 | expect(eventSpy).toHaveBeenCalledTimes(1); 87 | 88 | // A simple filter that should returns the only movie that we have 89 | expect(libInstance.filterMovies({ 90 | year: 2012, 91 | })).toEqual(new Set([ 92 | Object.assign( 93 | nameParser(path.basename(files[2])), 94 | {filePath: files[2]}, 95 | ), 96 | ])); 97 | 98 | // A complex filter that should returns nothing 99 | expect(libInstance.filterMovies({ 100 | additionalProperties: [ 101 | {type: "number", name: "whateverFieldThatDoesn'tExist", value: "<50"}, 102 | {type: "number", name: "AnotherField", value: undefined}, 103 | {type: "number", name: "AnotherField2", value: "<=25"}, 104 | {type: "number", name: "AnotherField3", value: ">25"}, 105 | {type: "number", name: "AnotherField4", value: "==25"}, 106 | ], 107 | year: ">=2012", 108 | })).toEqual(new Set()); 109 | }); 110 | 111 | /** @test {MediaScan#filterMovies} */ 112 | test("default string parameters search", async () => { 113 | const libInstance = new MediaScan(); 114 | const eventSpy = jest.spyOn(libInstance, "scan"); 115 | // whatever path that should exists 116 | await expect(libInstance.addNewPath(...folders)).resolves; 117 | await expect(libInstance.scan()).resolves; 118 | expect(eventSpy).toHaveBeenCalled(); 119 | expect(eventSpy).toHaveBeenCalledTimes(1); 120 | 121 | // A simple filter that should returns the only movie that we have 122 | expect(new Set([ 123 | Object.assign( 124 | nameParser(path.basename(files[2])), 125 | {filePath: files[2]}, 126 | ), 127 | ])).toEqual(libInstance.filterMovies({ 128 | title: "Bad Ass", 129 | })); 130 | 131 | // A complex filter that should returns nothing 132 | expect(libInstance.filterMovies({ 133 | additionalProperties: [ 134 | { 135 | name: "whateverField", 136 | type: "string", 137 | value: ["NothingExists"], 138 | }, 139 | { 140 | name: "AnotherField", 141 | type: "string", 142 | value: ["NothingExists", "NothingExists"], 143 | }, 144 | {type: "string", name: "AnotherField2", value: "<=25"}, 145 | {type: "string", name: "AnotherField3", value: ">25"}, 146 | ], 147 | title: "Bad Ass", 148 | })).toEqual(new Set()); 149 | }); 150 | 151 | }); 152 | -------------------------------------------------------------------------------- /__tests__/methods/filterTvSeries.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | import {parse as nameParser} from "parse-torrent-title"; 8 | import * as path from "path"; 9 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 10 | 11 | describe("filterTvSeries", () => { 12 | 13 | beforeAll(() => { 14 | // Set up some mocked out file info before each test 15 | require("fs").__setMockPaths(folders); 16 | require("filehound").__setResult(files); 17 | }); 18 | 19 | /** @test {MediaScan#filterTvSeries} */ 20 | test("Should work without parameters", async () => { 21 | const libInstance = new MediaScan(); 22 | const eventSpy = jest.spyOn(libInstance, "scan"); 23 | // whatever path that should exists 24 | await expect(libInstance.addNewPath(...folders)).resolves; 25 | await expect(libInstance.scan()).resolves; 26 | expect(eventSpy).toHaveBeenCalled(); 27 | expect(eventSpy).toHaveBeenCalledTimes(1); 28 | expect(libInstance.filterTvSeries()).toEqual( 29 | new Map([ 30 | [nameParser(path.basename(files[0])).title, new Set([ 31 | Object.assign( 32 | nameParser(path.basename(files[0])), 33 | {filePath: files[0]}, 34 | ), 35 | Object.assign( 36 | nameParser(path.basename(files[1])), 37 | {filePath: files[1]}, 38 | ), 39 | ])], 40 | ]), 41 | ); 42 | }); 43 | 44 | /** @test {MediaScan#filterTvSeries} */ 45 | test("default boolean parameters search", async () => { 46 | const libInstance = new MediaScan(); 47 | const eventSpy = jest.spyOn(libInstance, "scan"); 48 | // whatever path that should exists 49 | await expect(libInstance.addNewPath(...folders)).resolves; 50 | await expect(libInstance.scan()).resolves; 51 | expect(eventSpy).toHaveBeenCalled(); 52 | expect(eventSpy).toHaveBeenCalledTimes(1); 53 | 54 | // A complex filter that should returns nothing 55 | expect(libInstance.filterTvSeries({ 56 | additionalProperties: [ 57 | {type: "boolean", name: "AnotherField", value: true}, 58 | ], 59 | convert: true, 60 | extended: true, 61 | hardcoded: true, 62 | proper: true, 63 | remastered: true, 64 | repack: true, 65 | retail: true, 66 | unrated: true, 67 | })).toEqual(new Map()); 68 | }); 69 | 70 | /** @test {MediaScan#filterTvSeries} */ 71 | test("default number parameters search", async () => { 72 | const libInstance = new MediaScan(); 73 | const eventSpy = jest.spyOn(libInstance, "scan"); 74 | // whatever path that should exists 75 | await expect(libInstance.addNewPath(...folders)).resolves; 76 | await expect(libInstance.scan()).resolves; 77 | expect(eventSpy).toHaveBeenCalled(); 78 | expect(eventSpy).toHaveBeenCalledTimes(1); 79 | 80 | // A simple filter that should returns the two tv series that we have 81 | expect(libInstance.filterTvSeries({ 82 | season: 4, 83 | })).toEqual( 84 | new Map([ 85 | [nameParser(path.basename(files[0])).title, new Set([ 86 | Object.assign( 87 | nameParser(path.basename(files[0])), 88 | {filePath: files[0]}, 89 | ), 90 | Object.assign( 91 | nameParser(path.basename(files[1])), 92 | {filePath: files[1]}, 93 | ), 94 | ])], 95 | ]), 96 | ); 97 | 98 | // A complex filter that should returns nothing 99 | expect((libInstance.filterTvSeries({ 100 | additionalProperties: [ 101 | {type: "number", name: "whateverFieldThatDoesn'tExist", value: "<50"}, 102 | {type: "number", name: "AnotherField", value: undefined}, 103 | {type: "number", name: "AnotherField2", value: "<=25"}, 104 | {type: "number", name: "AnotherField3", value: ">25"}, 105 | {type: "number", name: "AnotherField4", value: "==25"}, 106 | ], 107 | season: ">=4", 108 | }))).toEqual( 109 | new Map(), 110 | ); 111 | }); 112 | 113 | /** @test {MediaScan#filterTvSeries} */ 114 | test("default string parameters search", async () => { 115 | const libInstance = new MediaScan(); 116 | const eventSpy = jest.spyOn(libInstance, "scan"); 117 | // whatever path that should exists 118 | await expect(libInstance.addNewPath(...folders)).resolves; 119 | await expect(libInstance.scan()).resolves; 120 | expect(eventSpy).toHaveBeenCalled(); 121 | expect(eventSpy).toHaveBeenCalledTimes(1); 122 | 123 | // A simple filter that should returns the only movie that we have 124 | expect(libInstance.filterTvSeries({ 125 | title: "The Blacklist", 126 | })).toEqual( 127 | new Map([ 128 | [nameParser(path.basename(files[0])).title, new Set([ 129 | Object.assign( 130 | nameParser(path.basename(files[0])), 131 | {filePath: files[0]}, 132 | ), 133 | Object.assign( 134 | nameParser(path.basename(files[1])), 135 | {filePath: files[1]}, 136 | ), 137 | ])], 138 | ]), 139 | ); 140 | 141 | // A complex filter that should returns nothing 142 | expect(libInstance.filterTvSeries({ 143 | additionalProperties: [ 144 | { 145 | name: "whateverField", 146 | type: "string", 147 | value: ["NothingExists"], 148 | }, 149 | { 150 | name: "AnotherField", 151 | type: "string", 152 | value: ["NothingExists", "NothingExists"], 153 | }, 154 | { name: "AnotherField2", type: "number", value: "<=25"}, 155 | { name: "AnotherField3", type: "number", value: ">25"}, 156 | ], 157 | title: "The Blacklist", 158 | })).toEqual(new Map()); 159 | }); 160 | 161 | }); 162 | -------------------------------------------------------------------------------- /__tests__/methods/removeOldFiles.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | import {parse as nameParser} from "parse-torrent-title"; 8 | import * as path from "path"; 9 | import {basename} from "path"; 10 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 11 | 12 | describe("removeOldFiles", () => { 13 | 14 | beforeAll(() => { 15 | // Set up some mocked out file info before each test 16 | require("fs").__setMockPaths(folders); 17 | require("filehound").__setResult(files); 18 | }); 19 | 20 | /** @test {MediaScan#removeOldFiles} */ 21 | test("Should not be able to remove not present files", async () => { 22 | const libInstance = new MediaScan(); 23 | await expect(libInstance.addNewPath(...folders)).resolves; 24 | await expect(libInstance.scan()).resolves; 25 | const wrongFile = path.join( 26 | __dirname, "folder1", 27 | "The.Blacklist.S04E22.FRENCH.WEBRip.XviD.avi", 28 | ); 29 | const allFiles = libInstance.allFilesWithCategory; 30 | const expectedTvSeriesMap = libInstance.allTvSeries; 31 | await expect(libInstance.removeOldFiles(wrongFile)).resolves; 32 | expect(libInstance.allFilesWithCategory).toEqual(allFiles); 33 | expect(libInstance.allTvSeries).toEqual(expectedTvSeriesMap); 34 | }); 35 | 36 | /** @test {MediaScan#removeOldFiles} */ 37 | test("Should be able to remove a movie", async () => { 38 | const libInstance = new MediaScan(); 39 | await expect(libInstance.addNewPath(...folders)).resolves; 40 | await expect(libInstance.scan()).resolves; 41 | const allFilesWithoutMovie = libInstance.allFilesWithCategory; 42 | // files[2] ; Bad Ass 43 | allFilesWithoutMovie.delete(files[2]); 44 | 45 | const eventSpy = jest.spyOn(libInstance, "removeOldFiles"); 46 | // files[2] ; Bad Ass 47 | await expect(libInstance.removeOldFiles(files[2])).resolves; 48 | expect(libInstance.allMovies).toEqual(new Set()); 49 | 50 | expect(libInstance.allFilesWithCategory).toEqual(allFilesWithoutMovie); 51 | expect(eventSpy).toHaveBeenCalled(); 52 | expect(eventSpy).toHaveBeenCalledTimes(1); 53 | }); 54 | 55 | /** @test {MediaScan#removeOldFiles} */ 56 | test("Should be able to remove an tv-serie episode", async () => { 57 | const libInstance = new MediaScan(); 58 | await expect(libInstance.addNewPath(...folders)).resolves; 59 | await expect(libInstance.scan()).resolves; 60 | const allFilesWithoutIt = libInstance.allFilesWithCategory; 61 | // files[1] ; The.Blacklist.S04E21 62 | allFilesWithoutIt.delete(files[1]); 63 | 64 | const eventSpy = jest.spyOn(libInstance, "removeOldFiles"); 65 | // files[1] ; The.Blacklist.S04E21 66 | await expect(libInstance.removeOldFiles(files[1])).resolves; 67 | expect(libInstance.allTvSeries).toEqual(new Map([ 68 | [nameParser(path.basename(files[0])).title, new Set([ 69 | Object.assign( 70 | nameParser(path.basename(files[0])), 71 | {filePath: files[0]}, 72 | ), 73 | ])], 74 | ])); 75 | 76 | expect(libInstance.allFilesWithCategory).toEqual(allFilesWithoutIt); 77 | expect(eventSpy).toHaveBeenCalled(); 78 | expect(eventSpy).toHaveBeenCalledTimes(1); 79 | }); 80 | 81 | /** @test {MediaScan#removeOldFiles} */ 82 | test("Should be able to remove multiples files : Tv-serie", async () => { 83 | const libInstance = new MediaScan(); 84 | await expect(libInstance.addNewPath(...folders)).resolves; 85 | await expect(libInstance.scan()).resolves; 86 | const allFilesWithoutIt = new Map([[files[2], MediaScan.MOVIES_TYPE]]); 87 | 88 | const eventSpy = jest.spyOn(libInstance, "removeOldFiles"); 89 | await expect(libInstance.removeOldFiles(...files.slice(0, 2))).resolves; 90 | expect(libInstance.allTvSeries).toEqual(new Map()); 91 | 92 | expect(libInstance.allFilesWithCategory).toEqual(allFilesWithoutIt); 93 | expect(eventSpy).toHaveBeenCalled(); 94 | expect(eventSpy).toHaveBeenCalledTimes(1); 95 | }); 96 | 97 | // test to handle default parameters 98 | /** @test {MediaScan#removeOldFiles} */ 99 | test("Should not be able to remove files : wrong custom parser", async () => { 100 | const libInstance = MediaScan.createFromJSON({ 101 | allFilesWithCategory: [ 102 | [ 103 | files[2], 104 | "MOVIES", 105 | ], 106 | [ 107 | files[0], 108 | "TV_SERIES", 109 | ], 110 | [ 111 | files[1], 112 | "TV_SERIES", 113 | ], 114 | ], 115 | movies: [ 116 | Object.assign(nameParser(basename(files[2])), { 117 | filePath: files[2], 118 | }), 119 | ], 120 | paths: [ 121 | ...folders, 122 | ], 123 | series: [ 124 | [ 125 | "The Blacklist", 126 | [ 127 | Object.assign(nameParser(basename(files[0])), { 128 | filePath: files[0], 129 | }), 130 | Object.assign(nameParser(basename(files[1])), { 131 | filePath: files[1], 132 | }), 133 | ], 134 | ], 135 | ], 136 | }, { 137 | parser : {}, 138 | }); 139 | const eventSpy = jest.spyOn(libInstance, "removeOldFiles"); 140 | await expect(libInstance.removeOldFiles(...files)).rejects.toThrowError(); 141 | expect(eventSpy).toHaveBeenCalled(); 142 | expect(eventSpy).toHaveBeenCalledTimes(1); 143 | }); 144 | 145 | }); 146 | -------------------------------------------------------------------------------- /__tests__/methods/scan.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | import {parse as nameParser} from "parse-torrent-title"; 6 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 7 | 8 | describe("scan", () => { 9 | 10 | beforeAll(() => { 11 | // Set up some mocked out file info before each test 12 | require("fs").__setMockPaths(folders); 13 | require("filehound").__setResult(files); 14 | }); 15 | 16 | // TESTS 17 | /** @test {MediaScan#scan} */ 18 | test("Scan without user provided paths", async () => { 19 | const libInstance = new MediaScan(); 20 | const eventSpy = jest.spyOn(libInstance, "scan"); 21 | await expect(libInstance.scan().resolves); 22 | expect(eventSpy).toHaveBeenCalled(); 23 | expect(eventSpy).toHaveBeenCalledTimes(1); 24 | }); 25 | 26 | /** @test {MediaScan#scan} */ 27 | test("Scan with user provided paths", async () => { 28 | const libInstance = new MediaScan(); 29 | const eventSpy = jest.spyOn(libInstance, "scan"); 30 | // whatever path that should exists 31 | await expect(libInstance.addNewPath(...folders).resolves); 32 | await expect(libInstance.scan().resolves); 33 | expect(eventSpy).toHaveBeenCalled(); 34 | expect(eventSpy).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | // test to handle default parameters 38 | /** @test {MediaScan#scan} */ 39 | test("Scan with user provided paths and custom parser", async () => { 40 | const libInstance = new MediaScan({}, {parser: nameParser}); 41 | const eventSpy = jest.spyOn(libInstance, "scan"); 42 | // whatever path that should exists 43 | await expect(libInstance.addNewPath(...folders).resolves); 44 | await expect(libInstance.scan().resolves); 45 | expect(eventSpy).toHaveBeenCalled(); 46 | expect(eventSpy).toHaveBeenCalledTimes(1); 47 | }); 48 | 49 | /** @test {MediaScan#scan} */ 50 | test("Scan with user provided paths and wrong custom parser", async () => { 51 | const libInstance = new MediaScan({}, {parser: {}}); 52 | const eventSpy = jest.spyOn(libInstance, "scan"); 53 | // whatever path that should exists 54 | await expect(libInstance.addNewPath(...folders).resolves); 55 | await expect(libInstance.scan()).rejects.toThrowError(); 56 | expect(eventSpy).toHaveBeenCalled(); 57 | expect(eventSpy).toHaveBeenCalledTimes(1); 58 | }); 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /__tests__/methods/toJSON.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | import {parse as nameParser} from "parse-torrent-title"; 8 | import {basename} from "path"; 9 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 10 | 11 | describe("toJSON", () => { 12 | 13 | beforeAll(() => { 14 | // Set up some mocked out file info before each test 15 | require("fs").__setMockPaths(folders); 16 | require("filehound").__setResult(files); 17 | }); 18 | 19 | /** @test {MediaScan#toJSON} */ 20 | test("return a valid stringified JSON", async () => { 21 | const expectedJson = { 22 | allFilesWithCategory: [ 23 | [ 24 | files[0], 25 | "TV_SERIES", 26 | ], 27 | [ 28 | files[1], 29 | "TV_SERIES", 30 | ], 31 | [ 32 | files[2], 33 | "MOVIES", 34 | ], 35 | ], 36 | movies: [ 37 | Object.assign(nameParser(basename(files[2])), { 38 | filePath: files[2], 39 | }), 40 | ], 41 | paths: folders, 42 | series: [ 43 | [ 44 | "The Blacklist", 45 | [ 46 | Object.assign(nameParser(basename(files[0])), { 47 | filePath: files[0], 48 | }), 49 | Object.assign(nameParser(basename(files[1])), { 50 | filePath: files[1], 51 | }), 52 | ], 53 | ], 54 | ], 55 | }; 56 | const libInstance = new MediaScan(); 57 | const data = await libInstance.addNewPath(...folders); 58 | await expect(data).resolves; 59 | const scan = await libInstance.scan(); 60 | await expect(scan).resolves; 61 | const dataFromInstance = libInstance.toJSON(); 62 | expect(JSON.parse(dataFromInstance)).toEqual(expectedJson); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /__tests__/methods/toJSONObject.ts: -------------------------------------------------------------------------------- 1 | // mock from jest 2 | "use strict"; 3 | jest.mock("fs"); 4 | jest.mock("filehound"); 5 | 6 | // imports 7 | import {parse as nameParser} from "parse-torrent-title"; 8 | import {basename} from "path"; 9 | import {files, folders, MediaScan} from "../__helpers__/_constants"; 10 | 11 | describe("toJSONObject", () => { 12 | 13 | beforeAll(() => { 14 | // Set up some mocked out file info before each test 15 | require("fs").__setMockPaths(folders); 16 | require("filehound").__setResult(files); 17 | }); 18 | 19 | /** @test {MediaScan#toJSONObject} */ 20 | test("return a valid JSON", async () => { 21 | const expectedJsonString = { 22 | allFilesWithCategory: [ 23 | [ 24 | files[0], 25 | "TV_SERIES", 26 | ], 27 | [ 28 | files[1], 29 | "TV_SERIES", 30 | ], 31 | [ 32 | files[2], 33 | "MOVIES", 34 | ], 35 | ], 36 | movies: [ 37 | Object.assign(nameParser(basename(files[2])), { 38 | filePath: files[2], 39 | }), 40 | ], 41 | paths: folders, 42 | series: [ 43 | [ 44 | "The Blacklist", 45 | [ 46 | Object.assign(nameParser(basename(files[0])), { 47 | filePath: files[0], 48 | }), 49 | Object.assign(nameParser(basename(files[1])), { 50 | filePath: files[1], 51 | }), 52 | ], 53 | ], 54 | ], 55 | }; 56 | const libInstance = new MediaScan(); 57 | const data = await libInstance.addNewPath(...folders); 58 | await expect(data).resolves; 59 | const scan = await libInstance.scan(); 60 | await expect(scan).resolves; 61 | const dataFromInstance = libInstance.toJSONObject(); 62 | expect(dataFromInstance).toEqual(expectedJsonString); 63 | }); 64 | 65 | test("return a valid JSON | loose mode", async () => { 66 | const expectedJsonString = JSON.stringify({ 67 | allFilesWithCategory: [ 68 | [ 69 | files[0], 70 | "TV_SERIES", 71 | ], 72 | [ 73 | files[1], 74 | "TV_SERIES", 75 | ], 76 | [ 77 | files[2], 78 | "MOVIES", 79 | ], 80 | ], 81 | }); 82 | const libInstance = new MediaScan(); 83 | const data = await libInstance.addNewPath(...folders); 84 | await expect(data).resolves; 85 | const scan = await libInstance.scan(); 86 | await expect(scan).resolves; 87 | const dataFromInstance = libInstance.toJSONObject(true); 88 | expect(JSON.stringify(dataFromInstance)).toEqual(expectedJsonString); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /config/release-rules.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | tag: 'Feat', 4 | release: 'minor', 5 | }, 6 | { 7 | tag: 'Refactor', 8 | release: 'patch', 9 | }, 10 | { 11 | tag: 'Fix', 12 | release: 'patch', 13 | }, 14 | { 15 | tag: 'Perf', 16 | release: 'patch', 17 | }, 18 | { 19 | tag: 'Breaking', 20 | release: 'major', 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /config/release.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-escape,max-len,no-template-curly-in-string */ 2 | module.exports = { 3 | analyzeCommits: { 4 | preset: 'eslint', 5 | releaseRules: './config/release-rules.js', 6 | parserOpts: { 7 | headerPattern: /^(?::([\w-]*):)?\s*(\w*):\s*(.*)$/, 8 | headerCorrespondence: [ 9 | 'emoji', 10 | 'tag', 11 | 'message', 12 | ], 13 | }, 14 | }, 15 | generateNotes: { 16 | preset: 'eslint', 17 | parserOpts: { 18 | headerPattern: /^(?::([\w-]*):)?\s*(\w*):\s*(.*)$/, 19 | headerCorrespondence: [ 20 | 'emoji', 21 | 'tag', 22 | 'message', 23 | ], 24 | }, 25 | }, 26 | verifyConditions: 27 | ['@semantic-release/changelog', '@semantic-release/npm', '@semantic-release/git', '@semantic-release/github'], 28 | prepare: 29 | ['@semantic-release/changelog', { 30 | path: '@semantic-release/git', 31 | message: ':wrench: Chore: update package.json and CHANGELOG.md for release ${nextRelease.version} [skip ci]', 32 | }], 33 | }; 34 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/MediaScan'); -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // jest.config.js 2 | module.exports = { 3 | verbose: true, 4 | "moduleFileExtensions": [ 5 | "ts", 6 | "tsx", 7 | "js" 8 | ], 9 | "transform": { 10 | "^(?!.*\\.d\\.ts$).*\\.tsx?$": "ts-jest" 11 | }, 12 | "testMatch": [ 13 | "/__tests__/**/*.(ts|tsx|js)" 14 | ], 15 | "testPathIgnorePatterns": ["/node_modules/", "/__tests__/__helpers__/"], 16 | "collectCoverage": true, 17 | "globals": { 18 | "ts-jest": { 19 | "tsConfig": "./tsconfig.jest.json", 20 | "diagnostics": true 21 | } 22 | } 23 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mediascan", 3 | "version": "1.2.0", 4 | "description": "A scanner for media files that follows a user-provided naming convention", 5 | "main": "index.js", 6 | "files": [ 7 | "lib/" 8 | ], 9 | "scripts": { 10 | "test": "jest", 11 | "semantic-release": "semantic-release -e ./config/release.config.js", 12 | "prepare": "npm run compile", 13 | "clean": "jest --clearCache", 14 | "compile": "babel src --out-dir lib --extensions \".ts\" --source-maps both", 15 | "lint": "tslint -c tslint.json -p tsconfig.json --project . --fix", 16 | "type-check": "tsc" 17 | }, 18 | "engines": { 19 | "node": ">=8", 20 | "npm": ">=5" 21 | }, 22 | "keywords": [ 23 | "media files", 24 | "media", 25 | "media files scanner", 26 | "naming", 27 | "convention", 28 | "naming convention", 29 | "torrent", 30 | "library", 31 | "parser", 32 | "torrent file", 33 | "parse torrent", 34 | "parse torrent file", 35 | "parse torrent name" 36 | ], 37 | "author": "jy95", 38 | "license": "MIT", 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/jy95/mediaScan.git" 42 | }, 43 | "dependencies": { 44 | "bluebird": "^3.7.2", 45 | "filehound": "^1.17.6", 46 | "lodash": "^4.17.21", 47 | "parse-torrent-title": "^1.4.0", 48 | "video-extensions": "^1.2.0" 49 | }, 50 | "devDependencies": { 51 | "@babel/cli": "^7.0.0-beta.47", 52 | "@babel/core": "^7.5.5", 53 | "@babel/plugin-proposal-class-properties": "^7.0.0-beta.47", 54 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.47", 55 | "@babel/preset-env": "^7.0.0-beta.47", 56 | "@babel/preset-typescript": "^7.0.0-beta.47", 57 | "@semantic-release/changelog": "^5.0.0", 58 | "@semantic-release/git": "^7.0.0", 59 | "@types/jest": "^25.1.0", 60 | "@types/node": "^14.0.9", 61 | "babel-minify": "^0.5.0", 62 | "conventional-changelog-eslint": "^3.0.0", 63 | "jest": "^29.7.0", 64 | "semantic-release": "^17.0.8", 65 | "ts-jest": "^26.1.0", 66 | "tslint": "^5.9.1", 67 | "typescript": "^3.5.3" 68 | }, 69 | "greenkeeper": { 70 | "commitMessages": { 71 | "initialBadge": ":memo: Docs: Add Greenkeeper badge", 72 | "initialDependencies": ":gem: Upgrade: Update dependencies", 73 | "initialBranches": ":tada: Build: Whitelist greenkeeper branches", 74 | "dependencyUpdate": ":gem: Upgrade: Update ${dependency} to version ${version}", 75 | "devDependencyUpdate": ":gem: Upgrade: Update ${dependency} to version ${version}", 76 | "dependencyPin": ":bug: Fix: Pin ${dependency} to ${oldVersion}", 77 | "devDependencyPin": ":bug: Fix: Pin ${dependency} to ${oldVersion}" 78 | } 79 | }, 80 | "snyk": true 81 | } 82 | -------------------------------------------------------------------------------- /src/MediaScan.ts: -------------------------------------------------------------------------------- 1 | // Imports 2 | import PromiseLib from "bluebird"; 3 | import FileHound from "filehound"; 4 | import { 5 | cloneDeep, 6 | difference, 7 | filter, 8 | forIn, 9 | has, 10 | includes, 11 | map, 12 | reduce, 13 | some, 14 | uniq, 15 | } from "lodash"; 16 | import { normalize } from "path"; 17 | 18 | const videosExtension = require("video-extensions"); 19 | import { EventEmitter } from "events"; 20 | 21 | import { 22 | compose, 23 | filter as filterFP, 24 | pluck, 25 | reduce as reduceFP, 26 | } from "lodash/fp"; 27 | 28 | // local import 29 | import { 30 | filterMoviesByProperties, 31 | filterTvSeriesByProperties, 32 | } from "./filters/filterProperties"; 33 | import * as MediaScanTypes from "./MediaScanTypes"; 34 | import { 35 | defaultParser, 36 | defaultWhichCategoryFunction, 37 | promisifiedAccess, 38 | } from "./utils/utils_functions"; 39 | 40 | /** 41 | * Class representing the MediaScan Library 42 | * @extends {EventEmitter} 43 | */ 44 | class MediaScan extends EventEmitter { 45 | // constants getter for external purposes (example create a custom whichCategory function) 46 | public static readonly MOVIES_TYPE = MediaScanTypes.Category.MOVIES_TYPE; 47 | public static readonly TV_SERIES_TYPE = MediaScanTypes.Category 48 | .TV_SERIES_TYPE; 49 | // properties 50 | // Default path , if paths is empty 51 | protected defaultPath: string; 52 | // the parser to extract the useful data from name 53 | protected parser: MediaScanTypes.ParseFunction; 54 | // Function that tell us what is the category of the TPN 55 | protected whichCategory: MediaScanTypes.WhichCategoryFunction; 56 | // all the paths that will be explored 57 | protected paths: string[]; 58 | // the mapping between file and Category 59 | protected categoryForFile: Map; 60 | // where I keep the result of Category 61 | protected stores: MediaScanTypes.MapSet< 62 | MediaScanTypes.TPN | MediaScanTypes.TPN_Extended 63 | >; 64 | 65 | constructor( 66 | { 67 | defaultPath = process.cwd(), 68 | paths = [], 69 | allFilesWithCategory = new Map(), 70 | movies = new Set(), 71 | series = new Map(), 72 | }: MediaScanTypes.DataParameters = {}, 73 | { 74 | parser = defaultParser, 75 | whichCategory = defaultWhichCategoryFunction, 76 | }: MediaScanTypes.CustomFunctionsConfig = {}, 77 | ) { 78 | super(); 79 | this.parser = parser; 80 | this.whichCategory = whichCategory; 81 | this.defaultPath = defaultPath; 82 | this.paths = paths; 83 | this.stores = new Map(); 84 | this.stores.set(MediaScan.MOVIES_TYPE, movies); 85 | this.stores.set(MediaScan.TV_SERIES_TYPE, series); 86 | this.categoryForFile = allFilesWithCategory; 87 | } 88 | 89 | private addNewFiles(files: string[]): Promise { 90 | return new PromiseLib((resolve, reject) => { 91 | try { 92 | // find the new files to be added 93 | const alreadyFoundFiles = [...this.categoryForFile.keys()]; 94 | const newFiles = difference(files, alreadyFoundFiles); 95 | 96 | // process each file 97 | const scanningResult = reduce( 98 | newFiles, 99 | (result, file) => { 100 | const jsonFile = this.parser(file); 101 | // extend this object in order to be used by this library 102 | Object.assign(jsonFile, { filePath: file }); 103 | // find out which type of this file 104 | // if it has not undefined properties (season and episode) => TV_SERIES , otherwise MOVIE 105 | const fileCategory = this.whichCategory(jsonFile); 106 | // add it in found files 107 | this.categoryForFile.set(file, fileCategory); 108 | // store the result for next usage 109 | if (has(result, fileCategory)) { 110 | Array.prototype.push.apply(result[fileCategory], [jsonFile]); 111 | } else { 112 | result[fileCategory] = [jsonFile]; 113 | } 114 | return result; 115 | }, 116 | {}, 117 | ); 118 | 119 | // add the found movies 120 | if (scanningResult[MediaScan.MOVIES_TYPE] !== undefined) { 121 | this.stores.set( 122 | MediaScan.MOVIES_TYPE, 123 | new Set([ 124 | ...this.allMovies, 125 | ...scanningResult[MediaScan.MOVIES_TYPE], 126 | ]), 127 | ); 128 | } 129 | 130 | // add the found tv-series 131 | if (scanningResult[MediaScan.TV_SERIES_TYPE] !== undefined) { 132 | // mapping for faster result(s) 133 | const newSeries = reduce( 134 | scanningResult[MediaScan.TV_SERIES_TYPE], 135 | (result, tvSeries) => { 136 | if (has(result, tvSeries.title)) { 137 | Array.prototype.push.apply(result[tvSeries.title], [tvSeries]); 138 | } else { 139 | result[tvSeries.title] = [tvSeries]; 140 | } 141 | return result; 142 | }, 143 | {}, 144 | ); 145 | // fastest way to update things 146 | const newTvSeries = this.allTvSeries; 147 | forIn(newSeries, (seriesArray, seriesName) => { 148 | const resultSet = newTvSeries.has(seriesName) 149 | ? newTvSeries.get(seriesName) 150 | : new Set(); 151 | newTvSeries.set( 152 | seriesName, 153 | new Set([...resultSet, ...seriesArray]), 154 | ); 155 | }); 156 | // update the stores var 157 | this.stores.set(MediaScan.TV_SERIES_TYPE, newTvSeries); 158 | } 159 | 160 | resolve(); 161 | } catch (err) { 162 | reject(err); 163 | } 164 | }).bind(this); 165 | } 166 | 167 | public static listVideosExtension(): string[] { 168 | return videosExtension; 169 | } 170 | 171 | public addNewPath(...paths: string[]): Promise { 172 | // the user should provide us at lest a path 173 | if (paths.length === 0) { 174 | this.emit("missing_parameter", { 175 | functionName: "addNewPath", 176 | }); 177 | return Promise.reject(new Error("Missing parameter")); 178 | } 179 | 180 | return new PromiseLib((resolve, reject) => { 181 | PromiseLib.map(paths, (path) => promisifiedAccess(path)) 182 | .then(() => { 183 | // keep only unique paths 184 | // use normalize for cross platform's code 185 | this.paths = uniq([...this.paths, ...paths.map(normalize)]); 186 | this.emit("addNewPath", { paths: this.paths }); 187 | resolve("All paths were added!"); 188 | }) 189 | .catch((e) => { 190 | this.emit("error_in_function", { 191 | error: e.message, 192 | functionName: "addNewPath", 193 | }); 194 | reject(e); 195 | }); 196 | }).bind(this); 197 | } 198 | 199 | public hasPathsProvidedByUser(): boolean { 200 | return this.paths.length !== 0; 201 | } 202 | 203 | public scan(): Promise { 204 | return new PromiseLib((resolve, reject) => { 205 | FileHound.create() 206 | .paths(this.paths.length === 0 ? this.defaultPath : this.paths) 207 | .ext(videosExtension) 208 | .find() 209 | .then((files) => 210 | PromiseLib.join(this.addNewFiles(files), () => { 211 | return Promise.resolve(files); 212 | }), 213 | ) 214 | .then((files) => { 215 | this.emit("scan", { files }); 216 | resolve("Scanning completed"); 217 | }) 218 | .catch((err) => { 219 | this.emit("error_in_function", { 220 | error: err.message, 221 | functionName: "scan", 222 | }); 223 | reject(err); 224 | }); 225 | }).bind(this); 226 | } 227 | 228 | public removeOldFiles(...files: string[]): Promise { 229 | return new PromiseLib((resolve, reject) => { 230 | try { 231 | // processing 232 | const mappedFiles = compose( 233 | filterFP((resultObject) => resultObject.category !== undefined), 234 | pluck((file) => { 235 | return { filePath: file, category: this.categoryForFile.get(file) }; 236 | }), 237 | // to handle platform support paths 238 | pluck((file) => normalize(file)), 239 | )(files); 240 | const filterContentType = (requestedType) => (file) => 241 | file.category === requestedType; 242 | 243 | // remove the mapping of each deleted file(s) 244 | for (const file of mappedFiles) { 245 | this.categoryForFile.delete(file.filePath); 246 | } 247 | 248 | // movies files 249 | const moviesFiles = filter( 250 | mappedFiles, 251 | filterContentType(MediaScan.MOVIES_TYPE), 252 | ); 253 | const moviesFilePaths = map(moviesFiles, "filePath"); 254 | 255 | // for movies, just an easy removal 256 | if (moviesFiles.length > 0) { 257 | // update the filtered Set 258 | this.stores.set( 259 | MediaScan.MOVIES_TYPE, 260 | new Set( 261 | filter( 262 | ...this.allMovies, 263 | (movie) => !some(moviesFilePaths, movie.filePath), 264 | ), 265 | ), 266 | ); 267 | } 268 | 269 | // tv-series 270 | const seriesFiles = filter( 271 | mappedFiles, 272 | filterContentType(MediaScan.TV_SERIES_TYPE), 273 | ); 274 | 275 | // for series , a bit more complex 276 | if (seriesFiles.length > 0) { 277 | // Get the series and their files that will be deleted 278 | const seriesShows = compose( 279 | reduceFP((acc, parsedFile) => { 280 | if (!has(acc, parsedFile.seriesName)) { 281 | acc[parsedFile.seriesName] = []; 282 | } 283 | Array.prototype.push.apply(acc[parsedFile.seriesName], [ 284 | parsedFile.filePath, 285 | ]); 286 | return acc; 287 | }, {}), 288 | pluck((series) => { 289 | return { 290 | ...series, 291 | seriesName: this.parser(series.filePath).title, 292 | }; 293 | }), 294 | )(seriesFiles); 295 | 296 | const newTvSeries = this.allTvSeries; 297 | // check if needed to store new Value 298 | let shouldUpdate = false; 299 | forIn(seriesShows, (seriesArray, seriesName) => { 300 | const previousSet = newTvSeries.has(seriesName) 301 | ? newTvSeries.get(seriesName) 302 | : new Set(); 303 | const filteredSet: Set = new Set( 304 | filter( 305 | [...previousSet], 306 | (episode) => !includes(seriesArray, episode.filePath), 307 | ), 308 | ); 309 | // should I update later ? 310 | if (previousSet.size !== filteredSet.size) { 311 | shouldUpdate = true; 312 | } 313 | // if the filtered set is empty => no more episodes for this series 314 | if (filteredSet.size === 0) { 315 | newTvSeries.delete(seriesName); 316 | } else { 317 | newTvSeries.set(seriesName, filteredSet); 318 | } 319 | }); 320 | // save the updated map 321 | if (shouldUpdate) { 322 | this.stores.set(MediaScan.TV_SERIES_TYPE, newTvSeries); 323 | } 324 | } 325 | 326 | this.emit("removeOldFiles", { files }); 327 | resolve({ 328 | files, 329 | message: "The files have been deleted from the library", 330 | }); 331 | } catch (err) { 332 | this.emit("error_in_function", { 333 | error: err.message, 334 | functionName: "removeOldFiles", 335 | }); 336 | reject(err); 337 | } 338 | }).bind(this); 339 | } 340 | 341 | get allMovies(): Set { 342 | return this.stores.get(MediaScan.MOVIES_TYPE) as Set< 343 | MediaScanTypes.TPN_Extended 344 | >; 345 | } 346 | 347 | get allTvSeries(): Map> { 348 | return this.stores.get(MediaScan.TV_SERIES_TYPE) as Map< 349 | string, 350 | Set 351 | >; 352 | } 353 | 354 | get allFilesWithCategory(): Map { 355 | return cloneDeep(this.categoryForFile); 356 | } 357 | 358 | get allTvSeriesNames(): string[] { 359 | return [...this.allTvSeries.keys()]; 360 | } 361 | 362 | // full data of lib as JSON string 363 | public toJSON(): string { 364 | return JSON.stringify(this.toJSONObject()); 365 | } 366 | 367 | // data as a JSON object 368 | public toJSONObject(looseMode?: boolean): MediaScanTypes.LibAsJson { 369 | // if in loose Mode , the objects will only contains the mapping between filepath and Category 370 | const toBeSerialized = looseMode 371 | ? [["allFilesWithCategory", [...this.allFilesWithCategory]]] 372 | : [ 373 | ["paths", [...this.paths]], 374 | ["allFilesWithCategory", [...this.allFilesWithCategory]], 375 | ["movies", [...this.allMovies]], 376 | [ 377 | "series", 378 | this.allTvSeriesNames.reduce((acc, currentSeries) => { 379 | acc.push([ 380 | currentSeries, 381 | [...this.allTvSeries.get(currentSeries)], 382 | ]); 383 | return acc; 384 | }, []), 385 | ], 386 | ]; 387 | return toBeSerialized.reduce((result, [key, value]) => { 388 | result[key as string] = value; 389 | return result; 390 | }, {}); 391 | } 392 | 393 | public static createFromJSON( 394 | json: MediaScanTypes.LibAsJson, 395 | customConfig?: MediaScanTypes.CustomFunctionsConfig, 396 | ): MediaScan { 397 | const config: MediaScanTypes.DataParameters = {}; 398 | // transform the param 399 | /* istanbul ignore else */ 400 | if (json.allFilesWithCategory) { 401 | config.allFilesWithCategory = new Map(json.allFilesWithCategory); 402 | } 403 | /* istanbul ignore else */ 404 | if (json.movies) { 405 | config.movies = new Set(json.movies); 406 | } 407 | /* istanbul ignore else */ 408 | if (json.series) { 409 | const createdMap = new Map(); 410 | for (const [seriesTitle, setSeries] of json.series) { 411 | createdMap.set(seriesTitle, new Set(setSeries)); 412 | } 413 | config.series = createdMap; 414 | } 415 | /* istanbul ignore else */ 416 | if (json.paths) { 417 | config.paths = json.paths; 418 | } 419 | return new MediaScan(config, customConfig); 420 | } 421 | 422 | public filterMovies(searchParameters: MediaScanTypes.SearchParameters = {}) { 423 | // apply params based on types 424 | return filterMoviesByProperties(searchParameters, this.allMovies); 425 | } 426 | 427 | public filterTvSeries( 428 | searchParameters: MediaScanTypes.SearchParameters = {}, 429 | ) { 430 | return filterTvSeriesByProperties(searchParameters, this.allTvSeries); 431 | } 432 | } 433 | 434 | // just to be sure Babel doesn't mess up common js 435 | module.exports = MediaScan; 436 | -------------------------------------------------------------------------------- /src/MediaScanTypes.ts: -------------------------------------------------------------------------------- 1 | export interface TPN { 2 | title: string; 3 | year?: number; 4 | resolution?: string; 5 | extended?: boolean; 6 | unrated?: boolean; 7 | proper?: boolean; 8 | repack?: boolean; 9 | convert?: boolean; 10 | hardcoded?: boolean; 11 | retail?: boolean; 12 | remastered?: boolean; 13 | region?: string; 14 | container?: string; 15 | source?: string; 16 | codec?: string; 17 | audio?: string; 18 | group?: string; 19 | season?: number; 20 | episode?: number; 21 | language?: string; 22 | } 23 | 24 | // extended by my own purpose 25 | export interface TPN_Extended extends TPN { 26 | filePath: string; 27 | } 28 | 29 | // A parsing function to be used with this lib 30 | // fullPathFile is the full path to the file - useful if you want to extrapolate things 31 | export type ParseFunction = (fullPathFile: string) => TPN | TPN_Extended; 32 | 33 | // the media files categories 34 | // const enum 35 | export enum Category { 36 | MOVIES_TYPE = "MOVIES", 37 | TV_SERIES_TYPE = "TV_SERIES", 38 | } 39 | 40 | // which category is this file 41 | export type WhichCategoryFunction = (object: TPN) => Category; 42 | 43 | // The sub way to store all kind of media files found in paths 44 | export type MappedType = Map>; 45 | export type MapSet = Map | Set>; 46 | 47 | // example '<=25' 48 | export type NumberSearchSyntax = string; 49 | 50 | // to handle number operations 51 | export interface NumberExpressionObject { 52 | operator: "==" | ">" | "<" | ">=" | "<="; 53 | number: number; 54 | } 55 | // const enum 56 | export enum AdditionalPropertiesType { 57 | STRING = "string", 58 | NUMBER = "number", 59 | BOOLEAN = "boolean", 60 | } 61 | 62 | // additional Properties 63 | export interface AdditionalProperties { 64 | type: AdditionalPropertiesType; 65 | name: string; 66 | value: boolean | string | string[] | number | NumberSearchSyntax; 67 | } 68 | 69 | export interface MinimalSearchParameters { 70 | additionalProperties?: AdditionalProperties[]; 71 | } 72 | 73 | export interface DefaultSearchParameters extends MinimalSearchParameters { 74 | extended?: boolean; 75 | unrated?: boolean; 76 | proper?: boolean; 77 | repack?: boolean; 78 | convert?: boolean; 79 | hardcoded?: boolean; 80 | retail?: boolean; 81 | remastered?: boolean; 82 | season?: number | NumberSearchSyntax; 83 | episode?: number | NumberSearchSyntax; 84 | year?: number | NumberSearchSyntax; 85 | title?: string | string[]; 86 | resolution?: string | string[]; 87 | codec?: string | string[]; 88 | audio?: string | string[]; 89 | group?: string | string[]; 90 | region?: string | string[]; 91 | container?: string | string[]; 92 | language?: string | string[]; 93 | source?: string | string[]; 94 | } 95 | 96 | // search parameters for filter functions 97 | export type SearchParameters = Partial; 98 | 99 | // for filtering tuples inside SearchParameters 100 | export type filterTuple = [string, T]; 101 | 102 | // for optimized filtering function 103 | export type filterFunctionTuple = [{(set: Set, propertiesMap: Map)}, Map]; 104 | 105 | // for tuples inside json in createFromJSON 106 | export type mappingStringAndCategory = [string, Category]; 107 | export type mappingStringAndTPNArray = [string, TPN[]]; 108 | 109 | // json result to be used in createFromJSON 110 | export interface LibAsJson { 111 | allFilesWithCategory?: mappingStringAndCategory[]; 112 | movies?: TPN[]; 113 | series?: mappingStringAndTPNArray[]; 114 | paths?: string[]; 115 | } 116 | 117 | // the data parameters for constructor (aka first argument) 118 | export interface DataParameters { 119 | defaultPath?: string; // Default path , if paths is empty 120 | paths?: string[]; // all the paths that will be explored 121 | allFilesWithCategory?: Map; // the mapping between file and Category 122 | movies?: Set; // all the movies 123 | series?: Map>; 124 | } 125 | 126 | // the custom functions (in order to have a different behaviour) for constructor (aka second argument) 127 | export interface CustomFunctionsConfig { 128 | parser?: ParseFunction; 129 | whichCategory?: WhichCategoryFunction; 130 | } 131 | -------------------------------------------------------------------------------- /src/filters/filterBooleanProperty.ts: -------------------------------------------------------------------------------- 1 | /** Provides a map with valid default properties */ 2 | import * as MediaScanTypes from "../MediaScanTypes"; 3 | import { filterDefaultProperties } from "../utils/utils_functions"; 4 | 5 | export function filterDefaultBooleanProperties( 6 | searchObject: MediaScanTypes.DefaultSearchParameters, 7 | ): Array> { 8 | const propertiesNames = [ 9 | "extended", 10 | "unrated", 11 | "proper", 12 | "repack", 13 | "convert", 14 | "hardcoded", 15 | "retail", 16 | "remastered", 17 | ]; 18 | return filterDefaultProperties( 19 | propertiesNames, 20 | searchObject, 21 | (value) => { 22 | return meetBooleanSpec(value); 23 | }, 24 | (key, value) => [key, value], 25 | ); 26 | } 27 | 28 | /** Filter the set based on boolean properties */ 29 | export function filterByBoolean( 30 | set: Set, 31 | propertiesMap: Map, 32 | ): Set { 33 | // first step : get an array so that we can do filter/reduce stuff 34 | // second step : iterate the propertiesMap and do filter and return the filtered array 35 | // val[0] : the key ; val[1] : the value 36 | return new Set( 37 | Array.from(propertiesMap.entries()).reduce( 38 | // eslint-disable-next-line max-len 39 | (currentMoviesArray, val) => 40 | currentMoviesArray.filter((TPN) => TPN[val[0]] === val[1]), 41 | [...set], 42 | ), 43 | ); 44 | } 45 | 46 | // Just for type check this type 47 | export function meetBooleanSpec(value) { 48 | return value === true || value === false; 49 | } 50 | -------------------------------------------------------------------------------- /src/filters/filterNumberProperty.ts: -------------------------------------------------------------------------------- 1 | import * as MediaScanTypes from "../MediaScanTypes"; 2 | import { filterDefaultProperties } from "../utils/utils_functions"; 3 | 4 | const validExpression = /^(==|>|<|>=|<=)(\d+)$/; 5 | 6 | // operator functions 7 | const ops = { 8 | "<": (a, b) => a < b, 9 | "<=": (a, b) => a <= b, 10 | "==": (a, b) => a == b, 11 | ">": (a, b) => a > b, 12 | ">=": (a, b) => a >= b, 13 | }; 14 | 15 | /** 16 | * Convert the param to valid expression object for filter function 17 | */ 18 | export function convertToValidExpression( 19 | param: string | number, 20 | ): MediaScanTypes.NumberExpressionObject { 21 | let returnValue; 22 | 23 | switch (typeof param) { 24 | case "string": 25 | // if it is a valid number expression like the regex 26 | /* istanbul ignore else */ 27 | if (validExpression.test(param as string)) { 28 | const result = (param as string).match(validExpression); 29 | returnValue = { 30 | number: Number(result[2]), 31 | operator: result[1], 32 | }; 33 | } 34 | break; 35 | 36 | // if the param is a number 37 | case "number": 38 | returnValue = { 39 | number: param as number, 40 | operator: "==", 41 | }; 42 | break; 43 | } 44 | 45 | return returnValue; 46 | } 47 | 48 | /** 49 | * Filter function for filterByNumber 50 | */ 51 | function resolveExpression( 52 | property: string, 53 | expressionObject: MediaScanTypes.NumberExpressionObject, 54 | object: MediaScanTypes.TPN | MediaScanTypes.TPN_Extended, 55 | ): boolean { 56 | return ops[expressionObject.operator]( 57 | object[property], 58 | expressionObject.number, 59 | ); 60 | } 61 | 62 | export function filterDefaultNumberProperties( 63 | searchObject: MediaScanTypes.DefaultSearchParameters, 64 | ): Array> { 65 | const propertiesNames = ["season", "episode", "year"]; 66 | return filterDefaultProperties( 67 | propertiesNames, 68 | searchObject, 69 | (value) => { 70 | return meetNumberSpec(value); 71 | }, 72 | (key, value) => [key, convertToValidExpression(value)], 73 | ); 74 | } 75 | 76 | /** Filter the set based on string properties */ 77 | // export function filterByNumber(set: Set, propertiesMap: Map) : Set 78 | export function filterByNumber( 79 | set: Set, 80 | propertiesMap: Map, 81 | ): Set { 82 | // first step : get an array so that we can do filter/reduce stuff 83 | // second step : iterate the propertiesMap and do filter and return the filtered array 84 | // val[0] : the key ; val[1] : the value 85 | return new Set( 86 | Array.from(propertiesMap.entries()).reduce( 87 | // eslint-disable-next-line max-len 88 | (currentMoviesArray, val) => 89 | currentMoviesArray.filter((TPN) => 90 | resolveExpression(val[0], val[1], TPN), 91 | ), 92 | [...set], 93 | ), 94 | ); 95 | } 96 | 97 | // Just for type check this type 98 | export function meetNumberSpec(value) { 99 | if (typeof value === "number") { 100 | return true; 101 | } else if (typeof value !== "string") { 102 | return false; 103 | } else { 104 | return validExpression.test(value as string); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/filters/filterProperties.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-escape,max-len */ 2 | import { isEmpty } from "lodash"; 3 | import { compose, filter as filterFP, pluck } from "lodash/fp"; 4 | /** 5 | * Boolean properties filter 6 | */ 7 | import { 8 | filterByBoolean, 9 | filterDefaultBooleanProperties, 10 | meetBooleanSpec, 11 | } from "./filterBooleanProperty"; 12 | 13 | /** 14 | * Number properties filter 15 | */ 16 | import { 17 | convertToValidExpression, 18 | filterByNumber, 19 | filterDefaultNumberProperties, 20 | meetNumberSpec, 21 | } from "./filterNumberProperty"; 22 | 23 | /** 24 | * String properties filter 25 | */ 26 | import * as MediaScanTypes from "../MediaScanTypes"; 27 | import { 28 | filterByString, 29 | filterDefaultStringProperties, 30 | meetStringSpec, 31 | } from "./filterStringProperty"; 32 | 33 | function mapProperties( 34 | searchParameters: MediaScanTypes.SearchParameters, 35 | ): { 36 | booleanFieldsSearchMap: Map; 37 | numberFieldsSearchMap: Map; 38 | stringFieldsSearchMap: Map; 39 | } { 40 | // organize search based on field type : boolean - string - number . Now optimized by Me XD 41 | // add the optional new properties , optionally provided by user 42 | const additionalProperties = 43 | searchParameters.additionalProperties === undefined 44 | ? [] 45 | : searchParameters.additionalProperties; 46 | const filterAdditionalProperties = (type) => (newProperty) => 47 | newProperty.type === type; 48 | const booleanFieldsSearchArray = filterDefaultBooleanProperties( 49 | searchParameters, 50 | ); 51 | const numberFieldsSearchArray = filterDefaultNumberProperties( 52 | searchParameters, 53 | ); 54 | const stringFieldsSearchArray = filterDefaultStringProperties( 55 | searchParameters, 56 | ); 57 | 58 | // add additional Properties into the proper array 59 | Array.prototype.push.apply( 60 | booleanFieldsSearchArray, 61 | compose( 62 | pluck(({ name, value }) => [name, value]), 63 | filterFP(({ value }) => meetBooleanSpec(value)), 64 | filterFP( 65 | filterAdditionalProperties( 66 | MediaScanTypes.AdditionalPropertiesType.BOOLEAN, 67 | ), 68 | ), 69 | )(additionalProperties), 70 | ); 71 | 72 | Array.prototype.push.apply( 73 | numberFieldsSearchArray, 74 | compose( 75 | pluck(({ name, value }) => [ 76 | name, 77 | convertToValidExpression(value as number | string), 78 | ]), 79 | filterFP(({ value }) => meetNumberSpec(value)), 80 | filterFP( 81 | filterAdditionalProperties( 82 | MediaScanTypes.AdditionalPropertiesType.NUMBER, 83 | ), 84 | ), 85 | )(additionalProperties), 86 | ); 87 | 88 | Array.prototype.push.apply( 89 | stringFieldsSearchArray, 90 | compose( 91 | pluck(({ name, value }) => [name, value]), 92 | filterFP(({ value }) => meetStringSpec(value)), 93 | filterFP( 94 | filterAdditionalProperties( 95 | MediaScanTypes.AdditionalPropertiesType.STRING, 96 | ), 97 | ), 98 | )(additionalProperties), 99 | ); 100 | 101 | return { 102 | booleanFieldsSearchMap: new Map(booleanFieldsSearchArray), 103 | numberFieldsSearchMap: new Map< 104 | string, 105 | MediaScanTypes.NumberExpressionObject 106 | >(numberFieldsSearchArray), 107 | stringFieldsSearchMap: new Map( 108 | stringFieldsSearchArray, 109 | ), 110 | }; 111 | } 112 | 113 | /** Filter the movies based on search parameters */ 114 | export function filterMoviesByProperties( 115 | searchParameters: MediaScanTypes.SearchParameters, 116 | allMovies: Set, 117 | ): Set { 118 | // Check if empty - for faster result 119 | if (isEmpty(searchParameters)) { 120 | return allMovies; 121 | } 122 | 123 | const { 124 | booleanFieldsSearchMap, 125 | stringFieldsSearchMap, 126 | numberFieldsSearchMap, 127 | } = mapProperties(searchParameters); 128 | const filterStuff: MediaScanTypes.filterFunctionTuple[] = [ 129 | [filterByBoolean, booleanFieldsSearchMap], 130 | [filterByString, stringFieldsSearchMap], 131 | [filterByNumber, numberFieldsSearchMap], 132 | ]; 133 | 134 | return filterStuff.reduce( 135 | (processingResult, [filterFunction, propertiesMap]) => { 136 | return processingResult.size > 0 && propertiesMap.size > 0 137 | ? filterFunction(processingResult, propertiesMap) 138 | : processingResult; 139 | }, 140 | allMovies, 141 | ); 142 | } 143 | 144 | /** Filter the tv series based on search parameters */ 145 | export function filterTvSeriesByProperties( 146 | searchParameters: MediaScanTypes.SearchParameters, 147 | allTvSeries: Map>, 148 | ): Map> { 149 | // Check if empty for faster result 150 | if (isEmpty(searchParameters)) { 151 | return allTvSeries; 152 | } 153 | 154 | const { 155 | booleanFieldsSearchMap, 156 | stringFieldsSearchMap, 157 | numberFieldsSearchMap, 158 | } = mapProperties(searchParameters); 159 | const filterStuff: MediaScanTypes.filterFunctionTuple[] = [ 160 | [filterByBoolean, booleanFieldsSearchMap], 161 | [filterByString, stringFieldsSearchMap], 162 | [filterByNumber, numberFieldsSearchMap], 163 | ]; 164 | 165 | // apply the filters 166 | return new Map( 167 | [...allTvSeries].reduce((processingArray, [showName, showSet]) => { 168 | // execute the filter functions 169 | const filteredSet = filterStuff.reduce( 170 | (currentFilteredSet, [filterFunction, propertiesMap]) => { 171 | return currentFilteredSet.size > 0 && propertiesMap.size > 0 172 | ? filterFunction(currentFilteredSet, propertiesMap) 173 | : currentFilteredSet; 174 | }, 175 | showSet, 176 | ); 177 | // add this entry if there is soms episode(s) left 178 | if (filteredSet.size > 0) { 179 | processingArray.push([showName, filteredSet]); 180 | } 181 | // reducer call 182 | return processingArray; 183 | }, []), 184 | ); 185 | } 186 | -------------------------------------------------------------------------------- /src/filters/filterStringProperty.ts: -------------------------------------------------------------------------------- 1 | /** Provides a map with valid default properties */ 2 | import * as MediaScanTypes from "../MediaScanTypes"; 3 | import { filterDefaultProperties } from "../utils/utils_functions"; 4 | 5 | export function filterDefaultStringProperties( 6 | searchObject: MediaScanTypes.DefaultSearchParameters, 7 | ): Array> { 8 | const propertiesNames = [ 9 | "title", 10 | "resolution", 11 | "codec", 12 | "audio", 13 | "group", 14 | "region", 15 | "container", 16 | "language", 17 | "source", 18 | ]; 19 | return filterDefaultProperties( 20 | propertiesNames, 21 | searchObject, 22 | (value) => { 23 | return meetStringSpec(value); 24 | }, 25 | (key, value) => [key, value], 26 | ); 27 | } 28 | 29 | /** Filter function for filterByString */ 30 | function filterFunctionByType( 31 | property: string, 32 | expected: string[] | string, 33 | object: MediaScanTypes.TPN, 34 | ): boolean { 35 | if (Array.isArray(expected)) { 36 | return expected.includes(object[property]); 37 | } 38 | return object[property] === expected; 39 | } 40 | 41 | /** Filter the set based on string properties */ 42 | export function filterByString( 43 | set: Set, 44 | propertiesMap: Map, 45 | ): Set { 46 | // first step : get an array so that we can do filter/reduce stuff 47 | // second step : iterate the propertiesMap and do filter and return the filtered array 48 | // val[0] : the key ; val[1] : the value 49 | return new Set( 50 | Array.from(propertiesMap.entries()).reduce( 51 | // eslint-disable-next-line max-len 52 | (currentMoviesArray, val) => 53 | currentMoviesArray.filter((TPN) => 54 | filterFunctionByType(val[0], val[1], TPN), 55 | ), 56 | [...set], 57 | ), 58 | ); 59 | } 60 | 61 | // Just for type check this type 62 | export function meetStringSpec(value) { 63 | if (Array.isArray(value)) { 64 | return value.every((elem) => typeof elem === "string"); 65 | } else { 66 | return typeof value === "string"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/utils/utils_functions.ts: -------------------------------------------------------------------------------- 1 | // Check properties 2 | import PromiseLib from "bluebird"; 3 | import { access, constants as FsConstants } from "fs"; 4 | import { compose, filter as filterFP, pluck } from "lodash/fp"; 5 | import { parse as nameParser } from "parse-torrent-title"; 6 | import { basename } from "path"; 7 | import * as MediaScanTypes from "../MediaScanTypes"; 8 | 9 | export function checkProperties(obj, properties): boolean { 10 | return properties.every((x) => x in obj && obj[x]); 11 | } 12 | 13 | /** 14 | * Bluebird seems to have an issue with fs.access - Workaround function 15 | */ 16 | export function promisifiedAccess(path): Promise { 17 | return new PromiseLib((resolve, reject) => { 18 | access(path, FsConstants.F_OK | FsConstants.R_OK, (err) => { 19 | if (err) { 20 | reject(err); 21 | } 22 | resolve(); 23 | }); 24 | }); 25 | } 26 | 27 | // Default implementation to know which category is this file 28 | export function defaultWhichCategoryFunction( 29 | object: MediaScanTypes.TPN, 30 | ): MediaScanTypes.Category { 31 | // workaround : const string enum aren't compiled correctly with Babel 32 | return checkProperties(object, ["season", "episode"]) 33 | ? MediaScanTypes.Category.TV_SERIES_TYPE 34 | : MediaScanTypes.Category.MOVIES_TYPE; 35 | } 36 | 37 | // Generic filter for default properties 38 | export function filterDefaultProperties( 39 | propertiesNames: string[], 40 | search: MediaScanTypes.SearchParameters, 41 | meetSpecFunction: (value) => boolean, 42 | transformFunction: (key: string, value) => MediaScanTypes.filterTuple, 43 | ): Array> { 44 | return compose( 45 | pluck((currentProperty) => 46 | transformFunction(currentProperty, search[currentProperty]), 47 | ), 48 | filterFP((currentProperty) => meetSpecFunction(search[currentProperty])), 49 | )(propertiesNames); 50 | } 51 | 52 | // default parser 53 | export function defaultParser(fullPathFile: string) { 54 | return nameParser(basename(fullPathFile)); 55 | } 56 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDeclarationOnly": false 6 | }, 7 | "preset": "ts-jest" 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions":{ 3 | "allowSyntheticDefaultImports":true, 4 | "module":"commonjs", 5 | "target":"es5", 6 | "lib":[ 7 | "ES6", 8 | "ES2017", 9 | "ESNext" 10 | ], 11 | "downlevelIteration":true, 12 | "moduleResolution":"node", 13 | "declaration":true, 14 | "emitDeclarationOnly":true, 15 | "declarationDir":"./lib" 16 | }, 17 | "exclude":[ 18 | "node_modules" 19 | ], 20 | "include":[ 21 | "src/**/*" 22 | ], 23 | "strict":true, 24 | "preset":"ts-jest" 25 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "class-name": false, 9 | "no-var-requires": false, 10 | "triple-equals": false, 11 | "no-bitwise": false, 12 | "interface-name": [ 13 | "never-prefix" 14 | ], 15 | "member-ordering": [ 16 | true, 17 | { 18 | "order": [ 19 | "public-static-field", 20 | "public-constructor", 21 | "private-instance-method", 22 | "public-instance-method" 23 | ] 24 | } 25 | ] 26 | }, 27 | "rulesDirectory": [] 28 | } --------------------------------------------------------------------------------