helpers.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. # This file is part of stov, written by Helmut Pozimski 2012-2021.
  2. #
  3. # stov is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, version 2 of the License.
  6. #
  7. # stov is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License
  13. # along with stov. If not, see <http://www.gnu.org/licenses/>.
  14. # -*- coding: utf8 -*-
  15. """ This module contains several functions that are necessary to set up
  16. the application. """
  17. import argparse
  18. import gettext
  19. import logging
  20. import os
  21. import signal
  22. import sys
  23. from distutils.spawn import find_executable
  24. from lib_stov import configuration
  25. from lib_stov import database
  26. from lib_stov import stov_exceptions
  27. LOGGER = logging.getLogger("stov")
  28. def initialize_gettext():
  29. """
  30. Installs gettext so localization can be used if necessary.
  31. """
  32. # Determine the path where the stov files are for localization
  33. locale_path = os.path.join(sys.path[0] + "/locale")
  34. if gettext.find("stov", locale_path) is None:
  35. base_path = os.path.split(sys.path[0])[0]
  36. if "share" in base_path:
  37. locale_path = os.path.join(base_path, "locale")
  38. else:
  39. locale_path = os.path.join(base_path, "share/locale")
  40. # Initialize gettext to support translation of the program
  41. try:
  42. trans = gettext.translation("stov", locale_path)
  43. except IOError:
  44. gettext.install("stov")
  45. if os.environ["LANG"] != "C" and os.environ["LANGUAGE"] != "C":
  46. print(_("The translation files could not be found, localization "
  47. "won't be available"), file=sys.stderr)
  48. else:
  49. trans.install()
  50. def sighandler(signum, frame):
  51. """Handler function for signals caught by stov."""
  52. if signum == 2:
  53. print(_("STRG+C has been pressed, quitting..."), file=sys.stderr)
  54. elif signum == 15:
  55. print(_("Received SIGTERM, quitting..."), file=sys.stderr)
  56. os.killpg(os.getpid(), 1)
  57. remove_lock()
  58. sys.exit(0)
  59. def setup_sighandler():
  60. """
  61. Installs a signal handler to catch external signals
  62. """
  63. signal.signal(signal.SIGTERM, sighandler)
  64. signal.signal(signal.SIGINT, sighandler)
  65. def parse_arguments():
  66. """
  67. Uses the argument parser to parse command line arguments and return them
  68. :return: parsed arguments
  69. :rtype: argparser.ArgumentParser
  70. """
  71. parser = argparse.ArgumentParser(conflict_handler="resolve")
  72. parser.add_argument("-h", "--help", action="store_true", dest="help",
  73. help=_("show this help message and exit"))
  74. parser.add_argument("-a", "--add", dest="add", action="store_true",
  75. help=_("Add a new subscription (requires either \
  76. --search, --channel or --playlist)"))
  77. parser.add_argument("-p", "--playlist", dest="playlist",
  78. help=_("Add a new Playlist subscription (requires "
  79. "add)"))
  80. parser.add_argument("-l", "--lssubs", action="store_true", dest="list",
  81. help=_("List the currently available subscriptions"))
  82. parser.add_argument("-r", "--remove", type=int, dest="deleteid",
  83. help=_("remove a subscription"))
  84. parser.add_argument("-u", "--update", nargs="*", dest="update", type=int,
  85. default=None,
  86. help=_(
  87. "update the information about the available "
  88. "videos"))
  89. parser.add_argument("-d", "--download", nargs="*", type=int, default=None,
  90. dest="download", help=_("download all available "
  91. "videos which haven't already"
  92. " been downloaded"))
  93. parser.add_argument("-s", "--search", dest="searchparameter",
  94. help=_("optionally add a search string to a new "
  95. "channel subscription or create a new search"
  96. " subscription (requires --add)"))
  97. parser.add_argument("-l", "--lsvids", type=int, dest="subscriptionid",
  98. help=_("Print all videos from a subscription"))
  99. parser.add_argument("-c", "--catchup", dest="catchup",
  100. help=_("Mark all videos from one channel as read \
  101. (requires subscription-id as argument)"))
  102. parser.add_argument("-c", "--channel", dest="channel",
  103. help=_("specify a channel for a new subscription "
  104. "(requires --add)"))
  105. parser.add_argument("-l", "--license", dest="license", action="store_true",
  106. help=_("show the license of the program"))
  107. parser.add_argument("-v", "--version", dest="version", action="store_true",
  108. help=_("show the current running version number"))
  109. parser.add_argument("-q", "--quiet", dest="quiet", action="store_true",
  110. help=_("Suppress all output"))
  111. parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
  112. help=_("Be verbose and print also diagnostic "
  113. "messages"))
  114. parser.add_argument("-c", "--clean-database", dest="cleanup",
  115. action="store_true",
  116. help=_("Clean the database of entries "
  117. "no longer listed in the current API response"))
  118. parser.add_argument("-e", "--enable", type=int, dest="enableid",
  119. help=_("enables the subscription with the "
  120. "provided ID"))
  121. parser.add_argument("--disable", type=int, dest="disableid",
  122. help=_("disables the subscription with the "
  123. "provided ID"))
  124. parser.add_argument("-s", "--site", dest="site",
  125. help=_("specify the site to work with (for --add)"))
  126. parser.add_argument("--lssites", dest="lssites", action="store_true",
  127. help=_("list the supported sites"))
  128. return parser
  129. def create_lock():
  130. """
  131. Creates the lock file for stov.
  132. """
  133. if os.access("/tmp/stov.lock", os.F_OK):
  134. try:
  135. lock_file = open("/tmp/stov.lock", "r")
  136. except IOError:
  137. LOGGER.error(_("The lock file could not be opened, please "
  138. "check that it exists and is readable, "
  139. "quitting now"))
  140. sys.exit(1)
  141. old_pid = lock_file.read().strip()
  142. if os.access("/proc/" + old_pid, os.F_OK):
  143. LOGGER.error(_("The lock file already exists, probably another "
  144. "instance of this program is already running\n"
  145. "if you are sure this is not the case, delete it "
  146. "manually and try again!"))
  147. sys.exit(1)
  148. lock_file.close()
  149. if not os.access("/proc/" + old_pid, os.F_OK):
  150. try:
  151. os.remove("/tmp/stov.lock")
  152. except os.error:
  153. LOGGER.error(_("The old lock file could not be deleted!"))
  154. try:
  155. lock_file = open("/tmp/stov.lock", "w")
  156. lock_file.write(str(os.getpid()))
  157. lock_file.close()
  158. except IOError:
  159. LOGGER.error(_("The lock file could not be created, please check "
  160. "that /tmp is writable and properly configured, "
  161. "quitting now."), file=sys.stderr)
  162. sys.exit(1)
  163. def remove_lock():
  164. """
  165. Removes the lock file when exiting the application.
  166. """
  167. LOGGER.debug(_("Removing lock file"))
  168. try:
  169. os.remove("/tmp/stov.lock")
  170. sys.exit(0)
  171. except os.error:
  172. LOGGER.error(_("Could not delete the lock file. Please check what "
  173. "went wrong and clean up manually!"))
  174. sys.exit(1)
  175. def setup_configuration(args):
  176. """
  177. Reads the configuration and sets it up to be used within the application.
  178. :param args: command line arguments
  179. :return: configuration object
  180. :rtype: lib_stov.configuration.Conf
  181. """
  182. if not os.access(os.environ['HOME'] + "/.stov", os.F_OK & os.W_OK):
  183. print(_("This seems to be the first time you run the programm, do "
  184. "you want to run the interactive assistant? (yes/no)"))
  185. conf = configuration.Conf()
  186. logger = conf.configure_logging(args.verbose, args.quiet)
  187. temp_input = input()
  188. if temp_input == "yes":
  189. conf.assist()
  190. try:
  191. conf.initialize()
  192. except stov_exceptions.ConfigFileWriteErrorException as error:
  193. logger.error(error)
  194. else:
  195. logger.info(_("Writing initial configuration according to "
  196. "your input, have fun!"))
  197. else:
  198. logger.info(_("Writing initial configuration according to default"
  199. " values."))
  200. logger.debug(_("Creating hidden directory in home for "
  201. "configuration and database."))
  202. try:
  203. conf.initialize()
  204. except stov_exceptions.DirectoryCreationFailedException as error:
  205. logger.error(error)
  206. except stov_exceptions.ConfigFileWriteErrorException as error:
  207. logger.error(error)
  208. else:
  209. conf = configuration.Conf()
  210. logger = conf.configure_logging(args.verbose, args.quiet)
  211. if os.access(str(os.environ['HOME']) + "/.stov/stov.config", os.F_OK):
  212. logger.debug(_("Migrating configuration from plain text to json."))
  213. conf.migrate_config()
  214. try:
  215. logger.debug(
  216. _("Comparing current and running configuration version."))
  217. check_result = conf.check_config()
  218. except stov_exceptions.ConfigFileReadErrorException as error:
  219. logger.error(error)
  220. sys.exit(1)
  221. except stov_exceptions.InvalidConfigurationVersionException as error:
  222. logger.error(error)
  223. sys.exit(1)
  224. else:
  225. if not check_result:
  226. logger.info(
  227. _("Your configuration needs to be updated, performing"
  228. " update now."))
  229. try:
  230. conf.update_config()
  231. except stov_exceptions.ConfigFileReadErrorException as error:
  232. logger.error(error)
  233. sys.exit(1)
  234. except stov_exceptions.ConfigFileWriteErrorException as error:
  235. logger.error(error)
  236. sys.exit(1)
  237. return conf
  238. def setup_database():
  239. """ Sets up the database and provides a DB object to talk to the database
  240. in the application.
  241. :return: database object
  242. :rtype: lib_stov.database.Db
  243. """
  244. conf = configuration.Conf.get_instance()
  245. LOGGER.debug(_("Connecting to stov database"))
  246. if os.access(conf.dbpath, os.F_OK):
  247. try:
  248. db = database.Db(path=conf.dbpath,
  249. version=conf.values["db_version"])
  250. except stov_exceptions.DBConnectionFailedException as error:
  251. LOGGER.error(error)
  252. sys.exit(1)
  253. else:
  254. try:
  255. db = database.Db(path=conf.dbpath,
  256. version=conf.values["db_version"])
  257. except stov_exceptions.DBConnectionFailedException as error:
  258. LOGGER.error(error)
  259. sys.exit(1)
  260. else:
  261. try:
  262. db.populate()
  263. except stov_exceptions.DBWriteAccessFailedException as error:
  264. LOGGER.error(error)
  265. sys.exit(1)
  266. else:
  267. LOGGER.debug(_("Created initial database tables."))
  268. try:
  269. conf = configuration.Conf()
  270. LOGGER.debug(_("Comparing current and running database version."))
  271. if not conf.check_db():
  272. LOGGER.info(
  273. _("Your database needs to be updated, performing update "
  274. "now."))
  275. db.update()
  276. conf.values["db_version"] = db.get_version()
  277. LOGGER.debug("Opening configuration file.")
  278. try:
  279. conf.write_config()
  280. except stov_exceptions.ConfigFileWriteErrorException as error:
  281. LOGGER.error(error)
  282. except stov_exceptions.DBWriteAccessFailedException as error:
  283. LOGGER.error(error)
  284. sys.exit(1)
  285. return db
  286. def find_youtubedl():
  287. """
  288. Tries to find youtube-dl and writes it's path to the configuration file
  289. """
  290. conf = configuration.Conf.get_instance()
  291. if conf.values["youtube-dl"] == "":
  292. youtubedl_path = find_executable("youtube-dl")
  293. conf.values["youtube-dl"] = youtubedl_path
  294. if os.access(conf.values["youtube-dl"], os.F_OK & os.R_OK & os.X_OK):
  295. LOGGER.info(_("Found youtube-dl at %s, writing it's path to the "
  296. "configuration file."), conf.values["youtube-dl"])
  297. LOGGER.debug("Opening configuration file.")
  298. try:
  299. conf.write_config()
  300. except stov_exceptions.ConfigFileWriteErrorException as error:
  301. LOGGER.error(error)
  302. sys.exit(1)
  303. else:
  304. LOGGER.error(
  305. _("Could not find youtube-dl, it either does not exist, "
  306. "is not readable or not executable. Please note that "
  307. "youtube-dl is not needed for the program to run but "
  308. "is needed to use the download option which won't work "
  309. "otherwise. If youtube-dl isn't found automatically, "
  310. "you may also enter the path to it in the configuration"
  311. " file."))