stov 29 KB

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