event driven watcher
This commit is contained in:
		
							parent
							
								
									b4e0e51588
								
							
						
					
					
						commit
						89bc827a7a
					
				
					 17 changed files with 330 additions and 116 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -11,3 +11,4 @@ devdb.sqlite3 | ||||||
| build/** | build/** | ||||||
| app/** | app/** | ||||||
| settings.json | settings.json | ||||||
|  | *config.json | ||||||
							
								
								
									
										12
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
										
									
									
									
								
							|  | @ -31,14 +31,16 @@ | ||||||
|   "author": "", |   "author": "", | ||||||
|   "license": "ISC", |   "license": "ISC", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "chokidar": "^3.5.1", | ||||||
|  |     "jsonschema": "^1.4.0", | ||||||
|     "jsonwebtoken": "^8.5.1", |     "jsonwebtoken": "^8.5.1", | ||||||
|     "knex": "^0.21.14", |     "knex": "^0.21.16", | ||||||
|     "koa": "^2.13.0", |     "koa": "^2.13.1", | ||||||
|     "koa-bodyparser": "^4.3.0", |     "koa-bodyparser": "^4.3.0", | ||||||
|     "koa-router": "^10.0.0", |     "koa-router": "^10.0.0", | ||||||
|     "natural-orderby": "^2.0.3", |     "natural-orderby": "^2.0.3", | ||||||
|     "node-stream-zip": "^1.12.0", |     "node-stream-zip": "^1.12.0", | ||||||
|     "sqlite3": "^5.0.0" |     "sqlite3": "^5.0.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/jsonwebtoken": "^8.5.0", |     "@types/jsonwebtoken": "^8.5.0", | ||||||
|  | @ -46,8 +48,8 @@ | ||||||
|     "@types/koa": "^2.11.6", |     "@types/koa": "^2.11.6", | ||||||
|     "@types/koa-bodyparser": "^4.3.0", |     "@types/koa-bodyparser": "^4.3.0", | ||||||
|     "@types/koa-router": "^7.4.1", |     "@types/koa-router": "^7.4.1", | ||||||
|     "@types/node": "^14.14.16", |     "@types/node": "^14.14.22", | ||||||
|     "electron": "^11.1.1", |     "electron": "^11.2.0", | ||||||
|     "electron-builder": "^22.9.1", |     "electron-builder": "^22.9.1", | ||||||
|     "eslint-plugin-node": "^11.1.0", |     "eslint-plugin-node": "^11.1.0", | ||||||
|     "ts-json-schema-generator": "^0.82.0", |     "ts-json-schema-generator": "^0.82.0", | ||||||
|  |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| import Schema from './MangaConfig.schema.json'; |  | ||||||
| 
 |  | ||||||
| export interface MangaConfig{ |  | ||||||
|     watch:string[] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {join as pathjoin} from 'path'; | import {basename, dirname, join as pathjoin} from 'path'; | ||||||
| import {Document, DocumentAccessor} from '../model/mod'; | import {Document, DocumentAccessor} from '../model/mod'; | ||||||
| import { ContentFile, createContentFile } from '../content/mod'; | import { ContentFile, createContentFile } from '../content/mod'; | ||||||
| import {IDiffWatcher} from './watcher'; | import {IDiffWatcher} from './watcher'; | ||||||
|  | @ -23,12 +23,13 @@ export class ContentDiffHandler{ | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     register(diff:IDiffWatcher){ |     register(diff:IDiffWatcher){ | ||||||
|         diff.on('create',(filename)=>this.OnCreated(diff.path,filename)) |         diff.on('create',(path)=>this.OnCreated(path)) | ||||||
|             .on('delete',(filename)=>this.OnDeleted(diff.path,filename)) |             .on('delete',(path)=>this.OnDeleted(path)) | ||||||
|             .on('change',(prev_filename,cur_filename)=>this.OnChanged(diff.path,prev_filename,cur_filename)); |             .on('change',(prev,cur)=>this.OnChanged(prev,cur)); | ||||||
|     } |     } | ||||||
|     private async OnDeleted(basepath:string,filename:string){ |     private async OnDeleted(cpath: string){ | ||||||
|         const cpath = pathjoin(basepath,filename); |         const basepath = dirname(cpath); | ||||||
|  |         const filename = basename(cpath); | ||||||
|         if(this.waiting_list.hasPath(cpath)){ |         if(this.waiting_list.hasPath(cpath)){ | ||||||
|             this.waiting_list.deletePath(cpath); |             this.waiting_list.deletePath(cpath); | ||||||
|             return; |             return; | ||||||
|  | @ -52,7 +53,9 @@ export class ContentDiffHandler{ | ||||||
|         }); |         }); | ||||||
|         this.tombstone.set(dbc[0].content_hash, dbc[0]); |         this.tombstone.set(dbc[0].content_hash, dbc[0]); | ||||||
|     } |     } | ||||||
|     private async OnCreated(basepath:string,filename:string){ |     private async OnCreated(cpath:string){ | ||||||
|  |         const basepath = dirname(cpath); | ||||||
|  |         const filename = basename(cpath); | ||||||
|         const content = createContentFile(this.content_type,pathjoin(basepath,filename)); |         const content = createContentFile(this.content_type,pathjoin(basepath,filename)); | ||||||
|         const hash = await content.getHash(); |         const hash = await content.getHash(); | ||||||
|         const c = this.tombstone.get(hash); |         const c = this.tombstone.get(hash); | ||||||
|  | @ -67,8 +70,14 @@ export class ContentDiffHandler{ | ||||||
|         } |         } | ||||||
|         this.waiting_list.set(content); |         this.waiting_list.set(content); | ||||||
|     } |     } | ||||||
|     private async OnChanged(basepath:string,prev_filename:string,cur_filename:string){ |     private async OnChanged(prev_path:string,cur_path:string){ | ||||||
|         const doc = await this.doc_cntr.findByPath(basepath,prev_filename); |         const prev_basepath = dirname(prev_path); | ||||||
|         await this.doc_cntr.update({...doc[0],filename:cur_filename}); |         const prev_filename = basename(prev_path); | ||||||
|  |         const cur_basepath = dirname(cur_path); | ||||||
|  |         const cur_filename = basename(cur_path); | ||||||
|  |         const doc = await this.doc_cntr.findByPath(prev_basepath,prev_filename); | ||||||
|  |         await this.doc_cntr.update({...doc[0], | ||||||
|  |             basepath:cur_basepath, | ||||||
|  |             filename:cur_filename}); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { DocumentAccessor } from '../model/doc'; | import { DocumentAccessor } from '../model/doc'; | ||||||
| import {ContentDiffHandler} from './content_handler'; | import {ContentDiffHandler} from './content_handler'; | ||||||
| import { CommonDiffWatcher } from './watcher'; | import { IDiffWatcher } from './watcher'; | ||||||
|  | 
 | ||||||
| //import {join as pathjoin} from 'path';
 | //import {join as pathjoin} from 'path';
 | ||||||
| export class DiffManager{ | export class DiffManager{ | ||||||
|     watching: {[content_type:string]:ContentDiffHandler}; |     watching: {[content_type:string]:ContentDiffHandler}; | ||||||
|  | @ -9,15 +10,12 @@ export class DiffManager{ | ||||||
|         this.watching = {}; |         this.watching = {}; | ||||||
|         this.doc_cntr = contorller; |         this.doc_cntr = contorller; | ||||||
|     } |     } | ||||||
|     async register(content_type:string,path:string){ |     async register(content_type:string,watcher:IDiffWatcher){ | ||||||
|         if(this.watching[content_type] === undefined){ |         if(this.watching[content_type] === undefined){ | ||||||
|             this.watching[content_type] = new ContentDiffHandler(this.doc_cntr,content_type); |             this.watching[content_type] = new ContentDiffHandler(this.doc_cntr,content_type); | ||||||
|         } |         } | ||||||
|         const watcher = new CommonDiffWatcher(path); |  | ||||||
|         this.watching[content_type].register(watcher); |         this.watching[content_type].register(watcher); | ||||||
|         const initial_doc = await this.doc_cntr.findByPath(path); |         await watcher.setup(this.doc_cntr); | ||||||
|         await watcher.setup(initial_doc.map(x=>x.filename)); |  | ||||||
|         watcher.watch(); |  | ||||||
|     } |     } | ||||||
|     async commit(type:string,path:string){ |     async commit(type:string,path:string){ | ||||||
|         const list = this.watching[type].waiting_list; |         const list = this.watching[type].waiting_list; | ||||||
|  |  | ||||||
|  | @ -1,78 +1,25 @@ | ||||||
| import { FSWatcher, watch } from 'fs'; | import { FSWatcher, watch } from 'fs'; | ||||||
| import { promises } from 'fs'; | import { promises } from 'fs'; | ||||||
| import event from 'events'; | import event from 'events'; | ||||||
| 
 | import { join } from 'path'; | ||||||
|  | import { DocumentAccessor } from '../model/doc'; | ||||||
| 
 | 
 | ||||||
| const readdir = promises.readdir; | const readdir = promises.readdir; | ||||||
| 
 | 
 | ||||||
| interface DiffWatcherEvent{ | export interface DiffWatcherEvent{ | ||||||
|     'create':(filename:string)=>void, |     'create':(path:string)=>void, | ||||||
|     'delete':(filename:string)=>void, |     'delete':(path:string)=>void, | ||||||
|     'change':(prev_filename:string,cur_filename:string)=>void, |     'change':(prev_path:string,cur_path:string)=>void, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface IDiffWatcher extends event.EventEmitter { | export interface IDiffWatcher extends event.EventEmitter { | ||||||
|     on<U extends keyof DiffWatcherEvent>(event:U,listener:DiffWatcherEvent[U]): this; |     on<U extends keyof DiffWatcherEvent>(event:U,listener:DiffWatcherEvent[U]): this; | ||||||
|     emit<U extends keyof DiffWatcherEvent>(event:U,...arg:Parameters<DiffWatcherEvent[U]>): boolean; |     emit<U extends keyof DiffWatcherEvent>(event:U,...arg:Parameters<DiffWatcherEvent[U]>): boolean; | ||||||
|     readonly path: string; |     setup(cntr:DocumentAccessor):Promise<void>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class CommonDiffWatcher extends event.EventEmitter implements IDiffWatcher{ | export function linkWatcher(fromWatcher :IDiffWatcher, toWatcher: IDiffWatcher){ | ||||||
|     on<U extends keyof DiffWatcherEvent>(event:U,listener:DiffWatcherEvent[U]): this{ |     fromWatcher.on("create",p=>toWatcher.emit("create",p)); | ||||||
|         return super.on(event,listener); |     fromWatcher.on("delete",p=>toWatcher.emit("delete",p)); | ||||||
|     } |     fromWatcher.on("change",(p,c)=>toWatcher.emit("change",p,c)); | ||||||
|     emit<U extends keyof DiffWatcherEvent>(event:U,...arg:Parameters<DiffWatcherEvent[U]>): boolean{ |  | ||||||
|         return super.emit(event,...arg); |  | ||||||
|     } |  | ||||||
|     private _path:string; |  | ||||||
|     private _watcher: FSWatcher|null; |  | ||||||
| 
 |  | ||||||
|     constructor(path:string){ |  | ||||||
|         super(); |  | ||||||
|         this._path = path; |  | ||||||
|         this._watcher = null; |  | ||||||
|     } |  | ||||||
|     public get path(){ |  | ||||||
|         return this._path; |  | ||||||
|     } |  | ||||||
|     /** |  | ||||||
|      * setup |  | ||||||
|      * @argument initial_filenames filename in path |  | ||||||
|      */ |  | ||||||
|     async setup(initial_filenames:string[]){ |  | ||||||
|         const cur = (await readdir(this._path,{ |  | ||||||
|             encoding:"utf8", |  | ||||||
|             withFileTypes: true, |  | ||||||
|         })).filter(x=>x.isFile).map(x=>x.name); |  | ||||||
|         //Todo : reduce O(nm) to O(n+m) using hash map.
 |  | ||||||
|         let added = cur.filter(x => !initial_filenames.includes(x)); |  | ||||||
|         let deleted = initial_filenames.filter(x=>!cur.includes(x)); |  | ||||||
|         for (const iterator of added) { |  | ||||||
|             this.emit('create',iterator); |  | ||||||
|         } |  | ||||||
|         for (const iterator of deleted){ |  | ||||||
|             this.emit('delete',iterator); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     watch():FSWatcher{ |  | ||||||
|         this._watcher = watch(this._path,{persistent: true, recursive:false},async (eventType,filename)=>{ |  | ||||||
|             if(eventType === "rename"){ |  | ||||||
|                 const cur = (await readdir(this._path,{ |  | ||||||
|                     encoding:"utf8", |  | ||||||
|                     withFileTypes: true, |  | ||||||
|                 })).filter(x=>x.isFile).map(x=>x.name); |  | ||||||
|                 //add
 |  | ||||||
|                 if(cur.includes(filename)){ |  | ||||||
|                     this.emit('create',filename); |  | ||||||
|                 } |  | ||||||
|                 else{ |  | ||||||
|                     this.emit('delete',filename) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|         return this._watcher; |  | ||||||
|     } |  | ||||||
|     watchClose(){ |  | ||||||
|         this._watcher?.close() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
							
								
								
									
										8
									
								
								src/diff/watcher/MangaConfig.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/diff/watcher/MangaConfig.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | import {ConfigManager} from '../../util/configRW'; | ||||||
|  | import MangaSchema from "./MangaConfig.schema.json" | ||||||
|  | export interface MangaConfig{ | ||||||
|  |     watch:string[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const MangaConfig = new ConfigManager<MangaConfig>("manga_config.json",{watch:[]},MangaSchema); | ||||||
|  | 
 | ||||||
							
								
								
									
										45
									
								
								src/diff/watcher/common_watcher.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/diff/watcher/common_watcher.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | import event from 'events'; | ||||||
|  | import {FSWatcher,watch,promises} from 'fs'; | ||||||
|  | import {IDiffWatcher, DiffWatcherEvent} from '../watcher'; | ||||||
|  | import {join} from 'path'; | ||||||
|  | import { DocumentAccessor } from '../../model/doc'; | ||||||
|  | import { setupHelp } from './util'; | ||||||
|  | 
 | ||||||
|  | const {readdir} = promises; | ||||||
|  | 
 | ||||||
|  | export class CommonDiffWatcher extends event.EventEmitter implements IDiffWatcher{ | ||||||
|  |     on<U extends keyof DiffWatcherEvent>(event:U,listener:DiffWatcherEvent[U]): this{ | ||||||
|  |         return super.on(event,listener); | ||||||
|  |     } | ||||||
|  |     emit<U extends keyof DiffWatcherEvent>(event:U,...arg:Parameters<DiffWatcherEvent[U]>): boolean{ | ||||||
|  |         return super.emit(event,...arg); | ||||||
|  |     } | ||||||
|  |     private _path:string; | ||||||
|  |     private _watcher: FSWatcher; | ||||||
|  | 
 | ||||||
|  |     constructor(path:string){ | ||||||
|  |         super(); | ||||||
|  |         this._path = path; | ||||||
|  |         this._watcher = watch(this._path,{persistent: true, recursive:false},async (eventType,filename)=>{ | ||||||
|  |             if(eventType === "rename"){ | ||||||
|  |                 const cur = await readdir(this._path); | ||||||
|  |                 //add
 | ||||||
|  |                 if(cur.includes(filename)){ | ||||||
|  |                     this.emit('create',join(this.path,filename)); | ||||||
|  |                 } | ||||||
|  |                 else{ | ||||||
|  |                     this.emit('delete',join(this.path,filename)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     async setup(cntr: DocumentAccessor): Promise<void> { | ||||||
|  |         await setupHelp(this,this.path,cntr); | ||||||
|  |     } | ||||||
|  |     public get path(){ | ||||||
|  |         return this._path; | ||||||
|  |     } | ||||||
|  |     watchClose(){ | ||||||
|  |         this._watcher.close() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								src/diff/watcher/compositer.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/diff/watcher/compositer.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | import { EventEmitter } from "events"; | ||||||
|  | import { DocumentAccessor } from "../../model/doc"; | ||||||
|  | import { DiffWatcherEvent, IDiffWatcher, linkWatcher } from "../watcher"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class WatcherCompositer extends EventEmitter implements IDiffWatcher{ | ||||||
|  |     refWatchers : IDiffWatcher[]; | ||||||
|  |     on<U extends keyof DiffWatcherEvent>(event:U,listener:DiffWatcherEvent[U]): this{ | ||||||
|  |         return super.on(event,listener); | ||||||
|  |     } | ||||||
|  |     emit<U extends keyof DiffWatcherEvent>(event:U,...arg:Parameters<DiffWatcherEvent[U]>): boolean{ | ||||||
|  |         return super.emit(event,...arg); | ||||||
|  |     } | ||||||
|  |     constructor(refWatchers:IDiffWatcher[]){ | ||||||
|  |         super(); | ||||||
|  |         this.refWatchers = refWatchers; | ||||||
|  |         for(const refWatcher of this.refWatchers){ | ||||||
|  |             linkWatcher(refWatcher,this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     async setup(cntr: DocumentAccessor): Promise<void> { | ||||||
|  |         await Promise.all(this.refWatchers.map(x=>x.setup(cntr))); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								src/diff/watcher/manga_watcher.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/diff/watcher/manga_watcher.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | import {IDiffWatcher, DiffWatcherEvent} from '../watcher'; | ||||||
|  | import {EventEmitter} from 'events'; | ||||||
|  | import { DocumentAccessor } from '../../model/doc'; | ||||||
|  | import { WatcherFilter } from './watcher_filter'; | ||||||
|  | import { RecursiveWatcher } from './recursive_watcher'; | ||||||
|  | import { MangaConfig } from './MangaConfig'; | ||||||
|  | import {WatcherCompositer} from './compositer' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const createMangaWatcherBase = (path:string)=> { | ||||||
|  |     return new WatcherFilter(new RecursiveWatcher(path),(x)=>x.endsWith(".zip")); | ||||||
|  | } | ||||||
|  | export const createMangaWatcher = ()=>{ | ||||||
|  |     const file = MangaConfig.get_config_file(); | ||||||
|  |     console.log(`register manga ${file.watch.join(",")}`) | ||||||
|  |     return new WatcherCompositer(file.watch.map(path=>createMangaWatcherBase(path))); | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								src/diff/watcher/recursive_watcher.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/diff/watcher/recursive_watcher.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | ||||||
|  | import {watch, FSWatcher} from 'chokidar'; | ||||||
|  | import { EventEmitter } from 'events'; | ||||||
|  | import { join } from 'path'; | ||||||
|  | import { DocumentAccessor } from '../../model/doc'; | ||||||
|  | import { DiffWatcherEvent, IDiffWatcher } from '../watcher'; | ||||||
|  | import { setupHelp, setupRecursive } from './util'; | ||||||
|  | 
 | ||||||
|  | type RecursiveWatcherOption={ | ||||||
|  |     /** @default true */ | ||||||
|  |     watchFile?:boolean, | ||||||
|  |     /** @default false */ | ||||||
|  |     watchDir?:boolean, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class RecursiveWatcher extends EventEmitter implements IDiffWatcher  { | ||||||
|  |     on<U extends keyof DiffWatcherEvent>(event:U,listener:DiffWatcherEvent[U]): this{ | ||||||
|  |         return super.on(event,listener); | ||||||
|  |     } | ||||||
|  |     emit<U extends keyof DiffWatcherEvent>(event:U,...arg:Parameters<DiffWatcherEvent[U]>): boolean{ | ||||||
|  |         return super.emit(event,...arg); | ||||||
|  |     } | ||||||
|  |     readonly path: string; | ||||||
|  |     private watcher: FSWatcher | ||||||
|  | 
 | ||||||
|  |     constructor(path:string, option?:RecursiveWatcherOption){ | ||||||
|  |         super(); | ||||||
|  |         this.path = path; | ||||||
|  |         option = option || { | ||||||
|  |             watchDir:false, | ||||||
|  |             watchFile:true, | ||||||
|  |         } | ||||||
|  |         this.watcher = watch(path,{ | ||||||
|  |             persistent:true, | ||||||
|  |             ignoreInitial:true, | ||||||
|  |             depth:100, | ||||||
|  |         }); | ||||||
|  |         if(option.watchFile === undefined || option.watchFile){ | ||||||
|  |             this.watcher.on("add",path=>{ | ||||||
|  |                 const cpath = join(this.path,path); | ||||||
|  |                 this.emit("create",cpath); | ||||||
|  |             }).on("unlink",path=>{ | ||||||
|  |                 const cpath = join(this.path,path); | ||||||
|  |                 this.emit("delete",cpath); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         if(option.watchDir){ | ||||||
|  |             this.watcher.on("addDir",path=>{ | ||||||
|  |                 const cpath = join(this.path,path); | ||||||
|  |                 this.emit("create",cpath); | ||||||
|  |             }).on("unlinkDir",path=>{ | ||||||
|  |                 const cpath = join(this.path,path); | ||||||
|  |                 this.emit("delete",cpath); | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     async setup(cntr: DocumentAccessor): Promise<void> { | ||||||
|  |         await setupRecursive(this,this.path,cntr); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								src/diff/watcher/util.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/diff/watcher/util.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | import { EventEmitter } from "events"; | ||||||
|  | import { promises } from "fs"; | ||||||
|  | import { join } from "path"; | ||||||
|  | const {readdir} = promises; | ||||||
|  | import { DocumentAccessor } from "../../model/doc"; | ||||||
|  | import { IDiffWatcher } from "../watcher"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function setupCommon(watcher:IDiffWatcher,basepath:string,initial_filenames:string[],cur:string[]){ | ||||||
|  |     //Todo : reduce O(nm) to O(n+m) using hash map.
 | ||||||
|  |     let added = cur.filter(x => !initial_filenames.includes(x)); | ||||||
|  |     let deleted = initial_filenames.filter(x=>!cur.includes(x)); | ||||||
|  |     for (const it of added) { | ||||||
|  |         const cpath = join(basepath,it); | ||||||
|  |         watcher.emit('create',cpath); | ||||||
|  |     } | ||||||
|  |     for (const it of deleted){ | ||||||
|  |         const cpath = join(basepath,it); | ||||||
|  |         watcher.emit('delete',cpath); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | export async function setupHelp(watcher:IDiffWatcher,basepath:string,cntr:DocumentAccessor){ | ||||||
|  |     const initial_document = await cntr.findByPath(basepath); | ||||||
|  |     const initial_filenames = initial_document.map(x=>x.filename); | ||||||
|  |     const cur = await readdir(basepath); | ||||||
|  |     setupCommon(watcher,basepath,initial_filenames,cur); | ||||||
|  | } | ||||||
|  | export async function setupRecursive(watcher:IDiffWatcher,basepath:string,cntr:DocumentAccessor){ | ||||||
|  |     const initial_document = await cntr.findByPath(basepath); | ||||||
|  |     const initial_filenames = initial_document.map(x=>x.filename); | ||||||
|  |     const cur = await readdir(basepath,{withFileTypes:true}); | ||||||
|  |     setupCommon(watcher,basepath,initial_filenames,cur.map(x=>x.name)); | ||||||
|  |     await Promise.all([cur.filter(x=>x.isDirectory()) | ||||||
|  |         .map(x=>setupHelp(watcher,join(basepath,x.name),cntr))]); | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								src/diff/watcher/watcher_filter.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/diff/watcher/watcher_filter.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | import { EventEmitter } from "events"; | ||||||
|  | import { DocumentAccessor } from "../../model/doc"; | ||||||
|  | import { DiffWatcherEvent, IDiffWatcher, linkWatcher } from "../watcher"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class WatcherFilter extends EventEmitter implements IDiffWatcher{ | ||||||
|  |     refWatcher : IDiffWatcher; | ||||||
|  |     filter : (filename:string)=>boolean;; | ||||||
|  |     on<U extends keyof DiffWatcherEvent>(event:U,listener:DiffWatcherEvent[U]): this{ | ||||||
|  |         return super.on(event,listener); | ||||||
|  |     } | ||||||
|  |     emit<U extends keyof DiffWatcherEvent>(event:U,...arg:Parameters<DiffWatcherEvent[U]>): boolean{ | ||||||
|  |         if(event === "change"){ | ||||||
|  |             const prev = arg[0]; | ||||||
|  |             const cur = arg[1] as string; | ||||||
|  |             if(this.filter(prev)){ | ||||||
|  |                 if(this.filter(cur)){ | ||||||
|  |                     return super.emit("change",prev,cur); | ||||||
|  |                 } | ||||||
|  |                 else{ | ||||||
|  |                     return super.emit("delete",cur); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else{ | ||||||
|  |                 if(this.filter(cur)){ | ||||||
|  |                     return super.emit("create",cur); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         else if(!this.filter(arg[0])){ | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         else return super.emit(event,...arg); | ||||||
|  |     } | ||||||
|  |     constructor(refWatcher:IDiffWatcher, filter:(filename:string)=>boolean){ | ||||||
|  |         super(); | ||||||
|  |         this.refWatcher = refWatcher; | ||||||
|  |         this.filter = filter; | ||||||
|  |         linkWatcher(refWatcher,this); | ||||||
|  |     } | ||||||
|  |     setup(cntr:DocumentAccessor): Promise<void> { | ||||||
|  |         return this.refWatcher.setup(cntr); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -14,6 +14,7 @@ import {createUserMiddleWare,createLoginMiddleware, isAdminFirst, getAdmin, Logo | ||||||
| 
 | 
 | ||||||
| import {createInterface as createReadlineInterface} from 'readline'; | import {createInterface as createReadlineInterface} from 'readline'; | ||||||
| import { DocumentAccessor, UserAccessor } from './model/mod'; | import { DocumentAccessor, UserAccessor } from './model/mod'; | ||||||
|  | import { createMangaWatcher } from './diff/watcher/manga_watcher'; | ||||||
| 
 | 
 | ||||||
| class ServerApplication{ | class ServerApplication{ | ||||||
|     readonly userController: UserAccessor; |     readonly userController: UserAccessor; | ||||||
|  | @ -21,7 +22,7 @@ class ServerApplication{ | ||||||
|     readonly diffManger; |     readonly diffManger; | ||||||
|     readonly app: Koa; |     readonly app: Koa; | ||||||
|     private index_html:Buffer; |     private index_html:Buffer; | ||||||
|     constructor(userController: UserAccessor,documentController:DocumentAccessor){ |     private constructor(userController: UserAccessor,documentController:DocumentAccessor){ | ||||||
|         this.userController = userController; |         this.userController = userController; | ||||||
|         this.documentController = documentController; |         this.documentController = documentController; | ||||||
|         this.diffManger = new DiffManager(documentController); |         this.diffManger = new DiffManager(documentController); | ||||||
|  | @ -49,7 +50,7 @@ class ServerApplication{ | ||||||
|         app.use(createUserMiddleWare(this.userController)); |         app.use(createUserMiddleWare(this.userController)); | ||||||
|          |          | ||||||
|         let diff_router = createDiffRouter(this.diffManger); |         let diff_router = createDiffRouter(this.diffManger); | ||||||
|         this.diffManger.register("manga","testdata"); |         this.diffManger.register("manga",createMangaWatcher()); | ||||||
|         let router = new Router(); |         let router = new Router(); | ||||||
|          |          | ||||||
|         router.use('/api/diff',diff_router.routes()); |         router.use('/api/diff',diff_router.routes()); | ||||||
|  |  | ||||||
							
								
								
									
										51
									
								
								src/util/configRW.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/util/configRW.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | ||||||
|  | import {readFileSync, existsSync, writeFileSync, promises as fs} from 'fs'; | ||||||
|  | import {validate} from 'jsonschema'; | ||||||
|  | 
 | ||||||
|  | export class ConfigManager<T>{ | ||||||
|  |     path:string; | ||||||
|  |     default_config: T; | ||||||
|  |     config: T| null; | ||||||
|  |     schema:object; | ||||||
|  |     constructor(path:string,default_config:T,schema:object){ | ||||||
|  |         this.path = path; | ||||||
|  |         this.default_config = default_config; | ||||||
|  |         this.config = null; | ||||||
|  |         this.schema = schema; | ||||||
|  |     } | ||||||
|  |     get_config_file(): T{ | ||||||
|  |         if(this.config !== null) return this.config; | ||||||
|  |         this.config = {...this.read_config_file()}; | ||||||
|  |         return this.config; | ||||||
|  |     } | ||||||
|  |     private emptyToDefault(target:T){ | ||||||
|  |         let occur = false; | ||||||
|  |         for(const key in this.default_config){ | ||||||
|  |             if(key === undefined || key in target){ | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             target[key] = this.default_config[key]; | ||||||
|  |             occur = true; | ||||||
|  |         } | ||||||
|  |         return occur; | ||||||
|  |     } | ||||||
|  |     read_config_file():T{ | ||||||
|  |         if(!existsSync(this.path)){ | ||||||
|  |             writeFileSync(this.path,JSON.stringify(this.default_config)); | ||||||
|  |             return this.default_config; | ||||||
|  |         } | ||||||
|  |         const ret = JSON.parse(readFileSync(this.path,{encoding:"utf8"})); | ||||||
|  |         if(this.emptyToDefault(ret)){ | ||||||
|  |             writeFileSync(this.path,JSON.stringify(ret)); | ||||||
|  |         } | ||||||
|  |         const result = validate(ret,this.schema); | ||||||
|  |         if(!result.valid){ | ||||||
|  |             throw new Error(result.toString()); | ||||||
|  |         } | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |     async write_config_file(new_config:T){ | ||||||
|  |         this.config = new_config; | ||||||
|  |         await fs.writeFile(`${this.path}.temp`,JSON.stringify(new_config)); | ||||||
|  |         await fs.rename(`${this.path}.temp`,this.path); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,22 +0,0 @@ | ||||||
| "use strict"; |  | ||||||
| Object.defineProperty(exports, "__esModule", { value: true }); |  | ||||||
| exports.check_type = void 0; |  | ||||||
| function check_type(obj, check_proto) { |  | ||||||
|     for (const it in check_proto) { |  | ||||||
|         let defined = check_proto[it]; |  | ||||||
|         if (defined === undefined) |  | ||||||
|             return false; |  | ||||||
|         defined = defined.trim(); |  | ||||||
|         if (defined.endsWith("[]")) { |  | ||||||
|             if (!(obj[it] instanceof Array)) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if (defined !== typeof obj[it]) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| exports.check_type = check_type; |  | ||||||
| ; |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue