#!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' import os, subprocess, hashlib, shutil, glob, stat, sys, time from subprocess import check_call from tempfile import NamedTemporaryFile, mkdtemp from zipfile import ZipFile if __name__ == '__main__': d = os.path.dirname sys.path.insert(0, d(d(os.path.abspath(__file__)))) from setup import Command, __version__, installer_name, __appname__ PREFIX = "/var/www/calibre-ebook.com" DOWNLOADS = PREFIX+"/htdocs/downloads" BETAS = DOWNLOADS +'/betas' HTML2LRF = "calibre/ebooks/lrf/html/demo" TXT2LRF = "src/calibre/ebooks/lrf/txt/demo" STAGING_HOST = 'download.calibre-ebook.com' STAGING_USER = 'root' STAGING_DIR = '/root/staging' def installers(): installers = list(map(installer_name, ('dmg', 'msi', 'tar.bz2'))) installers.append(installer_name('tar.bz2', is64bit=True)) installers.append(installer_name('msi', is64bit=True)) installers.insert(0, 'dist/%s-%s.tar.xz'%(__appname__, __version__)) installers.append('dist/%s-portable-installer-%s.exe'%(__appname__, __version__)) return installers def installer_description(fname): if fname.endswith('.tar.xz'): return 'Source code' if fname.endswith('.tar.bz2'): bits = '32' if 'i686' in fname else '64' return bits + 'bit Linux binary' if fname.endswith('.msi'): return 'Windows %sinstaller'%('64bit ' if '64bit' in fname else '') if fname.endswith('.dmg'): return 'OS X dmg' if fname.endswith('.exe'): return 'Calibre Portable' return 'Unknown file' def upload_signatures(): tdir = mkdtemp() for installer in installers(): if not os.path.exists(installer): continue with open(installer, 'rb') as f: raw = f.read() fingerprint = hashlib.sha512(raw).hexdigest() fname = os.path.basename(installer+'.sha512') with open(os.path.join(tdir, fname), 'wb') as f: f.write(fingerprint) check_call('scp %s/*.sha512 divok:%s/signatures/' % (tdir, DOWNLOADS), shell=True) shutil.rmtree(tdir) class ReUpload(Command): # {{{ description = 'Re-upload any installers present in dist/' sub_commands = ['upload_installers'] def pre_sub_commands(self, opts): opts.replace = True exists = {x for x in installers() if os.path.exists(x)} if not exists: print ('There appear to be no installers!') raise SystemExit(1) def run(self, opts): for x in installers(): if os.path.exists(x): os.remove(x) # }}} # Data {{{ def get_google_data(): with open(os.path.expanduser('~/work/env/private/googlecodecalibre'), 'rb') as f: gc_password, ga_un, pw = f.read().strip().split('|') return { 'username':ga_un, 'password':pw, 'gc_password':gc_password, 'path_map_server':'root@kovidgoyal.net', 'path_map_location':'/var/www/status.calibre-ebook.com/googlepaths', # If you change this remember to change it in the # status.calibre-ebook.com server as well 'project':'calibre-ebook' } def get_sourceforge_data(): return {'username':'kovidgoyal', 'project':'calibre'} def send_data(loc): subprocess.check_call(['rsync', '--inplace', '--delete', '-r', '-z', '-h', '--progress', '-e', 'ssh -x', loc+'/', '%s@%s:%s'%(STAGING_USER, STAGING_HOST, STAGING_DIR)]) def gc_cmdline(ver, gdata): return [__appname__, ver, 'fmap', 'googlecode', gdata['project'], gdata['username'], gdata['password'], gdata['gc_password'], '--path-map-server', gdata['path_map_server'], '--path-map-location', gdata['path_map_location']] def sf_cmdline(ver, sdata): return [__appname__, ver, 'fmap', 'sourceforge', sdata['project'], sdata['username']] def calibre_cmdline(ver): return [__appname__, ver, 'fmap', 'calibre'] def dbs_cmdline(ver): return [__appname__, ver, 'fmap', 'dbs'] def run_remote_upload(args): print 'Running remotely:', ' '.join(args) subprocess.check_call(['ssh', '-x', '%s@%s'%(STAGING_USER, STAGING_HOST), 'cd', STAGING_DIR, '&&', 'python', 'hosting.py']+args) # }}} class UploadInstallers(Command): # {{{ def add_options(self, parser): parser.add_option('--replace', default=False, action='store_true', help= 'Replace existing installers') def run(self, opts): all_possible = set(installers()) available = set(glob.glob('dist/*')) files = {x:installer_description(x) for x in all_possible.intersection(available)} for x in files: os.chmod(x, stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH) sizes = {os.path.basename(x):os.path.getsize(x) for x in files} self.record_sizes(sizes) tdir = mkdtemp() backup = os.path.join('/mnt/external/calibre/%s' % __version__) if not os.path.exists(backup): os.mkdir(backup) try: self.upload_to_staging(tdir, backup, files) self.upload_to_calibre() if opts.replace: upload_signatures() self.upload_to_sourceforge() self.upload_to_dbs() # self.upload_to_google(opts.replace) finally: shutil.rmtree(tdir, ignore_errors=True) def record_sizes(self, sizes): print ('\nRecording dist sizes') args = ['%s:%s:%s' % (__version__, fname, size) for fname, size in sizes.iteritems()] check_call(['ssh', 'divok', 'dist_sizes'] + args) def upload_to_staging(self, tdir, backup, files): os.mkdir(tdir+'/dist') hosting = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'hosting.py') shutil.copyfile(hosting, os.path.join(tdir, 'hosting.py')) for f in files: for x in (tdir+'/dist', backup): dest = os.path.join(x, os.path.basename(f)) shutil.copy2(f, x) os.chmod(dest, stat.S_IREAD|stat.S_IWRITE|stat.S_IRGRP|stat.S_IROTH) with open(os.path.join(tdir, 'fmap'), 'wb') as fo: for f, desc in files.iteritems(): fo.write('%s: %s\n'%(f, desc)) while True: try: send_data(tdir) except: print('\nUpload to staging failed, retrying in a minute') time.sleep(60) else: break def upload_to_google(self, replace): gdata = get_google_data() args = gc_cmdline(__version__, gdata) if replace: args = ['--replace'] + args run_remote_upload(args) def upload_to_sourceforge(self): sdata = get_sourceforge_data() args = sf_cmdline(__version__, sdata) run_remote_upload(args) def upload_to_calibre(self): run_remote_upload(calibre_cmdline(__version__)) def upload_to_dbs(self): run_remote_upload(dbs_cmdline(__version__)) # }}} class UploadUserManual(Command): # {{{ description = 'Build and upload the User Manual' sub_commands = ['manual'] def build_plugin_example(self, path): from calibre import CurrentDir with NamedTemporaryFile(suffix='.zip') as f: os.fchmod(f.fileno(), stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH|stat.S_IWRITE) with CurrentDir(path): with ZipFile(f, 'w') as zf: for x in os.listdir('.'): if x.endswith('.swp'): continue zf.write(x) if os.path.isdir(x): for y in os.listdir(x): zf.write(os.path.join(x, y)) bname = self.b(path) + '_plugin.zip' dest = '%s/%s'%(DOWNLOADS, bname) subprocess.check_call(['scp', f.name, 'divok:'+dest]) def run(self, opts): path = self.j(self.SRC, '..', 'manual', 'plugin_examples') for x in glob.glob(self.j(path, '*')): self.build_plugin_example(x) for host in ('download', 'files'): check_call(' '.join(['rsync', '-z', '-r', '--progress', 'manual/.build/html/', '%s:/srv/manual/' % host]), shell=True) # }}} class UploadDemo(Command): # {{{ description = 'Rebuild and upload various demos' def run(self, opts): check_call( '''ebook-convert %s/demo.html /tmp/html2lrf.lrf ''' '''--title='Demonstration of html2lrf' --authors='Kovid Goyal' ''' '''--header ''' '''--serif-family "/usr/share/fonts/corefonts, Times New Roman" ''' '''--mono-family "/usr/share/fonts/corefonts, Andale Mono" ''' ''''''%self.j(self.SRC, HTML2LRF), shell=True) check_call( 'cd src/calibre/ebooks/lrf/html/demo/ && ' 'zip -j /tmp/html-demo.zip * /tmp/html2lrf.lrf', shell=True) check_call('scp /tmp/html-demo.zip divok:%s/'%(DOWNLOADS,), shell=True) # }}} class UploadToServer(Command): # {{{ description = 'Upload miscellaneous data to calibre server' def run(self, opts): check_call('gpg --armor --detach-sign dist/calibre-*.tar.xz', shell=True) check_call('scp dist/calibre-*.tar.xz.asc divok:%s/signatures/'%DOWNLOADS, shell=True) check_call('ssh divok /usr/local/bin/update-calibre', shell=True) check_call('''ssh divok echo %s \\> %s/latest_version''' %(__version__, DOWNLOADS), shell=True) check_call('ssh divok /etc/init.d/apache2 graceful', shell=True) upload_signatures() # }}} # Testing {{{ def write_files(fmap): for f in fmap: with open(f, 'wb') as f: f.write(os.urandom(100)) f.write(b'a'*1000000) with open('fmap', 'wb') as fo: for f, desc in fmap.iteritems(): fo.write('%s: %s\n'%(f, desc)) def setup_installers(): ver = '0.0.1' files = {x.replace(__version__, ver):installer_description(x) for x in installers()} tdir = mkdtemp() os.chdir(tdir) return tdir, files, ver def test_google_uploader(): gdata = get_google_data() gdata['project'] = 'calibre-hosting-uploader' gdata['path_map_location'] += '-test' hosting = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'hosting.py') tdir, files, ver = setup_installers() try: os.mkdir('dist') write_files(files) shutil.copyfile(hosting, 'hosting.py') send_data(tdir) args = gc_cmdline(ver, gdata) print ('Doing initial upload') run_remote_upload(args) raw_input('Press Enter to proceed:') print ('\nDoing re-upload') run_remote_upload(['--replace']+args) raw_input('Press Enter to proceed:') nv = ver + '.1' files = {x.replace(__version__, nv):installer_description(x) for x in installers()} write_files(files) send_data(tdir) args[1] = nv print ('\nDoing update upload') run_remote_upload(args) print ('\nDont forget to delete any remaining files in the %s project'% gdata['project']) finally: shutil.rmtree(tdir) # }}} if __name__ == '__main__': test_google_uploader()