__filename__ = "schedule.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.1.0" __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" import os import time import datetime from utils import getStatusNumber from utils import loadJson from outbox import postMessageToOutbox def updatePostSchedule(baseDir: str, handle: str, httpd, maxScheduledPosts: int) -> None: """Checks if posts are due to be delivered and if so moves them to the outbox """ scheduleIndexFilename = baseDir + '/accounts/' + handle + '/schedule.index' if not os.path.isfile(scheduleIndexFilename): return # get the current time as an int currTime = datetime.datetime.utcnow() daysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days scheduleDir = baseDir + '/accounts/' + handle + '/scheduled/' indexLines = [] deleteSchedulePost = False nickname = handle.split('@')[0] with open(scheduleIndexFilename, 'r') as fp: for line in fp: if ' ' not in line: continue dateStr = line.split(' ')[0] if 'T' not in dateStr: continue postId = line.split(' ', 1)[1].replace('\n', '') postFilename = scheduleDir + postId + '.json' if deleteSchedulePost: # delete extraneous scheduled posts if os.path.isfile(postFilename): os.remove(postFilename) continue # create the new index file indexLines.append(line) # convert string date to int postTime = \ datetime.datetime.strptime(dateStr, "%Y-%m-%dT%H:%M:%S%z") postTime = postTime.replace(tzinfo=None) postDaysSinceEpoch = \ (postTime - datetime.datetime(1970, 1, 1)).days if daysSinceEpoch < postDaysSinceEpoch: continue if daysSinceEpoch == postDaysSinceEpoch: if currTime.time().hour < postTime.time().hour: continue if currTime.time().minute < postTime.time().minute: continue if not os.path.isfile(postFilename): print('WARN: schedule missing postFilename=' + postFilename) indexLines.remove(line) continue # load post postJsonObject = loadJson(postFilename) if not postJsonObject: print('WARN: schedule json not loaded') indexLines.remove(line) continue # set the published time # If this is not recent then http checks on the receiving side # will reject it statusNumber, published = getStatusNumber() if postJsonObject.get('published'): postJsonObject['published'] = published if postJsonObject.get('object'): if isinstance(postJsonObject['object'], dict): if postJsonObject['object'].get('published'): postJsonObject['published'] = published print('Sending scheduled post ' + postId) if nickname: httpd.postToNickname = nickname if not postMessageToOutbox(postJsonObject, nickname, httpd, baseDir, httpd.httpPrefix, httpd.domain, httpd.domainFull, httpd.onionDomain, httpd.port, httpd.recentPostsCache, httpd.followersThreads, httpd.federationList, httpd.sendThreads, httpd.postLog, httpd.cachedWebfingers, httpd.personCache, httpd.allowDeletion, httpd.useTor, httpd.projectVersion, httpd.debug): indexLines.remove(line) os.remove(postFilename) continue # move to the outbox outboxPostFilename = \ postFilename.replace('/scheduled/', '/outbox/') os.rename(postFilename, outboxPostFilename) print('Scheduled post sent ' + postId) indexLines.remove(line) if len(indexLines) > maxScheduledPosts: deleteSchedulePost = True # write the new schedule index file scheduleIndexFile = \ baseDir + '/accounts/' + handle + '/schedule.index' scheduleFile = open(scheduleIndexFile, "w+") if scheduleFile: for line in indexLines: scheduleFile.write(line) scheduleFile.close() def runPostSchedule(baseDir: str, httpd, maxScheduledPosts: int): """Dispatches scheduled posts """ while True: time.sleep(60) # for each account for subdir, dirs, files in os.walk(baseDir + '/accounts'): for account in dirs: if '@' not in account: continue # scheduled posts index for this account scheduleIndexFilename = \ baseDir + '/accounts/' + account + '/schedule.index' if not os.path.isfile(scheduleIndexFilename): continue updatePostSchedule(baseDir, account, httpd, maxScheduledPosts) def runPostScheduleWatchdog(projectVersion: str, httpd) -> None: """This tries to keep the scheduled post thread running even if it dies """ print('Starting scheduled post watchdog') postScheduleOriginal = \ httpd.thrPostSchedule.clone(runPostSchedule) httpd.thrPostSchedule.start() while True: time.sleep(20) if not httpd.thrPostSchedule.isAlive(): httpd.thrPostSchedule.kill() httpd.thrPostSchedule = \ postScheduleOriginal.clone(runPostSchedule) httpd.thrPostSchedule.start() print('Restarting scheduled posts...') def removeScheduledPosts(baseDir: str, nickname: str, domain: str) -> None: """Removes any scheduled posts """ # remove the index scheduleIndexFilename = \ baseDir + '/accounts/' + nickname + '@' + domain + '/schedule.index' if os.path.isfile(scheduleIndexFilename): os.remove(scheduleIndexFilename) # remove the scheduled posts scheduledDir = baseDir + '/accounts/' + \ nickname + '@' + domain + '/scheduled' if not os.path.isdir(scheduledDir): return for scheduledPostFilename in os.listdir(scheduledDir): filePath = os.path.join(scheduledDir, scheduledPostFilename) try: if os.path.isfile(filePath): os.remove(filePath) except BaseException: pass