├── .babelrc.js ├── .esdoc.json ├── .gitignore ├── .npmignore ├── .sgcrc ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── config ├── .eslintrc.js ├── release-rules.js └── release.config.js ├── docs ├── ast │ └── source │ │ ├── .external-ecmascript.js.json │ │ ├── TorrentLibrary.js.json │ │ ├── docs │ │ ├── events │ │ │ └── events.js.json │ │ └── types │ │ │ └── types.js.json │ │ └── filters │ │ ├── filterBooleanProperty.js.json │ │ ├── filterNumberProperty.js.json │ │ ├── filterProperties.js.json │ │ └── filterStringProperty.js.json ├── badge.svg ├── class │ └── src │ │ └── TorrentLibrary.js~TorrentLibrary.html ├── coverage.json ├── css │ ├── github.css │ ├── identifiers.css │ ├── manual.css │ ├── prettify-tomorrow.css │ ├── search.css │ ├── source.css │ ├── style.css │ └── test.css ├── file │ └── src │ │ ├── TorrentLibrary.js.html │ │ ├── docs │ │ ├── events │ │ │ └── events.js.html │ │ └── types │ │ │ └── types.js.html │ │ └── filters │ │ ├── filterBooleanProperty.js.html │ │ ├── filterNumberProperty.js.html │ │ ├── filterProperties.js.html │ │ └── filterStringProperty.js.html ├── function │ └── index.html ├── identifiers.html ├── image │ ├── badge.svg │ ├── esdoc-logo-mini-black.png │ ├── esdoc-logo-mini.png │ ├── github.png │ ├── manual-badge.svg │ └── search.png ├── index.html ├── index.json ├── lint.json ├── manual │ ├── CHANGELOG.html │ ├── asset │ │ ├── fileMapping.png │ │ ├── filterMovies.png │ │ ├── filterTvSeries.png │ │ ├── foundMovies.png │ │ └── foundTvSeries.png │ ├── createPlaylist.html │ ├── filterMoviesByParameters.html │ ├── filterTvSeriesByParameters.html │ ├── getCategoryForEachFile.html │ ├── index.html │ ├── listEachTvSerie.html │ └── listFoundMovies.html ├── script │ ├── inherited-summary.js │ ├── inner-link.js │ ├── manual.js │ ├── patch-for-local.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ └── prettify.js │ ├── pretty-print.js │ ├── search.js │ ├── search_index.js │ └── test-summary.js ├── source.html ├── test-file │ └── test │ │ ├── getters │ │ ├── allFilesWithCategory.js.html │ │ ├── allMovies.js.html │ │ ├── allTvSeries.js.html │ │ └── constants.js.html │ │ └── methods │ │ ├── addNewPath.js.html │ │ ├── createFromJSON.js.html │ │ ├── filterMovies.js.html │ │ ├── filterTvSeries.js.html │ │ ├── removeOldFiles.js.html │ │ ├── scan.js.html │ │ └── toJSON.js.html ├── test.html └── typedef │ └── index.html ├── index.js ├── manual ├── assets │ ├── fileMapping.png │ ├── filterMovies.png │ ├── filterTvSeries.png │ ├── foundMovies.png │ └── foundTvSeries.png └── examples │ ├── createPlaylist.md │ ├── filterMoviesByParameters.md │ ├── filterTvSeriesByParameters.md │ ├── getCategoryForEachFile.md │ ├── listEachTvSerie.md │ └── listFoundMovies.md ├── package-lock.json ├── package.json ├── src ├── TorrentLibrary.js ├── docs │ ├── events │ │ └── events.js │ └── types │ │ └── types.js └── filters │ ├── filterBooleanProperty.js │ ├── filterNumberProperty.js │ ├── filterProperties.js │ └── filterStringProperty.js └── test ├── _constants.js ├── fixtures ├── example.json ├── folder1 │ ├── Bad.Ass.2012.REMASTERED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi │ └── The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi └── folder2 │ └── The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi ├── getters ├── allFilesWithCategory.js ├── allMovies.js ├── allTvSeries.js └── constants.js └── methods ├── addNewPath.js ├── createFromJSON.js ├── filterMovies.js ├── filterTvSeries.js ├── removeOldFiles.js ├── scan.js └── toJSON.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | let env = process.env.BABEL_ENV || process.env.NODE_ENV; 2 | let presets = []; 3 | let plugins = ["@babel/plugin-proposal-object-rest-spread"]; 4 | let ignore = ["src/docs/**/*.js"]; 5 | let comments = false; 6 | 7 | // custom settings for prod/test ; for example babel-istanbul-plugin , etc. 8 | switch (env){ 9 | case 'test': 10 | // ./fix-coverage/arrow-function-coverage-fix.js : Because some odd issue for istanbul/nyc 11 | plugins.push.apply(plugins, ["babel-plugin-istanbul"]); 12 | break; 13 | 14 | case 'production': 15 | // workaround : https://github.com/babel/minify/issues/790#issuecomment-365750066 16 | presets.push.apply(presets, [['minify', { removeUndefined: false }]] ); 17 | break; 18 | } 19 | 20 | // default preset 21 | presets.push.apply(presets, ["@babel/preset-env"]); 22 | 23 | module.exports = { 24 | presets: presets, 25 | plugins: plugins, 26 | ignore: ignore, 27 | comments: comments, 28 | }; -------------------------------------------------------------------------------- /.esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs", 4 | "plugins": [ 5 | {"name": "esdoc-standard-plugin", 6 | "option": { 7 | "test": { 8 | "source": "./test/", 9 | "interfaces": ["test"], 10 | "includes": ["\\.js$"], 11 | "excludes": ["_constants.js"] 12 | }, 13 | "manual": { 14 | "globalIndex": true, 15 | "asset": "./manual/assets", 16 | "files": [ 17 | "./manual/examples/getCategoryForEachFile.md", 18 | "./manual/examples/listFoundMovies.md", 19 | "./manual/examples/listEachTvSerie.md", 20 | "./manual/examples/filterMoviesByParameters.md", 21 | "./manual/examples/filterTvSeriesByParameters.md", 22 | "./manual/examples/createPlaylist.md", 23 | "./CHANGELOG.md" 24 | ] 25 | } 26 | } 27 | }, 28 | {"name": "esdoc-ecmascript-proposal-plugin", "option": {"all": true}} 29 | ] 30 | } -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.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 | ], 63 | "rules": { 64 | "max-char": 72, 65 | "min-char": 10, 66 | "end-with-dot": false 67 | } 68 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | cache: 5 | directories: 6 | - node_modules 7 | notifications: 8 | slack: 9 | on_success: change 10 | on_failure: always 11 | node_js: 12 | - 'node' 13 | - '7' 14 | - '6' 15 | os: 16 | - linux 17 | before_install: 18 | - npm i -g npm@latest 19 | - npm i -g travis-deploy-once 20 | - npm install -g greenkeeper-lockfile@latest 21 | install: 22 | - npm install 23 | - npm audit fix 24 | before_script: 25 | - npm prune 26 | - echo "GreenKeeper lockfile update" 27 | - greenkeeper-lockfile-update 28 | after_script: 29 | - greenkeeper-lockfile-upload 30 | after_success: 31 | - npm run coverage 32 | # In order to have production files for release : I should rerun compile in production mode in the master build 33 | - travis-deploy-once && npm run prepare && npx -p node@8 npm run semantic-release 34 | branches: 35 | except: 36 | - /^v\d+\.\d+\.\d+$/ 37 | -------------------------------------------------------------------------------- /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/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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 | # torrent-files-library [![Build Status](https://img.shields.io/travis/jy95/torrent-files-library.svg)](https://travis-ci.org/jy95/torrent-files-library) [![Coveralls branch](https://img.shields.io/coveralls/jy95/torrent-files-library/master.svg)](https://coveralls.io/github/jy95/torrent-files-library?branch=master) [![Dependency Status](https://img.shields.io/david/jy95/torrent-files-library.svg)](https://david-dm.org/jy95/torrent-files-library) [![Dev Dependency Status](https://img.shields.io/david/dev/jy95/torrent-files-library.svg)](https://david-dm.org/jy95/torrent-files-library?type=dev) [![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) [![Join the chat at https://gitter.im/torrent-files-library-/Lobby](https://badges.gitter.im/torrent-files-library-/Lobby.svg)](https://gitter.im/torrent-files-library-/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Greenkeeper badge](https://badges.greenkeeper.io/jy95/torrent-files-library.svg)](https://greenkeeper.io/) 2 | > Scan directories to build a library of media files (movie or tv show) that follows torrent naming conventions 3 | 4 | PS: see the repository [mediaScan](https://github.com/jy95/mediaScan) for the TypeScript version of this lib. 5 | 6 | ## What to do with this library ? 7 | 8 | A lot of things : 9 | * Basic listing purposes : 10 | * [List found movies](https://jy95.github.io/torrent-files-library/manual/listFoundMovies.html) 11 | * [List each found tv serie](https://jy95.github.io/torrent-files-library/manual/listEachTvSerie.html) 12 | * [Detect the category of each file](https://jy95.github.io/torrent-files-library/manual/getCategoryForEachFile.html) 13 | * Filtering purposes : 14 | * [Filter movies based on search parameters](https://jy95.github.io/torrent-files-library/manual/filterMoviesByParameters.html) 15 | * [Filter tv-shown based on search parameters](https://jy95.github.io/torrent-files-library/manual/filterTvSeriesByParameters.html) 16 | * Miscellaneous purposes 17 | * [Create custom playlist(s)](https://jy95.github.io/torrent-files-library/manual/createPlaylist.html) 18 | * ... 19 | 20 | Don't hesitate to suggest new features : it is always worthy :) 21 | 22 | ## Documentation 23 | For more examples and API details, see [API documentation with manual](https://jy95.github.io/torrent-files-library/) 24 | 25 | ## Installation 26 | 27 | For npm users : 28 | 29 | ```shell 30 | $ npm install --save torrent-files-library 31 | ``` 32 | 33 | for Yarn : 34 | ```shell 35 | $ yarn add torrent-files-library 36 | ``` 37 | 38 | ## Test 39 | 40 | ```shell 41 | npm test 42 | ``` 43 | 44 | To generate a test coverage report: 45 | 46 | ```shell 47 | npm run coverage 48 | ``` 49 | ## Contributing 50 | 51 | * If you're unsure if a feature would make a good addition, you can always [create an issue](https://github.com/jy95/torrent-files-library/issues/new) first. 52 | * We aim for 100% test coverage. Please write tests for any new functionality or changes. 53 | * Any API changes should be fully documented. 54 | * Make sure your code meets our linting standards. Run `npm run lint` to check your code. 55 | * Be mindful of others when making suggestions and/or code reviewing. 56 | -------------------------------------------------------------------------------- /config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb-base", 3 | "rules" :{ 4 | "linebreak-style": 0, 5 | "valid-jsdoc": "error", 6 | "max-len": ["error", { "ignoreComments": true }], 7 | "import/extensions": ['off', 'never'], 8 | "no-bitwise": ["error", { "allow": ["|"] }], 9 | "no-restricted-syntax": ['off', "ForInStatement"], 10 | "prefer-const" : "off" 11 | }, 12 | "env" : { 13 | "es6" : true, 14 | "node": true, 15 | "mocha" : true 16 | } 17 | }; -------------------------------------------------------------------------------- /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 | publish: 29 | ['@semantic-release/changelog', '@semantic-release/npm', 30 | { 31 | path: '@semantic-release/git', 32 | message: ':wrench: Chore: update package.json and CHANGELOG.md for release ${nextRelease.version} [skip ci]', 33 | }, '@semantic-release/github'], 34 | }; 35 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | 100% 15 | 100% 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage": "100%", 3 | "expectCount": 39, 4 | "actualCount": 39, 5 | "files": { 6 | "src/filters/filterBooleanProperty.js": { 7 | "expectCount": 3, 8 | "actualCount": 3, 9 | "undocumentLines": [] 10 | }, 11 | "src/filters/filterNumberProperty.js": { 12 | "expectCount": 5, 13 | "actualCount": 5, 14 | "undocumentLines": [] 15 | }, 16 | "src/filters/filterProperties.js": { 17 | "expectCount": 3, 18 | "actualCount": 3, 19 | "undocumentLines": [] 20 | }, 21 | "src/filters/filterStringProperty.js": { 22 | "expectCount": 4, 23 | "actualCount": 4, 24 | "undocumentLines": [] 25 | }, 26 | "src/TorrentLibrary.js": { 27 | "expectCount": 24, 28 | "actualCount": 24, 29 | "undocumentLines": [] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /docs/css/github.css: -------------------------------------------------------------------------------- 1 | /* github markdown */ 2 | .github-markdown { 3 | font-size: 16px; 4 | } 5 | 6 | .github-markdown h1, 7 | .github-markdown h2, 8 | .github-markdown h3, 9 | .github-markdown h4, 10 | .github-markdown h5 { 11 | margin-top: 1em; 12 | margin-bottom: 16px; 13 | font-weight: bold; 14 | padding: 0; 15 | } 16 | 17 | .github-markdown h1:nth-of-type(1) { 18 | margin-top: 0; 19 | } 20 | 21 | .github-markdown h1 { 22 | font-size: 2em; 23 | padding-bottom: 0.3em; 24 | } 25 | 26 | .github-markdown h2 { 27 | font-size: 1.75em; 28 | padding-bottom: 0.3em; 29 | } 30 | 31 | .github-markdown h3 { 32 | font-size: 1.5em; 33 | } 34 | 35 | .github-markdown h4 { 36 | font-size: 1.25em; 37 | } 38 | 39 | .github-markdown h5 { 40 | font-size: 1em; 41 | } 42 | 43 | .github-markdown ul, .github-markdown ol { 44 | padding-left: 2em; 45 | } 46 | 47 | .github-markdown pre > code { 48 | font-size: 0.85em; 49 | } 50 | 51 | .github-markdown table { 52 | margin-bottom: 1em; 53 | border-collapse: collapse; 54 | border-spacing: 0; 55 | } 56 | 57 | .github-markdown table tr { 58 | background-color: #fff; 59 | border-top: 1px solid #ccc; 60 | } 61 | 62 | .github-markdown table th, 63 | .github-markdown table td { 64 | padding: 6px 13px; 65 | border: 1px solid #ddd; 66 | } 67 | 68 | .github-markdown table tr:nth-child(2n) { 69 | background-color: #f8f8f8; 70 | } 71 | 72 | .github-markdown hr { 73 | border-right: 0; 74 | border-bottom: 1px solid #e5e5e5; 75 | border-left: 0; 76 | border-top: 0; 77 | } 78 | 79 | /** badge(.svg) does not have border */ 80 | .github-markdown img:not([src*=".svg"]) { 81 | max-width: 100%; 82 | box-shadow: 1px 1px 1px rgba(0,0,0,0.5); 83 | } 84 | -------------------------------------------------------------------------------- /docs/css/identifiers.css: -------------------------------------------------------------------------------- 1 | .identifiers-wrap { 2 | display: flex; 3 | align-items: flex-start; 4 | } 5 | 6 | .identifier-dir-tree { 7 | background: #fff; 8 | border: solid 1px #ddd; 9 | border-radius: 0.25em; 10 | top: 52px; 11 | position: -webkit-sticky; 12 | position: sticky; 13 | max-height: calc(100vh - 155px); 14 | overflow-y: scroll; 15 | min-width: 200px; 16 | margin-left: 1em; 17 | } 18 | 19 | .identifier-dir-tree-header { 20 | padding: 0.5em; 21 | background-color: #fafafa; 22 | border-bottom: solid 1px #ddd; 23 | } 24 | 25 | .identifier-dir-tree-content { 26 | padding: 0 0.5em 0; 27 | } 28 | 29 | .identifier-dir-tree-content > div { 30 | padding-top: 0.25em; 31 | padding-bottom: 0.25em; 32 | } 33 | 34 | .identifier-dir-tree-content a { 35 | color: inherit; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /docs/css/manual.css: -------------------------------------------------------------------------------- 1 | .github-markdown .manual-toc { 2 | padding-left: 0; 3 | } 4 | 5 | .manual-index .manual-cards { 6 | display: flex; 7 | flex-wrap: wrap; 8 | } 9 | 10 | .manual-index .manual-card-wrap { 11 | width: 280px; 12 | padding: 10px 20px 10px 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | .manual-index .manual-card-wrap > h1 { 17 | margin: 0; 18 | font-size: 1em; 19 | font-weight: 600; 20 | padding: 0.2em 0 0.2em 0.5em; 21 | border-radius: 0.1em 0.1em 0 0; 22 | border: none; 23 | } 24 | 25 | .manual-index .manual-card-wrap > h1 span { 26 | color: #555; 27 | } 28 | 29 | .manual-index .manual-card { 30 | height: 200px; 31 | overflow: hidden; 32 | border: solid 1px rgba(230, 230, 230, 0.84); 33 | border-radius: 0 0 0.1em 0.1em; 34 | padding: 8px; 35 | position: relative; 36 | } 37 | 38 | .manual-index .manual-card > div { 39 | transform: scale(0.4); 40 | transform-origin: 0 0; 41 | width: 250%; 42 | } 43 | 44 | .manual-index .manual-card > a { 45 | position: absolute; 46 | top: 0; 47 | left: 0; 48 | width: 100%; 49 | height: 100%; 50 | background: rgba(210, 210, 210, 0.1); 51 | } 52 | 53 | .manual-index .manual-card > a:hover { 54 | background: none; 55 | } 56 | 57 | .manual-index .manual-badge { 58 | margin: 0; 59 | } 60 | 61 | .manual-index .manual-user-index { 62 | margin-bottom: 1em; 63 | border-bottom: solid 1px #ddd; 64 | } 65 | 66 | .manual-root .navigation { 67 | padding-left: 4px; 68 | margin-top: 4px; 69 | } 70 | 71 | .navigation .manual-toc-root > div { 72 | padding-left: 0.25em; 73 | padding-right: 0.75em; 74 | } 75 | 76 | .github-markdown .manual-toc-title a { 77 | color: inherit; 78 | } 79 | 80 | .manual-breadcrumb-list { 81 | font-size: 0.8em; 82 | margin-bottom: 1em; 83 | } 84 | 85 | .manual-toc-title a:hover { 86 | color: #039BE5; 87 | } 88 | 89 | .manual-toc li { 90 | margin: 0.75em 0; 91 | list-style-type: none; 92 | } 93 | 94 | .navigation .manual-toc [class^="indent-h"] a { 95 | color: #666; 96 | } 97 | 98 | .navigation .manual-toc .indent-h1 a { 99 | color: #555; 100 | font-weight: 600; 101 | display: block; 102 | } 103 | 104 | .manual-toc .indent-h1 { 105 | display: block; 106 | margin: 0.4em 0 0 0.25em; 107 | padding: 0.2em 0 0.2em 0.5em; 108 | border-radius: 0.1em; 109 | } 110 | 111 | .manual-root .navigation .manual-toc li:not(.indent-h1) { 112 | margin-top: 0.5em; 113 | } 114 | 115 | .manual-toc .indent-h2 { 116 | display: none; 117 | margin-left: 1.5em; 118 | } 119 | .manual-toc .indent-h3 { 120 | display: none; 121 | margin-left: 2.5em; 122 | } 123 | .manual-toc .indent-h4 { 124 | display: none; 125 | margin-left: 3.5em; 126 | } 127 | .manual-toc .indent-h5 { 128 | display: none; 129 | margin-left: 4.5em; 130 | } 131 | 132 | .manual-nav li { 133 | margin: 0.75em 0; 134 | } 135 | -------------------------------------------------------------------------------- /docs/css/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/css/search.css: -------------------------------------------------------------------------------- 1 | /* search box */ 2 | .search-box { 3 | position: absolute; 4 | top: 10px; 5 | right: 50px; 6 | padding-right: 8px; 7 | padding-bottom: 10px; 8 | line-height: normal; 9 | font-size: 12px; 10 | } 11 | 12 | .search-box img { 13 | width: 20px; 14 | vertical-align: top; 15 | } 16 | 17 | .search-input { 18 | display: inline; 19 | visibility: hidden; 20 | width: 0; 21 | padding: 2px; 22 | height: 1.5em; 23 | outline: none; 24 | background: transparent; 25 | border: 1px #0af; 26 | border-style: none none solid none; 27 | vertical-align: bottom; 28 | } 29 | 30 | .search-input-edge { 31 | display: none; 32 | width: 1px; 33 | height: 5px; 34 | background-color: #0af; 35 | vertical-align: bottom; 36 | } 37 | 38 | .search-result { 39 | position: absolute; 40 | display: none; 41 | height: 600px; 42 | width: 100%; 43 | padding: 0; 44 | margin-top: 5px; 45 | margin-left: 24px; 46 | background: white; 47 | box-shadow: 1px 1px 4px rgb(0,0,0); 48 | white-space: nowrap; 49 | overflow-y: scroll; 50 | } 51 | 52 | .search-result-import-path { 53 | color: #aaa; 54 | font-size: 12px; 55 | } 56 | 57 | .search-result li { 58 | list-style: none; 59 | padding: 2px 4px; 60 | } 61 | 62 | .search-result li a { 63 | display: block; 64 | } 65 | 66 | .search-result li.selected { 67 | background: #ddd; 68 | } 69 | 70 | .search-result li.search-separator { 71 | background: rgb(37, 138, 175); 72 | color: white; 73 | } 74 | 75 | .search-box.active .search-input { 76 | visibility: visible; 77 | transition: width 0.2s ease-out; 78 | width: 300px; 79 | } 80 | 81 | .search-box.active .search-input-edge { 82 | display: inline-block; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /docs/css/source.css: -------------------------------------------------------------------------------- 1 | table.files-summary { 2 | width: 100%; 3 | margin: 10px 0; 4 | border-spacing: 0; 5 | border: 0; 6 | border-collapse: collapse; 7 | text-align: right; 8 | } 9 | 10 | table.files-summary tbody tr:hover { 11 | background: #eee; 12 | } 13 | 14 | table.files-summary td:first-child, 15 | table.files-summary td:nth-of-type(2) { 16 | text-align: left; 17 | } 18 | 19 | table.files-summary[data-use-coverage="false"] td.coverage { 20 | display: none; 21 | } 22 | 23 | table.files-summary thead { 24 | background: #fafafa; 25 | } 26 | 27 | table.files-summary td { 28 | border: solid 1px #ddd; 29 | padding: 4px 10px; 30 | vertical-align: top; 31 | } 32 | 33 | table.files-summary td.identifiers > span { 34 | display: block; 35 | margin-top: 4px; 36 | } 37 | table.files-summary td.identifiers > span:first-child { 38 | margin-top: 0; 39 | } 40 | 41 | table.files-summary .coverage-count { 42 | font-size: 12px; 43 | color: #aaa; 44 | display: inline-block; 45 | min-width: 40px; 46 | } 47 | 48 | .total-coverage-count { 49 | position: relative; 50 | bottom: 2px; 51 | font-size: 12px; 52 | color: #666; 53 | font-weight: 500; 54 | padding-left: 5px; 55 | } 56 | -------------------------------------------------------------------------------- /docs/css/test.css: -------------------------------------------------------------------------------- 1 | table.test-summary thead { 2 | background: #fafafa; 3 | } 4 | 5 | table.test-summary thead .test-description { 6 | width: 50%; 7 | } 8 | 9 | table.test-summary { 10 | width: 100%; 11 | margin: 10px 0; 12 | border-spacing: 0; 13 | border: 0; 14 | border-collapse: collapse; 15 | } 16 | 17 | table.test-summary thead .test-count { 18 | width: 3em; 19 | } 20 | 21 | table.test-summary tbody tr:hover { 22 | background-color: #eee; 23 | } 24 | 25 | table.test-summary td { 26 | border: solid 1px #ddd; 27 | padding: 4px 10px; 28 | vertical-align: top; 29 | } 30 | 31 | table.test-summary td p { 32 | margin: 0; 33 | } 34 | 35 | table.test-summary tr.test-interface .toggle { 36 | display: inline-block; 37 | float: left; 38 | margin-right: 4px; 39 | cursor: pointer; 40 | font-size: 0.8em; 41 | padding-top: 0.25em; 42 | } 43 | 44 | table.test-summary tr.test-interface .toggle.opened:before { 45 | content: '▼'; 46 | } 47 | 48 | table.test-summary tr.test-interface .toggle.closed:before { 49 | content: '▶'; 50 | } 51 | 52 | table.test-summary .test-target > span { 53 | display: block; 54 | margin-top: 4px; 55 | } 56 | table.test-summary .test-target > span:first-child { 57 | margin-top: 0; 58 | } 59 | -------------------------------------------------------------------------------- /docs/file/src/docs/events/events.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/docs/events/events.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
28 | 29 | 65 | 66 |

