mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			248 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| //
 | |
| // used by archiver
 | |
| // Source: https://github.com/Yqnn/node-readdir-glob
 | |
| //
 | |
| 
 | |
| module.exports = readdirGlob;
 | |
| 
 | |
| const fs = require('fs');
 | |
| const { EventEmitter } = require('events');
 | |
| const { Minimatch } = require('../archiverUtils/minimatch');
 | |
| const { resolve } = require('path');
 | |
| 
 | |
| function readdir(dir, strict) {
 | |
|   return new Promise((resolve, reject) => {
 | |
|     fs.readdir(dir, { withFileTypes: true }, (err, files) => {
 | |
|       if (err) {
 | |
|         switch (err.code) {
 | |
|           case 'ENOTDIR':      // Not a directory
 | |
|             if (strict) {
 | |
|               reject(err);
 | |
|             } else {
 | |
|               resolve([]);
 | |
|             }
 | |
|             break;
 | |
|           case 'ENOTSUP':      // Operation not supported
 | |
|           case 'ENOENT':       // No such file or directory
 | |
|           case 'ENAMETOOLONG': // Filename too long
 | |
|           case 'UNKNOWN':
 | |
|             resolve([]);
 | |
|             break;
 | |
|           case 'ELOOP':        // Too many levels of symbolic links
 | |
|           default:
 | |
|             reject(err);
 | |
|             break;
 | |
|         }
 | |
|       } else {
 | |
|         resolve(files);
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| }
 | |
| function stat(file, followSymlinks) {
 | |
|   return new Promise((resolve, reject) => {
 | |
|     const statFunc = followSymlinks ? fs.stat : fs.lstat;
 | |
|     statFunc(file, (err, stats) => {
 | |
|       if (err) {
 | |
|         switch (err.code) {
 | |
|           case 'ENOENT':
 | |
|             if (followSymlinks) {
 | |
|               // Fallback to lstat to handle broken links as files
 | |
|               resolve(stat(file, false));
 | |
|             } else {
 | |
|               resolve(null);
 | |
|             }
 | |
|             break;
 | |
|           default:
 | |
|             resolve(null);
 | |
|             break;
 | |
|         }
 | |
|       } else {
 | |
|         resolve(stats);
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| }
 | |
| 
 | |
| async function* exploreWalkAsync(dir, path, followSymlinks, useStat, shouldSkip, strict) {
 | |
|   let files = await readdir(path + dir, strict);
 | |
|   for (const file of files) {
 | |
|     let name = file.name;
 | |
|     if (name === undefined) {
 | |
|       // undefined file.name means the `withFileTypes` options is not supported by node
 | |
|       // we have to call the stat function to know if file is directory or not.
 | |
|       name = file;
 | |
|       useStat = true;
 | |
|     }
 | |
|     const filename = dir + '/' + name;
 | |
|     const relative = filename.slice(1); // Remove the leading /
 | |
|     const absolute = path + '/' + relative;
 | |
|     let stats = null;
 | |
|     if (useStat || followSymlinks) {
 | |
|       stats = await stat(absolute, followSymlinks);
 | |
|     }
 | |
|     if (!stats && file.name !== undefined) {
 | |
|       stats = file;
 | |
|     }
 | |
|     if (stats === null) {
 | |
|       stats = { isDirectory: () => false };
 | |
|     }
 | |
| 
 | |
|     if (stats.isDirectory()) {
 | |
|       if (!shouldSkip(relative)) {
 | |
|         yield { relative, absolute, stats };
 | |
|         yield* exploreWalkAsync(filename, path, followSymlinks, useStat, shouldSkip, false);
 | |
|       }
 | |
|     } else {
 | |
|       yield { relative, absolute, stats };
 | |
|     }
 | |
|   }
 | |
| }
 | |
| async function* explore(path, followSymlinks, useStat, shouldSkip) {
 | |
|   yield* exploreWalkAsync('', path, followSymlinks, useStat, shouldSkip, true);
 | |
| }
 | |
| 
 | |
| 
 | |
| function readOptions(options) {
 | |
|   return {
 | |
|     pattern: options.pattern,
 | |
|     dot: !!options.dot,
 | |
|     noglobstar: !!options.noglobstar,
 | |
|     matchBase: !!options.matchBase,
 | |
|     nocase: !!options.nocase,
 | |
|     ignore: options.ignore,
 | |
|     skip: options.skip,
 | |
| 
 | |
|     follow: !!options.follow,
 | |
|     stat: !!options.stat,
 | |
|     nodir: !!options.nodir,
 | |
|     mark: !!options.mark,
 | |
|     silent: !!options.silent,
 | |
|     absolute: !!options.absolute
 | |
|   };
 | |
| }
 | |
| 
 | |
| class ReaddirGlob extends EventEmitter {
 | |
|   constructor(cwd, options, cb) {
 | |
|     super();
 | |
|     if (typeof options === 'function') {
 | |
|       cb = options;
 | |
|       options = null;
 | |
|     }
 | |
| 
 | |
|     this.options = readOptions(options || {});
 | |
| 
 | |
|     this.matchers = [];
 | |
|     if (this.options.pattern) {
 | |
|       const matchers = Array.isArray(this.options.pattern) ? this.options.pattern : [this.options.pattern];
 | |
|       this.matchers = matchers.map(m =>
 | |
|         new Minimatch(m, {
 | |
|           dot: this.options.dot,
 | |
|           noglobstar: this.options.noglobstar,
 | |
|           matchBase: this.options.matchBase,
 | |
|           nocase: this.options.nocase
 | |
|         })
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     this.ignoreMatchers = [];
 | |
|     if (this.options.ignore) {
 | |
|       const ignorePatterns = Array.isArray(this.options.ignore) ? this.options.ignore : [this.options.ignore];
 | |
|       this.ignoreMatchers = ignorePatterns.map(ignore =>
 | |
|         new Minimatch(ignore, { dot: true })
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     this.skipMatchers = [];
 | |
|     if (this.options.skip) {
 | |
|       const skipPatterns = Array.isArray(this.options.skip) ? this.options.skip : [this.options.skip];
 | |
|       this.skipMatchers = skipPatterns.map(skip =>
 | |
|         new Minimatch(skip, { dot: true })
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     this.iterator = explore(resolve(cwd || '.'), this.options.follow, this.options.stat, this._shouldSkipDirectory.bind(this));
 | |
|     this.paused = false;
 | |
|     this.inactive = false;
 | |
|     this.aborted = false;
 | |
| 
 | |
|     if (cb) {
 | |
|       this._matches = [];
 | |
|       this.on('match', match => this._matches.push(this.options.absolute ? match.absolute : match.relative));
 | |
|       this.on('error', err => cb(err));
 | |
|       this.on('end', () => cb(null, this._matches));
 | |
|     }
 | |
| 
 | |
|     setTimeout(() => this._next(), 0);
 | |
|   }
 | |
| 
 | |
|   _shouldSkipDirectory(relative) {
 | |
|     //console.log(relative, this.skipMatchers.some(m => m.match(relative)));
 | |
|     return this.skipMatchers.some(m => m.match(relative));
 | |
|   }
 | |
| 
 | |
|   _fileMatches(relative, isDirectory) {
 | |
|     const file = relative + (isDirectory ? '/' : '');
 | |
|     return (this.matchers.length === 0 || this.matchers.some(m => m.match(file)))
 | |
|       && !this.ignoreMatchers.some(m => m.match(file))
 | |
|       && (!this.options.nodir || !isDirectory);
 | |
|   }
 | |
| 
 | |
|   _next() {
 | |
|     if (!this.paused && !this.aborted) {
 | |
|       this.iterator.next()
 | |
|         .then((obj) => {
 | |
|           if (!obj.done) {
 | |
|             const isDirectory = obj.value.stats.isDirectory();
 | |
|             if (this._fileMatches(obj.value.relative, isDirectory)) {
 | |
|               let relative = obj.value.relative;
 | |
|               let absolute = obj.value.absolute;
 | |
|               if (this.options.mark && isDirectory) {
 | |
|                 relative += '/';
 | |
|                 absolute += '/';
 | |
|               }
 | |
|               if (this.options.stat) {
 | |
|                 this.emit('match', { relative, absolute, stat: obj.value.stats });
 | |
|               } else {
 | |
|                 this.emit('match', { relative, absolute });
 | |
|               }
 | |
|             }
 | |
|             this._next(this.iterator);
 | |
|           } else {
 | |
|             this.emit('end');
 | |
|           }
 | |
|         })
 | |
|         .catch((err) => {
 | |
|           this.abort();
 | |
|           this.emit('error', err);
 | |
|           if (!err.code && !this.options.silent) {
 | |
|             console.error(err);
 | |
|           }
 | |
|         });
 | |
|     } else {
 | |
|       this.inactive = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   abort() {
 | |
|     this.aborted = true;
 | |
|   }
 | |
| 
 | |
|   pause() {
 | |
|     this.paused = true;
 | |
|   }
 | |
| 
 | |
|   resume() {
 | |
|     this.paused = false;
 | |
|     if (this.inactive) {
 | |
|       this.inactive = false;
 | |
|       this._next();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| function readdirGlob(pattern, options, cb) {
 | |
|   return new ReaddirGlob(pattern, options, cb);
 | |
| }
 | |
| readdirGlob.ReaddirGlob = ReaddirGlob; |