stov 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. #! /usr/bin/env python
  2. # -*- coding: utf8 -*-
  3. # stov - a program to subscribe to channels and users from youtube
  4. # and download the videos automatically
  5. #
  6. # written by Helmut Pozimski 2012-2015
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # as published by the Free Software Foundation; version 2
  11. # of the License.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor,
  21. # Boston, MA 02110-1301, USA.
  22. from __future__ import unicode_literals
  23. from __future__ import print_function
  24. import sys
  25. import gettext
  26. import os
  27. import smtplib
  28. import subprocess
  29. import signal
  30. import socket
  31. import logging
  32. from email.mime.multipart import MIMEMultipart
  33. from email.mime.text import MIMEText
  34. from optparse import OptionParser
  35. from lib_stov import subscription
  36. from lib_stov import configuration
  37. from lib_stov import stov_exceptions
  38. from lib_stov import database
  39. """Setup the logger to log messages to stdout and stderr"""
  40. logger = logging.getLogger("stov")
  41. logger.setLevel(logging.DEBUG)
  42. console_handler = logging.StreamHandler()
  43. logger.addHandler(console_handler)
  44. """Determine the path where the stov files are for localization"""
  45. locale_path = os.path.join(sys.path[0] + "/locale")
  46. if gettext.find("stov", locale_path) is None:
  47. base_path = os.path.split(sys.path[0])[0]
  48. if "share" in base_path:
  49. locale_path = os.path.join(base_path, "locale")
  50. else:
  51. locale_path = os.path.join(base_path, "share/locale")
  52. """Initialize gettext to support translation of the program"""
  53. try:
  54. trans = gettext.translation("stov", locale_path)
  55. except IOError:
  56. gettext.install("stov")
  57. if os.environ["LANG"] != "C" and os.environ["LANGUAGE"] != "C":
  58. print(_("The translation files could not be found, localization "
  59. "won't be available"), file=sys.stderr)
  60. else:
  61. if sys.version_info >= (3, 0):
  62. trans.install()
  63. else:
  64. trans.install(unicode=True)
  65. """Define a handler for signals sent to the program"""
  66. def sighandler(signum, frame):
  67. if signum == 2:
  68. print(_("STRG+C has been pressed, quitting..."), file=sys.stderr)
  69. elif signum == 15:
  70. print(_("Received SIGTERM, quitting..."), file=sys.stderr)
  71. os.killpg(os.getpid(), 1)
  72. os.remove("/tmp/stov.lock")
  73. sys.exit(0)
  74. signal.signal(signal.SIGTERM, sighandler)
  75. signal.signal(signal.SIGINT, sighandler)
  76. """Overwrite the default OptionParser class so error messages
  77. can be localized
  78. """
  79. class MyOptionParser(OptionParser):
  80. def error(self, parser_msg):
  81. if "invalid integer" in parser_msg:
  82. print(_("option %s requires an integer value")
  83. % parser_msg.split()[1], file=sys.stderr)
  84. self.exit()
  85. elif "an argument" in parser_msg:
  86. print(_("option %s requires an argument") % parser_msg.split()[0],
  87. file=sys.stderr)
  88. self.exit()
  89. elif "no such" in parser_msg:
  90. print(_("invalid option %s") % parser_msg.split()[3],
  91. file=sys.stderr)
  92. self.exit()
  93. else:
  94. print(parser_msg, file=sys.stderr)
  95. self.exit()
  96. """Process the given options and parameters,
  97. add: Add a new subscription (which can be a search, channel or
  98. playlist)
  99. channel: with add, specify the name of the channel or user
  100. lssubs: List the currently available subscriptions
  101. remove: remove a subscription
  102. update: update the information about the available videos
  103. download: download all available videos which haven't already been
  104. downloaded
  105. search: optionally add a search string to a new subscription or
  106. create a search subscription with add
  107. playlist: with add, subscribe to a youtube playlist
  108. catchup: Mark all videos in a subscription as downloaded
  109. version: Print version number
  110. quiet: Suppress all output
  111. verbose: Print normal output + diagnostical messages
  112. clean-database: Clean the database of old entries, meaning videos that
  113. are no longer present in the current API response of youtube
  114. enable: enables a previously disabled subscription
  115. disable: disables a previously enabled subscription
  116. """
  117. parser = MyOptionParser(usage=_("Usage: %prog [options]"), prog="stov",
  118. add_help_option=True, conflict_handler="resolve")
  119. parser.add_option("-h", "--help", action="store_true", dest="help",
  120. help=_("show this help message and exit"))
  121. parser.add_option("-a", "--add", dest="add", action="store_true",
  122. help=_("Add a new subscription (requires either \
  123. --search, --channel or --playlist)"))
  124. parser.add_option("-p", "--playlist", dest="playlist",
  125. help=_("Add a new Playlist subscription (requires add)"))
  126. parser.add_option("-l", "--lssubs", action="store_true", dest="list",
  127. help=_("List the currently available subscriptions"))
  128. parser.add_option("-r", "--remove", type="int", dest="deleteid",
  129. help=_("remove a subscription"))
  130. parser.add_option("-u", "--update", action="store_true", dest="update",
  131. help=_("update the information about the available videos"))
  132. parser.add_option("-d", "--download", action="store_true", dest="download",
  133. help=_("download all available videos which haven't already "
  134. "been downloaded"))
  135. parser.add_option("-s", "--search", dest="searchparameter",
  136. help=_("optionally add a search string to a new channel "
  137. "subscription or create a new search subscription "
  138. "(requires --add)"))
  139. parser.add_option("-l", "--lsvids", type="int", dest="subscriptionid",
  140. help=_("Print all videos from a subscription"))
  141. parser.add_option("-c", "--catchup", dest="catchup",
  142. help=_("Mark all videos from one channel as read \
  143. (requires subscription-id as argument)"))
  144. parser.add_option("-c", "--channel", dest="channel",
  145. help=_("specify a channel for a new subscription "
  146. "(requires --add)"))
  147. parser.add_option("-l", "--license", dest="license", action="store_true",
  148. help=_("show the license of the program"))
  149. parser.add_option("-v", "--version", dest="version", action="store_true",
  150. help=_("show the current running version number"))
  151. parser.add_option("-q", "--quiet", dest="quiet", action="store_true",
  152. help=_("Suppress all output"))
  153. parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
  154. help=_("Be verbose and print also diagnostical messages"))
  155. parser.add_option("-c", "--clean-database", dest="cleanup",
  156. action="store_true", help=_("Clean the database of entries "
  157. "no longer listed in the current"
  158. " API response"))
  159. parser.add_option("-e", "--enable", type="int", dest="enableid",
  160. help=_("enables the subscription with the provided ID"))
  161. parser.add_option("--disable", type="int", dest="disableid",
  162. help=_("disables the subscription with the provided ID"))
  163. (options, arguments) = parser.parse_args()
  164. """Check if stov is run directly from command line since it shouldn't be
  165. loaded as a module
  166. """
  167. if __name__ != "__main__":
  168. print("This file should not be imported as a module"
  169. "please run it directly from command line")
  170. sys.exit(1)
  171. """Variable to determine if the exit code should be success or not"""
  172. exit_status = True
  173. """Check which outputlevel is defined and save it to a temporary variable
  174. accordingly. Output generated before this will be printed to stdout regardless
  175. of the user defined setting
  176. """
  177. if options.verbose is True and options.quiet is True:
  178. print(_("--quiet and --verbose can't be defined at the same time, "
  179. "exiting."), file=sys.stderr)
  180. sys.exit(1)
  181. elif options.verbose is True:
  182. outputlevel = "verbose"
  183. logger.setLevel(logging.DEBUG)
  184. elif options.quiet is True:
  185. outputlevel = "quiet"
  186. logger.setLevel(logging.ERROR)
  187. else:
  188. outputlevel = "default"
  189. logger.setLevel(logging.INFO)
  190. """Create the lock file which is used to determine if another instance is
  191. already running by chance, the program shouldn't be run in this case since
  192. we want to prevent concurent access to the database.
  193. """
  194. if os.access("/tmp/stov.lock", os.F_OK):
  195. try:
  196. lockfile = open("/tmp/stov.lock", "r")
  197. except IOError:
  198. logger.error(_("The lock file could not be opened, please check that "
  199. "it exists and is readable, quitting now"))
  200. sys.exit(1)
  201. oldpid = lockfile.read().strip()
  202. if os.access("/proc/" + oldpid, os.F_OK):
  203. logger.error(_("The lock file already exists, probably another "
  204. "instance of this program is already running\n"
  205. "if you are sure this is not the case, delete it"
  206. " manually and try again!"))
  207. sys.exit(1)
  208. lockfile.close()
  209. if os.access("/proc/" + oldpid, os.F_OK) is not True:
  210. try:
  211. os.remove("/tmp/stov.lock")
  212. except os.error:
  213. logger.error(_("The old lock file could not be deleted!"))
  214. try:
  215. lockfile = open("/tmp/stov.lock", "w")
  216. lockfile.write(str(os.getpid()))
  217. lockfile.close()
  218. except IOError:
  219. logger.error(_("The lock file could not be created, please check that /tmp"
  220. " is writable and properly configured, quitting now."))
  221. sys.exit(1)
  222. """Check if the configuration directory exists and is writeable. If it
  223. doesnt, create it using the configuration class.
  224. """
  225. if os.access(os.environ['HOME'] + "/.stov", os.F_OK & os.W_OK) is not True:
  226. logger.info(_("This seems to be the first time you run the programm, do "
  227. "you want to run the interactive assistant? (yes/no)"))
  228. conf = configuration.conf()
  229. if sys.version_info >= (3, 0):
  230. temp_input = input()
  231. else:
  232. # noinspection PyCompatibility
  233. temp_input = raw_input()
  234. if temp_input == "yes":
  235. conf.assist()
  236. try:
  237. conf.Initialize()
  238. except stov_exceptions.ConfigFileWriteErrorException as e:
  239. logger.error(e)
  240. else:
  241. logger.info(_("Writing initial configuration according to your "
  242. "input, have fun!"))
  243. else:
  244. logger.info(_("Writing initial configuration according to default"
  245. " values."))
  246. logger.debug(_("Creating hidden directory in home for configuration"
  247. " and database."))
  248. try:
  249. conf.Initialize()
  250. except stov_exceptions.DirectoryCreationFailedException as e:
  251. logger.error(e)
  252. except stov_exceptions.ConfigFileWriteErrorException as e:
  253. logger.error(e)
  254. else:
  255. conf = configuration.conf()
  256. if os.access(str(os.environ['HOME']) + "/.stov/stov.config", os.F_OK):
  257. logger.debug(_("Migrating configuration from plain text to json."))
  258. conf.migrate_config()
  259. try:
  260. logger.debug(_("Comparing current and running configuration version."))
  261. check_result = conf.CheckConfig()
  262. except stov_exceptions.ConfigFileReadErrorException as e:
  263. logger.error(e)
  264. sys.exit(1)
  265. except stov_exceptions.InvalidConfigurationVersionException as e:
  266. logger.error(e)
  267. sys.exit(1)
  268. else:
  269. if check_result is not True:
  270. logger.info(_("Your configuration needs to be updated, performing"
  271. " update now."))
  272. try:
  273. conf.UpdateConfig()
  274. except stov_exceptions.ConfigFileReadErrorException as e:
  275. logger.error(e)
  276. sys.exit(1)
  277. except stov_exceptions.ConfigFileWriteErrorException as e:
  278. logger.error(e)
  279. sys.exit(1)
  280. """Create the initial connection to the database"""
  281. if os.access(conf.dbpath, os.F_OK):
  282. try:
  283. db = database.db(path=conf.dbpath, version=conf.values["db_version"])
  284. except stov_exceptions.DBConnectionFailedException as e:
  285. logger.error(e)
  286. sys.exit(1)
  287. else:
  288. try:
  289. db = database.db(path=conf.dbpath, version=conf.values["db_version"])
  290. except stov_exceptions.DBConnectionFailedException as e:
  291. logger.error(e)
  292. sys.exit(1)
  293. else:
  294. try:
  295. db.Populate()
  296. except stov_exceptions.DBWriteAccessFailedException as e:
  297. logger.error(e)
  298. sys.exit(1)
  299. else:
  300. logger.debug(_("Created initial database tables."))
  301. try:
  302. logger.debug(_("Comparing current and running database version."))
  303. if conf.CheckDB() is not True:
  304. logger.info(_("Your database needs to be updated, performing"
  305. " update now."))
  306. db.Update()
  307. conf.values["db_version"] = db.GetVersion()
  308. logger.debug("Opening configuration file.")
  309. try:
  310. conf.WriteConfig()
  311. except stov_exceptions.ConfigFileWriteErrorException as e:
  312. logger.error(e)
  313. except stov_exceptions.DBWriteAccessFailedException as e:
  314. logger.error(e)
  315. sys.exit(1)
  316. """Check which outputlevel is defined and update the configuration object
  317. accordingly.
  318. """
  319. conf.outputlevel = outputlevel
  320. """youtube-dl is really a dependency but the program will run with limited
  321. functionality without it so we need to check that here
  322. """
  323. if conf.values["youtube-dl"] == "":
  324. youtube_dl_path = subprocess.Popen(["which", "youtube-dl"],
  325. stdout=subprocess.PIPE)\
  326. .communicate()[0]
  327. conf.values["youtube-dl"] = youtube_dl_path.strip()
  328. if os.access(conf.values["youtube-dl"], os.F_OK & os.R_OK & os.X_OK):
  329. logger.info(_("Found youtube-dl, writing it's path to the "
  330. "configuration file."))
  331. logger.debug("Opening configuration file.")
  332. try:
  333. conf.WriteConfig()
  334. except stov_exceptions.ConfigFileWriteErrorException as e:
  335. logger.error(e)
  336. sys.exit(1)
  337. else:
  338. logger.error(_("Could not find youtube-dl, it either does not exist, "
  339. "is not readable or not executable. Please note that "
  340. "youtube-dl is not needed for the program to run but "
  341. "is needed to use the download option which won't work "
  342. "otherwise. If youtube-dl isn't found automatically, "
  343. "you may also enter the path to it in the configuration"
  344. " file."))
  345. """Variable to save the text that is later sent as e-mail"""
  346. mailcontent = []
  347. """Check which options are given on the command line and
  348. run the corresponding code
  349. """
  350. if options.add is True:
  351. if options.channel is not None and options.searchparameter is None:
  352. NewSubscription = subscription.sub(type="channel",
  353. name=options.channel, conf=conf)
  354. elif options.channel is not None and options.searchparameter is not None:
  355. NewSubscription = subscription.sub(type="channel",
  356. name=options.channel,
  357. search=options.searchparameter,
  358. conf=conf)
  359. elif options.channel is None and options.searchparameter is not None:
  360. NewSubscription = subscription.sub(type="search",
  361. name=_("Search_"),
  362. search=options.searchparameter,
  363. conf=conf)
  364. elif options.playlist is not None:
  365. if options.searchparameter is not None:
  366. logger.error(_("Playlists do not support searching, the search "
  367. "option will be ignored!"))
  368. NewSubscription = subscription.sub(type="playlist",
  369. name=options.playlist,
  370. conf=conf)
  371. else:
  372. logger.error(_("None or invalid subscription type given, please check "
  373. "the type option and try again."))
  374. sys.exit(1)
  375. try:
  376. id = db.InsertSubscription(NewSubscription.AddSub())
  377. NewSubscription.SetID(id)
  378. except stov_exceptions.DBWriteAccessFailedException as e:
  379. logger.error(e)
  380. sys.exit(1)
  381. else:
  382. logger.debug(_("Subscription sucessfully inserted into database."))
  383. try:
  384. NewSubscription.update_data()
  385. except stov_exceptions.YoutubeAPITimeoutException as e:
  386. logger.error(e)
  387. except stov_exceptions.NoDataFromYoutubeAPIException as e:
  388. logger.error(e)
  389. for video in NewSubscription.parsed_response.videos:
  390. if not db.VideoInDatabase(video.ytid):
  391. if NewSubscription.CheckStringMatch(video):
  392. try:
  393. db.InsertVideo(video, NewSubscription.GetId())
  394. except stov_exceptions.DBWriteAccessFailedException as e:
  395. logger.error(e)
  396. sys.exit(1)
  397. else:
  398. logger.debug(_("Video %s successfully inserted into "
  399. "database.") % video.title)
  400. logger.info(_("New subscription ") + NewSubscription.GetTitle()
  401. + _(" successfully added"))
  402. elif options.list is True:
  403. Listofsubscriptions = db.GetSubscriptions(conf)
  404. sub_state = None
  405. if len(Listofsubscriptions) != 0:
  406. logger.info(_("ID Title"))
  407. for sub in Listofsubscriptions:
  408. if not sub.disabled:
  409. sub_state = _("enabled")
  410. elif sub.disabled:
  411. sub_state = _("disabled")
  412. logger.info(str(sub.GetId()) + " " + sub.GetTitle()
  413. + " (%s)" % sub_state)
  414. else:
  415. logger.info(_("No subscriptions added yet, add one!"))
  416. elif options.deleteid is not None:
  417. try:
  418. DeleteID = int(options.deleteid)
  419. except ValueError:
  420. logger.error(_("Invalid option, please use the ID of the subscription "
  421. "you want to delete as parameter for the remove "
  422. "option."))
  423. else:
  424. try:
  425. db.DeleteSubscription(DeleteID)
  426. except stov_exceptions.SubscriptionNotFoundException as e:
  427. logger.error(e)
  428. sys.exit(1)
  429. except stov_exceptions.DBWriteAccessFailedException as e:
  430. logger.error(e)
  431. sys.exit(1)
  432. else:
  433. logger.info(_("Subscription deleted successfully!"))
  434. elif options.update is True:
  435. listofsubscriptions = db.GetSubscriptions(conf)
  436. for element in listofsubscriptions:
  437. videos = db.GetVideos(element.GetId(), conf)
  438. element.GatherVideos(videos)
  439. try:
  440. element.update_data()
  441. except stov_exceptions.YoutubeAPITimeoutException as e:
  442. logger.error(e)
  443. except stov_exceptions.NoDataFromYoutubeAPIException as e:
  444. logger.error(e)
  445. for video in element.parsed_response.videos:
  446. if not db.VideoInDatabase(video.ytid):
  447. if element.CheckStringMatch(video):
  448. try:
  449. db.InsertVideo(video, element.GetId())
  450. except stov_exceptions.DBWriteAccessFailedException as e:
  451. logger.error(e)
  452. sys.exit(1)
  453. else:
  454. logger.debug(_("Video %s successfully inserted into "
  455. "database.") % video.title)
  456. elif options.download is True:
  457. listofsubscriptions = db.GetSubscriptions(conf)
  458. logger.debug(_("Trying to determine the itag value for youtube-dl from"
  459. " your quality and codec settings."))
  460. itag_value = conf.GetYoutubeParameter()
  461. logger.debug(_("Found value: %s.") % itag_value)
  462. if itag_value == 0:
  463. logger.debug(_("Codec and resolution could not be determined, using "
  464. "maximum possible value."))
  465. itag_value = 38
  466. videosdownloaded = 0
  467. videosfailed = 0
  468. for element in listofsubscriptions:
  469. videos = db.GetVideos(element.GetId(), conf)
  470. element.GatherVideos(videos)
  471. try:
  472. element.DownloadVideos(itag_value)
  473. except stov_exceptions.SubscriptionDisabledException as e:
  474. logger.debug(e)
  475. for entry in element.DownloadedVideos:
  476. db.UpdateVideoDownloadStatus(entry.GetID(), 1)
  477. mailcontent.append(entry.title)
  478. videosdownloaded = len(mailcontent)
  479. videosfailed = videosfailed + element.FailedVideosCount
  480. for video in element.FailedVideos:
  481. try:
  482. db.UpdateVideoFailCount(video.failcnt, video.GetID())
  483. if video.failcnt >= int(conf.values["maxfails"]):
  484. db.DisableFailedVideo(video.GetID())
  485. except stov_exceptions.DBWriteAccessFailedException as e:
  486. logger.error(e)
  487. sys.exit(1)
  488. if videosdownloaded > 0 and conf.values["notify"] == "yes":
  489. MailText = ""
  490. msg = MIMEMultipart()
  491. if videosdownloaded == 1:
  492. msg["Subject"] = _("Downloaded %i new video") % videosdownloaded
  493. MailText = _("The following episode has been downloaded by stov: "
  494. "\n\n")
  495. else:
  496. msg["Subject"] = _("Downloaded %i new videos") % videosdownloaded
  497. MailText = _("The following episodes have been downloaded by stov:"
  498. " \n\n")
  499. msg["From"] = "stov <%s>" % conf.values["mailfrom"]
  500. msg["To"] = "<%s>" % conf.values["mailto"]
  501. for line in mailcontent:
  502. MailText += line + "\n"
  503. msgtext = MIMEText(MailText.encode("utf8"), _charset="utf8")
  504. msg.attach(msgtext)
  505. serverconnection = smtplib.SMTP()
  506. try:
  507. if sys.version_info >= (3, 0):
  508. serverconnection.connect(conf.values["mailhost"],
  509. conf.values["smtpport"])
  510. else:
  511. serverconnection.connect(str(conf.values["mailhost"]),
  512. str(conf.values["smtpport"]))
  513. except (smtplib.SMTPConnectError, smtplib.SMTPServerDisconnected,
  514. socket.error):
  515. logger.error(_("Could not connect to the smtp server, please check"
  516. " your settings!"))
  517. logger.error(MailText)
  518. else:
  519. try:
  520. serverconnection.starttls()
  521. except smtplib.SMTPException:
  522. logger.debug(_("TLS not available, proceeding unencrypted."))
  523. if conf.values["auth_needed"] == "yes":
  524. try:
  525. serverconnection.login(conf.values["user_name"],
  526. conf.values["password"])
  527. except smtplib.SMTPAuthenticationError:
  528. logger.error(_("Authentication failed, please check user "
  529. "name and password!"))
  530. except smtplib.SMTPException:
  531. logger.error(_("Could not authenticate, server probably "
  532. "does not support authentication!"))
  533. try:
  534. serverconnection.sendmail(conf.values["mailfrom"],
  535. conf.values["mailto"],
  536. msg.as_string())
  537. except smtplib.SMTPRecipientsRefused:
  538. logger.error(_("The server refused the recipient address, "
  539. "please check your settings."))
  540. except smtplib.SMTPSenderRefused:
  541. logger.error(_("The server refused the sender address, "
  542. "please check your settings."))
  543. serverconnection.quit()
  544. elif videosdownloaded == 0 and videosfailed == 0:
  545. if conf.values["notify"] == "no":
  546. logger.info(_("There are no videos to be downloaded."))
  547. elif conf.values["notify"] == "no":
  548. if videosfailed == 0:
  549. logger.info(_("The following videos have been downloaded:\n"))
  550. for i in mailcontent:
  551. logger.info(i)
  552. else:
  553. if conf.values["notify"] != "yes":
  554. logger.error(_("Could not determine how you want to be informed "
  555. "about new videos, please check the notify "
  556. "parameter in your configuration."))
  557. elif options.subscriptionid is not None:
  558. try:
  559. Data = db.GetSubscription(options.subscriptionid)
  560. except stov_exceptions.DBWriteAccessFailedException as e:
  561. logger.error(e)
  562. sys.exit(1)
  563. else:
  564. if Data:
  565. Subscription = subscription.sub(id=Data[0][0], title=Data[0][1],
  566. type=Data[0][2], name=Data[0][3],
  567. search=Data[0][4],
  568. directory=Data[0][5],
  569. disabled=Data[0][6], conf=conf)
  570. videos = db.GetVideos(Subscription.GetId(), conf)
  571. Subscription.GatherVideos(videos)
  572. video_list = Subscription.PrintVideos()
  573. for video in video_list:
  574. logger.info(video)
  575. else:
  576. logger.error(_("Invalid subscription, please check the list and "
  577. "try again."))
  578. elif options.catchup is not None:
  579. try:
  580. sub_data = db.GetSubscriptionTitle(options.catchup)
  581. except stov_exceptions.DBWriteAccessFailedException as e:
  582. logger.error(e)
  583. sys.exit(1)
  584. else:
  585. if sub_data != []:
  586. try:
  587. db.MarkVideosDownloaded(options.catchup)
  588. except stov_exceptions.DBWriteAccessFailedException as e:
  589. logger.error(e)
  590. else:
  591. logger.error(_("The subscription could not be updated, "
  592. "please check if the ID given is correct."))
  593. elif options.cleanup is True:
  594. subscriptions_list = db.GetSubscriptions(conf)
  595. for element in subscriptions_list:
  596. videos = db.GetVideos(element.GetId(), conf)
  597. element.CheckAndDelete(videos)
  598. for delete_video in element.ToDelete:
  599. logger.debug(_("Deleting video %s from "
  600. "database") % delete_video.title)
  601. try:
  602. db.DeleteVideo(delete_video.GetID())
  603. except stov_exceptions.DBWriteAccessFailedException as e:
  604. logger.error(e)
  605. sys.exit(1)
  606. try:
  607. db.Vacuum()
  608. except stov_exceptions.DBWriteAccessFailedException as e:
  609. logger.error(e)
  610. sys.exit(1)
  611. elif options.enableid is not None:
  612. subscription_state = db.GetSubscription(options.enableid)
  613. try:
  614. if int(subscription_state[0][6]) == 0:
  615. logger.error(_("The subscription ID %s is already enabled.")
  616. % options.enableid)
  617. elif int(subscription_state[0][6]) == 1:
  618. try:
  619. db.ChangeSubscriptionState(options.enableid, 0)
  620. except stov_exceptions.DBWriteAccessFailedException as e:
  621. logger.error(e)
  622. sys.exit(1)
  623. else:
  624. logger.info(_("Enabled subscription ID %s.")
  625. % options.enableid)
  626. except IndexError:
  627. logger.error(_("Could not find the subscription with ID %s, "
  628. "please check and try again.") % options.enableid)
  629. elif options.disableid is not None:
  630. subscription_state = db.GetSubscription(options.disableid)
  631. try:
  632. if int(subscription_state[0][6]) == 1:
  633. logger.error(_("Subscription ID %s is already disabled.")
  634. % options.disableid)
  635. elif int(subscription_state[0][6]) == 0:
  636. try:
  637. db.ChangeSubscriptionState(options.disableid, 1)
  638. except stov_exceptions.DBWriteAccessFailedException as e:
  639. logger.error(e)
  640. sys.exit(1)
  641. else:
  642. logger.info(_("Disabled subscription ID %s.")
  643. % options.disableid)
  644. except IndexError:
  645. logger.error(_("Could not find the subscription with ID %s, please"
  646. " check and try again.") % options.disableid)
  647. elif options.license is True:
  648. logger.info("""
  649. stov is free software: you can redistribute it and/or modify
  650. it under the terms of the GNU General Public License as published by
  651. the Free Software Foundation, version 2 of the License.
  652. stov is distributed in the hope that it will be useful,
  653. but WITHOUT ANY WARRANTY; without even the implied warranty of
  654. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  655. GNU General Public License for more details.
  656. You should have received a copy of the GNU General Public License
  657. along with stov. If not, see <http://www.gnu.org/licenses/>.""")
  658. elif options.version is True:
  659. logger.info("0.9.2")
  660. else:
  661. parser.print_help()
  662. """Remove the lock file and end the program so it can be run again"""
  663. try:
  664. os.remove("/tmp/stov.lock")
  665. if exit_status is True:
  666. sys.exit(0)
  667. else:
  668. sys.exit(1)
  669. except os.error:
  670. logger.error(_("Could not delete the lock file. Please check what "
  671. "went wrong and clean up manually!"))