/** * Wechaty Open Source Software - https://github.com/wechaty * * @copyright 2016 Huan LI (李卓桓) , and * Wechaty Contributors . * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import path from 'path' import os from 'os' import fs from 'fs' import semverPkg from 'semver' import type * as PUPPET from '@juzi/wechaty-puppet' import { FlashStore } from 'flash-store' import { VERSION, log, } from '../config.js' import { WECHATY_PUPPET_SERVICE_DISABLE_EVENT_CACHE } from '../env-vars.js' const { major, minor } = semverPkg interface PayloadStoreOptions { token: string } interface StoreRoomMemberPayload { [roomMemberContactId: string]: PUPPET.payloads.RoomMember } class PayloadStore { // public message? : LRU public contact? : FlashStore public roomMember? : FlashStore public room? : FlashStore public tag? : FlashStore public tagGroup? : FlashStore public miscellaneous?: FlashStore protected storeDir: string protected accountId?: string constructor (private options: PayloadStoreOptions) { log.verbose('PayloadStore', 'constructor(%s)', JSON.stringify(options)) this.storeDir = path.join( os.homedir(), '.wechaty', 'wechaty-puppet-service', this.options.token, `v${major(VERSION)}.${minor(VERSION)}`, ) log.silly('PayloadStore', 'constructor() storeDir: "%s"', this.storeDir) if (!fs.existsSync(this.storeDir)) { fs.mkdirSync(this.storeDir, { recursive: true }) } if (!WECHATY_PUPPET_SERVICE_DISABLE_EVENT_CACHE()) { this.miscellaneous = new FlashStore(path.join(this.storeDir, 'miscellaneous')) // account-free data } } /** * When starting the store, we need to know the accountId * so that we can save the payloads under a specific account folder. */ async start (accountId: string): Promise { log.verbose('PayloadStore', 'start(%s)', accountId) if (this.accountId) { throw new Error('PayloadStore should be stop() before start() again.') } this.accountId = accountId const accountDir = path.join(this.storeDir, accountId) if (!fs.existsSync(accountDir)) { fs.mkdirSync(accountDir, { recursive: true }) } this.contact = new FlashStore(path.join(accountDir, 'contact-payload')) this.roomMember = new FlashStore(path.join(accountDir, 'room-member-payload')) this.room = new FlashStore(path.join(accountDir, 'room-payload')) this.tag = new FlashStore(path.join(accountDir, 'tag-payload')) this.tagGroup = new FlashStore(path.join(accountDir, 'tag-group')) /** * LRU * * Huan(202108): the Wechaty Puppet has LRU cache already, * there's no need to do it again. * * We can focus on providing a persistent store for the performance. */ // const lruOptions: LRU.Options = { // dispose (key, val) { // log.silly('PayloadStore', `constructor() lruOptions.dispose(${key}, ${JSON.stringify(val)})`) // }, // max : 1000, // 1000 messages // maxAge : 60 * 60 * 1000, // 1 hour // } // this.message = new LRU(lruOptions) } async stop (): Promise { log.verbose('PayloadStore', 'stop()') const contactStore = this.contact const roomMemberStore = this.roomMember const roomStore = this.room const tagStore = this.tag const tagGroupStore = this.tagGroup /** * Huan(202108): we must set all the instances of the store to underfined * in the current event loop as soon as possible * to prevent the future store calls. */ this.contact = undefined this.roomMember = undefined this.room = undefined this.tag = undefined this.tagGroup = undefined // LRU // this.message = undefined // clear accountId this.accountId = undefined await contactStore?.close() await roomMemberStore?.close() await roomStore?.close() await tagStore?.close() await tagGroupStore?.close() } async destroy (): Promise { log.verbose('PayloadStore', 'destroy()') if (this.accountId) { throw new Error('Can not destroy() a start()-ed store. Call stop() to stop it first') } await this.miscellaneous?.close() /** * Huan(202108): `fs.rm` was introduced from Node.js v14.14 * https://nodejs.org/api/fs.html#fs_fspromises_rm_path_options */ await fs.promises.rmdir(this.storeDir, { // force: true, recursive: true, }) } } export { PayloadStore }