import { inject , injectable } from 'inversify' ; import * as apid from '../../../../api' ; import * as mapid from '../../../../node_modules/mirakurun/api' ; import Channel from '../../../db/entities/Channel' ; import Program from '../../../db/entities/Program' ; import Reserve from '../../../db/entities/Reserve' ; import DateUtil from '../../../util/DateUtil' ; import StrUtil from '../../../util/StrUtil' ; import Util from '../../../util/Util' ; import IChannelDB from '../../db/IChannelDB' ; import IProgramDB , { ProgramWithOverlap } from '../../db/IProgramDB' ; import IReserveDB , { IFindTimeRangesOption , IReserveTimeOption } from '../../db/IReserveDB' ; import IRuleDB , { RuleWithCnt } from '../../db/IRuleDB' ; import IReserveEvent , { IReserveUpdateValues } from '../../event/IReserveEvent' ; import IConfigFile from '../../IConfigFile' ; import IConfiguration from '../../IConfiguration' ; import IExecutionManagementModel from '../../IExecutionManagementModel' ; import ILogger from '../../ILogger' ; import ILoggerModel from '../../ILoggerModel' ; import IReserveOptionChecker from '../IReserveOptionChecker' ; import IReservationManageModel from './IReservationManageModel' ; import Tuner from './Tuner' ; interface ReserveDiffData { reserve : Reserve ; isChecked : boolean ; } @ injectable ( ) class ReservationManageModel implements IReservationManageModel { private log : ILogger ; private config : IConfigFile ; private executeManagementModel : IExecutionManagementModel ; private optionChecker : IReserveOptionChecker ; private reserveDB : IReserveDB ; private channelDB : IChannelDB ; private programDB : IProgramDB ; private ruleDB : IRuleDB ; private reserveEvent : IReserveEvent ; private tuners : Tuner [ ] = [ ] ; private broadcastStatus : apid . BroadcastStatus = { GR : false , BS : false , CS : false , SKY : false , } ; constructor ( @ inject ( 'ILoggerModel' ) logger , @ inject ( 'IConfiguration' ) configuration , @ inject ( 'IExecutionManagementModel' ) executeManagementModel , @ inject ( 'IReserveOptionChecker' ) optionChecker , @ inject ( 'IReserveDB' ) reserveDB , @ inject ( 'IChannelDB' ) channelDB , @ inject ( 'IProgramDB' ) programDB , @ inject ( 'IRuleDB' ) ruleDB , @ inject ( 'IReserveEvent' ) reserveEvent ) { this . log = logger . getLogger ( ) ; this . config = configuration . getConfig ( ) ; this . executeManagementModel = executeManagementModel ; this . optionChecker = optionChecker ; this . reserveDB = reserveDB ; this . channelDB = channelDB ; this . programDB = programDB ; this . ruleDB = ruleDB ; this . reserveEvent = reserveEvent ; } param tuners public setTuners ( tuners ) { this . tuners = tuners . map ( tuner => { for ( const key in this . broadcastStatus ) { if ( tuner . types . indexOf ( < mapid . ChannelType > key ) !== - 1 ) { ( < any > this . broadcastStatus ) [ key ] = true ; } } return new Tuner ( tuner ) ; } ) ; } return public getBroadcastStatus ( ) { return this . broadcastStatus ; } /**
     * 手動予約追加
     */ public async add ( option ) { this . log . system . info ( 'add reservation' + ( typeof option . programId !== 'undefined' ? `: ${ option . programId }` : '' ) ) ; if ( this . checkManualReserveOption ( option ) === false ) { this . log . system . error ( 'add reservation option error' ) ; throw new Error ( 'AddReservationOptionError' ) ; } const exeId = await this . executeManagementModel . getExecution ( ReservationManageModel . ADD_RESERVE_PRIORITY ) ; const finalize = ( ) => { this . executeManagementModel . unLockExecution ( exeId ) ; } ; const newReserve = new Reserve ( ) ; newReserve . updateTime = new Date ( ) . getTime ( ) ; if ( typeof option . programId === 'undefined' ) { if ( typeof option . timeSpecifiedOption === 'undefined' ) { this . log . system . error ( 'time specified option error' ) ; finalize ( ) ; throw new Error ( 'TimeSpecifiedOptionIsUndefined' ) ; } if ( typeof option . timeSpecifiedOption . name === 'undefined' ) { finalize ( ) ; this . log . system . error ( 'name is undefined' ) ; throw new Error ( 'NameIsUndefinedError' ) ; } if ( option . timeSpecifiedOption . endAt <= new Date ( ) . getTime ( ) ) { finalize ( ) ; this . log . system . error ( 'timeSpecifiedOption error' ) ; throw new Error ( 'TimeSpecifiedOptionError' ) ; } const oldReserve = await this . reserveDB . findTimeSpecification ( option . timeSpecifiedOption ) . catch ( err => { finalize ( ) ; this . log . system . error ( 'get old reservation error' ) ; throw err ; } ) ; if ( oldReserve !== null ) { finalize ( ) ; this . log . system . error ( 'conflict add reservation' ) ; throw new Error ( 'AddReservationConflictError' ) ; } const channel = await this . channelDB . findId ( option . timeSpecifiedOption . channelId ) . catch ( err => { finalize ( ) ; if ( typeof option . timeSpecifiedOption !== 'undefined' ) { this . log . system . error ( `channelId find error: ${ option . timeSpecifiedOption . channelId }` ) ; } this . log . system . error ( err ) ; throw new Error ( 'ReservationManageModelFindChannelError' ) ; } ) ; if ( channel === null ) { finalize ( ) ; this . log . stream . error ( `channelId is not found: ${ option . timeSpecifiedOption . channelId }` ) ; throw new Error ( 'eservationManageModelFindChannelIsNotFound' ) ; } newReserve . isTimeSpecified = true ; newReserve . name = StrUtil . toDBStr ( option . timeSpecifiedOption . name ) ; newReserve . halfWidthName = StrUtil . toHalf ( newReserve . name ) ; newReserve . startAt = option . timeSpecifiedOption . startAt ; newReserve . endAt = option . timeSpecifiedOption . endAt ; newReserve . channelId = channel . id ; newReserve . channel = channel . channel ; newReserve . channelType = channel . channelType ; } else { try { const r = await this . reserveDB . findProgramId ( option . programId ) ; if ( r . length > 0 ) { finalize ( ) ; this . log . system . error ( `program is reserved: ${ option . programId }` ) ; throw new Error ( 'ReservationManageModelReservedError' ) ; } } catch ( err ) { finalize ( ) ; this . log . system . error ( 'check reserved programs error' ) ; throw new Error ( 'ReservationManageModelCheckReservedProgramError' ) ; } let program = null ; try { program = await this . programDB . findId ( option . programId ) ; } catch ( err ) { this . log . system . error ( `program is not found: ${ option . programId }` ) ; finalize ( ) ; throw err ; } if ( program === null ) { finalize ( ) ; this . log . system . info ( `program is not found: ${ option . programId }` ) ; throw new Error ( 'ProgramIsNotFound' ) ; } this . setProgramToReserve ( newReserve , program ) ; } newReserve . allowEndLack = option . allowEndLack ; if ( typeof option . tags !== 'undefined' ) { newReserve . tags = JSON . stringify ( option . tags ) ; } if ( typeof option . saveOption !== 'undefined' ) { this . setSaveOptionToReserve ( newReserve , option . saveOption ) ; } if ( typeof option . encodeOption !== 'undefined' ) { this . setEncodeOptionToReserve ( newReserve , option . encodeOption ) ; } let reserves = [ ] ; try { reserves = await this . reserveDB . findTimeRanges ( { times : [ { startAt : newReserve . startAt , endAt : newReserve . endAt , } , ] , hasSkip : false , hasConflict : false , hasOverlap : false , } ) ; } catch ( err ) { finalize ( ) ; this . log . system . error ( 'reservation get error' ) ; throw err ; } reserves . push ( newReserve ) ; const newReserves = this . createReserves ( reserves ) ; for ( const reserve of newReserves ) { if ( reserve . isConflict ) { finalize ( ) ; this . log . system . error ( `program is conflict: ${ option . programId }` ) ; throw new Error ( 'ReservationManageModelAddReserveConflict' ) ; } } let insertedId ; try { insertedId = await this . reserveDB . insertOnce ( newReserve ) ; newReserve . id = insertedId ; } catch ( err ) { finalize ( ) ; this . log . system . info ( `add reservation error: ${ option . programId }` ) ; throw new Error ( 'ReservationManageModelAddReserveError' ) ; } finalize ( ) ; this . log . system . info ( `successful add reservation: ${ insertedId }` + ( typeof option . programId !== 'undefined' ? `, ${ option . programId }` : '' ) ) ; this . reserveEvent . emitUpdated ( { insert : [ newReserve ] , isSuppressLog : false , } ) ; return insertedId ; } param option return private checkManualReserveOption ( option , isEdit = false ) { let isFail = false ; isFail = typeof option . encodeOption !== 'undefined' && this . optionChecker . checkEncodeOption ( option . encodeOption ) === false ; if ( isEdit === false ) { isFail = typeof option . programId === 'undefined' && typeof option . timeSpecifiedOption === 'undefined' ; } return ! isFail ; } param reserve param program private setProgramToReserve ( reserve , program ) { reserve . programId = program . id ; reserve . programUpdateTime = program . updateTime ; reserve . channelId = program . channelId ; reserve . channel = program . channel ; reserve . channelType = program . channelType ; reserve . startAt = program . startAt ; reserve . endAt = program . endAt ; reserve . name = program . name ; reserve . shortName = program . shortName ; reserve . halfWidthName = program . halfWidthName ; reserve . description = program . description ; reserve . halfWidthDescription = program . halfWidthDescription ; reserve . extended = program . extended ; reserve . halfWidthExtended = program . halfWidthExtended ; reserve . rawExtended = program . rawExtended ; reserve . rawHalfWidthExtended = program . rawHalfWidthExtended ; reserve . genre1 = program . genre1 ; reserve . subGenre1 = program . subGenre1 ; reserve . genre2 = program . genre2 ; reserve . subGenre2 = program . subGenre2 ; reserve . genre3 = program . genre3 ; reserve . subGenre3 = program . subGenre3 ; reserve . videoType = program . videoType ; reserve . videoResolution = program . videoResolution ; reserve . videoComponentType = program . videoComponentType ; reserve . videoStreamContent = program . videoStreamContent ; reserve . audioSamplingRate = program . audioSamplingRate ; reserve . audioComponentType = program . audioComponentType ; } param reserve param saveOption private setSaveOptionToReserve ( reserve , saveOption ) { if ( typeof saveOption === 'undefined' ) { reserve . parentDirectoryName = null ; reserve . directory = null ; reserve . recordedFormat = null ; return ; } reserve . parentDirectoryName = typeof saveOption . parentDirectoryName === 'undefined' ? null : saveOption . parentDirectoryName ; reserve . directory = typeof saveOption . directory === 'undefined' ? null : saveOption . directory ; reserve . recordedFormat = typeof saveOption . recordedFormat === 'undefined' ? null : saveOption . recordedFormat ; } param reserve param encodeOption private setEncodeOptionToReserve ( reserve , encodeOption ) { if ( typeof encodeOption === 'undefined' ) { reserve . encodeMode1 = null ; reserve . encodeMode2 = null ; reserve . encodeMode3 = null ; reserve . encodeParentDirectoryName1 = null ; reserve . encodeParentDirectoryName2 = null ; reserve . encodeParentDirectoryName3 = null ; reserve . encodeDirectory1 = null ; reserve . encodeDirectory2 = null ; reserve . encodeDirectory3 = null ; reserve . isDeleteOriginalAfterEncode = false ; return ; } reserve . encodeMode1 = typeof encodeOption . mode1 === 'undefined' ? null : encodeOption . mode1 ; reserve . encodeMode2 = typeof encodeOption . mode2 === 'undefined' ? null : encodeOption . mode2 ; reserve . encodeMode3 = typeof encodeOption . mode3 === 'undefined' ? null : encodeOption . mode3 ; reserve . encodeParentDirectoryName1 = typeof encodeOption . encodeParentDirectoryName1 === 'undefined' ? null : encodeOption . encodeParentDirectoryName1 ; reserve . encodeParentDirectoryName2 = typeof encodeOption . encodeParentDirectoryName2 === 'undefined' ? null : encodeOption . encodeParentDirectoryName2 ; reserve . encodeParentDirectoryName3 = typeof encodeOption . encodeParentDirectoryName3 === 'undefined' ? null : encodeOption . encodeParentDirectoryName3 ; reserve . encodeDirectory1 = typeof encodeOption . directory1 === 'undefined' ? null : encodeOption . directory1 ; reserve . encodeDirectory2 = typeof encodeOption . directory2 === 'undefined' ? null : encodeOption . directory2 ; reserve . encodeDirectory3 = typeof encodeOption . directory3 === 'undefined' ? null : encodeOption . directory3 ; reserve . isDeleteOriginalAfterEncode = encodeOption . isDeleteOriginalAfterEncode ; } /**
     * 手動予約の更新
     */ public async update ( reserveId , isSuppressLog = false ) { const exeId = await this . executeManagementModel . getExecution ( ReservationManageModel . UPDATE_RESERVE_PRIORITY ) ; const finalize = ( ) => { this . executeManagementModel . unLockExecution ( exeId ) ; } ; if ( isSuppressLog === false ) { this . log . system . info ( `update reservation: ${ reserveId }` ) ; } const oldReserve = await this . reserveDB . findId ( reserveId ) . catch ( err => { finalize ( ) ; this . log . system . error ( `get reservation error: ${ reserveId }` ) ; throw err ; } ) ; if ( oldReserve === null ) { finalize ( ) ; this . log . system . error ( `reservation is not  is not found: ${ reserveId }` ) ; throw new Error ( 'ReservationIsNotFound' ) ; } if ( oldReserve . programId === null ) { finalize ( ) ; this . log . system . warn ( `reservation is not program id reservation: ${ reserveId }` ) ; return ; } const newProgram = await this . programDB . findId ( oldReserve . programId ) . catch ( err => { finalize ( ) ; this . log . system . error ( `get program error: ${ reserveId }` ) ; throw err ; } ) ; if ( newProgram === null ) { finalize ( ) ; this . log . system . warn ( `program is not found: ${ reserveId }` ) ; return ; } if ( oldReserve . programUpdateTime === newProgram . updateTime ) { finalize ( ) ; if ( isSuppressLog === false ) { this . log . system . info ( `no update reservation: ${ reserveId }` ) ; } return ; } const newReserve = Object . assign ( { } , oldReserve ) ; this . setProgramToReserve ( newReserve , newProgram ) ; newReserve . updateTime = oldReserve . updateTime ; newReserve . isConflict = false ; const diff = await this . createDiff ( { times : [ { startAt : oldReserve . startAt , endAt : oldReserve . endAt , } , { startAt : newReserve . startAt , endAt : newReserve . endAt , } , ] , hasSkip : false , hasConflict : true , hasOverlap : false , excludeReserveId : reserveId , } , [ newReserve ] , [ oldReserve ] , isSuppressLog ) . catch ( err => { finalize ( ) ; throw err ; } ) ; finalize ( ) ; if ( isSuppressLog === false ) { this . log . system . info ( `successful update reservation: ${ reserveId }` ) ; } this . reserveEvent . emitUpdated ( diff ) ; } param ruleId param isSuppressLog param isFirstUpdate public async updateRule ( ruleId , isSuppressLog = false , isFirstUpdate = false ) { const exeId = await this . executeManagementModel . getExecution ( ReservationManageModel . RULE_UPDATE_RESERVE_PRIORITY ) ; const finalize = ( ) => { this . executeManagementModel . unLockExecution ( exeId ) ; } ; if ( isSuppressLog === false ) { this . log . system . info ( `update rule reservation: ${ ruleId }` ) ; } const rule = await this . ruleDB . findId ( ruleId , true ) . catch ( err => { this . log . system . error ( `get rule error: ${ ruleId }` ) ; this . log . system . error ( err ) ; throw err ; } ) ; /**
         * 更新前のルール予約と更新後のルール予約による他の予約の影響を計算する必要があるので、
         * 古いルール予約と新しいルール予約の番組情報を取得し、
         * reserveDB.findTimeRanges で該当する予約を取り出す
         */ const times = [ ] ; const oldRuleReserves = await this . reserveDB . findRuleId ( { ruleId : ruleId , hasSkip : true , hasConflict : true , hasOverlap : true , } ) . catch ( err => { finalize ( ) ; this . log . system . error ( `find rule reservation error: ${ ruleId }` ) ; this . log . system . error ( err ) ; throw err ; } ) ; const newRulePrograms = rule !== null && rule . reserveOption . enable === true && rule . isTimeSpecification === false ? await this . programDB . findRule ( { searchOption : rule . searchOption , reserveOption : rule . reserveOption , } ) . catch ( err => { finalize ( ) ; this . log . system . error ( `find rule error: ${ ruleId }` ) ; this . log . system . error ( err ) ; throw err ; } ) : [ ] ; const newRuleReserves = [ ] ; if ( rule !== null && rule . reserveOption . enable === true ) { const oldRuleIndex = { } ; for ( const old of oldRuleReserves ) { oldRuleIndex [ this . createReserveKey ( old ) ] = old ; } const updateTime = new Date ( ) . getTime ( ) ; if ( rule . isTimeSpecification === true ) { if ( typeof rule . searchOption . keyword === 'undefined' || typeof rule . searchOption . channelIds === 'undefined' || typeof rule . searchOption . times === 'undefined' ) { finalize ( ) ; this . log . system . error ( `rule search option error: ${ ruleId }` ) ; throw new Error ( 'RuleSearchOptionError' ) ; } const baseTime = new Date ( DateUtil . format ( new Date ( ) , 'yyyy/MM/dd 00:00:00 +0900' ) ) . getTime ( ) ; for ( const time of rule . searchOption . times ) { if ( typeof time . start === 'undefined' || typeof time . range === 'undefined' ) { throw new Error ( 'RuleSearchTimesOptionError' ) ; } const weeks = [ ( time . week & 0x01 ) !== 0 , ( time . week & 0x02 ) !== 0 , ( time . week & 0x04 ) !== 0 , ( time . week & 0x08 ) !== 0 , ( time . week & 0x10 ) !== 0 , ( time . week & 0x20 ) !== 0 , ( time . week & 0x40 ) !== 0 , ] ; for ( let i = 0 ; i < 8 ; i ++ ) { const startAt = baseTime + 1000 * 60 * 60 * 24 * i + time . start * 1000 ; const endAt = baseTime + 1000 * 60 * 60 * 24 * i + ( time . start + time . range ) * 1000 ; if ( endAt < updateTime || weeks [ new Date ( startAt ) . getDay ( ) ] === false ) { continue ; } times . push ( { startAt : startAt , endAt : endAt , } ) ; } } for ( const channelId of rule . searchOption . channelIds ) { let channel ; try { channel = await this . channelDB . findId ( channelId ) ; } catch ( err ) { this . log . system . error ( `get channel id error: ${ channelId }` ) ; continue ; } if ( channel === null ) { this . log . system . error ( `channel id is not found: ${ channelId }` ) ; continue ; } for ( const time of times ) { const newReserve = new Reserve ( ) ; newReserve . isTimeSpecified = true ; newReserve . name = StrUtil . toDBStr ( rule . searchOption . keyword ) ; newReserve . halfWidthName = StrUtil . toHalf ( newReserve . name ) ; newReserve . updateTime = updateTime ; newReserve . startAt = time . startAt ; newReserve . endAt = time . endAt ; newReserve . channelId = channelId ; newReserve . channel = channel . channel ; newReserve . channelType = channel . channelType ; this . setProgramToRuleReserve ( newReserve , null , < RuleWithCnt > rule , updateTime ) ; const oldReserve = oldRuleIndex [ this . createReserveKey ( newReserve ) ] ; if ( typeof oldReserve !== 'undefined' ) { newReserve . isSkip = oldReserve . isSkip ; newReserve . isIgnoreOverlap = oldReserve . isIgnoreOverlap ; newReserve . isOverlap = oldReserve . isOverlap ; } newRuleReserves . push ( newReserve ) ; } } } else if ( newRulePrograms . length !== 0 ) { for ( const program of newRulePrograms ) { const newReserve = new Reserve ( ) ; this . setProgramToRuleReserve ( newReserve , program , < RuleWithCnt > rule , updateTime ) ; const oldReserve = oldRuleIndex [ this . createReserveKey ( newReserve ) ] ; if ( typeof oldReserve !== 'undefined' ) { newReserve . isSkip = oldReserve . isSkip ; newReserve . isIgnoreOverlap = oldReserve . isIgnoreOverlap ; newReserve . isOverlap = oldReserve . isIgnoreOverlap === true ? oldReserve . isOverlap : program . overlap ; } newRuleReserves . push ( newReserve ) ; times . push ( { startAt : program . startAt , endAt : program . endAt , } ) ; } } } for ( const reserve of oldRuleReserves ) { times . push ( { startAt : reserve . startAt , endAt : reserve . endAt } ) ; } if ( isFirstUpdate === true && rule ?. isTimeSpecification === true ) { for ( const r of oldRuleReserves ) { r . ruleUpdateCnt = - 1 ; } } const diff = await this . createDiff ( { times : times , hasSkip : false , hasConflict : true , hasOverlap : false , excludeRuleId : ruleId , } , newRuleReserves , oldRuleReserves , isSuppressLog ) . catch ( err => { finalize ( ) ; throw err ; } ) ; finalize ( ) ; if ( isSuppressLog === false ) { this . log . system . info ( `successful update rule reservation: ${ ruleId }` ) ; } this . reserveEvent . emitUpdated ( diff ) ; } param reserve param program param rule param updateTime private setProgramToRuleReserve ( reserve , program , rule , updateTime ) { reserve . ruleId = rule . id ; reserve . ruleUpdateCnt = rule . updateCnt ; reserve . updateTime = updateTime ; reserve . allowEndLack = rule . reserveOption . allowEndLack ; if ( typeof rule . reserveOption . tags !== 'undefined' ) { reserve . tags = JSON . stringify ( rule . reserveOption . tags ) ; } if ( program !== null ) { reserve . isOverlap = program . overlap ; this . setProgramToReserve ( reserve , program ) ; } if ( typeof rule . saveOption !== 'undefined' ) { this . setSaveOptionToReserve ( reserve , rule . saveOption ) ; } if ( typeof rule . encodeOption !== 'undefined' ) { this . setEncodeOptionToReserve ( reserve , rule . encodeOption ) ; } } param findOption param addNewReserves param addOldReserves param isSuppressLog return private async createDiff ( findOption , addNewReserves , addOldReserves , isSuppressLog ) { const baseReserves = await this . reserveDB . findTimeRanges ( findOption ) . catch ( err => { this . log . system . error ( 'reserve get error' ) ; throw err ; } ) ; let newReserves = this . copyReserveArray ( addNewReserves ) ; Array . prototype . push . apply ( newReserves , baseReserves ) ; newReserves = this . createReserves ( newReserves ) ; const oldReserves = this . copyReserveArray ( addOldReserves ) ; Array . prototype . push . apply ( oldReserves , baseReserves ) ; const diff = this . createReservesDiff ( oldReserves , newReserves , isSuppressLog ) ; if ( isSuppressLog === false ) { this . log . system . info ( { insert : typeof diff . insert === 'undefined' ? 0 : diff . insert . length , update : typeof diff . update === 'undefined' ? 0 : diff . update . length , delete : typeof diff . delete === 'undefined' ? 0 : diff . delete . length , } ) ; } await this . reserveDB . updateMany ( diff ) . catch ( err => { this . log . system . error ( 'reserves update many error' ) ; throw err ; } ) ; return diff ; } param src return private copyReserveArray ( src ) { const newReserves = [ ] ; for ( const reserve of src ) { newReserves . push ( Object . assign ( { } , reserve ) ) ; } return newReserves ; } param oldReserves param newReserves param isSuppressLog return private createReservesDiff ( oldReserves , newReserves , isSuppressLog ) { const diff = { isSuppressLog : isSuppressLog , } ; diff . insert = [ ] ; diff . update = [ ] ; diff . delete = [ ] ; const idIndex = { } ; const timeIndex = { } ; for ( const reserve of oldReserves ) { if ( reserve . programId === null ) { timeIndex [ this . createReserveKey ( reserve ) ] = { reserve : reserve , isChecked : false , } ; } else { idIndex [ this . createReserveKey ( reserve ) ] = { reserve : reserve , isChecked : false , } ; } } for ( const newReserve of newReserves ) { const key = this . createReserveKey ( newReserve ) ; if ( typeof idIndex [ key ] !== 'undefined' ) { idIndex [ key ] . isChecked = true ; const oldReserve = idIndex [ key ] . reserve ; if ( this . checkProgramIdReserveDiff ( oldReserve , newReserve ) ) { newReserve . id = oldReserve . id ; diff . update . push ( newReserve ) ; } } else if ( typeof timeIndex [ key ] !== 'undefined' ) { timeIndex [ key ] . isChecked = true ; const oldReserve = timeIndex [ key ] . reserve ; if ( this . checkTimeRuleReserveDiff ( oldReserve , newReserve ) ) { newReserve . id = oldReserve . id ; diff . update . push ( newReserve ) ; } } else { diff . insert . push ( newReserve ) ; } } for ( const key in idIndex ) { if ( idIndex [ key ] . isChecked === false ) { diff . delete . push ( idIndex [ key ] . reserve ) ; } } for ( const key in timeIndex ) { if ( timeIndex [ key ] . isChecked === false ) { diff . delete . push ( timeIndex [ key ] . reserve ) ; } } return diff ; } param reserve return private createReserveKey ( reserve ) { return ( ( reserve . programId === null ? `${ reserve . startAt }-${ reserve . endAt }-${ reserve . channel }` : `${ reserve . programId }` ) + `-${ reserve . ruleId }` ) ; } param oldReserves param newReserve return private checkProgramIdReserveDiff ( oldReserve , newReserve ) { return ( oldReserve . programId === newReserve . programId && ( oldReserve . programUpdateTime !== newReserve . programUpdateTime || oldReserve . ruleUpdateCnt !== newReserve . ruleUpdateCnt || oldReserve . isSkip !== newReserve . isSkip || oldReserve . isConflict !== newReserve . isConflict || oldReserve . isOverlap !== newReserve . isOverlap ) ) ; } param oldReserve param newReserve return private checkTimeRuleReserveDiff ( oldReserve , newReserve ) { return ( oldReserve . ruleId !== null && newReserve . ruleId !== null && ( oldReserve . ruleUpdateCnt !== newReserve . ruleUpdateCnt || oldReserve . isSkip !== newReserve . isSkip || oldReserve . isConflict !== newReserve . isConflict || oldReserve . isOverlap !== newReserve . isOverlap ) ) ; } param isFirstUpdate public async updateAll ( isFirstUpdate = false ) { this . log . system . info ( 'all reservation update start' ) ; const isSuppressLog = this . config . isSuppressReservesUpdateAllLog ; const manualIds = await this . reserveDB . getManualIds ( { hasTimeReserve : false } ) . catch ( err => { this . log . system . error ( 'get manual reservation ids error' ) ; throw err ; } ) ; const ruleIds = await this . ruleDB . getIds ( ) . catch ( err => { this . log . system . error ( 'get rule ids error' ) ; throw err ; } ) ; for ( const manualId of manualIds ) { await this . update ( manualId , isSuppressLog ) . catch ( err => { this . log . system . error ( err ) ; } ) ; await Util . sleep ( 10 ) ; } for ( const ruleId of ruleIds ) { await this . updateRule ( ruleId , isSuppressLog , isFirstUpdate ) . catch ( err => { this . log . system . error ( err ) ; } ) ; await Util . sleep ( 10 ) ; } this . log . system . info ( 'all reservation update finish' ) ; } param reserveId public async cancel ( reserveId ) { const exeId = await this . executeManagementModel . getExecution ( ReservationManageModel . CANCEL_RESERVE_PRIORITY ) ; const finalize = ( ) => { this . executeManagementModel . unLockExecution ( exeId ) ; } ; this . log . system . info ( `cancel reservation: ${ reserveId }` ) ; const cancelReserve = await this . reserveDB . findId ( reserveId ) . catch ( err => { finalize ( ) ; this . log . system . error ( `get reservation error: ${ reserveId }` ) ; throw err ; } ) ; if ( cancelReserve === null ) { finalize ( ) ; this . log . system . error ( `reservation is not found: ${ reserveId }` ) ; throw new Error ( 'ReservationIsNotFound' ) ; } const oldReserves = [ Object . assign ( { } , cancelReserve ) ] ; const newReserves = [ ] ; if ( cancelReserve . ruleId !== null ) { if ( cancelReserve . isOverlap === true ) { cancelReserve . isIgnoreOverlap = false ; cancelReserve . isOverlap = true ; } else { cancelReserve . isSkip = true ; } cancelReserve . isConflict = false ; newReserves . push ( cancelReserve ) ; } const diff = await this . createDiff ( { times : [ { startAt : cancelReserve . startAt , endAt : cancelReserve . endAt , } , ] , hasSkip : false , hasConflict : true , hasOverlap : false , excludeReserveId : reserveId , } , newReserves , oldReserves , false ) . catch ( err => { finalize ( ) ; throw err ; } ) ; finalize ( ) ; this . log . system . info ( `successful cancel reservation: ${ reserveId }` ) ; this . reserveEvent . emitUpdated ( diff ) ; } param reserveId public async removeSkip ( reserveId ) { const exeId = await this . executeManagementModel . getExecution ( ReservationManageModel . REMOVE_SKIP_RESERVE_PRIORITY ) ; const finalize = ( ) => { this . executeManagementModel . unLockExecution ( exeId ) ; } ; this . log . system . info ( `remove skip reservation: ${ reserveId }` ) ; const oldReserve = await this . reserveDB . findId ( reserveId ) . catch ( err => { finalize ( ) ; this . log . system . error ( `get reservation error: ${ reserveId }` ) ; throw err ; } ) ; if ( oldReserve === null ) { finalize ( ) ; this . log . system . error ( `reservation is not found: ${ reserveId }` ) ; throw new Error ( 'ReservationIsNotFound' ) ; } if ( oldReserve . ruleId === null ) { finalize ( ) ; this . log . system . warn ( `reservation is not rule reservation: ${ reserveId }` ) ; return ; } if ( oldReserve . isSkip !== true ) { finalize ( ) ; this . log . system . warn ( `reservation is not skiped: ${ reserveId }` ) ; return ; } const newReserves = Object . assign ( { } , oldReserve ) ; newReserves . isSkip = false ; const diff = await this . createDiff ( { times : [ { startAt : oldReserve . startAt , endAt : oldReserve . endAt , } , ] , hasSkip : false , hasConflict : true , hasOverlap : false , excludeReserveId : reserveId , } , [ newReserves ] , [ oldReserve ] , false ) . catch ( err => { finalize ( ) ; throw err ; } ) ; finalize ( ) ; this . log . system . info ( `successful remove skip reservation: ${ reserveId }` ) ; this . reserveEvent . emitUpdated ( diff ) ; } param reserveId return public async removeOverlap ( reserveId ) { const exeId = await this . executeManagementModel . getExecution ( ReservationManageModel . REMOVE_OVERLAP_RESERVE_PRIORITY ) ; const finalize = ( ) => { this . executeManagementModel . unLockExecution ( exeId ) ; } ; this . log . system . info ( `remove overlap reservation: ${ reserveId }` ) ; const oldReserve = await this . reserveDB . findId ( reserveId ) . catch ( err => { finalize ( ) ; this . log . system . error ( `get reservation error: ${ reserveId }` ) ; throw err ; } ) ; if ( oldReserve === null ) { finalize ( ) ; this . log . system . error ( `reservation is not found: ${ reserveId }` ) ; throw new Error ( 'ReservationIsNotFound' ) ; } if ( oldReserve . ruleId === null ) { finalize ( ) ; this . log . system . warn ( `reservation is not rule reservation: ${ reserveId }` ) ; return ; } if ( oldReserve . isIgnoreOverlap === true && oldReserve . isOverlap === false ) { finalize ( ) ; this . log . system . warn ( `reservation is removed overlap: ${ reserveId }` ) ; return ; } const newReserves = Object . assign ( { } , oldReserve ) ; newReserves . isIgnoreOverlap = true ; newReserves . isOverlap = false ; const diff = await this . createDiff ( { times : [ { startAt : oldReserve . startAt , endAt : oldReserve . endAt , } , ] , hasSkip : false , hasConflict : true , hasOverlap : false , excludeReserveId : reserveId , } , [ newReserves ] , [ oldReserve ] , false ) . catch ( err => { finalize ( ) ; throw err ; } ) ; finalize ( ) ; this . log . system . info ( `successful remove overlap reservation: ${ reserveId }` ) ; this . reserveEvent . emitUpdated ( diff ) ; } param reserveId param option public async edit ( reserveId , option ) { const exeId = await this . executeManagementModel . getExecution ( ReservationManageModel . EDIT_RESERVE_PRIORITY ) ; const finalize = ( ) => { this . executeManagementModel . unLockExecution ( exeId ) ; } ; this . log . system . info ( `edit reservation: ${ reserveId }` ) ; if ( this . checkManualReserveOption ( option , true ) === false ) { this . log . system . error ( 'edit reservation option error' ) ; throw new Error ( 'ReservationEditError' ) ; } const newReserve = await this . reserveDB . findId ( reserveId ) . catch ( err => { finalize ( ) ; this . log . system . error ( `get reservation error: ${ reserveId }` ) ; throw err ; } ) ; if ( newReserve === null ) { finalize ( ) ; this . log . system . error ( `reservation is not found: ${ reserveId }` ) ; throw new Error ( 'ReservationIsNotFound' ) ; } newReserve . allowEndLack = option . allowEndLack ; if ( typeof option . tags !== 'undefined' ) { newReserve . tags = JSON . stringify ( option . tags ) ; } this . setSaveOptionToReserve ( newReserve , option . saveOption ) ; this . setEncodeOptionToReserve ( newReserve , option . encodeOption ) ; await this . reserveDB . updateOnce ( newReserve ) . catch ( err => { finalize ( ) ; this . log . system . error ( `update reservation error: ${ reserveId }` ) ; throw err ; } ) ; finalize ( ) ; this . log . system . info ( `successful edit reservation: ${ reserveId }` ) ; this . reserveEvent . emitUpdated ( { update : [ newReserve ] , isSuppressLog : false , } ) ; } /**
     * 現在時刻より古い予約を削除する
     */ public async cleanup ( ) { const exeId = await this . executeManagementModel . getExecution ( ReservationManageModel . EDIT_RESERVE_PRIORITY ) ; const finalize = ( ) => { this . executeManagementModel . unLockExecution ( exeId ) ; } ; this . log . system . info ( 'start reserves cleanup' ) ; const deleteReserves = await this . reserveDB . findOldTime ( new Date ( ) . getTime ( ) ) . catch ( err => { finalize ( ) ; this . log . system . error ( 'get delete old reservation error' ) ; throw err ; } ) ; await this . reserveDB . updateMany ( { delete : deleteReserves , isSuppressLog : false , } ) . catch ( err => { finalize ( ) ; this . log . system . error ( 'delete old reservation error' ) ; throw err ; } ) ; finalize ( ) ; this . log . system . info ( 'finish reserves cleanup' ) ; this . reserveEvent . emitUpdated ( { delete : deleteReserves , isSuppressLog : false , } ) ; } param matches return private createReserves ( matches ) { matches . sort ( this . sortReserve ) ; const list = [ ] ; const programIdIndex = { } ; for ( let i = 0 ; i < matches . length ; i ++ ) { const matchProgramIdKey = this . getRuleProgramIdKey ( matches [ i ] ) ; if ( matchProgramIdKey !== null ) { if ( typeof programIdIndex [ matchProgramIdKey ] === 'undefined' ) { programIdIndex [ matchProgramIdKey ] = true ; } else { continue ; } } list . push ( { time : matches [ i ] . startAt , isStart : true , idx : i , } ) ; list . push ( { time : matches [ i ] . endAt , isStart : false , idx : i , } ) ; } list . sort ( ( a , b ) => { const time = a . time - b . time ; if ( time !== 0 ) { return time ; } else if ( a . isStart && ! b . isStart ) { return 1 ; } else if ( ! a . isStart && b . isStart ) { return - 1 ; } else { return a . idx - b . idx ; } } ) ; const reserves = [ ] ; for ( const l of list ) { if ( matches [ l . idx ] . isSkip ) { continue ; } if ( l . isStart ) { reserves . push ( { reserve : matches [ l . idx ] , idx : l . idx } ) ; } else { const index = reserves . findIndex ( r => { return r . idx === l . idx ; } ) ; reserves . splice ( index , 1 ) ; } reserves . sort ( ( a , b ) => { return this . sortReserve ( a . reserve , b . reserve ) ; } ) ; this . log . system . debug ( '--------------------' ) ; for ( const r of reserves ) { this . log . system . debug ( < any > { name : r . reserve . name , ruleId : r . reserve . ruleId , } ) ; } for ( let i = 0 ; i < this . tuners . length ; i ++ ) { this . tuners [ i ] . clear ( ) ; } for ( const reserve of reserves ) { if ( matches [ reserve . idx ] . isSkip || matches [ reserve . idx ] . isOverlap ) { continue ; } let isConflict = true ; for ( let i = 0 ; i < this . tuners . length ; i ++ ) { if ( this . tuners [ i ] . add ( matches [ reserve . idx ] ) ) { isConflict = false ; break ; } } if ( isConflict ) { matches [ reserve . idx ] . isConflict = true ; } } } const newReserves = [ ] ; for ( const l of list ) { if ( l . isStart ) { newReserves . push ( matches [ l . idx ] ) ; } } return newReserves . sort ( ( a , b ) => { return a . startAt - b . startAt ; } ) ; } param a param b return private sortReserve ( a , b ) { const aIsManual = a . ruleId === null ; const bIsManual = b . ruleId === null ; if ( aIsManual && bIsManual ) { if ( a . isTimeSpecified === b . isTimeSpecified ) { return a . updateTime - b . updateTime ; } else { return a . isTimeSpecified && ! b . isTimeSpecified ? - 1 : 1 ; } } if ( aIsManual && ! bIsManual ) { return - 1 ; } if ( ! aIsManual && bIsManual ) { return 1 ; } if ( ! aIsManual && ! bIsManual && a . ruleId !== null && b . ruleId !== null ) { return a . ruleId - b . ruleId ; } return 0 ; } param re returns private getRuleProgramIdKey ( re ) { if ( re . programId === null ) { return null ; } return re . ruleId === null ? re . programId . toString ( 10 ) : `${ re . programId . toString ( 10 ) }-${ re . isConflict }-${ re . isOverlap }-${ re . isSkip }` ; } } namespace ReservationManageModel { export const ADD_RESERVE_PRIORITY = 1 ; export const UPDATE_RESERVE_PRIORITY = 1 ; export const RULE_UPDATE_RESERVE_PRIORITY = 1 ; export const CANCEL_RESERVE_PRIORITY = 2 ; export const REMOVE_SKIP_RESERVE_PRIORITY = 2 ; export const REMOVE_OVERLAP_RESERVE_PRIORITY = 2 ; export const EDIT_RESERVE_PRIORITY = 2 ; } export default ReservationManageModel ;