src/docs/events/events.js

67 |
// events
 68 | 
 69 | /**
 70 |  * missing_parameter event
 71 |  * @typedef {Object} Events#missing_parameter
 72 |  * @property {string} functionName - Indicates the function name when the error occurs.
 73 |  * @example
 74 |  * TorrentLibraryInstance.on('missing_parameter',function(callback){
 75 |  *      console.log('Parameter is missing in ' + callback.functionName);
 76 |  * })
 77 |  */
 78 | 
 79 | /**
 80 |  * error_in_function event
 81 |  * @typedef {Object} Events#error_in_function
 82 |  * @property {string} functionName - Indicates the function name when the error occurs.
 83 |  * @property {string} error - The error message got by error.message
 84 |  * @example
 85 |  * TorrentLibraryInstance.on('error_in_function',function(callback){
 86 |  *      console.log('Function ' + callback.functionName + ' has the following error : ' + callback.error);
 87 |  * })
 88 |  */
 89 | 
 90 | /**
 91 |  * addNewPath event
 92 |  * @typedef {Object} Events#addNewPath
 93 |  * @property {...string} paths - all the paths that were added
 94 |  * @example
 95 |  * TorrentLibraryInstance.on('addNewPath',function(callback){
 96 |  *      console.log('The following files were added : ' + callback.paths);
 97 |  * })
 98 |  */
 99 | 
100 | /**
101 |  * scan event
102 |  * @typedef {object} Events#scan
103 |  * @property {...string} files - all the files that were found and added if not yet in lib
104 |  * @example
105 |  * TorrentLibraryInstance.on('scan',function(callback){
106 |  *      console.log('The following files were found : ' + callback.files);
107 |  * })
108 |  */
109 | 
110 | /**
111 |  * removeOldFiles event
112 |  * @typedef {object}  Events#removeOldFiles
113 |  * @property {...string} files - all the files that were found and removed if not yet in lib
114 |  * @example
115 |  * TorrentLibraryInstance.on('removeOldFiles',function(callback){
116 |  *      console.log('The following files were added : ' + callback.files);
117 |  * })
118 |  */
119 | 
120 | 121 |
122 | 123 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /docs/file/src/filters/filterBooleanProperty.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/filters/filterBooleanProperty.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
28 | 29 | 65 | 66 |

src/filters/filterBooleanProperty.js

67 |
/**
 68 |  * Provides a map with valid default properties
 69 |  * @param {searchParameters} searchObject - search parameters
 70 |  * @return {Map<string, boolean>} the result map
 71 |  */
 72 | export function filterDefaultBooleanProperties(searchObject) {
 73 |   const {
 74 |     extended, unrated, proper, repack, convert, hardcoded, retail, remastered,
 75 |   } = searchObject;
 76 | 
 77 | 
 78 |   const propertiesArray = [extended, unrated, proper,
 79 |     repack, convert, hardcoded, retail, remastered];
 80 |   const propertiesNames = ['extended', 'unrated', 'proper', 'repack', 'convert',
 81 |     'hardcoded', 'retail', 'remastered'];
 82 | 
 83 |   return propertiesArray.reduce((propertiesMap, val, index) => {
 84 |     // eslint-disable-next-line max-len
 85 |     if (val === true || val === false) { propertiesMap.set(propertiesNames[index], val); }
 86 |     return propertiesMap;
 87 |   }, new Map());
 88 | }
 89 | 
 90 | /**
 91 |  * Remove the default boolean properties
 92 |  * @param {searchParameters} searchObject - search parameters
 93 |  * @return {searchParameters} searchParameters without these properties
 94 |  */
 95 | export function excludeDefaultBooleanProperties(searchObject) {
 96 |   let {
 97 |     extended, unrated, proper, repack, convert, hardcoded, retail, remastered,
 98 |     ...rest
 99 |   } = searchObject;
100 |   return rest;
101 | }
102 | 
103 | /**
104 |  * Filter the set based on boolean properties
105 |  * @param {Set<TPN>} set The TPN set
106 |  * @param {Map<string, boolean>} propertiesMap The map from filterDefaultBooleanProperties
107 |  * @return {Set<TPN>} the filtered set
108 |  */
109 | export function filterByBoolean(set, propertiesMap) {
110 |   // first step : get an array so that we can do filter/reduce stuff
111 |   // second step : iterate the propertiesMap and do filter and return the filtered array
112 |   // val[0] : the key ; val[1] : the value
113 |   return new Set(Array
114 |     .from(propertiesMap.entries())
115 |     .reduce(
116 |       // eslint-disable-next-line max-len
117 |       (currentMoviesArray, val) => currentMoviesArray.filter(TPN => TPN[val[0]] === val[1])
118 |       , [...set],
119 |     ));
120 | }
121 | 
122 | 123 |
124 | 125 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /docs/file/src/filters/filterStringProperty.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/filters/filterStringProperty.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
28 | 29 | 65 | 66 |

src/filters/filterStringProperty.js

