home *** CD-ROM | disk | FTP | other *** search
/ Freelog 125 / Freelog_MarsAvril2015_No125.iso / Internet / gpodder / gpodder-portable.exe / gpodder-portable / src / gpodder / schema.py < prev    next >
Text File  |  2014-10-30  |  9KB  |  294 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # gPodder - A media aggregator and podcast client
  4. # Copyright (c) 2005-2014 Thomas Perl and the gPodder Team
  5. #
  6. # gPodder is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # gPodder is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18. #
  19.  
  20. # gpodder.schema - Database schema update and migration facility
  21. # Thomas Perl <thp@gpodder.org>; 2011-02-01
  22.  
  23. from sqlite3 import dbapi2 as sqlite
  24.  
  25. import time
  26. import shutil
  27.  
  28. import logging
  29. logger = logging.getLogger(__name__)
  30.  
  31. EpisodeColumns = (
  32.     'podcast_id',
  33.     'title',
  34.     'description',
  35.     'url',
  36.     'published',
  37.     'guid',
  38.     'link',
  39.     'file_size',
  40.     'mime_type',
  41.     'state',
  42.     'is_new',
  43.     'archive',
  44.     'download_filename',
  45.     'total_time',
  46.     'current_position',
  47.     'current_position_updated',
  48.     'last_playback',
  49.     'payment_url',
  50. )
  51.  
  52. PodcastColumns = (
  53.     'title',
  54.     'url',
  55.     'link',
  56.     'description',
  57.     'cover_url',
  58.     'auth_username',
  59.     'auth_password',
  60.     'http_last_modified',
  61.     'http_etag',
  62.     'auto_archive_episodes',
  63.     'download_folder',
  64.     'pause_subscription',
  65.     'section',
  66.     'payment_url',
  67.     'download_strategy',
  68.     'sync_to_mp3_player',
  69. )
  70.  
  71. CURRENT_VERSION = 5
  72.  
  73.  
  74. # SQL commands to upgrade old database versions to new ones
  75. # Each item is a tuple (old_version, new_version, sql_commands) that should be
  76. # applied to the database to migrate from old_version to new_version.
  77. UPGRADE_SQL = [
  78.         # Version 2: Section labels for the podcast list
  79.         (1, 2, """
  80.         ALTER TABLE podcast ADD COLUMN section TEXT NOT NULL DEFAULT ''
  81.         """),
  82.  
  83.         # Version 3: Flattr integration (+ invalidate http_* fields to force
  84.         # a feed update, so that payment URLs are parsed during the next check)
  85.         (2, 3, """
  86.         ALTER TABLE podcast ADD COLUMN payment_url TEXT NULL DEFAULT NULL
  87.         ALTER TABLE episode ADD COLUMN payment_url TEXT NULL DEFAULT NULL
  88.         UPDATE podcast SET http_last_modified=NULL, http_etag=NULL
  89.         """),
  90.  
  91.         # Version 4: Per-podcast download strategy management
  92.         (3, 4, """
  93.         ALTER TABLE podcast ADD COLUMN download_strategy INTEGER NOT NULL DEFAULT 0
  94.         """),
  95.  
  96.         # Version 5: Per-podcast MP3 player device synchronization option
  97.         (4, 5, """
  98.         ALTER TABLE podcast ADD COLUMN sync_to_mp3_player INTEGER NOT NULL DEFAULT 1
  99.         """)
  100. ]
  101.  
  102. def initialize_database(db):
  103.     # Create table for podcasts
  104.     db.execute("""
  105.     CREATE TABLE podcast (
  106.         id INTEGER PRIMARY KEY NOT NULL,
  107.         title TEXT NOT NULL DEFAULT '',
  108.         url TEXT NOT NULL DEFAULT '',
  109.         link TEXT NOT NULL DEFAULT '',
  110.         description TEXT NOT NULL DEFAULT '',
  111.         cover_url TEXT NULL DEFAULT NULL,
  112.         auth_username TEXT NULL DEFAULT NULL,
  113.         auth_password TEXT NULL DEFAULT NULL,
  114.         http_last_modified TEXT NULL DEFAULT NULL,
  115.         http_etag TEXT NULL DEFAULT NULL,
  116.         auto_archive_episodes INTEGER NOT NULL DEFAULT 0,
  117.         download_folder TEXT NOT NULL DEFAULT '',
  118.         pause_subscription INTEGER NOT NULL DEFAULT 0,
  119.         section TEXT NOT NULL DEFAULT '',
  120.         payment_url TEXT NULL DEFAULT NULL,
  121.         download_strategy INTEGER NOT NULL DEFAULT 0,
  122.         sync_to_mp3_player INTEGER NOT NULL DEFAULT 1
  123.     )
  124.     """)
  125.  
  126.     INDEX_SQL = """
  127.     CREATE UNIQUE INDEX idx_podcast_url ON podcast (url)
  128.     CREATE UNIQUE INDEX idx_podcast_download_folder ON podcast (download_folder)
  129.     """
  130.  
  131.     for sql in INDEX_SQL.strip().split('\n'):
  132.         db.execute(sql)
  133.  
  134.     # Create table for episodes
  135.     db.execute("""
  136.     CREATE TABLE episode (
  137.         id INTEGER PRIMARY KEY NOT NULL,
  138.         podcast_id INTEGER NOT NULL,
  139.         title TEXT NOT NULL DEFAULT '',
  140.         description TEXT NOT NULL DEFAULT '',
  141.         url TEXT NOT NULL,
  142.         published INTEGER NOT NULL DEFAULT 0,
  143.         guid TEXT NOT NULL,
  144.         link TEXT NOT NULL DEFAULT '',
  145.         file_size INTEGER NOT NULL DEFAULT 0,
  146.         mime_type TEXT NOT NULL DEFAULT 'application/octet-stream',
  147.         state INTEGER NOT NULL DEFAULT 0,
  148.         is_new INTEGER NOT NULL DEFAULT 0,
  149.         archive INTEGER NOT NULL DEFAULT 0,
  150.         download_filename TEXT NULL DEFAULT NULL,
  151.         total_time INTEGER NOT NULL DEFAULT 0,
  152.         current_position INTEGER NOT NULL DEFAULT 0,
  153.         current_position_updated INTEGER NOT NULL DEFAULT 0,
  154.         last_playback INTEGER NOT NULL DEFAULT 0,
  155.         payment_url TEXT NULL DEFAULT NULL
  156.     )
  157.     """)
  158.  
  159.     INDEX_SQL = """
  160.     CREATE INDEX idx_episode_podcast_id ON episode (podcast_id)
  161.     CREATE UNIQUE INDEX idx_episode_download_filename ON episode (podcast_id, download_filename)
  162.     CREATE UNIQUE INDEX idx_episode_guid ON episode (podcast_id, guid)
  163.     CREATE INDEX idx_episode_state ON episode (state)
  164.     CREATE INDEX idx_episode_is_new ON episode (is_new)
  165.     CREATE INDEX idx_episode_archive ON episode (archive)
  166.     CREATE INDEX idx_episode_published ON episode (published)
  167.     """
  168.  
  169.     for sql in INDEX_SQL.strip().split('\n'):
  170.         db.execute(sql)
  171.  
  172.     # Create table for version info / metadata + insert initial data
  173.     db.execute("""CREATE TABLE version (version integer)""")
  174.     db.execute("INSERT INTO version (version) VALUES (%d)" % CURRENT_VERSION)
  175.     db.commit()
  176.  
  177.  
  178. def upgrade(db, filename):
  179.     if not list(db.execute('PRAGMA table_info(version)')):
  180.         initialize_database(db)
  181.         return
  182.  
  183.     version = db.execute('SELECT version FROM version').fetchone()[0]
  184.     if version == CURRENT_VERSION:
  185.         return
  186.  
  187.     # We are trying an upgrade - save the current version of the DB
  188.     backup = '%s_upgraded-v%d_%d' % (filename, int(version), int(time.time()))
  189.     try:
  190.         shutil.copy(filename, backup)
  191.     except Exception, e:
  192.         raise Exception('Cannot create DB backup before upgrade: ' + e)
  193.  
  194.     db.execute("DELETE FROM version")
  195.  
  196.     for old_version, new_version, upgrade in UPGRADE_SQL:
  197.         if version == old_version:
  198.             for sql in upgrade.strip().split('\n'):
  199.                 db.execute(sql)
  200.             version = new_version
  201.  
  202.     assert version == CURRENT_VERSION
  203.  
  204.     db.execute("INSERT INTO version (version) VALUES (%d)" % version)
  205.     db.commit()
  206.  
  207.     if version != CURRENT_VERSION:
  208.         raise Exception('Database schema version unknown')
  209.  
  210.  
  211. def convert_gpodder2_db(old_db, new_db):
  212.     """Convert gPodder 2.x databases to the new format
  213.  
  214.     Both arguments should be SQLite3 connections to the
  215.     corresponding databases.
  216.     """
  217.  
  218.     old_db = sqlite.connect(old_db)
  219.     new_db_filename = new_db
  220.     new_db = sqlite.connect(new_db)
  221.     upgrade(new_db, new_db_filename)
  222.  
  223.     # Copy data for podcasts
  224.     old_cur = old_db.cursor()
  225.     columns = [x[1] for x in old_cur.execute('PRAGMA table_info(channels)')]
  226.     for row in old_cur.execute('SELECT * FROM channels'):
  227.         row = dict(zip(columns, row))
  228.         values = (
  229.                 row['id'],
  230.                 row['override_title'] or row['title'],
  231.                 row['url'],
  232.                 row['link'],
  233.                 row['description'],
  234.                 row['image'],
  235.                 row['username'] or None,
  236.                 row['password'] or None,
  237.                 row['last_modified'] or None,
  238.                 row['etag'] or None,
  239.                 row['channel_is_locked'],
  240.                 row['foldername'],
  241.                 not row['feed_update_enabled'],
  242.                 '',
  243.                 None,
  244.                 0,
  245.                 row['sync_to_devices'],
  246.         )
  247.         new_db.execute("""
  248.         INSERT INTO podcast VALUES (%s)
  249.         """ % ', '.join('?'*len(values)), values)
  250.     old_cur.close()
  251.  
  252.     # Copy data for episodes
  253.     old_cur = old_db.cursor()
  254.     columns = [x[1] for x in old_cur.execute('PRAGMA table_info(episodes)')]
  255.     for row in old_cur.execute('SELECT * FROM episodes'):
  256.         row = dict(zip(columns, row))
  257.         values = (
  258.                 row['id'],
  259.                 row['channel_id'],
  260.                 row['title'],
  261.                 row['description'],
  262.                 row['url'],
  263.                 row['pubDate'],
  264.                 row['guid'],
  265.                 row['link'],
  266.                 row['length'],
  267.                 row['mimetype'],
  268.                 row['state'],
  269.                 not row['played'],
  270.                 row['locked'],
  271.                 row['filename'],
  272.                 row['total_time'],
  273.                 row['current_position'],
  274.                 row['current_position_updated'],
  275.                 0,
  276.                 None,
  277.         )
  278.         new_db.execute("""
  279.         INSERT INTO episode VALUES (%s)
  280.         """ % ', '.join('?'*len(values)), values)
  281.     old_cur.close()
  282.  
  283.     old_db.close()
  284.     new_db.commit()
  285.     new_db.close()
  286.  
  287. def check_data(db):
  288.     # All episodes must be assigned to a podcast
  289.     orphan_episodes = db.get('SELECT COUNT(id) FROM episode '
  290.             'WHERE podcast_id NOT IN (SELECT id FROM podcast)')
  291.     if orphan_episodes > 0:
  292.         logger.error('Orphaned episodes found in database')
  293.  
  294.