import { Conf, d, g } from "../globals/globals"; import $ from "../platform/$"; import { dict, HOUR } from "../platform/helpers"; /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns * DS104: Avoid inline assignments * DS206: Consider reworking classes to avoid initClass * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md */ export default class DataBoard { static initClass() { this.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'watcherLastModified', 'customTitles']; this.changes = []; } constructor(key, sync, dontClean) { this.onSync = this.onSync.bind(this); this.key = key; this.initData(Conf[this.key]); $.sync(this.key, this.onSync); if (!dontClean) { this.clean(); } if (!sync) { return; } // Chrome also fires the onChanged callback on the current tab, // so we only start syncing when we're ready. var init = () => { $.off(d, '4chanXInitFinished', init); return this.sync = sync; }; $.on(d, '4chanXInitFinished', init); } initData(data) { let boards; this.data = data; if (this.data.boards) { let lastChecked; ({boards, lastChecked} = this.data); this.data['4chan.org'] = {boards, lastChecked}; delete this.data.boards; delete this.data.lastChecked; } return this.data[g.SITE.ID] || (this.data[g.SITE.ID] = { boards: dict() }); } save(change, cb) { change(); DataBoard.changes.push(change); return $.get(this.key, { boards: dict() }, items => { if (!DataBoard.changes.length) { return; } const needSync = ((items[this.key].version || 0) > (this.data.version || 0)); if (needSync) { this.initData(items[this.key]); for (change of DataBoard.changes) { change(); } } DataBoard.changes = []; this.data.version = (this.data.version || 0) + 1; return $.set(this.key, this.data, () => { if (needSync) { this.sync?.(); } return cb?.(); }); }); } forceSync(cb) { return $.get(this.key, { boards: dict() }, items => { if ((items[this.key].version || 0) > (this.data.version || 0)) { this.initData(items[this.key]); for (var change of DataBoard.changes) { change(); } this.sync?.(); } return cb?.(); }); } delete({siteID, boardID, threadID, postID}, cb) { if (!siteID) { siteID = g.SITE.ID; } if (!this.data[siteID]) { return; } return this.save(() => { if (postID) { if (!this.data[siteID].boards[boardID]?.[threadID]) { return; } delete this.data[siteID].boards[boardID][threadID][postID]; return this.deleteIfEmpty({siteID, boardID, threadID}); } else if (threadID) { if (!this.data[siteID].boards[boardID]) { return; } delete this.data[siteID].boards[boardID][threadID]; return this.deleteIfEmpty({siteID, boardID}); } else { return delete this.data[siteID].boards[boardID]; } } , cb); } deleteIfEmpty({siteID, boardID, threadID}) { if (!this.data[siteID]) { return; } if (threadID) { if (!Object.keys(this.data[siteID].boards[boardID][threadID]).length) { delete this.data[siteID].boards[boardID][threadID]; return this.deleteIfEmpty({siteID, boardID}); } } else if (!Object.keys(this.data[siteID].boards[boardID]).length) { return delete this.data[siteID].boards[boardID]; } } set(data, cb) { return this.save(() => { return this.setUnsafe(data); } , cb); } setUnsafe({siteID, boardID, threadID, postID, val}) { if (!siteID) { siteID = g.SITE.ID; } if (!this.data[siteID]) { this.data[siteID] = { boards: dict() }; } if (postID !== undefined) { let base; return (((base = this.data[siteID].boards[boardID] || (this.data[siteID].boards[boardID] = dict())))[threadID] || (base[threadID] = dict()))[postID] = val; } else if (threadID !== undefined) { return (this.data[siteID].boards[boardID] || (this.data[siteID].boards[boardID] = dict()))[threadID] = val; } else { return this.data[siteID].boards[boardID] = val; } } extend({siteID, boardID, threadID, postID, val}, cb) { return this.save(() => { const oldVal = this.get({ siteID, boardID, threadID, postID, defaultValue: dict() }); for (var key in val) { var subVal = val[key]; if (typeof subVal === 'undefined') { delete oldVal[key]; } else { oldVal[key] = subVal; } } return this.setUnsafe({siteID, boardID, threadID, postID, val: oldVal}); } , cb); } setLastChecked(key='lastChecked') { return this.save(() => { return this.data[key] = Date.now(); }); } get({siteID, boardID, threadID, postID, defaultValue}) { let board, val; if (!siteID) { siteID = g.SITE.ID; } if (board = this.data[siteID]?.boards[boardID]) { let thread; if (threadID == null) { if (postID != null) { for (thread = 0; thread < board.length; thread++) { var ID = board[thread]; if (postID in thread) { val = thread[postID]; break; } } } else { val = board; } } else if (thread = board[threadID]) { val = (postID != null) ? thread[postID] : thread; } } return val || defaultValue; } clean() { let boardID, middle; const siteID = g.SITE.ID; for (boardID in this.data[siteID].boards) { var val = this.data[siteID].boards[boardID]; this.deleteIfEmpty({siteID, boardID}); } const now = Date.now(); if (now - (2 * HOUR) >= ((middle = this.data[siteID].lastChecked || 0)) || middle > now) { this.data[siteID].lastChecked = now; for (boardID in this.data[siteID].boards) { this.ajaxClean(boardID); } } } ajaxClean(boardID) { const that = this; const siteID = g.SITE.ID; const threadsList = g.SITE.urls.threadsListJSON?.({siteID, boardID}); if (!threadsList) { return; } return $.cache(threadsList, function() { if (this.status !== 200) { return; } const archiveList = g.SITE.urls.archiveListJSON?.({siteID, boardID}); if (!archiveList) { return that.ajaxCleanParse(boardID, this.response); } const response1 = this.response; return $.cache(archiveList, function() { if ((this.status !== 200) && (!!g.SITE.archivedBoardsKnown || (this.status !== 404))) { return; } return that.ajaxCleanParse(boardID, response1, this.response); }); }); } ajaxCleanParse(boardID, response1, response2) { let board, ID; const siteID = g.SITE.ID; if (!(board = this.data[siteID].boards[boardID])) { return; } const threads = dict(); if (response1) { for (var page of response1) { for (var thread of page.threads) { ID = thread.no; if (ID in board) { threads[ID] = board[ID]; } } } } if (response2) { for (ID of response2) { if (ID in board) { threads[ID] = board[ID]; } } } this.data[siteID].boards[boardID] = threads; this.deleteIfEmpty({siteID, boardID}); return $.set(this.key, this.data); } onSync(data) { if ((data.version || 0) <= (this.data.version || 0)) { return; } this.initData(data); return this.sync?.(); } } DataBoard.initClass();