import os import time import datetime import functools from pandac.PandaModules import Filename, DSearchPath, TextNode from pandac.PandaModules import HTTPClient, Ramfile, DocumentSpec from direct.showbase import DirectObject from direct.gui.DirectGui import DirectFrame, DGG from direct.directnotify import DirectNotifyGlobal from direct.task.Task import Task from direct.showbase import AppRunnerGlobal from toontown.shtiker import IssueFrame from toontown.shtiker import IssueFrameV2 from toontown.toonbase import TTLocalizer class DirectNewsFrame(DirectObject.DirectObject): TaskName = 'HtmlViewUpdateTask' TaskChainName = 'RedownladTaskChain' RedownloadTaskName = 'RedownloadNewsTask' NewsBaseDir = config.GetString('news-base-dir', '/httpNews') NewsStageDir = config.GetString('news-stage-dir', 'news') FrameDimensions = (-1.30666637421, 1.30666637421, -0.751666665077, 0.751666665077) notify = DirectNotifyGlobal.directNotify.newCategory('DirectNewsFrame') NewsIndexFilename = config.GetString('news-index-filename', 'http_news_index.txt') NewsOverHttp = config.GetBool('news-over-http', True) CacheIndexFilename = 'cache_index.txt' SectionIdents = ['hom', 'new', 'evt', 'tot', 'att', 'tnr'] def __init__(self, parent = aspect2d): DirectObject.DirectObject.__init__(self) self.accept('newsSnapshot', self.doSnapshot) self.active = False self.parent = parent self.issues = [] self.accept('newsChangeWeek', self.changeWeek) self.curIssueIndex = 0 self.strFilenames = None self.redownloadingNews = False self.startRedownload = datetime.datetime.now() self.endRedownload = datetime.datetime.now() self.load() self.percentDownloaded = 0.0 self.numIssuesExpected = 0 self.needsParseNews = True self.newsIndexEntries = [] if self.NewsOverHttp: self.redownloadNews() self.accept('newIssueOut', self.handleNewIssueOut) self.accept('clientCleanup', self.handleClientCleanup) return def parseNewsContent(self): if not self.needsParseNews: return self.needsParseNews = False result = False newsDir = self.findNewsDir() if newsDir: allHomeFiles = self.getAllHomeFilenames(newsDir) self.notify.debug('len allHomeFiles = %s' % len(allHomeFiles)) self.numIssuesExpected = len(allHomeFiles) if allHomeFiles: for myIssueIndex, oneHomeFile in enumerate(allHomeFiles): if type(oneHomeFile) == type(''): justFilename = oneHomeFile else: justFilename = oneHomeFile.getFilename().getBasename() self.notify.debug('parseNewContent %s' % justFilename) parts = justFilename.split('_') dateStr = parts[3] majorVer, minorVer = self.calcIssueVersion(dateStr) if majorVer == 1: oneIssue = IssueFrame.IssueFrame(self.backFrame, newsDir, dateStr, myIssueIndex, len(allHomeFiles), self.strFilenames) elif majorVer == 2: oneIssue = IssueFrameV2.IssueFrameV2(self.backFrame, newsDir, dateStr, myIssueIndex, len(allHomeFiles), self.strFilenames, self.newsIndexEntries) else: self.notify.warning('Dont know how to handle version %s, asuming v2' % majorVer) oneIssue = IssueFrameV2.IssueFrameV2(self.backFrame, newsDir, dateStr, myIssueIndex, len(allHomeFiles), self.strFilenames, self.newsIndexEntries) oneIssue.hide() self.issues.append(oneIssue) if self.issues: self.issues[-1].show() self.curIssueIndex = len(self.issues) - 1 result = True if hasattr(base.cr, 'inGameNewsMgr') and base.cr.inGameNewsMgr: self.createdTime = base.cr.inGameNewsMgr.getLatestIssue() self.notify.debug('setting created time to latest issue %s' % self.createdTime) else: self.createdTime = base.cr.toontownTimeManager.getCurServerDateTime() self.notify.debug('setting created time cur server time %s' % self.createdTime) return result def getAllHomeFilenames(self, newsDir): self.notify.debug('getAllHomeFilenames') newsDirAsFile = vfs.getFile(Filename(newsDir)) fileList = newsDirAsFile.scanDirectory() fileNames = fileList.getFiles() self.notify.debug('filenames=%s' % str(fileNames)) homeFileNames = set([]) for name in fileNames: self.notify.debug('processing %s' % name) baseName = name.getFilename().getBasename() self.notify.debug('baseName=%s' % baseName) if 'hom1.' in baseName: homeFileNames.add(name) else: self.notify.debug('hom1. not in baseName') if not homeFileNames: self.notify.warning('couldnt find hom1. in %s' % fileNames) self.setErrorMessage(TTLocalizer.NewsPageNoIssues) return [] def fileCmp(fileA, fileB): return fileA.getFilename().compareTo(fileB.getFilename()) homeFileNames = list(homeFileNames) homeFileNames.sort(key=functools.cmp_to_key(fileCmp)) self.notify.debug('returned homeFileNames=%s' % homeFileNames) return homeFileNames def findNewsDir(self): if self.NewsOverHttp: return self.NewsStageDir searchPath = DSearchPath() if AppRunnerGlobal.appRunner: searchPath.appendDirectory(Filename.expandFrom('$TT_3_5_ROOT/phase_3.5/models/news')) else: basePath = os.path.expandvars('$TTMODELS') or './ttmodels' searchPath.appendDirectory(Filename.fromOsSpecific(basePath + '/built/' + self.NewsBaseDir)) searchPath.appendDirectory(Filename(self.NewsBaseDir)) pfile = Filename(self.NewsIndexFilename) found = vfs.resolveFilename(pfile, searchPath) if not found: self.notify.warning('findNewsDir - no path: %s' % self.NewsIndexFilename) self.setErrorMessage(TTLocalizer.NewsPageErrorDownloadingFile % self.NewsIndexFilename) return None self.notify.debug('found index file %s' % pfile) realDir = pfile.getDirname() return realDir def load(self): self.loadBackground() def loadBackground(self): upsellBackground = loader.loadModel('phase_3.5/models/gui/tt_m_gui_ign_newsStatusBackground') imageScaleX = self.FrameDimensions[1] - self.FrameDimensions[0] imageScaleY = self.FrameDimensions[3] - self.FrameDimensions[2] self.backFrame = DirectFrame(parent=self.parent, image=upsellBackground, image_scale=(imageScaleX, 1, imageScaleY), frameColor=(1, 1, 1, 0), frameSize=self.FrameDimensions, pos=(0, 0, 0), relief=DGG.FLAT, text=TTLocalizer.NewsPageDownloadingNews1, text_scale=0.06, text_pos=(0, -0.4)) def addDownloadingTextTask(self): self.removeDownloadingTextTask() task = taskMgr.doMethodLater(1, self.loadingTextTask, 'DirectNewsFrameDownloadingTextTask') task.startTime = globalClock.getFrameTime() self.loadingTextTask(task) def removeDownloadingTextTask(self): taskMgr.remove('DirectNewsFrameDownloadingTextTask') def loadMainPage(self): self.mainFrame = DirectFrame(parent=self.backFrame, frameSize=self.FrameDimensions, frameColor=(1, 0, 0, 1)) def activate(self): if hasattr(self, 'createdTime') and self.createdTime < base.cr.inGameNewsMgr.getLatestIssue() and self.NewsOverHttp and not self.redownloadingNews: self.redownloadNews() else: self.addDownloadingTextTask() if self.needsParseNews and not self.redownloadingNews: self.parseNewsContent() self.active = True def deactivate(self): self.removeDownloadingTextTask() self.active = False def unload(self): self.removeDownloadingTextTask() result = taskMgr.remove(self.RedownloadTaskName) self.ignore('newsSnapshot') self.ignore('newsChangeWeek') self.ignore('newIssueOut') self.ignore('clientCleanup') def handleClientCleanup(self): pass def doSnapshot(self): pass def changeWeek(self, issueIndex): if 0 <= issueIndex and issueIndex < len(self.issues): self.issues[self.curIssueIndex].hide() self.issues[issueIndex].show() self.curIssueIndex = issueIndex def loadingTextTask(self, task): timeIndex = int(globalClock.getFrameTime() - task.startTime) % 3 timeStrs = (TTLocalizer.NewsPageDownloadingNews0, TTLocalizer.NewsPageDownloadingNews1, TTLocalizer.NewsPageDownloadingNews2) textToDisplay = timeStrs[timeIndex] % int(self.percentDownloaded * 100) if self.backFrame['text'] != textToDisplay: if TTLocalizer.NewsPageDownloadingNewsSubstr in self.backFrame['text']: self.backFrame['text'] = textToDisplay return task.again def setErrorMessage(self, errText): self.backFrame['text'] = errText def redownloadNews(self): if self.redownloadingNews: self.notify.warning('averting potential crash redownloadNews called twice, just returning') return self.percentDownloaded = 0.0 self.notify.info('starting redownloadNews') self.startRedownload = datetime.datetime.now() self.redownloadingNews = True self.addDownloadingTextTask() for issue in self.issues: issue.destroy() self.issues = [] self.curIssueIndex = 0 self.strFilenames = None self.needsParseNews = True self.newsUrl = self.getInGameNewsUrl() self.newsDir = Filename(self.findNewsDir()) Filename(self.newsDir + '/.').makeDir() http = HTTPClient.getGlobalPtr() self.url = self.newsUrl + self.NewsIndexFilename self.ch = http.makeChannel(True) self.ch.beginGetDocument(self.url) self.rf = Ramfile() self.ch.downloadToRam(self.rf) taskMgr.remove(self.RedownloadTaskName) taskMgr.add(self.downloadIndexTask, self.RedownloadTaskName) return def downloadIndexTask(self, task): if self.ch.run(): return task.cont if not self.ch.isValid(): self.notify.warning('Unable to download %s' % self.url) self.redownloadingNews = False return task.done self.newsFiles = [] filename = self.rf.readline() while filename: filename = filename.strip() if filename: self.newsFiles.append(filename) filename = self.rf.readline() del self.rf self.newsFiles.sort() self.newsIndexEntries = list(self.newsFiles) self.notify.info('Server lists %s news files' % len(self.newsFiles)) self.notify.debug('self.newsIndexEntries=%s' % self.newsIndexEntries) self.readNewsCache() for basename in os.listdir(self.newsDir.toOsSpecific()): if basename != self.CacheIndexFilename and basename not in self.newsCache: junk = Filename(self.newsDir, basename) self.notify.info('Removing %s' % junk) junk.unlink() self.nextNewsFile = 0 return self.downloadNextFile(task) def downloadNextFile(self, task): while self.nextNewsFile < len(self.newsFiles) and b'aaver' in self.newsFiles[self.nextNewsFile]: self.nextNewsFile += 1 if self.nextNewsFile >= len(self.newsFiles): self.notify.info('Done downloading news.') self.percentDownloaded = 1 del self.newsFiles del self.nextNewsFile del self.newsUrl del self.newsDir del self.ch del self.url if hasattr(self, 'filename'): del self.filename self.redownloadingNews = False if self.active: self.parseNewsContent() return task.done self.percentDownloaded = float(self.nextNewsFile) / float(len(self.newsFiles)) self.filename = self.newsFiles[self.nextNewsFile] self.nextNewsFile += 1 self.url = self.newsUrl + self.filename.decode('utf-8') localFilename = Filename(self.newsDir, self.filename) self.notify.info('testing for %s' % localFilename.getFullpath()) doc = DocumentSpec(self.url) if self.filename in self.newsCache: size, date = self.newsCache[self.filename] if date and localFilename.exists() and (size == 0 or localFilename.getFileSize() == size): doc.setDate(date) doc.setRequestMode(doc.RMNewer) self.ch.beginGetDocument(doc) self.ch.downloadToFile(localFilename) taskMgr.remove(self.RedownloadTaskName) taskMgr.add(self.downloadCurrentFileTask, self.RedownloadTaskName) def downloadCurrentFileTask(self, task): if self.ch.run(): return task.cont if self.ch.getStatusCode() == 304: self.notify.info('already cached: %s' % self.filename) return self.downloadNextFile(task) localFilename = Filename(self.newsDir, self.filename) if not self.ch.isValid(): self.notify.warning('Unable to download %s' % self.url) localFilename.unlink() if self.filename in self.newsCache: del self.newsCache[self.filename] self.saveNewsCache() return self.downloadNextFile(task) self.notify.info('downloaded %s' % self.filename.decode('utf-8')) size = self.ch.getFileSize() doc = self.ch.getDocumentSpec() date = '' if doc.hasDate(): date = doc.getDate().getString() self.newsCache[self.filename] = (size, date) self.saveNewsCache() return self.downloadNextFile(task) def readNewsCache(self): cacheIndexFilename = Filename(self.newsDir, self.CacheIndexFilename) self.newsCache = {} if cacheIndexFilename.isRegularFile(): file = open(cacheIndexFilename.toOsSpecific(), 'r') for line in file.readlines(): line = line.strip() keywords = line.split('\t') if len(keywords) == 3: filename, size, date = keywords if filename in self.newsFiles: try: size = int(size) except ValueError: size = 0 self.newsCache[filename] = (size, date) def saveNewsCache(self): cacheIndexFilename = Filename(self.newsDir, self.CacheIndexFilename) try: file = open(cacheIndexFilename.toOsSpecific(), 'w') except IOError as e: self.notify.warning('error opening news cache file %s: %s' % (cacheIndexFilename, str(e))) return for filename, (size, date) in list(self.newsCache.items()): print('%s\t%s\t%s' % (filename, size, date), file=file) def handleNewIssueOut(self): if hasattr(self, 'createdTime') and base.cr.inGameNewsMgr.getLatestIssue() < self.createdTime: self.createdTime = base.cr.inGameNewsMgr.getLatestIssue() elif self.NewsOverHttp and not self.redownloadingNews: if not self.active: self.redownloadNews() def getInGameNewsUrl(self): result = base.config.GetString('fallback-news-url', 'http://cdn.toontown.disney.go.com/toontown/en/gamenews/') override = base.config.GetString('in-game-news-url', '') if override: self.notify.info('got an override url, using %s for in game news' % override) result = override else: try: launcherUrl = base.launcher.getValue('GAME_IN_GAME_NEWS_URL', '') if launcherUrl: result = launcherUrl self.notify.info('got GAME_IN_GAME_NEWS_URL from launcher using %s' % result) else: self.notify.info('blank GAME_IN_GAME_NEWS_URL from launcher, using %s' % result) except: self.notify.warning('got exception getting GAME_IN_GAME_NEWS_URL from launcher, using %s' % result) return result def calcIssueVersion(self, dateStr): majorVer = 1 minorVer = 0 for entry in self.newsIndexEntries: if b'aaver' in entry and dateStr.encode('utf-8') in entry: parts = entry.split(b'_') if len(parts) > 5: try: majorVer = int(parts[5]) except: self.notify.warning('could not int %s' % parts[5]) else: self.notify.warning('expected more than 5 parts in %s' % entry) if len(parts) > 6: try: minorVer = int(parts[6]) except: self.notify.warning('could not int %s' % parts[6]) else: self.notify.warning('expected more than 6 parts in %s' % entry) break return (majorVer, minorVer)