123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- # This file is part of stov, written by Helmut Pozimski 2012-2017.
- #
- # stov 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, version 2 of the License.
- #
- # stov 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 stov. If not, see <http://www.gnu.org/licenses/>.
- # -*- coding: utf8 -*-
- """ This module contains the functions that make up the core of the
- application.
- """
- import logging
- import sys
- import smtplib
- import socket
- from email.mime.text import MIMEText
- from email.mime.multipart import MIMEMultipart
- from lib_stov import subscription
- from lib_stov import stov_exceptions
- LOGGER = logging.getLogger("stov")
- def add_subscription(conf, database, channel="",
- search="", playlist="", site="youtube"):
- """
- Takes care of adding a new subscription to the database.
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- :param database: database object
- :type database: lib_stov.database.Db
- :param site: site the subscription is about to be created for
- :type site: str
- :param channel: optional channel name
- :type channel: str
- :param search: optional search string
- :type search: str
- :param playlist: optional playlist ID
- :type playlist: str
- """
- LOGGER.debug(_("Creating new subscription with the following "
- "parameters:\nChannel: %s\nSearch: %s\nPlaylist: %s"),
- channel, search, playlist)
- if channel and not search:
- new_subscription = subscription.Sub(subscription_type="user",
- name=channel, conf=conf, site=site)
- elif channel and search:
- new_subscription = subscription.Sub(subscription_type="user",
- name=channel,
- search=search,
- conf=conf, site=site)
- elif not channel and search:
- new_subscription = subscription.Sub(subscription_type="search",
- name=_("Search_"),
- search=search,
- conf=conf, site=site)
- elif playlist:
- if search:
- LOGGER.error(_("Playlists do not support searching, the search "
- "option will be ignored!"))
- new_subscription = subscription.Sub(subscription_type="playlist",
- name=playlist,
- conf=conf, site=site)
- else:
- LOGGER.error(_("None or invalid subscription type given, please check "
- "the type option and try again."))
- sys.exit(1)
- try:
- subscription_data = new_subscription.add_sub()
- site_id = database.get_site_id(subscription_data[6])
- new_sub_data = (subscription_data[0], subscription_data[1],
- subscription_data[2], subscription_data[3],
- subscription_data[4], subscription_data[5],
- site_id)
- subscription_id = database.insert_subscription(new_sub_data)
- new_subscription.set_id(subscription_id)
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- except stov_exceptions.ChannelNotFound as error:
- LOGGER.error(error)
- sys.exit(1)
- else:
- LOGGER.debug(_("Subscription sucessfully inserted into database."))
- try:
- new_subscription.update_data()
- except stov_exceptions.YoutubeAPITimeoutException as error:
- LOGGER.error(error)
- except stov_exceptions.NoDataFromYoutubeAPIException as error:
- LOGGER.error(error)
- for video in new_subscription.parsed_response.videos:
- if not database.video_in_database(video.ytid):
- if new_subscription.check_string_match(video):
- try:
- database.insert_video(video, new_subscription.get_id())
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- else:
- LOGGER.debug(_("Video %s successfully inserted into "
- "database."), video.title)
- LOGGER.info(_("New subscription ") + new_subscription.get_title() +
- _(" successfully added"))
- def list_subscriptions(conf, database):
- """
- Prints a list of subscriptions from the database.
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- :param database: database object
- :type database: lib_stov.database.Db
- """
- subscriptions_list = database.get_subscriptions(conf)
- sub_state = None
- if subscriptions_list:
- LOGGER.info(_("ID Title Site"))
- for sub in subscriptions_list:
- if not sub.disabled:
- sub_state = _("enabled")
- elif sub.disabled:
- sub_state = _("disabled")
- LOGGER.info(str(sub.get_id()) + " " + sub.get_title() +
- " " + sub.site + " " + "(%s)" % sub_state)
- else:
- LOGGER.info(_("No subscriptions added yet, add one!"))
- def delete_subscription(database, sub_id):
- """
- Deletes a specified subscription from the database
- :param database: database object
- :type database: lib_stov.database.Db
- :param sub_id: ID of the subscription to be deleted
- :type sub_id: int
- """
- try:
- database.delete_subscription(sub_id)
- except stov_exceptions.SubscriptionNotFoundException as error:
- LOGGER.error(error)
- sys.exit(1)
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- else:
- LOGGER.info(_("Subscription deleted successfully!"))
- def update_subscriptions(database, conf):
- """
- Updates data about videos in a subscription.
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- :param database: database object
- :type database: lib_stov.database.Db
- """
- subscriptions_list = database.get_subscriptions(conf)
- for element in subscriptions_list:
- LOGGER.debug(_("Updating subscription %s"), element.get_title())
- videos = database.get_videos(element.get_id(), conf)
- element.gather_videos(videos)
- try:
- element.update_data()
- except stov_exceptions.YoutubeAPITimeoutException as error:
- LOGGER.error(error)
- except stov_exceptions.NoDataFromYoutubeAPIException as error:
- LOGGER.error(error)
- for video in element.parsed_response.videos:
- if not database.video_in_database(video.ytid):
- if element.check_string_match(video):
- try:
- database.insert_video(video, element.get_id())
- except stov_exceptions.DBWriteAccessFailedException as \
- error:
- LOGGER.error(error)
- sys.exit(1)
- else:
- LOGGER.debug(_("Video %s successfully inserted into "
- "database."), video.title)
- def download_videos(database, conf):
- """
- Downloads videos that haven't been previously downloaded.
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- :param database: database object
- :type database: lib_stov.database.Db
- :return: tuple containing (in that order) downloaded videos, failed \
- videos and a list of the videos downloaded
- :rtype: tuple
- """
- video_titles = []
- subscriptions_list = database.get_subscriptions(conf)
- LOGGER.debug(_("Trying to determine the itag value for youtube-dl from"
- " your quality and codec settings."))
- itag = conf.get_youtube_parameter()
- LOGGER.debug(_("Found value: %s."), itag)
- if itag == 0:
- LOGGER.debug(_("Codec and resolution could not be determined, using "
- "maximum possible value."))
- itag = 38
- videos_downloaded = 0
- videos_failed = 0
- for sub in subscriptions_list:
- videos = database.get_videos(sub.get_id(), conf)
- sub.gather_videos(videos)
- try:
- sub.download_videos(itag)
- except stov_exceptions.SubscriptionDisabledException as error:
- LOGGER.debug(error)
- for entry in sub.downloaded_videos:
- database.update_video_download_status(entry.get_id(), 1)
- video_titles.append(entry.title)
- videos_downloaded = len(video_titles)
- videos_failed = videos_failed + sub.failed_videos_count
- for video in sub.failed_videos:
- try:
- database.update_video_fail_count(video.failcnt, video.get_id())
- if video.failcnt >= int(conf.values["maxfails"]):
- database.disable_failed_video(video.get_id())
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- return (videos_downloaded, videos_failed, video_titles)
- def compose_email(conf, downloaded_videos, video_titles):
- """
- Composes an e-mail that can be send out to the user.
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- :param downloaded_videos: number of downloaded videos
- :type downloaded_videos: int
- :param video_titles: titles of the downloaded videos
- :type video_titles: list
- :return: e-mail contents
- :rtype: MIMEMultipart
- """
- mail_text = ""
- msg = MIMEMultipart()
- if downloaded_videos == 1:
- msg["Subject"] = _("Downloaded %i new video") % downloaded_videos
- mail_text = _("The following episode has been downloaded by stov: "
- "\n\n")
- else:
- msg["Subject"] = _("Downloaded %i new videos") % downloaded_videos
- mail_text = _("The following episodes have been downloaded by "
- "stov: \n\n")
- msg["From"] = "stov <%s>" % conf.values["mailfrom"]
- msg["To"] = "<%s>" % conf.values["mailto"]
- for line in video_titles:
- mail_text += line + "\n"
- msg_text = MIMEText(mail_text.encode("utf8"), _charset="utf8")
- msg.attach(msg_text)
- return msg
- def send_email(conf, msg):
- """
- Sends an e-mail to the user.
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- :param msg: message to be sent
- :type msg: MIMEMultipart
- """
- server_connection = smtplib.SMTP(conf.values["mailhost"],
- conf.values["smtpport"])
- try:
- server_connection.connect()
- except (smtplib.SMTPConnectError, smtplib.SMTPServerDisconnected,
- socket.error):
- LOGGER.error(_("Could not connect to the smtp server, please check"
- " your settings!"))
- else:
- try:
- server_connection.starttls()
- except smtplib.SMTPException:
- LOGGER.debug(_("TLS not available, proceeding unencrypted."))
- if conf.values["auth_needed"] == "yes":
- try:
- server_connection.login(conf.values["user_name"],
- conf.values["password"])
- except smtplib.SMTPAuthenticationError:
- LOGGER.error(_("Authentication failed, please check user "
- "name and password!"))
- except smtplib.SMTPException:
- LOGGER.error(_("Could not authenticate, server probably "
- "does not support authentication!"))
- try:
- server_connection.sendmail(conf.values["mailfrom"],
- conf.values["mailto"],
- msg.as_string())
- except smtplib.SMTPRecipientsRefused:
- LOGGER.error(_("The server refused the recipient address, "
- "please check your settings."))
- except smtplib.SMTPSenderRefused:
- LOGGER.error(_("The server refused the sender address, "
- "please check your settings."))
- server_connection.quit()
- def list_videos(database, conf, sub_id):
- """
- Lists all videos in a specified subscription
- :param database: database object
- :type database: lib_stov.database.Db
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- :param sub_id: ID of the subscription
- :type sub_id: int
- """
- try:
- data = database.get_subscription(sub_id)
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- else:
- if data:
- sub = subscription.Sub(subscription_id=data[0][0],
- title=data[0][1],
- subscription_type=data[0][2],
- name=data[0][3],
- search=data[0][4],
- directory=data[0][5],
- disabled=data[0][6],
- site=data[0][7], conf=conf)
- videos = database.get_videos(sub.get_id(), conf)
- sub.gather_videos(videos)
- videos_list = sub.print_videos()
- for video in videos_list:
- LOGGER.info(video)
- else:
- LOGGER.error(_("Invalid subscription, please check the list and "
- "try again."))
- def catchup(database, sub_id):
- """
- Marks all videos in a subscription as downloaded
- :param database: database object
- :type database: lib_stov.database.Db
- :param sub_id: ID of the subscription
- :type sub_id: int
- """
- try:
- sub_data = database.get_subscription_title(sub_id)
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- else:
- if sub_data:
- try:
- database.mark_video_downloaded(sub_id)
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- else:
- LOGGER.error(_("The subscription could not be updated, "
- "please check if the ID given is correct."))
- def clean_database(database, conf):
- """
- Initiates a database cleanup, deleting all videos that are no longer
- in the scope of the query and vacuuming the database to free up space.
- :param database: database object
- :type database: lib_stov.database.Db
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- """
- subscription_list = database.get_subscriptions(conf)
- for element in subscription_list:
- videos = database.get_videos(element.get_id(), conf)
- element.check_and_delete(videos)
- for delete_video in element.to_delete:
- LOGGER.debug(_("Deleting video %s from "
- "database"), delete_video.title)
- try:
- database.delete_video(delete_video.get_id())
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- try:
- database.vacuum()
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- def change_subscription_state(database, sub_id, enable=False):
- """
- Enables or disables a subscription.
- :param database: database object
- :type database: lib_stov.database.Db
- :param sub_id: ID of the subscription
- :type sub_id: int
- :param enable: whether to enable or disable the subscription
- :type enable: bool
- """
- subscription_state = database.get_subscription(sub_id)
- try:
- if enable:
- if int(subscription_state[0][6]) == 0:
- LOGGER.error(_("The subscription ID %s is already enabled."),
- sub_id)
- elif int(subscription_state[0][6]) == 1:
- try:
- database.change_subscription_state(sub_id, 0)
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- else:
- LOGGER.info(_("Enabled subscription ID %s."), sub_id)
- else:
- if int(subscription_state[0][6]) == 1:
- LOGGER.error(_("Subscription ID %s is already disabled."),
- sub_id)
- elif int(subscription_state[0][6]) == 0:
- try:
- database.change_subscription_state(sub_id, 1)
- except stov_exceptions.DBWriteAccessFailedException as error:
- LOGGER.error(error)
- sys.exit(1)
- else:
- LOGGER.info(_("Disabled subscription ID %s."),
- sub_id)
- except IndexError:
- LOGGER.error(_("Could not find the subscription with ID %s, "
- "please check and try again."), sub_id)
- def print_license():
- """
- Prints the license of the application.
- """
- LOGGER.info("""
- stov 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, version 2 of the License.
- stov 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 stov. If not, see <http://www.gnu.org/licenses/>.""")
- def download_notify(database, conf):
- """
- starts an update of not yet downloaded videos and notifies the user
- :param database: database object
- :type database: lib_stov.database.Db
- :param conf: configuration object
- :type conf: lib_stov.configuration.Conf
- """
- videos_downloaded, videos_failed, video_titles = \
- download_videos(database, conf)
- if videos_downloaded > 0 and conf.values["notify"] == "yes":
- msg = compose_email(conf, videos_downloaded, video_titles)
- send_email(conf, msg)
- elif videos_downloaded == 0 and videos_failed == 0:
- if conf.values["notify"] == "no":
- LOGGER.info(_("There are no videos to be downloaded."))
- elif conf.values["notify"] == "no":
- if videos_failed == 0:
- LOGGER.info(_("The following videos have been downloaded:\n"))
- for i in video_titles:
- LOGGER.info(i)
- else:
- if conf.values["notify"] != "yes":
- LOGGER.error(_("Could not determine how you want to be informed "
- "about new videos, please check the notify "
- "parameter in your configuration."))
- def initialize_sites(database):
- """
- Adds sites to the database if they are not in there yet.
- :param database: database object
- :type database: lib_stov.database.Db
- """
- supported_sites = ["youtube"]
- sites = database.get_sites()
- for site in supported_sites:
- site_found = False
- for result in sites:
- if site in result:
- site_found = True
- if not site_found:
- database.add_site(site)
- def list_sites(database):
- """
- Lists the currently supported sites.
- :param database: database object
- :type database: lib_stov.database.Db
- """
- sites = database.get_sites()
- LOGGER.info(_("Sites currently supported by stov:"))
- for entry in sites:
- LOGGER.info(entry[1])
|