database.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. # This file is part of stov, written by Helmut Pozimski 2012-2017.
  2. #
  3. # stov is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, version 2 of the License.
  6. #
  7. # stov is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License
  13. # along with stov. If not, see <http://www.gnu.org/licenses/>.
  14. # -*- coding: utf8 -*-
  15. """This module is responsible for all database related operations."""
  16. import sqlite3
  17. import logging
  18. from lib_stov import stov_exceptions
  19. from lib_stov import youtube
  20. from lib_stov import subscription
  21. LOGGER = logging.getLogger("stov")
  22. class Db(object):
  23. """This class is used to cosntruct the module which will take care of all
  24. database related operations like opening the database, reading from and
  25. writing to it.
  26. """
  27. def __init__(self, path, version):
  28. """Constructor of the db class, populates the object with the relevant
  29. attributes, connects to the database and creates it if asked to.
  30. """
  31. self.__path = path
  32. self.__version = version
  33. try:
  34. self.__connection = sqlite3.connect(self.__path)
  35. except sqlite3.OperationalError:
  36. raise stov_exceptions.DBConnectionFailedException()
  37. else:
  38. self.__cursor = self.__connection.cursor()
  39. def __del__(self):
  40. """Destructor, closes the connection to the database."""
  41. self.__connection.close()
  42. def _execute_statement(self, statement, argument=None):
  43. """Executes a statement, works as a wrapper around cursor execute."""
  44. try:
  45. if not argument:
  46. result = self.__cursor.execute(statement)
  47. else:
  48. result = self.__cursor.execute(statement, argument)
  49. except sqlite3.OperationalError as error:
  50. LOGGER.debug(error)
  51. raise stov_exceptions.DBWriteAccessFailedException()
  52. else:
  53. self.__connection.commit()
  54. return result
  55. def populate(self):
  56. """Populates the database with the initial structure."""
  57. self._execute_statement("""CREATE TABLE subscriptions (
  58. id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  59. title TEXT,
  60. name TEXT,
  61. type TEXT,
  62. searchstring TEXT,
  63. directory TEXT,
  64. disabled INTEGER DEFAULT 0,
  65. site INTEGER NOT NULL
  66. );""")
  67. self._execute_statement("""CREATE TABLE videos (
  68. id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  69. title TEXT,
  70. ytid TEXT,
  71. subscription_id INTEGER,
  72. downloaded INT,
  73. failcnt INTEGER DEFAULT 0
  74. );""")
  75. self._execute_statement("""CREATE TABLE sites(
  76. id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  77. title TEXT);""")
  78. def update(self):
  79. """Updates the database structure to match the current version"""
  80. if int(self.__version) == 1:
  81. # Changes between version 1 and 2
  82. self._execute_statement("ALTER TABLE videos ADD COLUMN failcnt \
  83. INTEGER DEFAULT 0;")
  84. self.__version = 2
  85. if int(self.__version) == 2:
  86. # Changes between version 2 and 3
  87. self._execute_statement("ALTER TABLE subscriptions ADD COLUMN"
  88. " disabled INTEGER DEFAULT 0;")
  89. self._execute_statement("UPDATE subscriptions SET disabled=0;")
  90. self.__version = 3
  91. if int(self.__version) == 3:
  92. # Pseudo version without changes to the database structure,
  93. # converts existing channel subscriptions into user ones.
  94. self._execute_statement("UPDATE subscriptions SET type='user' "
  95. "WHERE type='channel'")
  96. self.__version = 4
  97. if int(self.__version) == 4:
  98. # Changes between version 4 and 5
  99. self._execute_statement("""CREATE TABLE videos_backup (
  100. id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  101. title TEXT,
  102. ytid TEXT,
  103. subscription_id INTEGER,
  104. downloaded INT,
  105. failcnt INTEGER DEFAULT 0
  106. );""")
  107. self._execute_statement("INSERT INTO videos_backup SELECT"
  108. " id, title, ytid, subscription_id, "
  109. "downloaded, failcnt FROM videos;")
  110. self._execute_statement("DROP TABLE videos;")
  111. self._execute_statement("ALTER TABLE videos_backup RENAME TO "
  112. "videos;")
  113. self.__version = 5
  114. if int(self.__version) == 5:
  115. self._execute_statement("""CREATE TABLE sites(
  116. id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  117. title TEXT);""")
  118. self._execute_statement("""ALTER TABLE subscriptions ADD
  119. COLUMN site INTEGER;""")
  120. self.add_site("youtube")
  121. self._execute_statement("""UPDATE subscriptions SET site=1;""")
  122. self._execute_statement("""CREATE TABLE subscriptions_backup (
  123. id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  124. title TEXT,
  125. name TEXT,
  126. type TEXT,
  127. searchstring TEXT,
  128. directory TEXT,
  129. disabled INTEGER DEFAULT 0,
  130. site INTEGER NOT NULL
  131. );""")
  132. self._execute_statement("INSERT INTO subscriptions_backup SELECT"
  133. " id, title, name, type, "
  134. "searchstring, directory, disabled, "
  135. "site FROM subscriptions;")
  136. self._execute_statement("DROP TABLE subscriptions;")
  137. self._execute_statement("ALTER TABLE subscriptions_backup "
  138. "RENAME TO subscriptions;")
  139. self.__version = 6
  140. def get_version(self):
  141. """Simple getter method, returns the DB version"""
  142. return self.__version
  143. def delete_subscription(self, sub_id):
  144. """Deletes a subscription and all associated videos from the
  145. database
  146. """
  147. checkquery = "SELECT * FROM subscriptions WHERE id=?"
  148. checkresult = self._execute_statement(checkquery, (sub_id,))
  149. if not checkresult.fetchall():
  150. raise stov_exceptions.SubscriptionNotFoundException()
  151. else:
  152. deletevideos = "DELETE FROM videos WHERE subscription_id=?"
  153. self._execute_statement(deletevideos, (sub_id,))
  154. deletesubscription = "DELETE FROM subscriptions WHERE id=?"
  155. self._execute_statement(deletesubscription, (sub_id,))
  156. def get_videos(self, subscription_id, conf):
  157. """Gets all videos of a given subscription id from the database and
  158. returns them
  159. """
  160. videos_list = []
  161. video_query = "SELECT id, title, ytid, downloaded, " \
  162. "failcnt FROM videos WHERE subscription_id=?"
  163. cursor = self._execute_statement(video_query, (subscription_id,))
  164. result = cursor.fetchall()
  165. for video in result:
  166. videos_list.append(youtube.Video(video_id=video[0], title=video[1],
  167. ytid=video[2],
  168. downloaded=video[3],
  169. failcount=video[4],
  170. conf=conf))
  171. return videos_list
  172. def video_in_database(self, ytid):
  173. """Checks if the video with a given youtube id already exists in the
  174. database
  175. """
  176. video_query = "SELECT id FROM videos WHERE ytid=?"
  177. cursor = self._execute_statement(video_query, (ytid,))
  178. result = cursor.fetchall()
  179. if not result:
  180. return False
  181. else:
  182. return True
  183. def insert_video(self, video, subscription_id):
  184. """Inserts a video with the given data into the database"""
  185. video_insert = "INSERT INTO videos (title, ytid, \
  186. subscription_id, downloaded) VALUES \
  187. (?, ?, ?, ?)"
  188. insert_data = (video.title, video.ytid,
  189. subscription_id, 0)
  190. self._execute_statement(video_insert, insert_data)
  191. def insert_subscription(self, data):
  192. """Inserts a subscription with the given data into the database"""
  193. subscription_insert = """INSERT INTO subscriptions (title, type,
  194. searchstring, directory, name, disabled, site)
  195. VALUES (?, ?, ?, ?, ?, ?, ?)"""
  196. self._execute_statement(subscription_insert, data)
  197. subscription_getid = "SELECT id from subscriptions where title=?"
  198. query_cursor = self._execute_statement(subscription_getid, (data[0],))
  199. subscription_id = query_cursor.fetchone()[0]
  200. return subscription_id
  201. def update_video_download_status(self, video_id, status):
  202. """Updates the download status of a video in the database"""
  203. update_statement = "UPDATE videos SET downloaded = ? WHERE id = ?"
  204. self._execute_statement(update_statement, (status, video_id))
  205. def disable_failed_video(self, video_id):
  206. """Disables a video in the database"""
  207. update_statement = "UPDATE videos SET downloaded = -1 WHERE id = ?"
  208. self._execute_statement(update_statement, (video_id,))
  209. def update_video_fail_count(self, count, video_id):
  210. """Updates the fail count of a video in the database"""
  211. update_statement = "UPDATE videos SET failcnt = ? WHERE id = ?"
  212. self._execute_statement(update_statement, (count, video_id))
  213. def delete_video(self, video_id):
  214. """Deletes a video from the database"""
  215. delete_statement = "DELETE FROM videos where id = ?"
  216. self._execute_statement(delete_statement, (video_id,))
  217. def get_subscription(self, sub_id):
  218. """Retrieves a specific subscription from the database"""
  219. sub_query = """SELECT subscriptions.id, subscriptions.title,
  220. type, name,searchstring, directory,disabled, sites.title FROM
  221. subscriptions INNER JOIN sites ON subscriptions.site=sites.id WHERE
  222. subscriptions.id=?"""
  223. result_cursor = self._execute_statement(sub_query, (sub_id,))
  224. result = result_cursor.fetchall()
  225. return result
  226. def get_subscription_title(self, sub_id):
  227. """Retrieves only the title of a specified subscription from the
  228. database
  229. """
  230. title_query = "SELECT title from subscriptions where id=?"
  231. result_cursor = self._execute_statement(title_query, (sub_id,))
  232. result = result_cursor.fetchall()
  233. return result
  234. def mark_video_downloaded(self, video_id):
  235. """ Marks all videos in a specified subscription as downloaded"""
  236. update_statement = "UPDATE videos SET downloaded = 1 " \
  237. "WHERE subscription_id =?"
  238. self._execute_statement(update_statement, (video_id,))
  239. def get_subscriptions(self, conf):
  240. """Retrieves all subscriptions from the database"""
  241. subscriptions_list = []
  242. subscriptions_query = """SELECT subscriptions.id, subscriptions.title,
  243. type, name,searchstring, directory,disabled, sites.title FROM
  244. subscriptions INNER JOIN sites ON subscriptions.site=sites.id;"""
  245. result_cursor = self._execute_statement(subscriptions_query)
  246. result = result_cursor.fetchall()
  247. for sub in result:
  248. subscriptions_list.append(subscription.Sub(
  249. subscription_id=sub[0], title=sub[1], subscription_type=sub[2],
  250. name=sub[3], search=sub[4], directory=sub[5], disabled=sub[6],
  251. site=sub[7], conf=conf))
  252. return subscriptions_list
  253. def vacuum(self):
  254. """Vacuums the database, shrinking it in size"""
  255. self._execute_statement("VACUUM")
  256. def change_subscription_state(self, sub_id, state):
  257. """Enables or disables a subscription depending on the parameter
  258. state
  259. """
  260. update_statement = "UPDATE subscriptions SET disabled=? WHERE id=?"
  261. self._execute_statement(update_statement, (state, sub_id))
  262. def add_site(self, name):
  263. """
  264. Adds a site with the specified name to the database.
  265. :param name: name of the new site
  266. :type site: str
  267. """
  268. insert_statement = "INSERT INTO sites (title) VALUES (?)"
  269. self._execute_statement(insert_statement, (name,))
  270. def get_site_id(self, name):
  271. """
  272. Get the ID of a specific site
  273. :param name: name of the new site
  274. :type site: str
  275. :return: the site ID
  276. :rtype: int
  277. """
  278. query = "SELECT id FROM sites WHERE title=?"
  279. cursor = self._execute_statement(query, (name,))
  280. result = cursor.fetchone()[0]
  281. return result
  282. def get_sites(self):
  283. """
  284. Retrieves all sites from the database.
  285. :return: list of sites with their respective IDs
  286. :rtype: tuple
  287. """
  288. query = "SELECT id,title FROM sites"
  289. cursor = self._execute_statement(query)
  290. result = cursor.fetchall()
  291. return result