else:
raise Exception('DB engine %s not supported' % engine)
+ def columns(self, table):
+
+ columns = []
+
+ if self.engine == 'mysqli':
+ self.cur.execute('DESCRIBE %s' % table)
+ for column in self.cur.fetchall():
+ columns.append(column[0])
+
+ return columns
+
def createdb(self, db):
if self.engine == 'mysqli':
elif self.engine == 'pgsql':
self.cur.execute('CREATE DATABASE %s WITH ENCODING \'UNICODE\'' % db)
- def dropdb(self, db):
-
- self.cur.execute('DROP DATABASE %s' % db)
-
def dbexists(self, db):
count = None
return count > 0
+ def dropdb(self, db):
+
+ self.cur.execute('DROP DATABASE %s' % db)
+
+ def dump(self, fd, prefix = ''):
+ """Dump a database to the file descriptor passed"""
+
+ if self.engine != 'mysqli':
+ raise Exception('Function dump not supported by %s' % self.engine)
+ if not type(fd) == file:
+ raise Exception('Passed parameter is not a file object')
+
+ # Looping over selected tables
+ tables = self.tables()
+ for table in tables:
+ if prefix != '' and not table.startswith(prefix):
+ continue
+
+ self.cur.execute('SHOW CREATE TABLE %s' % table)
+ schema = self.cur.fetchone()[1]
+ fd.write(schema + ';\n')
+
+ # Get the columns
+ columns = self.columns(table)
+
+ # Get the field values
+ self.cur.execute('SELECT %s FROM %s' % (','.join(columns), table))
+ for row in self.cur.fetchall():
+ values = []
+ for value in row:
+ if value == None:
+ value = 'null'
+ else:
+ value = str(self.conn.escape(value))
+ values.append(value)
+ insert = 'INSERT INTO %s (%s) VALUES(%s)' % (table, ','.join(columns), ','.join(values))
+ fd.write(insert + ';\n')
+
def selectdb(self, db):
if self.engine == 'mysqli':
dbname=str(db)
)
self.cur = self.conn.cursor()
+
+ def tables(self):
+
+ tables = []
+
+ if self.engine == 'mysqli':
+ self.cur.execute('SHOW TABLES')
+ for row in self.cur.fetchall():
+ tables.append(row[0])
+
+ return tables
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+import os
+import argparse
+import json
+import time
+from distutils.dir_util import copy_tree
+from lib import config, workplace, moodle, tools
+from lib.tools import debug
+
+C = config.Conf().get
+Wp = workplace.Workplace()
+
+# Arguments
+parser = argparse.ArgumentParser(description='Backup a Moodle instance')
+parser.add_argument('-l', '--list', action='store_true', help='list the backups', dest='list')
+parser.add_argument('name', metavar='name', default=None, nargs='?', help='name of the instance')
+args = parser.parse_args()
+
+name = args.name
+backupdir = os.path.join(C('dirs.moodle'), 'backup')
+
+# List the backups
+if args.list:
+ dirs = os.listdir(backupdir)
+ backups = {}
+
+ for d in dirs:
+ path = os.path.join(backupdir, d)
+ jsonfile = os.path.join(path, 'info.json')
+ if d == '.' or d == '..': continue
+ if not os.path.isdir(path): continue
+ if not os.path.isfile(jsonfile): continue
+ infos = json.load(open(jsonfile, 'r'))
+ backups[d] = infos
+
+ for name, info in backups.iteritems():
+ backuptime = time.ctime(info['backup_time'])
+ print '{0:<25}: {1:<30} {2}'.format(name, info['release'], backuptime)
+
+ sys.exit(0)
+
+# Resolve the instance
+M = Wp.resolve(name)
+if not M:
+ debug('This is not a Moodle instance')
+ sys.exit(1)
+
+if M.get('dbtype') != 'mysqli':
+ debug('Does not support backup for this DB engine %s yet, sorry!' % M.get('dbtype'))
+
+debug('Backuping %s' % name)
+now = int(time.time())
+backup_identifier = '%s_%s' % (name, now)
+
+# Copy whole directory, shutil will create topath
+topath = os.path.join(backupdir, backup_identifier)
+path = Wp.getPath(name)
+try:
+ debug('Copying instance directory')
+ copy_tree(path, topath, preserve_symlinks = 1)
+except Exception as e:
+ debug('Error while backuping directory %s to %s' % (path, topath))
+ debug(e)
+ sys.exit(1)
+
+# Dump the whole database
+if M.isInstalled():
+ debug('Dumping database')
+ dumpto = os.path.join(topath, 'dump.sql')
+ fd = open(dumpto, 'w')
+ M.dbo().selectdb(M.get('dbname'))
+ M.dbo().dump(fd)
+else:
+ debug('Instance not installed. Do not dump database.')
+
+# Create a JSON file containing all known information
+debug('Saving instance information')
+jsonto = os.path.join(topath, 'info.json')
+info = M.info()
+info['backup_identifier'] = backup_identifier
+info['backup_time'] = now
+json.dump(info, open(jsonto, 'w'), sort_keys = True, indent = 4)
+
+debug('Done.')