From 8dbf986b342328cbf0dee195650628abf1fef6b7 Mon Sep 17 00:00:00 2001 From: Carl Chenet Date: Sat, 17 Aug 2019 15:26:49 +0200 Subject: [PATCH] use a lock file to check if only one feed2toot process runs at a given time --- feed2toot/cliparse.py | 6 ++++ feed2toot/confparse.py | 5 +++ feed2toot/confparsers/lock.py | 48 ++++++++++++++++++++++++++ feed2toot/lock.py | 63 +++++++++++++++++++++++++++++++++++ feed2toot/main.py | 5 +++ 5 files changed, 127 insertions(+) create mode 100644 feed2toot/confparsers/lock.py create mode 100644 feed2toot/lock.py diff --git a/feed2toot/cliparse.py b/feed2toot/cliparse.py index 349edcc..2e47476 100644 --- a/feed2toot/cliparse.py +++ b/feed2toot/cliparse.py @@ -52,8 +52,14 @@ class CliParse: help='tweet all RSS items, regardless of cache') parser.add_argument('-l', '--limit', dest='limit', default=10, type=int, help='tweet only LIMIT items (default: %(default)s)') + parser.add_argument('-t', '--lock-timeout', dest='locktimeout', default=3600, type=int, + help='lock timeout in seconds after which feed2toot can removes the lock itself') parser.add_argument('--cachefile', dest='cachefile', help='location of the cache file (default: %(default)s)') + parser.add_argument('--lockfile', dest='lockfile', + default=os.path.join(os.getenv('XDG_CONFIG_HOME', '~/.config'), + 'feed2toot.lock'), + help='location of the lock file (default: %(default)s)') parser.add_argument('-n', '--dry-run', dest='dryrun', action='store_true', default=False, help='Do not actually post tweets') diff --git a/feed2toot/confparse.py b/feed2toot/confparse.py index a44d5e3..21d3a86 100644 --- a/feed2toot/confparse.py +++ b/feed2toot/confparse.py @@ -31,6 +31,7 @@ import feedparser from feed2toot.confparsers.cache import parsecache from feed2toot.confparsers.hashtaglist import parsehashtaglist from feed2toot.confparsers.feedparser import parsefeedparser +from feed2toot.confparsers.lock import parselock from feed2toot.confparsers.media import parsemedia from feed2toot.confparsers.plugins import parseplugins from feed2toot.confparsers.rss.pattern import parsepattern @@ -69,6 +70,10 @@ class ConfParse: # pattern and patter_case_sensitive format option ################################################# options['patterns'], options['patternscasesensitive'] = parsepattern(config) + ################################################# + # lock file options + ################################################# + options['lockfile'], options['locktimeout'] = parselock(self.clioptions.lockfile, self.clioptions.locktimeout, config) ############################### # addtags option, default: True ############################### diff --git a/feed2toot/confparsers/lock.py b/feed2toot/confparsers/lock.py new file mode 100644 index 0000000..06f478a --- /dev/null +++ b/feed2toot/confparsers/lock.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright © 2015-2019 Carl Chenet +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +'''Manage a lock file''' + +# standard libraires imports +import datetime +import logging +import os +import os.path +import sys + +class LockFile: + '''LockFile object''' + def __init__(self, lockfile, locktimeout): + '''check the lockfile and the locktimeout''' + self.lockfile = lockfile + ltimeout = datetime.timedelta(seconds=locktimeout) + self.lfdateformat = '%Y-%m-%d_%H-%M-%S' + # if a lock file exists + if os.path.exists(self.lockfile): + if os.path.isfile(self.lockfile): + with open(self.lockfile, 'r') as lf: + lfcontent = lf.read().rstrip() + # lfcontent should be a datetime + logging.debug('Check if lock file is older than timeout ({timeout} secs)'.format(timeout=locktimeout)) + locktime = datetime.datetime.strptime(lfcontent, self.lfdateformat) + if locktime < (datetime.datetime.now() - ltimeout): + # remove the lock file + logging.debug('Found an expired lock file') + self.release() + self.create_lock() + else: + # quit because another feed2toot process is running + logging.debug('Found a valid lock file. Exiting immediately.') + sys.exit(0) + else: + # no lock file. Creating one + self.create_lock() + + def create_lock(self): + '''Create a lock file''' + with open(self.lockfile, 'w') as lf: + currentdatestring = datetime.datetime.now().strftime(self.lfdateformat) + lf.write(currentdatestring) + logging.debug('lockfile {lockfile} created.'.format(lockfile=self.lockfile)) + + def release(self): + '''Release the lockfile''' + os.remove(self.lockfile) + logging.debug('Removed lock file.') diff --git a/feed2toot/main.py b/feed2toot/main.py index c573651..625589e 100644 --- a/feed2toot/main.py +++ b/feed2toot/main.py @@ -32,6 +32,7 @@ from feed2toot.filterentry import FilterEntry from feed2toot.removeduplicates import RemoveDuplicates from feed2toot.tootpost import TootPost from feed2toot.feedcache import FeedCache +from feed2toot.lock import LockFile from bs4 import BeautifulSoup class Main: @@ -71,6 +72,8 @@ class Main: tweetformat = conf[2] feeds = conf[3] plugins = conf[4] + # check the logfile and logtimeout + lockfile = LockFile(options['lockfile'], options['locktimeout']) # create link to the persistent list cache = FeedCache(options) if 'hashtaglist' in options and options['hashtaglist']: @@ -221,3 +224,5 @@ class Main: print(err) # do not forget to close cache (shelf object) cache.close() + # release the lock file + lockfile.release()