67 |
/**
 68 |  * Provides a map with valid default properties
 69 |  * @param {searchParameters} searchObject - search parameters
 70 |  * @return {Map<string, string|string[]>} the result map
 71 |  */
 72 | export function filterDefaultStringProperties(searchObject) {
 73 |   const {
 74 |     title, resolution, codec, audio, group, region, container, language, source,
 75 |   } = searchObject;
 76 | 
 77 | 
 78 |   const propertiesArray = [title, resolution, codec, audio, group,
 79 |     region, container, language, source];
 80 |   const propertiesNames = ['title', 'resolution', 'codec', 'audio', 'group',
 81 |     'region', 'container', 'language', 'source'];
 82 | 
 83 |   return propertiesArray.reduce((propertiesMap, val, index) => {
 84 |     if (val !== undefined) {
 85 |       propertiesMap.set(propertiesNames[index], val);
 86 |     }
 87 |     return propertiesMap;
 88 |   }, new Map());
 89 | }
 90 | 
 91 | /**
 92 |  * Remove the default string properties
 93 |  * @param {searchParameters} searchObject - search parameters
 94 |  * @return {searchParameters} searchParameters without these properties
 95 |  */
 96 | export function excludeDefaultStringProperties(searchObject) {
 97 |   let {
 98 |     title, resolution, codec, audio, group, region, container, language, source,
 99 |     ...rest
100 |   } = searchObject;
101 |   return rest;
102 | }
103 | 
104 | /**
105 |  * Filter function for filterByString
106 |  * @param {string} property The property to be checked
107 |  * @param {string[]|string} expected The expected result
108 |  * @param {TPN} object the object to be checked
109 |  * @return {boolean} the result
110 |  */
111 | function filterFunctionByType(property, expected, object) {
112 |   if (Array.isArray(expected)) { return expected.includes(object[property]); }
113 |   return object[property] === expected;
114 | }
115 | 
116 | /**
117 |  * Filter the set based on string properties
118 |  * @param {Set<TPN>} set The TPN set
119 |  * @param {Map<string, string|string[]>} propertiesMap The map from filterDefaultStringProperties
120 |  * @return {Set<TPN>} the filtered set
121 |  */
122 | export function filterByString(set, propertiesMap) {
123 |   // first step : get an array so that we can do filter/reduce stuff
124 |   // second step : iterate the propertiesMap and do filter and return the filtered array
125 |   // val[0] : the key ; val[1] : the value
126 |   return new Set(Array
127 |     .from(propertiesMap.entries())
128 |     .reduce(
129 |       // eslint-disable-next-line max-len
130 |       (currentMoviesArray, val) => currentMoviesArray.filter(TPN => filterFunctionByType(val[0], val[1], TPN))
131 |       , [...set],
132 |     ));
133 | }
134 | 
135 | 136 |
137 | 138 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /docs/image/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | @ratio@ 15 | @ratio@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/image/esdoc-logo-mini-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/image/esdoc-logo-mini-black.png -------------------------------------------------------------------------------- /docs/image/esdoc-logo-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/image/esdoc-logo-mini.png -------------------------------------------------------------------------------- /docs/image/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/image/github.png -------------------------------------------------------------------------------- /docs/image/manual-badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | manual 13 | manual 14 | @value@ 15 | @value@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/image/search.png -------------------------------------------------------------------------------- /docs/lint.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /docs/manual/asset/fileMapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/fileMapping.png -------------------------------------------------------------------------------- /docs/manual/asset/filterMovies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/filterMovies.png -------------------------------------------------------------------------------- /docs/manual/asset/filterTvSeries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/filterTvSeries.png -------------------------------------------------------------------------------- /docs/manual/asset/foundMovies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/foundMovies.png -------------------------------------------------------------------------------- /docs/manual/asset/foundTvSeries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/docs/manual/asset/foundTvSeries.png -------------------------------------------------------------------------------- /docs/script/inherited-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var tbody = parent.querySelector('tbody'); 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | tbody.style.display = 'none'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | tbody.style.display = 'block'; 21 | } 22 | } 23 | 24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle'); 25 | for (var i = 0; i < buttons.length; i++) { 26 | buttons[i].addEventListener('click', toggle); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /docs/script/inner-link.js: -------------------------------------------------------------------------------- 1 | // inner link(#foo) can not correctly scroll, because page has fixed header, 2 | // so, I manually scroll. 3 | (function(){ 4 | var matched = location.hash.match(/errorLines=([\d,]+)/); 5 | if (matched) return; 6 | 7 | function adjust() { 8 | window.scrollBy(0, -55); 9 | var el = document.querySelector('.inner-link-active'); 10 | if (el) el.classList.remove('inner-link-active'); 11 | 12 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 13 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 14 | var el = document.querySelector(id); 15 | if (el) el.classList.add('inner-link-active'); 16 | } 17 | 18 | window.addEventListener('hashchange', adjust); 19 | 20 | if (location.hash) { 21 | setTimeout(adjust, 0); 22 | } 23 | })(); 24 | 25 | (function(){ 26 | var els = document.querySelectorAll('[href^="#"]'); 27 | var href = location.href.replace(/#.*$/, ''); // remove existed hash 28 | for (var i = 0; i < els.length; i++) { 29 | var el = els[i]; 30 | el.href = href + el.getAttribute('href'); // because el.href is absolute path 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /docs/script/manual.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var matched = location.pathname.match(/\/(manual\/.*\.html)$/); 3 | if (!matched) return; 4 | 5 | var currentName = matched[1]; 6 | var cssClass = '.navigation .manual-toc li[data-link="' + currentName + '"]'; 7 | var styleText = cssClass + '{ display: block; }\n'; 8 | styleText += cssClass + '.indent-h1 a { color: #039BE5 }'; 9 | var style = document.createElement('style'); 10 | style.textContent = styleText; 11 | document.querySelector('head').appendChild(style); 12 | })(); 13 | -------------------------------------------------------------------------------- /docs/script/patch-for-local.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | if (location.protocol === 'file:') { 3 | var elms = document.querySelectorAll('a[href="./"]'); 4 | for (var i = 0; i < elms.length; i++) { 5 | elms[i].href = './index.html'; 6 | } 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /docs/script/pretty-print.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | prettyPrint(); 3 | var lines = document.querySelectorAll('.prettyprint.linenums li[class^="L"]'); 4 | for (var i = 0; i < lines.length; i++) { 5 | lines[i].id = 'lineNumber' + (i + 1); 6 | } 7 | 8 | var matched = location.hash.match(/errorLines=([\d,]+)/); 9 | if (matched) { 10 | var lines = matched[1].split(','); 11 | for (var i = 0; i < lines.length; i++) { 12 | var id = '#lineNumber' + lines[i]; 13 | var el = document.querySelector(id); 14 | el.classList.add('error-line'); 15 | } 16 | return; 17 | } 18 | 19 | if (location.hash) { 20 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 21 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 22 | var line = document.querySelector(id); 23 | if (line) line.classList.add('active'); 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/script/search.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var searchIndex = window.esdocSearchIndex; 3 | var searchBox = document.querySelector('.search-box'); 4 | var input = document.querySelector('.search-input'); 5 | var result = document.querySelector('.search-result'); 6 | var selectedIndex = -1; 7 | var prevText; 8 | 9 | // active search box and focus when mouse enter on search box. 10 | searchBox.addEventListener('mouseenter', function(){ 11 | searchBox.classList.add('active'); 12 | input.focus(); 13 | }); 14 | 15 | // search with text when key is upped. 16 | input.addEventListener('keyup', function(ev){ 17 | var text = ev.target.value.toLowerCase(); 18 | if (!text) { 19 | result.style.display = 'none'; 20 | result.innerHTML = ''; 21 | return; 22 | } 23 | 24 | if (text === prevText) return; 25 | prevText = text; 26 | 27 | var html = {class: [], method: [], member: [], function: [], variable: [], typedef: [], external: [], file: [], test: [], testFile: []}; 28 | var len = searchIndex.length; 29 | var kind; 30 | for (var i = 0; i < len; i++) { 31 | var pair = searchIndex[i]; 32 | if (pair[0].indexOf(text) !== -1) { 33 | kind = pair[3]; 34 | html[kind].push('
  • ' + pair[2] + '
  • '); 35 | } 36 | } 37 | 38 | var innerHTML = ''; 39 | for (kind in html) { 40 | var list = html[kind]; 41 | if (!list.length) continue; 42 | innerHTML += '
  • ' + kind + '
  • \n' + list.join('\n'); 43 | } 44 | result.innerHTML = innerHTML; 45 | if (innerHTML) result.style.display = 'block'; 46 | selectedIndex = -1; 47 | }); 48 | 49 | // down, up and enter key are pressed, select search result. 50 | input.addEventListener('keydown', function(ev){ 51 | if (ev.keyCode === 40) { 52 | // arrow down 53 | var current = result.children[selectedIndex]; 54 | var selected = result.children[selectedIndex + 1]; 55 | if (selected && selected.classList.contains('search-separator')) { 56 | var selected = result.children[selectedIndex + 2]; 57 | selectedIndex++; 58 | } 59 | 60 | if (selected) { 61 | if (current) current.classList.remove('selected'); 62 | selectedIndex++; 63 | selected.classList.add('selected'); 64 | } 65 | } else if (ev.keyCode === 38) { 66 | // arrow up 67 | var current = result.children[selectedIndex]; 68 | var selected = result.children[selectedIndex - 1]; 69 | if (selected && selected.classList.contains('search-separator')) { 70 | var selected = result.children[selectedIndex - 2]; 71 | selectedIndex--; 72 | } 73 | 74 | if (selected) { 75 | if (current) current.classList.remove('selected'); 76 | selectedIndex--; 77 | selected.classList.add('selected'); 78 | } 79 | } else if (ev.keyCode === 13) { 80 | // enter 81 | var current = result.children[selectedIndex]; 82 | if (current) { 83 | var link = current.querySelector('a'); 84 | if (link) location.href = link.href; 85 | } 86 | } else { 87 | return; 88 | } 89 | 90 | ev.preventDefault(); 91 | }); 92 | 93 | // select search result when search result is mouse over. 94 | result.addEventListener('mousemove', function(ev){ 95 | var current = result.children[selectedIndex]; 96 | if (current) current.classList.remove('selected'); 97 | 98 | var li = ev.target; 99 | while (li) { 100 | if (li.nodeName === 'LI') break; 101 | li = li.parentElement; 102 | } 103 | 104 | if (li) { 105 | selectedIndex = Array.prototype.indexOf.call(result.children, li); 106 | li.classList.add('selected'); 107 | } 108 | }); 109 | 110 | // clear search result when body is clicked. 111 | document.body.addEventListener('click', function(ev){ 112 | selectedIndex = -1; 113 | result.style.display = 'none'; 114 | result.innerHTML = ''; 115 | }); 116 | 117 | })(); 118 | -------------------------------------------------------------------------------- /docs/script/test-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TR' && parent.classList.contains('test-interface')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var direction; 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | direction = 'closed'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | direction = 'opened'; 21 | } 22 | 23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1; 24 | var nextElement = parent.nextElementSibling; 25 | while (nextElement) { 26 | var depth = parseInt(nextElement.dataset.testDepth, 10); 27 | if (depth >= targetDepth) { 28 | if (direction === 'opened') { 29 | if (depth === targetDepth) nextElement.style.display = ''; 30 | } else if (direction === 'closed') { 31 | nextElement.style.display = 'none'; 32 | var innerButton = nextElement.querySelector('.toggle'); 33 | if (innerButton && innerButton.classList.contains('opened')) { 34 | innerButton.classList.remove('opened'); 35 | innerButton.classList.add('closed'); 36 | } 37 | } 38 | } else { 39 | break; 40 | } 41 | nextElement = nextElement.nextElementSibling; 42 | } 43 | } 44 | 45 | var buttons = document.querySelectorAll('.test-summary tr.test-interface .toggle'); 46 | for (var i = 0; i < buttons.length; i++) { 47 | buttons[i].addEventListener('click', toggle); 48 | } 49 | 50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]'); 51 | for (var i = 0; i < topDescribes.length; i++) { 52 | topDescribes[i].style.display = ''; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /docs/test-file/test/getters/allFilesWithCategory.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/getters/allFilesWithCategory.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 65 | 66 |

    test/getters/allFilesWithCategory.js

    67 |
    import test from 'ava';
     68 | import TorrentLibrary from '../../index';
     69 | import { files, folders } from '../_constants';
     70 | 
     71 | 
     72 | // TESTS
     73 | /** @test {TorrentLibrary#allFilesWithCategory} */
     74 | test('Should correctly detect the category of each file', async (t) => {
     75 |   let libInstance = new TorrentLibrary();
     76 |   await t.notThrows(libInstance.addNewPath(...folders));
     77 |   await t.notThrows(libInstance.scan());
     78 |   t.deepEqual(
     79 |     new Map([
     80 |       [files[2], TorrentLibrary.MOVIES_TYPE],
     81 |       [files[0], TorrentLibrary.TV_SERIES_TYPE],
     82 |       [files[1], TorrentLibrary.TV_SERIES_TYPE],
     83 |     ]),
     84 |     libInstance.allFilesWithCategory,
     85 |     'Not the same',
     86 |   );
     87 | });
     88 | 
    89 | 90 |
    91 | 92 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /docs/test-file/test/getters/allMovies.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/getters/allMovies.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 65 | 66 |

    test/getters/allMovies.js

    67 |
    import test from 'ava';
     68 | import path from 'path';
     69 | import { parse as nameParser } from 'parse-torrent-title';
     70 | import TorrentLibrary from '../../index';
     71 | import { files, folders } from '../_constants';
     72 | 
     73 | // TESTS
     74 | /** @test {TorrentLibrary#allMovies} */
     75 | test('Returns the movies', async (t) => {
     76 |   let libInstance = new TorrentLibrary();
     77 |   await t.notThrows(libInstance.addNewPath(...folders));
     78 |   await t.notThrows(libInstance.scan());
     79 |   t.deepEqual(
     80 |     new Set([
     81 |       Object.assign(
     82 |         nameParser(path.basename(files[2])),
     83 |         { filePath: files[2] },
     84 |       ),
     85 |     ]),
     86 |     libInstance.allMovies,
     87 |     'Not the same',
     88 |   );
     89 | });
     90 | 
     91 | 
    92 | 93 |
    94 | 95 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /docs/test-file/test/getters/allTvSeries.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/getters/allTvSeries.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 65 | 66 |

    test/getters/allTvSeries.js

    67 |
    import test from 'ava';
     68 | import path from 'path';
     69 | import { parse as nameParser } from 'parse-torrent-title';
     70 | import TorrentLibrary from '../../index';
     71 | import { files, folders } from '../_constants';
     72 | 
     73 | // TESTS
     74 | /** @test {TorrentLibrary#allTvSeries} */
     75 | test('Returns the tv-shows', async (t) => {
     76 |   let libInstance = new TorrentLibrary();
     77 |   await t.notThrows(libInstance.addNewPath(...folders));
     78 |   await t.notThrows(libInstance.scan());
     79 |   t.deepEqual(
     80 |     new Map([
     81 |       [nameParser(path.basename(files[0])).title, new Set([
     82 |         Object.assign(
     83 |           nameParser(path.basename(files[0])),
     84 |           { filePath: files[0] },
     85 |         ),
     86 |         Object.assign(
     87 |           nameParser(path.basename(files[1])),
     88 |           { filePath: files[1] },
     89 |         ),
     90 |       ])],
     91 |     ]),
     92 |     libInstance.allTvSeries,
     93 |     'Not the same',
     94 |   );
     95 | });
     96 | 
     97 | 
    98 | 99 |
    100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/test-file/test/getters/constants.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/getters/constants.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 65 | 66 |

    test/getters/constants.js

    67 |
    import test from 'ava';
     68 | import videosExtension from 'video-extensions';
     69 | import TorrentLibrary from '../../index';
     70 | 
     71 | /** @test {TorrentLibrary.MOVIES_TYPE} */
     72 | test('Constant MOVIES_TYPE', (t) => {
     73 |   t.is(
     74 |     TorrentLibrary.MOVIES_TYPE,
     75 |     'MOVIES',
     76 |     'Someone changed this constant value !',
     77 |   );
     78 | });
     79 | 
     80 | /** @test {TorrentLibrary.TV_SERIES_TYPE} */
     81 | test('Constant TV_SERIES', (t) => {
     82 |   t.is(
     83 |     TorrentLibrary.TV_SERIES_TYPE,
     84 |     'TV_SERIES',
     85 |     'Someone changed this constant value !',
     86 |   );
     87 | });
     88 | 
     89 | /** @test {TorrentLibrary.listVideosExtension} */
     90 | test('List of videos extension', (t) => {
     91 |   t.is(
     92 |     TorrentLibrary.listVideosExtension(),
     93 |     videosExtension,
     94 |     'Someone changed this constant value !',
     95 |   );
     96 | });
     97 | 
    98 | 99 |
    100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/test-file/test/methods/addNewPath.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/methods/addNewPath.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 65 | 66 |

    test/methods/addNewPath.js

    67 |
    import test from 'ava';
     68 | import path from 'path';
     69 | import * as sinon from 'sinon';
     70 | import TorrentLibrary from '../../index';
     71 | import { folders } from '../_constants';
     72 | 
     73 | // TESTS
     74 | /** @test {TorrentLibrary#addNewPath} */
     75 | test('missing parameter', async (t) => {
     76 |   let eventSpy = sinon.spy();
     77 |   let libInstance = new TorrentLibrary();
     78 |   libInstance.on('missing_parameter', eventSpy);
     79 |   await t.throws(libInstance.addNewPath());
     80 |   t.truthy(eventSpy.called, 'Event did not fire.');
     81 |   t.truthy(eventSpy.calledOnce, 'Event fired more than once');
     82 |   t.is(
     83 |     libInstance.hasPathsProvidedByUser(),
     84 |     false, 'No paths by user should be added',
     85 |   );
     86 | });
     87 | 
     88 | /** @test {TorrentLibrary#addNewPath} */
     89 | test('Not an existent path', async (t) => {
     90 |   let eventSpy = sinon.spy();
     91 |   let libInstance = new TorrentLibrary();
     92 | 
     93 |   libInstance.on('error_in_function', eventSpy);
     94 |   await t.throws(libInstance.addNewPath(path.join(__dirname, 'wrongPath')));
     95 |   t.truthy(eventSpy.called, 'Event did not fire.');
     96 |   t.truthy(eventSpy.calledOnce, 'Event fired more than once');
     97 |   t.is(
     98 |     libInstance.hasPathsProvidedByUser(),
     99 |     false, 'No paths by user should be added',
    100 |   );
    101 | });
    102 | 
    103 | /** @test {TorrentLibrary#addNewPath} */
    104 | test('existent paths', async (t) => {
    105 |   let eventSpy = sinon.spy();
    106 |   let libInstance = new TorrentLibrary();
    107 |   libInstance.on('addNewPath', eventSpy);
    108 |   await t.notThrows(libInstance.addNewPath(...folders));
    109 |   t.truthy(eventSpy.called, 'Event did not fire.');
    110 |   t.truthy(eventSpy.calledOnce, 'Event fired more than once');
    111 |   t.is(
    112 |     libInstance.hasPathsProvidedByUser(),
    113 |     true, 'The path should be added',
    114 |   );
    115 | });
    116 | 
    117 | 
    118 | 119 |
    120 | 121 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /docs/test-file/test/methods/createFromJSON.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/methods/createFromJSON.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 65 | 66 |

    test/methods/createFromJSON.js

    67 |
    import test from 'ava';
     68 | import TorrentLibrary from '../../index';
     69 | import { folders } from '../_constants';
     70 | 
     71 | /** @test {TorrentLibrary.createFromJSON} */
     72 | test('create a perfect copy of instance', async (t) => {
     73 |   let libInstance = new TorrentLibrary();
     74 |   await t.notThrows(libInstance.addNewPath(...folders));
     75 |   await t.notThrows(libInstance.scan());
     76 |   const jsonFromLib = JSON.parse(libInstance.toJSON());
     77 |   const createdInstance = TorrentLibrary.createFromJSON(jsonFromLib);
     78 |   t.deepEqual(
     79 |     createdInstance.allFilesWithCategory,
     80 |     libInstance.allFilesWithCategory,
     81 |     'allFilesWithCategory different',
     82 |   );
     83 |   t.deepEqual(
     84 |     createdInstance.allMovies,
     85 |     libInstance.allMovies, 'allMovies different',
     86 |   );
     87 |   t.deepEqual(
     88 |     createdInstance.allTvSeries,
     89 |     libInstance.allTvSeries, 'allTvSeries different',
     90 |   );
     91 | });
     92 | 
     93 | // dummy test for ES6 code coverage
     94 | /** @test {TorrentLibrary.createFromJSON} */
     95 | test('empty instance(s)', async (t) => {
     96 |   TorrentLibrary.createFromJSON({});
     97 |   t.pass();
     98 | });
     99 | 
    100 | 101 |
    102 | 103 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/test-file/test/methods/scan.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/methods/scan.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 65 | 66 |

    test/methods/scan.js

    67 |
    import test from 'ava';
     68 | import * as sinon from 'sinon';
     69 | import { parse as nameParser } from 'parse-torrent-title';
     70 | import TorrentLibrary from '../../index';
     71 | import { folders } from '../_constants';
     72 | 
     73 | // TESTS
     74 | /** @test {TorrentLibrary#scan} */
     75 | test('Scan without user provided paths', async (t) => {
     76 |   let eventSpy = sinon.spy();
     77 |   let libInstance = new TorrentLibrary();
     78 |   libInstance.on('scan', eventSpy);
     79 |   await t.notThrows(libInstance.scan());
     80 |   t.truthy(eventSpy.called, 'Event did not fire.');
     81 |   t.truthy(eventSpy.calledOnce, 'Event fired more than once');
     82 | });
     83 | 
     84 | /** @test {TorrentLibrary#scan} */
     85 | test('Scan with user provided paths', async (t) => {
     86 |   let eventSpy = sinon.spy();
     87 |   let libInstance = new TorrentLibrary();
     88 |   libInstance.on('scan', eventSpy);
     89 |   // whatever path that should exists
     90 |   await t.notThrows(libInstance.addNewPath(...folders));
     91 |   await t.notThrows(libInstance.scan());
     92 |   t.truthy(eventSpy.called, 'Event did not fire.');
     93 |   t.truthy(eventSpy.calledOnce, 'Event fired more than once');
     94 | });
     95 | 
     96 | // test to handle default parameters
     97 | /** @test {TorrentLibrary#scan} */
     98 | test('Scan with user provided paths and custom parser', async (t) => {
     99 |   let eventSpy = sinon.spy();
    100 |   let libInstance = new TorrentLibrary({}, nameParser);
    101 |   libInstance.on('scan', eventSpy);
    102 |   // whatever path that should exists
    103 |   await t.notThrows(libInstance.addNewPath(...folders));
    104 |   await t.notThrows(libInstance.scan());
    105 |   t.truthy(eventSpy.called, 'Event did not fire.');
    106 |   t.truthy(eventSpy.calledOnce, 'Event fired more than once');
    107 | });
    108 | 
    109 | /** @test {TorrentLibrary#scan} */
    110 | test('Scan with user provided paths and wrong custom parser', async (t) => {
    111 |   let eventSpy = sinon.spy();
    112 |   let libInstance = new TorrentLibrary({}, {});
    113 |   libInstance.on('error_in_function', eventSpy);
    114 |   // whatever path that should exists
    115 |   await t.notThrows(libInstance.addNewPath(...folders));
    116 |   await t.throws(libInstance.scan());
    117 |   t.truthy(eventSpy.called, 'Event did not fire.');
    118 |   t.truthy(eventSpy.calledOnce, 'Event fired more than once');
    119 | });
    120 | 
    121 | 122 |
    123 | 124 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /docs/test-file/test/methods/toJSON.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | test/methods/toJSON.js | torrent-files-library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | Manual 17 | Reference 18 | Source 19 | Test 20 | 27 |
    28 | 29 | 65 | 66 |

    test/methods/toJSON.js

    67 |
    import test from 'ava';
     68 | import TorrentLibrary from '../../index';
     69 | import { expectedJson, folders } from '../_constants';
     70 | 
     71 | /** @test {TorrentLibrary#toJSON} */
     72 | test('return a valid stringified JSON', async (t) => {
     73 |   const expectedJsonString = JSON.stringify(expectedJson);
     74 |   let libInstance = new TorrentLibrary();
     75 |   await t.notThrows(libInstance.addNewPath(...folders));
     76 |   await t.notThrows(libInstance.scan());
     77 |   const dataFromInstance = libInstance.toJSON();
     78 |   t.deepEqual(
     79 |     JSON.stringify(JSON.parse(dataFromInstance)),
     80 |     expectedJsonString,
     81 |     'Not the same JSON',
     82 |   );
     83 | });
     84 | 
    85 | 86 |
    87 | 88 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/TorrentLibrary').default; 2 | -------------------------------------------------------------------------------- /manual/assets/fileMapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/fileMapping.png -------------------------------------------------------------------------------- /manual/assets/filterMovies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/filterMovies.png -------------------------------------------------------------------------------- /manual/assets/filterTvSeries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/filterTvSeries.png -------------------------------------------------------------------------------- /manual/assets/foundMovies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/foundMovies.png -------------------------------------------------------------------------------- /manual/assets/foundTvSeries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/manual/assets/foundTvSeries.png -------------------------------------------------------------------------------- /manual/examples/createPlaylist.md: -------------------------------------------------------------------------------- 1 | # Create custom playlist(s) 2 | 3 | ```js 4 | const TorrentLibrary = require("torrent-files-library"); 5 | const m3u = require('m3u'); // a basic playlist writer (in m3u format) 6 | 7 | let paths = [ 8 | 'D:/DDL/SERIES TV/Le juge et le pilote', 9 | 'D:/DDL/ANIME', 10 | ]; 11 | 12 | // create an instance 13 | let libInstance = new TorrentLibrary(); 14 | 15 | // add these paths inside this lib 16 | libInstance 17 | .addNewPath(...paths) 18 | .then(() => libInstance.scan()) 19 | .then(() => { 20 | console.log('Now time to search all episodes <=5 in season 1 of Hardcastle And McCormick / Assassination Classroom , in one of following container avi/mp4'); 21 | let filteredMap = libInstance.filterTvSeries({ 22 | title: ['Hardcastle And McCormick', 'Assassination Classroom'], 23 | season: 1, 24 | episode: '<=5', 25 | container: ['avi', 'mp4'], 26 | }); 27 | var writer = m3u.writer(); 28 | for (let [foundTvShow, episodeSet] of filteredMap.entries()) { 29 | let foundSeasons = new Set([...episodeSet].map(episode => episode.season)); 30 | for (let seasonNumber of foundSeasons) { 31 | writer.comment(`${foundTvShow} - Season ${seasonNumber}`); 32 | let seasonEpisodes = [...episodeSet].filter(episode => episode.season === seasonNumber); 33 | seasonEpisodes.forEach(episode => writer.file(`${episode.filePath}`)); 34 | } 35 | } 36 | let m3uAsString = writer.toString(); 37 | // save this result into a *.m3u file , using fs.writeFile or whatever you want to do that 38 | // ... 39 | }) 40 | .catch((err) => { 41 | console.log(err.message); 42 | }); 43 | ``` 44 | 45 | ```m3u 46 | # Assassination Classroom - Season 1 47 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E01.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4 48 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E02.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4 49 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E03.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4 50 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E04.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4 51 | D:\DDL\ANIME\Assassination.Classroom.S01.FRENCH.720p.WEB-DL.x264-GODSPACE\Assassination.Classroom.S01E05.FRENCH.720p.WEB-DL.x264-GODSPACE.mp4 52 | # Hardcastle And McCormick - Season 1 53 | D:\DDL\SERIES TV\Le juge et le pilote\Saison 1\Hardcastle.And.McCormick.1x01.Le.Monstre.D_acier.avi 54 | D:\DDL\SERIES TV\Le juge et le pilote\Saison 1\Hardcastle.And.McCormick.1x03.Le.Canard.De.Cristal.avi 55 | D:\DDL\SERIES TV\Le juge et le pilote\Saison 1\Hardcastle.And.McCormick.1x04.Je.Ne.Sais.Pas.Ou.Je.Vais.Mais.J.y.Vais..avi 56 | D:\DDL\SERIES TV\Le juge et le pilote\Saison 1\Hardcastle.And.McCormick.1x05.La.Veuve.Noire..avi 57 | ``` -------------------------------------------------------------------------------- /manual/examples/filterMoviesByParameters.md: -------------------------------------------------------------------------------- 1 | # Filter movies by parameters 2 | 3 | ```js 4 | const TorrentLibrary = require("torrent-files-library"); 5 | 6 | let paths = [ 7 | "D:/", 8 | ]; 9 | 10 | // create an instance 11 | let libInstance = new TorrentLibrary(); 12 | 13 | // add these paths inside this lib 14 | libInstance 15 | .addNewPath(...paths) 16 | .then( () => { 17 | return libInstance.scan(); 18 | }) 19 | .then( () => { 20 | console.log('Now time to search all remastered movies with year >= 2012 in one of following container avi/mp4'); 21 | let filteredSet = libInstance.filterMovies({ 22 | year: '>=2012', 23 | remastered: true, 24 | container: ['avi', 'mp4'], 25 | }); 26 | for (let movie of filteredSet) { 27 | console.log(`${movie.title + ((movie.year) ? ` - ${movie.year}` : '')} at ${movie.filePath}`); 28 | } 29 | }) 30 | .catch( (err) => { 31 | console.log(err.message); 32 | }); 33 | ``` 34 | 35 | ![filterMovies](asset/filterMovies.png) -------------------------------------------------------------------------------- /manual/examples/filterTvSeriesByParameters.md: -------------------------------------------------------------------------------- 1 | # Filter tv series by parameters 2 | 3 | ```js 4 | const TorrentLibrary = require("torrent-files-library"); 5 | 6 | let paths = [ 7 | 'D:/DDL/SERIES TV/Le juge et le pilote', 8 | 'D:/DDL/ANIME', 9 | ]; 10 | 11 | // create an instance 12 | let libInstance = new TorrentLibrary(); 13 | 14 | // add these paths inside this lib 15 | libInstance 16 | .addNewPath(...paths) 17 | .then(() => libInstance.scan()) 18 | .then(() => { 19 | console.log('Now time to search all episodes <=5 in season 1 of Hardcastle And McCormick / Assassination Classroom , in one of following container avi/mp4'); 20 | let filteredMap = libInstance.filterTvSeries({ 21 | title: ['Hardcastle And McCormick', 'Assassination Classroom'], 22 | season: 1, 23 | episode: '<=5', 24 | container: ['avi', 'mp4'], 25 | }); 26 | for (let [foundTvShow, episodeSet] of filteredMap.entries()) { 27 | console.log(`\n${foundTvShow}`); 28 | console.log('\t Total found episodes : ', episodeSet.size); 29 | let foundSeasons = new Set([...episodeSet].map(episode => episode.season)); 30 | console.log('\t Found season(s) count : ', foundSeasons.size); 31 | for (let seasonNumber of foundSeasons) { 32 | console.log('\t\t Season %d', seasonNumber); 33 | let seasonEpisodes = [...episodeSet].filter(episode => episode.season === seasonNumber); 34 | console.log(`\t\t\t Season count : ${seasonEpisodes.length}`); 35 | console.log('\t\t\t Files : '); 36 | seasonEpisodes.forEach(episode => console.log(`\t\t\t ${episode.filePath}`)); 37 | } 38 | } 39 | }) 40 | .catch((err) => { 41 | console.log(err.message); 42 | }); 43 | ``` 44 | 45 | ![filterTvSeries](asset/filterTvSeries.png) -------------------------------------------------------------------------------- /manual/examples/getCategoryForEachFile.md: -------------------------------------------------------------------------------- 1 | # Get category for each file 2 | 3 | ```js 4 | const TorrentLibrary = require("torrent-files-library"); 5 | 6 | let paths = [ 7 | "D:\\DDL\\FILMS", // a path where I can find both movies and tv-series 8 | "D:\\DDL\\SERIES TV\\Le juge et le pilote" // a path where I can find episodes of a tv-serie 9 | ]; 10 | 11 | // create an instance 12 | let libInstance = new TorrentLibrary(); 13 | 14 | // add these paths inside this lib 15 | libInstance 16 | .addNewPath(...paths) 17 | .then( (message) => { 18 | console.log(message); 19 | return libInstance.scan(); 20 | }) 21 | .then( (otherMessage) => { 22 | console.log(otherMessage); 23 | 24 | setTimeout(function(){ 25 | // display the found files and their category 26 | libInstance 27 | .allFilesWithCategory 28 | .forEach(function(value,key){ 29 | console.log(key + " : " + value); 30 | }); 31 | }, 1000); 32 | }) 33 | .catch( (err) => { 34 | console.log(err.message); 35 | }); 36 | ``` 37 | ![fileMappingDemo](asset/fileMapping.png) -------------------------------------------------------------------------------- /manual/examples/listEachTvSerie.md: -------------------------------------------------------------------------------- 1 | # List each found tv serie 2 | 3 | ```js 4 | const TorrentLibrary = require("torrent-files-library"); 5 | 6 | let paths = [ 7 | "D:/DDL/FILMS", // a path where I can find both movies and tv-series 8 | "D:\\DDL\\SERIES TV\\Le juge et le pilote" // a path where I can find episodes of a tv-serie 9 | ]; 10 | 11 | // create an instance 12 | let libInstance = new TorrentLibrary(); 13 | 14 | // add these paths inside this lib 15 | libInstance 16 | .addNewPath(...paths) 17 | .then( (message) => { 18 | console.log(message); 19 | return libInstance.scan(); 20 | }) 21 | .then( (otherMessage) => { 22 | console.log(otherMessage); 23 | console.log("I found these tv-series :"); 24 | let mapSeries = libInstance.allTvSeries; 25 | 26 | for (let [foundTvShow,episodeSet] of mapSeries.entries() ) { 27 | console.log("\n"+foundTvShow); 28 | console.log("\t Total found episodes : ", episodeSet.size); 29 | let foundSeasons = new Set([...episodeSet].map( episode => episode.season)); 30 | console.log("\t Found season(s) count : ", foundSeasons.size); 31 | for (let seasonNumber of foundSeasons){ 32 | console.log("\t\t Season %d", seasonNumber); 33 | let seasonEpisodes = [...episodeSet].filter(episode => episode.season === seasonNumber); 34 | console.log("\t\t\t Season count : " + seasonEpisodes.length); 35 | console.log("\t\t\t Files : "); 36 | seasonEpisodes.forEach( episode => console.log("\t\t\t " + episode.filePath)); 37 | } 38 | } 39 | 40 | }) 41 | .catch( (err) => { 42 | console.log(err.message); 43 | }); 44 | ``` 45 | 46 | ![foundTvSeries](asset/foundTvSeries.png) -------------------------------------------------------------------------------- /manual/examples/listFoundMovies.md: -------------------------------------------------------------------------------- 1 | # List found movies 2 | 3 | ```js 4 | const TorrentLibrary = require("torrent-files-library"); 5 | 6 | let paths = [ 7 | "D:\\DDL\\FILMS", // a path where I can find both movies and tv-series 8 | "D:\\DDL\\SERIES TV\\Le juge et le pilote" // a path where I can find episodes of a tv-serie 9 | ]; 10 | 11 | // create an instance 12 | let libInstance = new TorrentLibrary(); 13 | 14 | // add these paths inside this lib 15 | libInstance 16 | .addNewPath(...paths) 17 | .then( (message) => { 18 | console.log(message); 19 | return libInstance.scan(); 20 | }) 21 | .then( (otherMessage) => { 22 | console.log(otherMessage); 23 | console.log("I found these movie(s) : "); 24 | setTimeout(function(){ 25 | // display the found movie(s) 26 | for (let movie of libInstance.allMovies) { 27 | console.log(movie.title + ((movie.year) ? " - " + movie.year : "") + " at " + movie.filePath ); 28 | } 29 | }, 1000); 30 | }) 31 | .catch( (err) => { 32 | console.log(err.message); 33 | }); 34 | ``` 35 | ![foundMovies](asset/foundMovies.png) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "torrent-files-library", 3 | "version": "1.5.1", 4 | "description": "Scan directories to build a library of media files (movie or tv show) that follows torrent naming conventions", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js", 8 | "lib/" 9 | ], 10 | "scripts": { 11 | "semantic-release": "semantic-release -e ./config/release.config.js", 12 | "compile": "babel -d lib/ src/ --source-maps both", 13 | "prepare": "cross-env BABEL_ENV=production npm run compile", 14 | "test": "cross-env BABEL_ENV=test npm run compile && nyc ava", 15 | "coverage": "nyc report --reporter=text-lcov | coveralls", 16 | "coverage-html": "nyc report --reporter=html", 17 | "esdoc": "node_modules/.bin/esdoc", 18 | "lint": "./node_modules/.bin/eslint -c config/.eslintrc.js ./src ./test --fix" 19 | }, 20 | "engines": { 21 | "node": ">=6", 22 | "npm": ">=5" 23 | }, 24 | "keywords": [ 25 | "torrent", 26 | "library", 27 | "parser", 28 | "torrent file", 29 | "torrent files", 30 | "parse torrent", 31 | "parse torrent file", 32 | "parse torrent files", 33 | "parse torrent name", 34 | "torrent library" 35 | ], 36 | "author": "jy95", 37 | "license": "MIT", 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/jy95/torrent-files-library.git" 41 | }, 42 | "greenkeeper": { 43 | "commitMessages": { 44 | "initialBadge": "Docs: Add Greenkeeper badge", 45 | "initialDependencies": "Upgrade: Update dependencies", 46 | "initialBranches": "Build: Whitelist greenkeeper branches", 47 | "dependencyUpdate": "Upgrade: Update ${dependency} to version ${version}", 48 | "devDependencyUpdate": "Upgrade: Update ${dependency} to version ${version}", 49 | "dependencyPin": "Fix: Pin ${dependency} to ${oldVersion}", 50 | "devDependencyPin": "Fix: Pin ${dependency} to ${oldVersion}" 51 | } 52 | }, 53 | "devDependencies": { 54 | "@babel/cli": "^7.1.2", 55 | "@babel/core": "^7.1.2", 56 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0", 57 | "@babel/preset-env": "^7.1.0", 58 | "@babel/register": "^7.0.0", 59 | "@semantic-release/changelog": "^3.0.1", 60 | "@semantic-release/git": "^7.0.3", 61 | "ava": "^0.25.0", 62 | "babel-plugin-istanbul": "^5.1.1", 63 | "babel-preset-minify": "^0.5.0", 64 | "conventional-changelog-eslint": "^3.0.0", 65 | "coveralls": "^3.0.0", 66 | "cross-env": "^6.0.0", 67 | "esdoc": "^1.0.4", 68 | "esdoc-ecmascript-proposal-plugin": "^1.0.0", 69 | "esdoc-standard-plugin": "^1.0.0", 70 | "eslint": "^6.0.0", 71 | "eslint-config-airbnb-base": "^14.0.0", 72 | "eslint-plugin-import": "^2.8.0", 73 | "nyc": "^14.0.0", 74 | "semantic-release": "^15.12.1", 75 | "sinon": "^7.2.4" 76 | }, 77 | "dependencies": { 78 | "bluebird": "^3.5.1", 79 | "filehound": "^1.17.3", 80 | "lodash": "^4.17.11", 81 | "parse-torrent-title": "^1.1.0", 82 | "video-extensions": "^1.1.0" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/docs/events/events.js: -------------------------------------------------------------------------------- 1 | // events 2 | 3 | /** 4 | * missing_parameter event 5 | * @typedef {Object} Events#missing_parameter 6 | * @property {string} functionName - Indicates the function name when the error occurs. 7 | * @example 8 | * TorrentLibraryInstance.on('missing_parameter',function(callback){ 9 | * console.log('Parameter is missing in ' + callback.functionName); 10 | * }) 11 | */ 12 | 13 | /** 14 | * error_in_function event 15 | * @typedef {Object} Events#error_in_function 16 | * @property {string} functionName - Indicates the function name when the error occurs. 17 | * @property {string} error - The error message got by error.message 18 | * @example 19 | * TorrentLibraryInstance.on('error_in_function',function(callback){ 20 | * console.log('Function ' + callback.functionName + ' has the following error : ' + callback.error); 21 | * }) 22 | */ 23 | 24 | /** 25 | * addNewPath event 26 | * @typedef {Object} Events#addNewPath 27 | * @property {...string} paths - all the paths that were added 28 | * @example 29 | * TorrentLibraryInstance.on('addNewPath',function(callback){ 30 | * console.log('The following files were added : ' + callback.paths); 31 | * }) 32 | */ 33 | 34 | /** 35 | * scan event 36 | * @typedef {object} Events#scan 37 | * @property {...string} files - all the files that were found and added if not yet in lib 38 | * @example 39 | * TorrentLibraryInstance.on('scan',function(callback){ 40 | * console.log('The following files were found : ' + callback.files); 41 | * }) 42 | */ 43 | 44 | /** 45 | * removeOldFiles event 46 | * @typedef {object} Events#removeOldFiles 47 | * @property {...string} files - all the files that were found and removed if not yet in lib 48 | * @example 49 | * TorrentLibraryInstance.on('removeOldFiles',function(callback){ 50 | * console.log('The following files were added : ' + callback.files); 51 | * }) 52 | */ 53 | -------------------------------------------------------------------------------- /src/docs/types/types.js: -------------------------------------------------------------------------------- 1 | // ESDoc custom typedef 2 | 3 | /** 4 | * The result of parsing file name 5 | * @typedef {Object} TPN 6 | * @see {@link https://github.com/clement-escolano/parse-torrent-title} 7 | * @property {(string)} title - The file title 8 | * @property {(number)} [season] - The season number 9 | * @property {(number)} [episode] - The episode number 10 | * @property {(number)} [year] - The year 11 | * @property {(string)} [resolution] - The resolution 12 | * @property {(string)} [codec] - The codec 13 | * @property {(string)} [audio] - The audio 14 | * @property {(string)} [group] - The group that releases this file 15 | * @property {(string)} [region] - The region 16 | * @property {(string)} [container] - The container 17 | * @property {(string)} [language] - The file language 18 | * @property {(boolean)} [extended] - extended ? 19 | * @property {(boolean)} [unrated] - unrated ? 20 | * @property {(boolean)} [proper] - proper ? 21 | * @property {(boolean)} [repack] - repack ? 22 | * @property {(boolean)} [convert] - convert ? 23 | * @property {(boolean)} [hardcoded] - hardcoded ? 24 | * @property {(boolean)} [retail] - retail ? 25 | * @property {(boolean)} [remastered] - remastered ? 26 | * @property {(string)} [source] - the source 27 | * @example 28 | * { 29 | * "container":"avi", 30 | * "source":"webrip", 31 | * "codec":"xvid", 32 | * "season":4, 33 | * "episode":14, 34 | * "language":"french", 35 | * "title":"The Blacklist" 36 | * } 37 | */ 38 | 39 | /** 40 | * The extended TPN object 41 | * @typedef {TPN} TPN_Extended 42 | * @property {string} filePath - additionnal property useful for this library 43 | * @example 44 | * { 45 | * "container":"avi", 46 | * "source":"webrip", 47 | * "codec":"xvid", 48 | * "season":4, 49 | * "episode":14, 50 | * "language":"french", 51 | * "title":"The Blacklist", 52 | * "filePath":"D:\\workspaceNodeJs\\torrent-files-library\\test\\folder2\\The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi" 53 | * } 54 | */ 55 | 56 | /** 57 | * The sub way to store all kind of media files found in paths 58 | * @typedef {Set| Map} StorageVar 59 | */ 60 | 61 | /** 62 | * The master variable where we store all kind of media files found in paths 63 | * @typedef {Map} StoreVar 64 | * @example 65 | * // An example of the variable after the scan method 66 | * [ 67 | * "MOVIES" : [ 68 | * { 69 | * "year": 2014, 70 | * "resolution": '1080p', 71 | * "source": 'brrip', 72 | * "codec": 'x264', 73 | * "container": 'mkv', 74 | * "title": 'Captain Russia The Summer Soldier', 75 | * "filePath": "D:\somePath\Captain Russia The Summer Soldier (2014) 1080p BrRip x264.MKV" 76 | * } 77 | * ], 78 | * "TV_SERIES" : [ 79 | * "The Blacklist" : [ 80 | * { 81 | * "season": 4, 82 | * "episode": 21, 83 | * "source": "webrip", 84 | * "codec": "xvid", 85 | * "container": "avi", 86 | * "language": "french" 87 | * "filePath" : "D:\somePath\The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi" 88 | * } 89 | * ] 90 | * ] 91 | * ] 92 | */ 93 | 94 | /** 95 | * The search syntax for number properties : a operator follows by a number 96 | * @typedef {string} numberSearchSyntax 97 | * @example 98 | * '<=25' 99 | * @example 100 | * '=25' 101 | */ 102 | 103 | /** 104 | * Number expression for number filtering 105 | * @typedef {Object} numberExpressionObject 106 | * @property {string} operator The operator for matching process 107 | * @property {number} number The extracted number for matching process 108 | * @example 109 | * { operator: '>=' , number: 5 } 110 | */ 111 | 112 | /** 113 | * allows user to provide custom filtering stuff 114 | * @typedef {Object} additionalProperties 115 | * @property {string} type Filter type - Possible values are 'number' , 'string' , 'number' 116 | * @property {string} name The requested property 117 | * @property {boolean|string|string[]|number|numberSearchSyntax} value The requested value 118 | * @example 119 | * { type: 'number', name: 'AnotherField2', value: '<=25' } 120 | * { type: 'boolean', name: 'AnotherField', value: true } 121 | * { type: 'number', name: 'AnotherField2', value: 25 } 122 | * { type: 'string', name: 'AnotherField', value: ['NothingExists', 'NothingExists'] } 123 | */ 124 | 125 | /** 126 | * search parameters object 127 | * @typedef {Object} searchParameters - search parameters. 128 | * @property {boolean} [extended=undefined] - extended ? 129 | * @property {boolean} [unrated=undefined] - unrated ? 130 | * @property {boolean} [proper=undefined] - proper ? 131 | * @property {boolean} [repack=undefined] - repack ? 132 | * @property {boolean} [convert=undefined] - convert ? 133 | * @property {boolean} [hardcoded=undefined] - hardcoded ? 134 | * @property {boolean} [retail=undefined] - retail ? 135 | * @property {boolean} [remastered=undefined] - remastered ? 136 | * @property {number|numberSearchSyntax} [season=undefined] - the season 137 | * @property {number|numberSearchSyntax} [episode=undefined] - the episode 138 | * @property {number|numberSearchSyntax} [year=undefined] - the year 139 | * @property {string|string[]} [title=undefined] - the title 140 | * @property {string|string[]} [resolution=undefined] - the resolution 141 | * @property {string|string[]} [codec=undefined] - the codec 142 | * @property {string|string[]} [audio=undefined] - the audio 143 | * @property {string|string[]} [group=undefined] - the group 144 | * @property {string|string[]} [region=undefined] - the region 145 | * @property {string|string[]} [container=undefined] - the container 146 | * @property {string|string[]} [language=undefined] - the language 147 | * @property {string|string[]} [source=undefined] - the source 148 | * @property {additionalProperties[]} [additionalProperties=[]] - additional Properties 149 | */ 150 | 151 | /** 152 | * A parsing function to be used with this lib 153 | * @typedef {Function} customParsingFunction 154 | * @param {string} title - The file name 155 | * @return {TPN} the result 156 | * @example 157 | * // default parser used in this lib 158 | * const parser = require("parse-torrent-title").parser; 159 | * @example 160 | * // extended default parser as explained here : https://github.com/clement-escolano/parse-torrent-title#advanced-usage 161 | * const ptt = require("parse-torrent-title"); 162 | * ptt.addHandler("part", /Part[. ]([0-9])/i, { type: "integer" }); 163 | * const parser = ptt.parse; 164 | * @example 165 | * // original parser used in this lib : https://github.com/jy95/torrent-name-parser 166 | * const parser = require('torrent-name-parser'); 167 | */ 168 | -------------------------------------------------------------------------------- /src/filters/filterBooleanProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides a map with valid default properties 3 | * @param {searchParameters} searchObject - search parameters 4 | * @return {Map} the result map 5 | */ 6 | export function filterDefaultBooleanProperties(searchObject) { 7 | const { 8 | extended, unrated, proper, repack, convert, hardcoded, retail, remastered, 9 | } = searchObject; 10 | 11 | 12 | const propertiesArray = [extended, unrated, proper, 13 | repack, convert, hardcoded, retail, remastered]; 14 | const propertiesNames = ['extended', 'unrated', 'proper', 'repack', 'convert', 15 | 'hardcoded', 'retail', 'remastered']; 16 | 17 | return propertiesArray.reduce((propertiesMap, val, index) => { 18 | // eslint-disable-next-line max-len 19 | if (val === true || val === false) { propertiesMap.set(propertiesNames[index], val); } 20 | return propertiesMap; 21 | }, new Map()); 22 | } 23 | 24 | /** 25 | * Remove the default boolean properties 26 | * @param {searchParameters} searchObject - search parameters 27 | * @return {searchParameters} searchParameters without these properties 28 | */ 29 | export function excludeDefaultBooleanProperties(searchObject) { 30 | let { 31 | extended, unrated, proper, repack, convert, hardcoded, retail, remastered, 32 | ...rest 33 | } = searchObject; 34 | return rest; 35 | } 36 | 37 | /** 38 | * Filter the set based on boolean properties 39 | * @param {Set} set The TPN set 40 | * @param {Map} propertiesMap The map from filterDefaultBooleanProperties 41 | * @return {Set} the filtered set 42 | */ 43 | export function filterByBoolean(set, propertiesMap) { 44 | // first step : get an array so that we can do filter/reduce stuff 45 | // second step : iterate the propertiesMap and do filter and return the filtered array 46 | // val[0] : the key ; val[1] : the value 47 | return new Set(Array 48 | .from(propertiesMap.entries()) 49 | .reduce( 50 | // eslint-disable-next-line max-len 51 | (currentMoviesArray, val) => currentMoviesArray.filter(TPN => TPN[val[0]] === val[1]) 52 | , [...set], 53 | )); 54 | } 55 | -------------------------------------------------------------------------------- /src/filters/filterNumberProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert the param to valid expression object for filter function 3 | * @param {number|numberSearchSyntax} param The param to be converted 4 | * @return {Object} If valid, returns a object. If not, returns undefined 5 | * @property {string} operator The operator for matching process 6 | * @property {number} number The extracted number for matching process 7 | * @example 8 | * // returns { operator: '==' , number: 5 } 9 | * convertToValidExpression(5); 10 | * @example 11 | * // returns { operator: '>=' , number: 5 } 12 | * convertToValidExpression(">5"); 13 | * @example 14 | * // returns undefined 15 | * convertToValidExpression(undefined); 16 | */ 17 | export function convertToValidExpression(param) { 18 | const validExpression = /^(==|>|<|>=|<=)(\d+)$/; 19 | let returnValue; 20 | // if it is a valid number expression like the regex 21 | if (validExpression.test(param)) { 22 | let result = param.match(validExpression); 23 | returnValue = { 24 | operator: result[1], 25 | number: Number(result[2]), 26 | }; 27 | } 28 | // if the param is a number 29 | if (Number.isInteger(param)) { 30 | returnValue = { 31 | operator: '==', 32 | number: param, 33 | }; 34 | } 35 | return returnValue; 36 | } 37 | 38 | /** 39 | * Filter function for filterByNumber 40 | * @param {string} property The property to be checked 41 | * @param {Object} expressionObject The object from convertToValidExpression 42 | * @param {string} expressionObject.operator The operator for matching process 43 | * @param {number} expressionObject.number The extracted number for matching process 44 | * @param {TPN} object the object to be checked 45 | * @return {boolean} the result 46 | */ 47 | function resolveExpression(property, expressionObject, object) { 48 | let { operator, number } = expressionObject; 49 | // No : eval is not all evil but you should know what you are doing 50 | // eslint-disable-next-line no-eval 51 | return eval(`${object[property]}${operator}${number}`); 52 | } 53 | 54 | /** 55 | * Provides a map with valid default properties 56 | * @param {searchParameters} searchObject - search parameters 57 | * @return {Map} the result map 58 | */ 59 | export function filterDefaultNumberProperties(searchObject) { 60 | const { 61 | season, episode, year, 62 | } = searchObject; 63 | 64 | 65 | const propertiesArray = [season, episode, year]; 66 | const propertiesNames = ['season', 'episode', 'year']; 67 | 68 | return propertiesArray.reduce((propertiesMap, val, index) => { 69 | if (val !== undefined) { 70 | propertiesMap.set(propertiesNames[index], convertToValidExpression(val)); 71 | } 72 | return propertiesMap; 73 | }, new Map()); 74 | } 75 | 76 | /** 77 | * Remove the default number properties 78 | * @param {searchParameters} searchObject - search parameters 79 | * @return {searchParameters} searchParameters without these properties 80 | */ 81 | export function excludeDefaultNumberProperties(searchObject) { 82 | const { 83 | season, episode, year, 84 | ...rest 85 | } = searchObject; 86 | return rest; 87 | } 88 | 89 | /** 90 | * Filter the set based on string properties 91 | * @param {Set} set The TPN set 92 | * @param {Map} propertiesMap The map from filterDefaultStringProperties 93 | * @return {Set} the filtered set 94 | */ 95 | export function filterByNumber(set, propertiesMap) { 96 | // first step : get an array so that we can do filter/reduce stuff 97 | // second step : iterate the propertiesMap and do filter and return the filtered array 98 | // val[0] : the key ; val[1] : the value 99 | return new Set(Array 100 | .from(propertiesMap.entries()) 101 | .reduce( 102 | // eslint-disable-next-line max-len 103 | (currentMoviesArray, val) => currentMoviesArray.filter(TPN => resolveExpression(val[0], val[1], TPN)) 104 | , [...set], 105 | )); 106 | } 107 | -------------------------------------------------------------------------------- /src/filters/filterProperties.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-escape,max-len */ 2 | /** 3 | * Boolean properties filter 4 | */ 5 | import { 6 | filterDefaultBooleanProperties, 7 | filterByBoolean, 8 | excludeDefaultBooleanProperties, 9 | } from './filterBooleanProperty'; 10 | 11 | /** 12 | * Number properties filter 13 | */ 14 | import { 15 | convertToValidExpression, 16 | excludeDefaultNumberProperties, 17 | filterDefaultNumberProperties, 18 | filterByNumber, 19 | } from './filterNumberProperty'; 20 | 21 | /** 22 | * String properties filter 23 | */ 24 | import { 25 | excludeDefaultStringProperties, 26 | filterDefaultStringProperties, 27 | filterByString, 28 | } from './filterStringProperty'; 29 | 30 | /** 31 | * Handle searchParameters provided by user to maps 32 | * @param {searchParameters} searchParameters - search parameters. 33 | * @return {{booleanFieldsSearchMap: Map, numberFieldsSearchMap: Map, stringFieldsSearchMap: Map}} an object that contains mapped properties for search 34 | */ 35 | function mapProperties(searchParameters) { 36 | // organize search based on field type : boolean - string - number 37 | const booleanFieldsSearchMap = filterDefaultBooleanProperties(searchParameters); 38 | let leftSearchParameters = excludeDefaultBooleanProperties(searchParameters); 39 | 40 | const numberFieldsSearchMap = filterDefaultNumberProperties(leftSearchParameters); 41 | leftSearchParameters = excludeDefaultNumberProperties(leftSearchParameters); 42 | 43 | const stringFieldsSearchMap = filterDefaultStringProperties(leftSearchParameters); 44 | leftSearchParameters = excludeDefaultStringProperties(leftSearchParameters); 45 | 46 | let { additionalProperties } = leftSearchParameters; 47 | // add the optional new properties , optionally provided by user 48 | /* istanbul ignore else */ 49 | if (additionalProperties !== undefined) { 50 | additionalProperties 51 | .filter(newProperty => newProperty.type === 'boolean') 52 | .forEach((newProperty) => { 53 | booleanFieldsSearchMap.set(newProperty.name, newProperty.value); 54 | }); 55 | 56 | additionalProperties 57 | .filter(newProperty => newProperty.type === 'number') 58 | .forEach((newProperty) => { 59 | let expression = convertToValidExpression(newProperty.value); 60 | /* istanbul ignore else */ 61 | if (expression !== undefined) { 62 | numberFieldsSearchMap.set(newProperty.name, expression); 63 | } 64 | }); 65 | 66 | additionalProperties 67 | .filter(newProperty => newProperty.type === 'string') 68 | .forEach((newProperty) => { 69 | stringFieldsSearchMap.set(newProperty.name, [...newProperty.value]); 70 | }); 71 | } 72 | 73 | return { 74 | booleanFieldsSearchMap, 75 | numberFieldsSearchMap, 76 | stringFieldsSearchMap, 77 | }; 78 | } 79 | 80 | /** 81 | * Filter the movies based on search parameters 82 | * @param {searchParameters} searchParameters - search parameters. 83 | * @param {Set} allMovies - the movies set 84 | * @return {Set} the filtered movie set 85 | */ 86 | export function filterMoviesByProperties(searchParameters, allMovies) { 87 | const { 88 | booleanFieldsSearchMap, stringFieldsSearchMap, 89 | numberFieldsSearchMap, 90 | } = mapProperties(searchParameters); 91 | const propertiesWithAllProperties 92 | = [booleanFieldsSearchMap, stringFieldsSearchMap, numberFieldsSearchMap]; 93 | let result = allMovies; 94 | [filterByBoolean, filterByString, filterByNumber] 95 | .forEach((filterFunction, index) => { 96 | result = filterFunction(result, propertiesWithAllProperties[index]); 97 | }); 98 | return result; 99 | } 100 | 101 | /** 102 | * Filter the tv series based on search parameters 103 | * @param {searchParameters} searchParameters - search parameters. 104 | * @param {(Map>)} allTvSeries - the tvSeries map 105 | * @return {(Map>)} the filtered tvSeries map 106 | */ 107 | export function filterTvSeriesByProperties(searchParameters, allTvSeries) { 108 | const { 109 | booleanFieldsSearchMap, stringFieldsSearchMap, 110 | numberFieldsSearchMap, 111 | } = mapProperties(searchParameters); 112 | const propertiesWithAllProperties 113 | = [booleanFieldsSearchMap, stringFieldsSearchMap, numberFieldsSearchMap]; 114 | let result = allTvSeries; 115 | // filtering stuff 116 | // it also removes all entries that have an empty Set so that we can clearly see only valid things 117 | 118 | [filterByBoolean, filterByString, filterByNumber] 119 | .forEach((filterFunction, index) => { 120 | result = new Map(Array 121 | .from( 122 | result.entries(), 123 | ([showName, showSet]) => [showName, filterFunction(showSet, propertiesWithAllProperties[index])], 124 | ) 125 | // eslint-disable-next-line no-unused-vars 126 | .filter(([showName, showSet]) => showSet.size > 0)); 127 | }); 128 | 129 | return result; 130 | } 131 | -------------------------------------------------------------------------------- /src/filters/filterStringProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides a map with valid default properties 3 | * @param {searchParameters} searchObject - search parameters 4 | * @return {Map} the result map 5 | */ 6 | export function filterDefaultStringProperties(searchObject) { 7 | const { 8 | title, resolution, codec, audio, group, region, container, language, source, 9 | } = searchObject; 10 | 11 | 12 | const propertiesArray = [title, resolution, codec, audio, group, 13 | region, container, language, source]; 14 | const propertiesNames = ['title', 'resolution', 'codec', 'audio', 'group', 15 | 'region', 'container', 'language', 'source']; 16 | 17 | return propertiesArray.reduce((propertiesMap, val, index) => { 18 | if (val !== undefined) { 19 | propertiesMap.set(propertiesNames[index], val); 20 | } 21 | return propertiesMap; 22 | }, new Map()); 23 | } 24 | 25 | /** 26 | * Remove the default string properties 27 | * @param {searchParameters} searchObject - search parameters 28 | * @return {searchParameters} searchParameters without these properties 29 | */ 30 | export function excludeDefaultStringProperties(searchObject) { 31 | let { 32 | title, resolution, codec, audio, group, region, container, language, source, 33 | ...rest 34 | } = searchObject; 35 | return rest; 36 | } 37 | 38 | /** 39 | * Filter function for filterByString 40 | * @param {string} property The property to be checked 41 | * @param {string[]|string} expected The expected result 42 | * @param {TPN} object the object to be checked 43 | * @return {boolean} the result 44 | */ 45 | function filterFunctionByType(property, expected, object) { 46 | if (Array.isArray(expected)) { return expected.includes(object[property]); } 47 | return object[property] === expected; 48 | } 49 | 50 | /** 51 | * Filter the set based on string properties 52 | * @param {Set} set The TPN set 53 | * @param {Map} propertiesMap The map from filterDefaultStringProperties 54 | * @return {Set} the filtered set 55 | */ 56 | export function filterByString(set, propertiesMap) { 57 | // first step : get an array so that we can do filter/reduce stuff 58 | // second step : iterate the propertiesMap and do filter and return the filtered array 59 | // val[0] : the key ; val[1] : the value 60 | return new Set(Array 61 | .from(propertiesMap.entries()) 62 | .reduce( 63 | // eslint-disable-next-line max-len 64 | (currentMoviesArray, val) => currentMoviesArray.filter(TPN => filterFunctionByType(val[0], val[1], TPN)) 65 | , [...set], 66 | )); 67 | } 68 | -------------------------------------------------------------------------------- /test/_constants.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { basename, join } from 'path'; 3 | import { parse as nameParser } from 'parse-torrent-title'; 4 | 5 | export const folders = [join(__dirname, 'fixtures', 'folder1'), 6 | join(__dirname, 'fixtures', '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 | 24 | // json for test 25 | export const expectedJson = { 26 | paths: [ 27 | ...folders, 28 | ], 29 | allFilesWithCategory: [ 30 | [ 31 | files[2], 32 | 'MOVIES', 33 | ], 34 | [ 35 | files[0], 36 | 'TV_SERIES', 37 | ], 38 | [ 39 | files[1], 40 | 'TV_SERIES', 41 | ], 42 | ], 43 | movies: [ 44 | Object.assign(nameParser(basename(files[2])), { 45 | filePath: files[2], 46 | }), 47 | ], 48 | 'tv-series': [ 49 | [ 50 | 'The Blacklist', 51 | [ 52 | Object.assign(nameParser(basename(files[0])), { 53 | filePath: files[0], 54 | }), 55 | Object.assign(nameParser(basename(files[1])), { 56 | filePath: files[1], 57 | }), 58 | ], 59 | ], 60 | ], 61 | }; 62 | 63 | -------------------------------------------------------------------------------- /test/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths":[ 3 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1", 4 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder2" 5 | ], 6 | "allFilesWithCategory":[ 7 | [ 8 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1\\Bad.Ass.2012.LiMiTED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi", 9 | "MOVIES" 10 | ], 11 | [ 12 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1\\The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi", 13 | "TV_SERIES" 14 | ], 15 | [ 16 | "D:\\workspaceNodeJs\\torrent-files-library\\test\\folder2\\The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi", 17 | "TV_SERIES" 18 | ] 19 | ], 20 | "movies":[ 21 | { 22 | "year":2012, 23 | "container":"avi", 24 | "source":"dvdrip", 25 | "codec":"xvid", 26 | "language":"truefrench", 27 | "title":"Bad Ass", 28 | "filePath":"D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1\\Bad.Ass.2012.LiMiTED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi" 29 | } 30 | ], 31 | "tv-series":[ 32 | [ 33 | "The Blacklist", 34 | [ 35 | { 36 | "container":"avi", 37 | "source":"webrip", 38 | "codec":"xvid", 39 | "season":4, 40 | "episode":21, 41 | "language":"french", 42 | "title":"The Blacklist", 43 | "filePath":"D:\\workspaceNodeJs\\torrent-files-library\\test\\folder1\\The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi" 44 | }, 45 | { 46 | "container":"avi", 47 | "source":"webrip", 48 | "codec":"xvid", 49 | "season":4, 50 | "episode":14, 51 | "language":"french", 52 | "title":"The Blacklist", 53 | "filePath":"D:\\workspaceNodeJs\\torrent-files-library\\test\\folder2\\The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi" 54 | } 55 | ] 56 | ] 57 | ] 58 | } -------------------------------------------------------------------------------- /test/fixtures/folder1/Bad.Ass.2012.REMASTERED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/test/fixtures/folder1/Bad.Ass.2012.REMASTERED.TRUEFRENCH.DVDRiP.XviD-www.zone-telechargement.ws.avi -------------------------------------------------------------------------------- /test/fixtures/folder1/The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/test/fixtures/folder1/The.Blacklist.S04E21.FRENCH.WEBRip.XviD.avi -------------------------------------------------------------------------------- /test/fixtures/folder2/The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jy95/torrent-files-library/9dd903d1886d6770b46d9eea2eaef79e55c81dac/test/fixtures/folder2/The.Blacklist.S04E14.FRENCH.WEBRip.XviD.avi -------------------------------------------------------------------------------- /test/getters/allFilesWithCategory.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import TorrentLibrary from '../../index'; 3 | import { files, folders } from '../_constants'; 4 | 5 | 6 | // TESTS 7 | /** @test {TorrentLibrary#allFilesWithCategory} */ 8 | test('Should correctly detect the category of each file', async (t) => { 9 | let libInstance = new TorrentLibrary(); 10 | await t.notThrows(libInstance.addNewPath(...folders)); 11 | await t.notThrows(libInstance.scan()); 12 | t.deepEqual( 13 | new Map([ 14 | [files[2], TorrentLibrary.MOVIES_TYPE], 15 | [files[0], TorrentLibrary.TV_SERIES_TYPE], 16 | [files[1], TorrentLibrary.TV_SERIES_TYPE], 17 | ]), 18 | libInstance.allFilesWithCategory, 19 | 'Not the same', 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /test/getters/allMovies.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import path from 'path'; 3 | import { parse as nameParser } from 'parse-torrent-title'; 4 | import TorrentLibrary from '../../index'; 5 | import { files, folders } from '../_constants'; 6 | 7 | // TESTS 8 | /** @test {TorrentLibrary#allMovies} */ 9 | test('Returns the movies', async (t) => { 10 | let libInstance = new TorrentLibrary(); 11 | await t.notThrows(libInstance.addNewPath(...folders)); 12 | await t.notThrows(libInstance.scan()); 13 | t.deepEqual( 14 | new Set([ 15 | Object.assign( 16 | nameParser(path.basename(files[2])), 17 | { filePath: files[2] }, 18 | ), 19 | ]), 20 | libInstance.allMovies, 21 | 'Not the same', 22 | ); 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /test/getters/allTvSeries.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import path from 'path'; 3 | import { parse as nameParser } from 'parse-torrent-title'; 4 | import TorrentLibrary from '../../index'; 5 | import { files, folders } from '../_constants'; 6 | 7 | // TESTS 8 | /** @test {TorrentLibrary#allTvSeries} */ 9 | test('Returns the tv-shows', async (t) => { 10 | let libInstance = new TorrentLibrary(); 11 | await t.notThrows(libInstance.addNewPath(...folders)); 12 | await t.notThrows(libInstance.scan()); 13 | t.deepEqual( 14 | new Map([ 15 | [nameParser(path.basename(files[0])).title, new Set([ 16 | Object.assign( 17 | nameParser(path.basename(files[0])), 18 | { filePath: files[0] }, 19 | ), 20 | Object.assign( 21 | nameParser(path.basename(files[1])), 22 | { filePath: files[1] }, 23 | ), 24 | ])], 25 | ]), 26 | libInstance.allTvSeries, 27 | 'Not the same', 28 | ); 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /test/getters/constants.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import videosExtension from 'video-extensions'; 3 | import TorrentLibrary from '../../index'; 4 | 5 | /** @test {TorrentLibrary.MOVIES_TYPE} */ 6 | test('Constant MOVIES_TYPE', (t) => { 7 | t.is( 8 | TorrentLibrary.MOVIES_TYPE, 9 | 'MOVIES', 10 | 'Someone changed this constant value !', 11 | ); 12 | }); 13 | 14 | /** @test {TorrentLibrary.TV_SERIES_TYPE} */ 15 | test('Constant TV_SERIES', (t) => { 16 | t.is( 17 | TorrentLibrary.TV_SERIES_TYPE, 18 | 'TV_SERIES', 19 | 'Someone changed this constant value !', 20 | ); 21 | }); 22 | 23 | /** @test {TorrentLibrary.listVideosExtension} */ 24 | test('List of videos extension', (t) => { 25 | t.is( 26 | TorrentLibrary.listVideosExtension(), 27 | videosExtension, 28 | 'Someone changed this constant value !', 29 | ); 30 | }); 31 | -------------------------------------------------------------------------------- /test/methods/addNewPath.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import path from 'path'; 3 | import * as sinon from 'sinon'; 4 | import TorrentLibrary from '../../index'; 5 | import { folders } from '../_constants'; 6 | 7 | // TESTS 8 | /** @test {TorrentLibrary#addNewPath} */ 9 | test('missing parameter', async (t) => { 10 | let eventSpy = sinon.spy(); 11 | let libInstance = new TorrentLibrary(); 12 | libInstance.on('missing_parameter', eventSpy); 13 | await t.throws(libInstance.addNewPath()); 14 | t.truthy(eventSpy.called, 'Event did not fire.'); 15 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 16 | t.is( 17 | libInstance.hasPathsProvidedByUser(), 18 | false, 'No paths by user should be added', 19 | ); 20 | }); 21 | 22 | /** @test {TorrentLibrary#addNewPath} */ 23 | test('Not an existent path', async (t) => { 24 | let eventSpy = sinon.spy(); 25 | let libInstance = new TorrentLibrary(); 26 | 27 | libInstance.on('error_in_function', eventSpy); 28 | await t.throws(libInstance.addNewPath(path.join(__dirname, 'wrongPath'))); 29 | t.truthy(eventSpy.called, 'Event did not fire.'); 30 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 31 | t.is( 32 | libInstance.hasPathsProvidedByUser(), 33 | false, 'No paths by user should be added', 34 | ); 35 | }); 36 | 37 | /** @test {TorrentLibrary#addNewPath} */ 38 | test('existent paths', async (t) => { 39 | let eventSpy = sinon.spy(); 40 | let libInstance = new TorrentLibrary(); 41 | libInstance.on('addNewPath', eventSpy); 42 | await t.notThrows(libInstance.addNewPath(...folders)); 43 | t.truthy(eventSpy.called, 'Event did not fire.'); 44 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 45 | t.is( 46 | libInstance.hasPathsProvidedByUser(), 47 | true, 'The path should be added', 48 | ); 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /test/methods/createFromJSON.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import TorrentLibrary from '../../index'; 3 | import { folders } from '../_constants'; 4 | 5 | /** @test {TorrentLibrary.createFromJSON} */ 6 | test('create a perfect copy of instance', async (t) => { 7 | let libInstance = new TorrentLibrary(); 8 | await t.notThrows(libInstance.addNewPath(...folders)); 9 | await t.notThrows(libInstance.scan()); 10 | const jsonFromLib = JSON.parse(libInstance.toJSON()); 11 | const createdInstance = TorrentLibrary.createFromJSON(jsonFromLib); 12 | t.deepEqual( 13 | createdInstance.allFilesWithCategory, 14 | libInstance.allFilesWithCategory, 15 | 'allFilesWithCategory different', 16 | ); 17 | t.deepEqual( 18 | createdInstance.allMovies, 19 | libInstance.allMovies, 'allMovies different', 20 | ); 21 | t.deepEqual( 22 | createdInstance.allTvSeries, 23 | libInstance.allTvSeries, 'allTvSeries different', 24 | ); 25 | }); 26 | 27 | // dummy test for ES6 code coverage 28 | /** @test {TorrentLibrary.createFromJSON} */ 29 | test('empty instance(s)', async (t) => { 30 | TorrentLibrary.createFromJSON({}); 31 | t.pass(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/methods/filterMovies.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as sinon from 'sinon'; 3 | import { parse as nameParser } from 'parse-torrent-title'; 4 | import path from 'path'; 5 | import TorrentLibrary from '../../index'; 6 | import { files, folders } from '../_constants'; 7 | 8 | /** @test {TorrentLibrary#filterMovies} */ 9 | test('Should work without parameters', async (t) => { 10 | let eventSpy = sinon.spy(); 11 | let libInstance = new TorrentLibrary(); 12 | libInstance.on('scan', eventSpy); 13 | // whatever path that should exists 14 | await t.notThrows(libInstance.addNewPath(...folders)); 15 | await t.notThrows(libInstance.scan()); 16 | t.truthy(eventSpy.called, 'Event did not fire.'); 17 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 18 | t.deepEqual( 19 | new Set([ 20 | Object.assign( 21 | nameParser(path.basename(files[2])), 22 | { filePath: files[2] }, 23 | ), 24 | ]), 25 | libInstance.filterMovies(), 26 | 'Not the same', 27 | ); 28 | }); 29 | 30 | /** @test {TorrentLibrary#filterMovies} */ 31 | test('default boolean parameters search', async (t) => { 32 | let eventSpy = sinon.spy(); 33 | let libInstance = new TorrentLibrary(); 34 | libInstance.on('scan', eventSpy); 35 | // whatever path that should exists 36 | await t.notThrows(libInstance.addNewPath(...folders)); 37 | await t.notThrows(libInstance.scan()); 38 | t.truthy(eventSpy.called, 'Event did not fire.'); 39 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 40 | 41 | // A simple filter that should returns the only movie that we have 42 | t.deepEqual( 43 | new Set([ 44 | Object.assign( 45 | nameParser(path.basename(files[2])), 46 | { filePath: files[2] }, 47 | ), 48 | ]), 49 | libInstance.filterMovies({ 50 | remastered: true, 51 | }), 52 | 'Not the same', 53 | ); 54 | 55 | // A complex filter that should returns nothing 56 | t.deepEqual( 57 | new Set(), 58 | libInstance.filterMovies({ 59 | extended: true, 60 | unrated: true, 61 | proper: true, 62 | repack: true, 63 | convert: true, 64 | hardcoded: true, 65 | retail: true, 66 | remastered: true, 67 | additionalProperties: [ 68 | { type: 'boolean', name: 'AnotherField', value: true }, 69 | ], 70 | }), 71 | 'Not the same', 72 | ); 73 | }); 74 | 75 | /** @test {TorrentLibrary#filterMovies} */ 76 | test('default number parameters search', async (t) => { 77 | let eventSpy = sinon.spy(); 78 | let libInstance = new TorrentLibrary(); 79 | libInstance.on('scan', eventSpy); 80 | // whatever path that should exists 81 | await t.notThrows(libInstance.addNewPath(...folders)); 82 | await t.notThrows(libInstance.scan()); 83 | t.truthy(eventSpy.called, 'Event did not fire.'); 84 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 85 | 86 | // A simple filter that should returns the only movie that we have 87 | t.deepEqual( 88 | new Set([ 89 | Object.assign( 90 | nameParser(path.basename(files[2])), 91 | { filePath: files[2] }, 92 | ), 93 | ]), 94 | libInstance.filterMovies({ 95 | year: 2012, 96 | }), 97 | 'Not the same', 98 | ); 99 | 100 | // A complex filter that should returns nothing 101 | t.deepEqual( 102 | new Set(), 103 | libInstance.filterMovies({ 104 | year: '>=2012', 105 | additionalProperties: [ 106 | { type: 'number', name: "whateverFieldThatDoesn'tExist", value: '<50' }, 107 | { type: 'number', name: 'AnotherField', value: undefined }, 108 | { type: 'number', name: 'AnotherField2', value: '<=25' }, 109 | { type: 'number', name: 'AnotherField3', value: '>25' }, 110 | { type: 'number', name: 'AnotherField4', value: '==25' }, 111 | ], 112 | }), 113 | 'Not the same', 114 | ); 115 | }); 116 | 117 | /** @test {TorrentLibrary#filterMovies} */ 118 | test('default string parameters search', async (t) => { 119 | let eventSpy = sinon.spy(); 120 | let libInstance = new TorrentLibrary(); 121 | libInstance.on('scan', eventSpy); 122 | // whatever path that should exists 123 | await t.notThrows(libInstance.addNewPath(...folders)); 124 | await t.notThrows(libInstance.scan()); 125 | t.truthy(eventSpy.called, 'Event did not fire.'); 126 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 127 | 128 | // A simple filter that should returns the only movie that we have 129 | t.deepEqual( 130 | new Set([ 131 | Object.assign( 132 | nameParser(path.basename(files[2])), 133 | { filePath: files[2] }, 134 | ), 135 | ]), 136 | libInstance.filterMovies({ 137 | title: 'Bad Ass', 138 | }), 139 | 'Not the same', 140 | ); 141 | 142 | // A complex filter that should returns nothing 143 | t.deepEqual( 144 | new Set(), 145 | libInstance.filterMovies({ 146 | title: 'Bad Ass', 147 | additionalProperties: [ 148 | { 149 | type: 'string', 150 | name: 'whateverField', 151 | value: ['NothingExists'], 152 | }, 153 | { 154 | type: 'string', 155 | name: 'AnotherField', 156 | value: ['NothingExists', 'NothingExists'], 157 | }, 158 | { type: 'string', name: 'AnotherField2', value: '<=25' }, 159 | { type: 'string', name: 'AnotherField3', value: '>25' }, 160 | ], 161 | }), 162 | 'Not the same', 163 | ); 164 | }); 165 | -------------------------------------------------------------------------------- /test/methods/filterTvSeries.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as sinon from 'sinon'; 3 | import { parse as nameParser } from 'parse-torrent-title'; 4 | import path from 'path'; 5 | import TorrentLibrary from '../../index'; 6 | import { files, folders } from '../_constants'; 7 | 8 | /** @test {TorrentLibrary#filterTvSeries} */ 9 | test('Should work without parameters', async (t) => { 10 | let eventSpy = sinon.spy(); 11 | let libInstance = new TorrentLibrary(); 12 | libInstance.on('scan', eventSpy); 13 | // whatever path that should exists 14 | await t.notThrows(libInstance.addNewPath(...folders)); 15 | await t.notThrows(libInstance.scan()); 16 | t.truthy(eventSpy.called, 'Event did not fire.'); 17 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 18 | t.deepEqual( 19 | new Map([ 20 | [nameParser(path.basename(files[0])).title, new Set([ 21 | Object.assign( 22 | nameParser(path.basename(files[0])), 23 | { filePath: files[0] }, 24 | ), 25 | Object.assign( 26 | nameParser(path.basename(files[1])), 27 | { filePath: files[1] }, 28 | ), 29 | ])], 30 | ]), 31 | libInstance.filterTvSeries(), 32 | 'Not the same', 33 | ); 34 | }); 35 | 36 | /** @test {TorrentLibrary#filterTvSeries} */ 37 | test('default boolean parameters search', async (t) => { 38 | let eventSpy = sinon.spy(); 39 | let libInstance = new TorrentLibrary(); 40 | libInstance.on('scan', eventSpy); 41 | // whatever path that should exists 42 | await t.notThrows(libInstance.addNewPath(...folders)); 43 | await t.notThrows(libInstance.scan()); 44 | t.truthy(eventSpy.called, 'Event did not fire.'); 45 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 46 | 47 | 48 | // A complex filter that should returns nothing 49 | t.deepEqual( 50 | new Map(), 51 | libInstance.filterTvSeries({ 52 | extended: true, 53 | unrated: true, 54 | proper: true, 55 | repack: true, 56 | convert: true, 57 | hardcoded: true, 58 | retail: true, 59 | remastered: true, 60 | additionalProperties: [ 61 | { type: 'boolean', name: 'AnotherField', value: true }, 62 | ], 63 | }), 64 | 'Not the same', 65 | ); 66 | }); 67 | 68 | /** @test {TorrentLibrary#filterTvSeries} */ 69 | test('default number parameters search', async (t) => { 70 | let eventSpy = sinon.spy(); 71 | let libInstance = new TorrentLibrary(); 72 | libInstance.on('scan', eventSpy); 73 | // whatever path that should exists 74 | await t.notThrows(libInstance.addNewPath(...folders)); 75 | await t.notThrows(libInstance.scan()); 76 | t.truthy(eventSpy.called, 'Event did not fire.'); 77 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 78 | 79 | // A simple filter that should returns the two tv series that we have 80 | t.deepEqual( 81 | new Map([ 82 | [nameParser(path.basename(files[0])).title, new Set([ 83 | Object.assign( 84 | nameParser(path.basename(files[0])), 85 | { filePath: files[0] }, 86 | ), 87 | Object.assign( 88 | nameParser(path.basename(files[1])), 89 | { filePath: files[1] }, 90 | ), 91 | ])], 92 | ]), 93 | libInstance.filterTvSeries({ 94 | season: 4, 95 | }), 96 | 'Not the same', 97 | ); 98 | 99 | // A complex filter that should returns nothing 100 | t.deepEqual( 101 | new Map(), 102 | libInstance.filterTvSeries({ 103 | season: '>=4', 104 | additionalProperties: [ 105 | { type: 'number', name: "whateverFieldThatDoesn'tExist", value: '<50' }, 106 | { type: 'number', name: 'AnotherField', value: undefined }, 107 | { type: 'number', name: 'AnotherField2', value: '<=25' }, 108 | { type: 'number', name: 'AnotherField3', value: '>25' }, 109 | { type: 'number', name: 'AnotherField4', value: '==25' }, 110 | ], 111 | }), 112 | 'Not the same', 113 | ); 114 | }); 115 | 116 | /** @test {TorrentLibrary#filterTvSeries} */ 117 | test('default string parameters search', async (t) => { 118 | let eventSpy = sinon.spy(); 119 | let libInstance = new TorrentLibrary(); 120 | libInstance.on('scan', eventSpy); 121 | // whatever path that should exists 122 | await t.notThrows(libInstance.addNewPath(...folders)); 123 | await t.notThrows(libInstance.scan()); 124 | t.truthy(eventSpy.called, 'Event did not fire.'); 125 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 126 | 127 | // A simple filter that should returns the only movie that we have 128 | t.deepEqual( 129 | new Map([ 130 | [nameParser(path.basename(files[0])).title, new Set([ 131 | Object.assign( 132 | nameParser(path.basename(files[0])), 133 | { filePath: files[0] }, 134 | ), 135 | Object.assign( 136 | nameParser(path.basename(files[1])), 137 | { filePath: files[1] }, 138 | ), 139 | ])], 140 | ]), 141 | libInstance.filterTvSeries({ 142 | title: 'The Blacklist', 143 | }), 144 | 'Not the same', 145 | ); 146 | 147 | // A complex filter that should returns nothing 148 | t.deepEqual( 149 | new Map(), 150 | libInstance.filterTvSeries({ 151 | title: 'The Blacklist', 152 | additionalProperties: [ 153 | { 154 | type: 'string', 155 | name: 'whateverField', 156 | value: ['NothingExists'], 157 | }, 158 | { 159 | type: 'string', 160 | name: 'AnotherField', 161 | value: ['NothingExists', 'NothingExists'], 162 | }, 163 | { type: 'string', name: 'AnotherField2', value: '<=25' }, 164 | { type: 'string', name: 'AnotherField3', value: '>25' }, 165 | ], 166 | }), 167 | 'Not the same', 168 | ); 169 | }); 170 | -------------------------------------------------------------------------------- /test/methods/removeOldFiles.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import path from 'path'; 3 | import * as sinon from 'sinon'; 4 | import { parse as nameParser } from 'parse-torrent-title'; 5 | import TorrentLibrary from '../../index'; 6 | import { files, folders, expectedJson } from '../_constants'; 7 | 8 | /** @test {TorrentLibrary#removeOldFiles} */ 9 | test('Should not be able to remove not present files', async (t) => { 10 | let libInstance = new TorrentLibrary(); 11 | await t.notThrows(libInstance.addNewPath(...folders)); 12 | await t.notThrows(libInstance.scan()); 13 | const wrongFile = path.join( 14 | __dirname, 'folder1', 15 | 'The.Blacklist.S04E22.FRENCH.WEBRip.XviD.avi', 16 | ); 17 | const allFiles = libInstance.allFilesWithCategory; 18 | const expectedTvSeriesMap = libInstance.allTvSeries; 19 | await t.notThrows(libInstance.removeOldFiles(wrongFile)); 20 | t.deepEqual( 21 | libInstance.allFilesWithCategory, 22 | allFiles, 23 | 'Nothing should have changed!', 24 | ); 25 | t.deepEqual( 26 | libInstance.allTvSeries, 27 | expectedTvSeriesMap, 28 | 'Nothing should have changed!', 29 | ); 30 | }); 31 | 32 | /** @test {TorrentLibrary#removeOldFiles} */ 33 | test('Should be able to remove a movie', async (t) => { 34 | let libInstance = new TorrentLibrary(); 35 | let eventSpy = sinon.spy(); 36 | await t.notThrows(libInstance.addNewPath(...folders)); 37 | await t.notThrows(libInstance.scan()); 38 | const allFilesWithoutMovie = libInstance.allFilesWithCategory; 39 | // files[2] ; Bad Ass 40 | allFilesWithoutMovie.delete(files[2]); 41 | 42 | libInstance.on('removeOldFiles', eventSpy); 43 | // files[2] ; Bad Ass 44 | await t.notThrows(libInstance.removeOldFiles(files[2])); 45 | t.deepEqual( 46 | libInstance.allMovies, 47 | new Set(), 48 | 'The movie should have been removed!', 49 | ); 50 | 51 | t.deepEqual( 52 | libInstance.allFilesWithCategory, 53 | allFilesWithoutMovie, 54 | 'The movie should have been removed!', 55 | ); 56 | t.truthy(eventSpy.called, 'Event did not fire.'); 57 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 58 | }); 59 | 60 | /** @test {TorrentLibrary#removeOldFiles} */ 61 | test('Should be able to remove an tv-serie episode', async (t) => { 62 | let libInstance = new TorrentLibrary(); 63 | let eventSpy = sinon.spy(); 64 | await t.notThrows(libInstance.addNewPath(...folders)); 65 | await t.notThrows(libInstance.scan()); 66 | const allFilesWithoutIt = libInstance.allFilesWithCategory; 67 | // files[1] ; The.Blacklist.S04E21 68 | allFilesWithoutIt.delete(files[1]); 69 | 70 | libInstance.on('removeOldFiles', eventSpy); 71 | // files[1] ; The.Blacklist.S04E21 72 | await t.notThrows(libInstance.removeOldFiles(files[1])); 73 | t.deepEqual( 74 | libInstance.allTvSeries, 75 | new Map([ 76 | [nameParser(path.basename(files[0])).title, new Set([ 77 | Object.assign( 78 | nameParser(path.basename(files[0])), 79 | { filePath: files[0] }, 80 | ), 81 | ])], 82 | ]), 83 | 'The tv-series should still exist!', 84 | ); 85 | 86 | t.deepEqual( 87 | libInstance.allFilesWithCategory, 88 | allFilesWithoutIt, 89 | 'The tv-series episode should have been removed!', 90 | ); 91 | t.truthy(eventSpy.called, 'Event did not fire.'); 92 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 93 | }); 94 | 95 | /** @test {TorrentLibrary#removeOldFiles} */ 96 | test('Should be able to remove multiples files : Tv-serie', async (t) => { 97 | let libInstance = new TorrentLibrary(); 98 | let eventSpy = sinon.spy(); 99 | await t.notThrows(libInstance.addNewPath(...folders)); 100 | await t.notThrows(libInstance.scan()); 101 | const allFilesWithoutIt = libInstance.allFilesWithCategory; 102 | 103 | allFilesWithoutIt.delete(files[1]); 104 | allFilesWithoutIt.delete(files[0]); 105 | 106 | libInstance.on('removeOldFiles', eventSpy); 107 | await t.notThrows(libInstance.removeOldFiles(...files.slice(0, 2))); 108 | t.deepEqual( 109 | libInstance.allTvSeries, 110 | new Map(), 111 | 'The tv-series episodes should have all been removed!', 112 | ); 113 | 114 | t.deepEqual( 115 | libInstance.allFilesWithCategory, 116 | allFilesWithoutIt, 117 | 'The tv-series episodes should have all been removed!', 118 | ); 119 | t.truthy(eventSpy.called, 'Event did not fire.'); 120 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 121 | }); 122 | 123 | // test to handle default parameters 124 | /** @test {TorrentLibrary#removeOldFiles} */ 125 | test('Should not be able to remove files : wrong custom parser', async (t) => { 126 | let libInstance = TorrentLibrary.createFromJSON(expectedJson, {}); 127 | let eventSpy = sinon.spy(); 128 | libInstance.on('error_in_function', eventSpy); 129 | await t.throws(libInstance.removeOldFiles(...files)); 130 | t.truthy(eventSpy.called, 'Event did not fire.'); 131 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 132 | }); 133 | -------------------------------------------------------------------------------- /test/methods/scan.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as sinon from 'sinon'; 3 | import { parse as nameParser } from 'parse-torrent-title'; 4 | import TorrentLibrary from '../../index'; 5 | import { folders } from '../_constants'; 6 | 7 | // TESTS 8 | /** @test {TorrentLibrary#scan} */ 9 | test('Scan without user provided paths', async (t) => { 10 | let eventSpy = sinon.spy(); 11 | let libInstance = new TorrentLibrary(); 12 | libInstance.on('scan', eventSpy); 13 | await t.notThrows(libInstance.scan()); 14 | t.truthy(eventSpy.called, 'Event did not fire.'); 15 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 16 | }); 17 | 18 | /** @test {TorrentLibrary#scan} */ 19 | test('Scan with user provided paths', async (t) => { 20 | let eventSpy = sinon.spy(); 21 | let libInstance = new TorrentLibrary(); 22 | libInstance.on('scan', eventSpy); 23 | // whatever path that should exists 24 | await t.notThrows(libInstance.addNewPath(...folders)); 25 | await t.notThrows(libInstance.scan()); 26 | t.truthy(eventSpy.called, 'Event did not fire.'); 27 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 28 | }); 29 | 30 | // test to handle default parameters 31 | /** @test {TorrentLibrary#scan} */ 32 | test('Scan with user provided paths and custom parser', async (t) => { 33 | let eventSpy = sinon.spy(); 34 | let libInstance = new TorrentLibrary({}, nameParser); 35 | libInstance.on('scan', eventSpy); 36 | // whatever path that should exists 37 | await t.notThrows(libInstance.addNewPath(...folders)); 38 | await t.notThrows(libInstance.scan()); 39 | t.truthy(eventSpy.called, 'Event did not fire.'); 40 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 41 | }); 42 | 43 | /** @test {TorrentLibrary#scan} */ 44 | test('Scan with user provided paths and wrong custom parser', async (t) => { 45 | let eventSpy = sinon.spy(); 46 | let libInstance = new TorrentLibrary({}, {}); 47 | libInstance.on('error_in_function', eventSpy); 48 | // whatever path that should exists 49 | await t.notThrows(libInstance.addNewPath(...folders)); 50 | await t.throws(libInstance.scan()); 51 | t.truthy(eventSpy.called, 'Event did not fire.'); 52 | t.truthy(eventSpy.calledOnce, 'Event fired more than once'); 53 | }); 54 | -------------------------------------------------------------------------------- /test/methods/toJSON.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import TorrentLibrary from '../../index'; 3 | import { expectedJson, folders } from '../_constants'; 4 | 5 | /** @test {TorrentLibrary#toJSON} */ 6 | test('return a valid stringified JSON', async (t) => { 7 | const expectedJsonString = JSON.stringify(expectedJson); 8 | let libInstance = new TorrentLibrary(); 9 | await t.notThrows(libInstance.addNewPath(...folders)); 10 | await t.notThrows(libInstance.scan()); 11 | const dataFromInstance = libInstance.toJSON(); 12 | t.deepEqual( 13 | JSON.stringify(JSON.parse(dataFromInstance)), 14 | expectedJsonString, 15 | 'Not the same JSON', 16 | ); 17 | }); 18 | --------------------------------------------------------------------------------