123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- # 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
- from lib_stov.database import Db
- from lib_stov.configuration import Conf
- LOGGER = logging.getLogger("stov")
- DATABASE = Db.get_instance()
- CONFIGURATION = Conf.get_instance()
- def add_subscription(channel="", search="", playlist="", site="youtube"):
- """
- Takes care of adding a new subscription to the database.
- :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)
- try:
- if channel and not search:
- new_subscription = subscription.Sub(subscription_type="user",
- name=channel, site=site)
- elif channel and search:
- new_subscription = subscription.Sub(subscription_type="user",
- name=channel, site=site,
- search=search)
- elif not channel and search:
- new_subscription = subscription.Sub(subscription_type="search",
- name=_("Search_"), site=site,
- search=search)
- 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, site=site)
- else:
- LOGGER.error(_("None or invalid subscription type given, please "
- "check the type option and try again."))
- sys.exit(1)
- except stov_exceptions.TypeNotSupported as error:
- LOGGER.error(error)
- 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.video_id):
- 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():
- """
- Prints a list of subscriptions from the database.
- """
- subscriptions_list = DATABASE.get_subscriptions(CONFIGURATION)
- 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(sub_id):
- """
- Deletes a specified subscription from the database
- :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(subscriptions=None):
- """
- Updates data about videos in a subscription.
- :param subscriptions: list of subscriptions to update
- :type subscriptions: list
- """
- subscriptions_list = get_subscriptions(subscriptions)
- for element in subscriptions_list:
- LOGGER.debug(_("Updating subscription %s"), element.get_title())
- videos = DATABASE.get_videos(element.get_id(), CONFIGURATION)
- 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.video_id):
- 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(subscriptions=None):
- """
- Downloads videos that haven't been previously downloaded.
- :param subscriptions: list of subscriptions to consider for downloading
- :type subscriptions: list
- :return: tuple containing (in that order) downloaded videos, failed \
- videos and a list of the videos downloaded
- :rtype: tuple
- """
- video_titles = []
- subscriptions_list = get_subscriptions(subscriptions)
- videos_downloaded = 0
- videos_failed = 0
- for sub in subscriptions_list:
- videos = DATABASE.get_videos(sub.get_id(), CONFIGURATION)
- sub.gather_videos(videos)
- try:
- sub.download_videos()
- 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(CONFIGURATION.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(downloaded_videos, video_titles):
- """
- Composes an e-mail that can be send out to the user.
- :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>" % CONFIGURATION.values["mailfrom"]
- msg["To"] = "<%s>" % CONFIGURATION.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(msg):
- """
- Sends an e-mail to the user.
- :param msg: message to be sent
- :type msg: MIMEMultipart
- """
- server_connection = smtplib.SMTP(CONFIGURATION.values["mailhost"],
- CONFIGURATION.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 CONFIGURATION.values["auth_needed"] == "yes":
- try:
- server_connection.login(CONFIGURATION.values["user_name"],
- CONFIGURATION.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(CONFIGURATION.values["mailfrom"],
- CONFIGURATION.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(sub_id):
- """
- Lists all videos in a specified subscription
- :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_type=data[0][2],
- name=data[0][3], site=data[0][7],
- search=data[0][4],
- subscription_id=data[0][0],
- title=data[0][1], directory=data[0][5],
- disabled=data[0][6])
- videos = DATABASE.get_videos(sub.get_id(), CONFIGURATION)
- 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(sub_id):
- """
- Marks all videos in a subscription as downloaded
- :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():
- """
- 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.
- """
- subscription_list = DATABASE.get_subscriptions(CONFIGURATION)
- for element in subscription_list:
- videos = DATABASE.get_videos(element.get_id(), CONFIGURATION)
- 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(sub_id, enable=False):
- """
- Enables or disables a subscription.
- :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(subscriptions=None):
- """
- starts an update of not yet downloaded videos and notifies the user
- :param subscriptions: list of subscriptions to consider for downloading
- :type subscriptions: list
- """
- videos_downloaded, videos_failed, video_titles = \
- download_videos(subscriptions)
- if videos_downloaded > 0 and CONFIGURATION.values["notify"] == "yes":
- msg = compose_email(videos_downloaded, video_titles)
- send_email(msg)
- elif videos_downloaded == 0 and videos_failed == 0:
- if CONFIGURATION.values["notify"] == "no":
- LOGGER.info(_("There are no videos to be downloaded."))
- elif CONFIGURATION.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 CONFIGURATION.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():
- """
- Adds sites to the database if they are not in there yet.
- """
- supported_sites = ["youtube", "zdf_mediathek", "twitch"]
- 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)
- for site in sites:
- if site[1] not in supported_sites:
- DATABASE.remove_site(site[1])
- def list_sites():
- """
- Lists the currently supported sites.
- """
- sites = DATABASE.get_sites()
- LOGGER.info(_("Sites currently supported by stov:"))
- for entry in sites:
- LOGGER.info(entry[1])
- def get_subscriptions(subscriptions=None):
- """
- Retrieves all or only specific subscriptions from the database and
- returns them as a list of subscription objects.
- :param subscriptions: list of subscriptions to retrieve
- :type subscriptions: list
- :return: list of subscription objects
- :rtype: list
- """
- if subscriptions:
- subscriptions_list = []
- for element in subscriptions:
- data = DATABASE.get_subscription(element)
- if data:
- sub = subscription.Sub(subscription_type=data[0][2],
- name=data[0][3], site=data[0][7],
- search=data[0][4],
- subscription_id=data[0][0],
- title=data[0][1], directory=data[0][5],
- disabled=data[0][6])
- subscriptions_list.append(sub)
- else:
- LOGGER.error(
- _("Invalid subscription, please check the list and "
- "try again."))
- else:
- subscriptions_list = DATABASE.get_subscriptions(CONFIGURATION)
- return subscriptions_list
|