Maemo 5: Proper empty episode list label handling
[gpodder.git] / bin / gpodder-backup
blobc4c57421ea55751de7d63c03b3540135d196cfbf
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
5 # gPodder - A media aggregator and podcast client
6 # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
8 # gPodder is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # gPodder 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.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # gpodder-backup - A backup/restore utility for gPodder user data
24 # by Thomas Perl <thp@gpodder.org>; 2009-02-14
27 """
28 This utility can be used to create a dump of the current gPodder
29 data (configuration files + downloads), optionally skipping the
30 real contents of the download folder (for submitting your data
31 to a bug report without having to transfer lots of data). Modes:
33 * Create (--create) a new archive from the current data
34 * Extract (--extract) a previously-created archive
35 * Purge (--purge) the current data ("start out fresh")
36 """
38 __version__ = '1.0'
40 from ConfigParser import ConfigParser
41 from optparse import OptionParser
42 from StringIO import StringIO
44 import sys
45 import os
46 import subprocess
47 import shutil
48 import tempfile
50 MANIFEST_NAME = 'manifest'
51 CONFIG_DIR = '~/.config/gpodder'
52 DOWNLOAD_FOLDER = 'DOWNLOADS'
53 CONFIG_FOLDER = 'CONFIG'
55 def implodeuser(s):
56 """Does the reverse of os.path.expanduser"""
57 home = os.path.expanduser('~/')
58 if s.startswith(home):
59 return os.path.join('~', s[len(home):])
60 else:
61 return s
63 class gPodderConfig(ConfigParser):
64 """A simple gpodder.conf-reading class
66 This class reads CONFIGFILE and then allows to
67 access all configuration options as attributes
68 of the object (e.g. config.download_dir)
69 """
70 CONFIGFILE = CONFIG_DIR + '/gpodder.conf'
71 SECTION = 'gpodder-conf-1'
73 def __init__(self):
74 ConfigParser.__init__(self)
75 self.read(os.path.expanduser(self.CONFIGFILE))
76 assert self.has_section(self.SECTION)
78 def __getattr__(self, key):
79 return self.get(self.SECTION, key)
81 def store_gpodder_config(self):
82 fp = open(os.path.expanduser(self.CONFIGFILE), 'w')
83 self.write(fp)
84 fp.close()
86 def do_purge():
87 print 'Purging:'
88 config_dir = os.path.expanduser(CONFIG_DIR)
89 if os.path.exists(config_dir):
90 if os.path.exists(os.path.expanduser(gPodderConfig.CONFIGFILE)):
91 config = gPodderConfig()
92 download_dir = config.download_dir
93 if os.path.exists(download_dir):
94 print ' Downloads in', download_dir
95 shutil.rmtree(download_dir)
96 print ' Configuration in', config_dir
97 shutil.rmtree(config_dir)
98 else:
99 print ' Nothing (already purged?)'
100 print 'done.'
102 def extract_archive(backup_filename, download_destination=None):
103 if not os.path.exists(backup_filename):
104 print 'File does not exist.'
105 sys.exit(-1)
107 config_dir = os.path.expanduser(CONFIG_DIR)
108 print 'Extracting config to %s' % config_dir
109 if not os.path.exists(config_dir):
110 os.makedirs(config_dir)
111 tar = subprocess.Popen(['tar', 'xzvf', backup_filename, '-C', config_dir,
112 '--strip', '1', CONFIG_FOLDER])
113 tar.wait()
114 print 'DONE.'
116 print 'Getting manifest'
117 tar = subprocess.Popen(['tar', 'xzf', backup_filename, '-O', MANIFEST_NAME],
118 stdout=subprocess.PIPE)
119 (manifest_data, stderr_unused) = tar.communicate()
120 manifest = ConfigParser()
121 manifest.readfp(StringIO(manifest_data))
122 if download_destination is None:
123 download_destination = os.path.expanduser(manifest.get(MANIFEST_NAME, 'download_dir'))
124 # update the "download_dir" setting in gpodder.conf,
125 # because we are extracting downloads somewhere else
126 gpocfg = gPodderConfig()
127 gpocfg.set(gPodderConfig.SECTION, 'download_dir', os.path.abspath(download_destination))
128 gpocfg.store_gpodder_config()
130 print 'Extracting downloads to %s' % download_destination
131 if not os.path.exists(download_destination):
132 os.makedirs(download_destination)
133 tar = subprocess.Popen(['tar', 'xzvf', backup_filename, '-C', download_destination,
134 '--strip', '1', DOWNLOAD_FOLDER])
135 tar.wait()
136 print 'DONE.'
138 def create_archive(backup_filename, fake_download_dir=True, add_cover_files=False):
139 if os.path.exists(backup_filename):
140 print 'refusing to overwrite existing file:', backup_filename
141 sys.exit(1)
143 tempfolder = tempfile.mkdtemp()
144 print 'using', tempfolder, 'to store temporary data'
145 config = gPodderConfig()
146 download_dir = implodeuser(config.download_dir)
147 manifest = ConfigParser()
148 manifest.add_section(MANIFEST_NAME)
149 configuration_dir = CONFIG_DIR
150 for key in ('fake_download_dir', 'download_dir', 'configuration_dir'):
151 manifest.set(MANIFEST_NAME, key, locals()[key])
152 manifp = open(os.path.join(tempfolder, MANIFEST_NAME), 'w')
153 manifest.write(manifp)
154 manifp.close()
155 if fake_download_dir:
156 os.mkdir(os.path.join(tempfolder, DOWNLOAD_FOLDER))
157 for dirpath, dirnames, filenames in os.walk(os.path.expanduser(download_dir)):
158 new_path = dirpath.replace(os.path.expanduser(download_dir), os.path.join(tempfolder, DOWNLOAD_FOLDER))
159 if not os.path.exists(new_path):
160 os.makedirs(new_path)
161 for filename in filenames:
162 if filename == 'folder.jpg' and add_cover_files:
163 shutil.copy(os.path.join(dirpath, filename), os.path.join(new_path, filename))
164 else:
165 open(os.path.join(new_path, filename), 'w').close()
166 else:
167 os.symlink(os.path.expanduser(download_dir), os.path.join(tempfolder, DOWNLOAD_FOLDER))
168 os.symlink(os.path.expanduser(CONFIG_DIR), os.path.join(tempfolder, CONFIG_FOLDER))
169 tar = subprocess.Popen(['tar', 'czvf', backup_filename, '--dereference',
170 '-C', tempfolder, MANIFEST_NAME, CONFIG_FOLDER, DOWNLOAD_FOLDER])
171 tar.wait()
172 shutil.rmtree(tempfolder)
176 if __name__ == '__main__':
177 parser = OptionParser(usage='usage: %%prog [--create|--extract] <archive.gpo.tar.gz> [options]\n %%prog --purge\n\n%s' % __doc__.strip(),
178 version='%%prog %s' % __version__)
179 parser.add_option('-c', '--create',
180 dest='create', metavar='<FILE>',
181 help='Create a new archive')
182 parser.add_option('-x', '--extract',
183 dest='extract', metavar='<FILE>',
184 help='Extract an existing archive')
185 parser.add_option('-f', '--fake-downloads',
186 action='store_true', dest='fake', default=False,
187 help='Do not store contents of downloaded files')
188 parser.add_option('-n', '--no-covers',
189 action='store_false', dest='covers', default=True,
190 help='Do not include cover files in archive')
191 parser.add_option('-D', '--destination',
192 dest='destination', metavar='<DIR>',
193 help='Extract downloads in different folder')
194 parser.add_option('-P', '--purge',
195 action='store_true', dest='purge', default=False,
196 help='Remove current data (can be combined with --extract)')
198 (options, args) = parser.parse_args(sys.argv)
200 if options.create:
201 create_archive(options.create, options.fake, options.covers)
202 elif options.extract:
203 if options.purge:
204 do_purge()
205 extract_archive(options.extract, options.destination)
206 elif options.purge:
207 do_purge()
208 else:
209 parser.print_help()