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
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")
40 from ConfigParser
import ConfigParser
41 from optparse
import OptionParser
42 from StringIO
import StringIO
50 MANIFEST_NAME
= 'manifest'
51 CONFIG_DIR
= '~/.config/gpodder'
52 DOWNLOAD_FOLDER
= 'DOWNLOADS'
53 CONFIG_FOLDER
= 'CONFIG'
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
):])
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)
70 CONFIGFILE
= CONFIG_DIR
+ '/gpodder.conf'
71 SECTION
= 'gpodder-conf-1'
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')
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
)
99 print ' Nothing (already purged?)'
102 def extract_archive(backup_filename
, download_destination
=None):
103 if not os
.path
.exists(backup_filename
):
104 print 'File does not exist.'
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
])
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
])
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
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
)
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
))
165 open(os
.path
.join(new_path
, filename
), 'w').close()
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
])
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
)
201 create_archive(options
.create
, options
.fake
, options
.covers
)
202 elif options
.extract
:
205 extract_archive(options
.extract
, options
.destination
)