S3/Config.py
ec50b5a7
 ## Amazon S3 manager
 ## Author: Michal Ludvig <michal@logix.cz>
 ##         http://www.logix.cz/michal
 ## License: GPL Version 2
 
b6e1cada
 import logging
f06cf98f
 from logging import debug, info, warning, error
b6e1cada
 import re
2d7ceec9
 import Progress
fa664913
 from SortedDict import SortedDict
b6e1cada
 
b008e471
 class Config(object):
 	_instance = None
 	_parsed_files = []
0d91ff3f
 	_doc = {}
b008e471
 	access_key = ""
 	secret_key = ""
dc758146
 	host_base = "s3.amazonaws.com"
 	host_bucket = "%(bucket)s.s3.amazonaws.com"
d2b144df
 	simpledb_host = "sdb.amazonaws.com"
b3488bab
 	cloudfront_host = "cloudfront.amazonaws.com"
b020ea02
 	cloudfront_resource = "/2010-06-01/distribution"
b008e471
 	verbosity = logging.WARNING
2d7ceec9
 	progress_meter = True
4396d217
 	progress_class = Progress.ProgressCR
b008e471
 	send_chunk = 4096
 	recv_chunk = 4096
8567b8ed
 	list_md5 = False
b008e471
 	human_readable_sizes = False
dc1c96cf
 	extra_headers = SortedDict(ignore_case = True)
b008e471
 	force = False
cb0bbaef
 	enable = None
9197e62e
 	get_continue = False
559c963f
 	skip_existing = False
7406fc6c
 	recursive = False
99b416bc
 	acl_public = None
 	acl_grants = []
 	acl_revokes = []
afe194f8
 	proxy_host = ""
d35b41f4
 	proxy_port = 3128
8ec1807f
 	encrypt = False
a368faf1
 	dry_run = False
 	preserve_attrs = True
 	preserve_attrs_list = [ 
 		'uname',	# Verbose owner Name (e.g. 'root')
bc4c306d
 		'uid',		# Numeric user ID (e.g. 0)
a368faf1
 		'gname',	# Group name (e.g. 'users')
bc4c306d
 		'gid',		# Numeric group ID (e.g. 100)
 		'atime',	# Last access timestamp
a368faf1
 		'mtime',	# Modification timestamp
 		'ctime',	# Creation timestamp
 		'mode',		# File mode (e.g. rwxr-xr-x = 755)
 		#'acl',		# Full ACL (not yet supported)
 	]
0d91ff3f
 	delete_removed = False
 	_doc['delete_removed'] = "[sync] Remove remote S3 objects when local file has been deleted"
8ec1807f
 	gpg_passphrase = ""
49731b40
 	gpg_command = ""
8ec1807f
 	gpg_encrypt = "%(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s"
 	gpg_decrypt = "%(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s"
d35b41f4
 	use_https = False
dc758146
 	bucket_location = "US"
eb31131e
 	default_mime_type = "binary/octet-stream"
39b813d5
 	guess_mime_type = True
227fabf8
 	# List of checks to be performed for 'sync'
 	sync_checks = ['size', 'md5']	# 'weak-timestamp'
d5e87cdf
 	# List of compiled REGEXPs
711318eb
 	exclude = []
227fabf8
 	include = []
d5e87cdf
 	# Dict mapping compiled REGEXPs back to their textual form
 	debug_exclude = {}
227fabf8
 	debug_include = {}
82d9eafa
 	encoding = "utf-8"
3c07424d
 	urlencoding_mode = "normal"
cb0bbaef
 	log_target_prefix = ""
a7ea0bee
 	reduced_redundancy = False
7b5df262
         follow_symlinks=False
b008e471
 
 	## Creating a singleton
 	def __new__(self, configfile = None):
 		if self._instance is None:
 			self._instance = object.__new__(self)
 		return self._instance
 
 	def __init__(self, configfile = None):
 		if configfile:
 			self.read_config_file(configfile)
 
 	def option_list(self):
 		retval = []
 		for option in dir(self):
 			## Skip attributes that start with underscore or are not string, int or bool
 			option_type = type(getattr(Config, option))
 			if option.startswith("_") or \
 			   not (option_type in (
 			   		type("string"),	# str
 			        	type(42),	# int
 					type(True))):	# bool
 				continue
 			retval.append(option)
 		return retval
 
 	def read_config_file(self, configfile):
 		cp = ConfigParser(configfile)
 		for option in self.option_list():
 			self.update_option(option, cp.get(option))
 		self._parsed_files.append(configfile)
 
5a736f08
 	def dump_config(self, stream):
 		ConfigDumper(stream).dump("default", self)
 
b008e471
 	def update_option(self, option, value):
 		if value is None:
 			return
 		#### Special treatment of some options
 		## verbosity must be known to "logging" module
 		if option == "verbosity":
 			try:
 				setattr(Config, "verbosity", logging._levelNames[value])
 			except KeyError:
 				error("Config: verbosity level '%s' is not valid" % value)
 		## allow yes/no, true/false, on/off and 1/0 for boolean options
 		elif type(getattr(Config, option)) is type(True):	# bool
 			if str(value).lower() in ("true", "yes", "on", "1"):
 				setattr(Config, option, True)
 			elif str(value).lower() in ("false", "no", "off", "0"):
 				setattr(Config, option, False)
 			else:
 				error("Config: value of option '%s' must be Yes or No, not '%s'" % (option, value))
 		elif type(getattr(Config, option)) is type(42):		# int
 			try:
 				setattr(Config, option, int(value))
 			except ValueError, e:
 				error("Config: value of option '%s' must be an integer, not '%s'" % (option, value))
 		else:							# string
 			setattr(Config, option, value)
 
5a736f08
 class ConfigParser(object):
b6e1cada
 	def __init__(self, file, sections = []):
 		self.cfg = {}
 		self.parse_file(file, sections)
 	
 	def parse_file(self, file, sections = []):
59864e57
 		debug("ConfigParser: Reading file '%s'" % file)
b6e1cada
 		if type(sections) != type([]):
 			sections = [sections]
 		in_our_section = True
 		f = open(file, "r")
 		r_comment = re.compile("^\s*#.*")
 		r_empty = re.compile("^\s*$")
 		r_section = re.compile("^\[([^\]]+)\]")
 		r_data = re.compile("^\s*(?P<key>\w+)\s*=\s*(?P<value>.*)")
 		r_quotes = re.compile("^\"(.*)\"\s*$")
 		for line in f:
 			if r_comment.match(line) or r_empty.match(line):
 				continue
 			is_section = r_section.match(line)
 			if is_section:
 				section = is_section.groups()[0]
 				in_our_section = (section in sections) or (len(sections) == 0)
 				continue
 			is_data = r_data.match(line)
 			if is_data and in_our_section:
 				data = is_data.groupdict()
 				if r_quotes.match(data["value"]):
 					data["value"] = data["value"][1:-1]
 				self.__setitem__(data["key"], data["value"])
8ec1807f
 				if data["key"] in ("access_key", "secret_key", "gpg_passphrase"):
 					print_value = (data["value"][:2]+"...%d_chars..."+data["value"][-1:]) % (len(data["value"]) - 3)
c72a850f
 				else:
 					print_value = data["value"]
 				debug("ConfigParser: %s->%s" % (data["key"], print_value))
b6e1cada
 				continue
f06cf98f
 			warning("Ignoring invalid line in '%s': %s" % (file, line))
b6e1cada
 
 	def __getitem__(self, name):
 		return self.cfg[name]
 	
 	def __setitem__(self, name, value):
 		self.cfg[name] = value
 	
 	def get(self, name, default = None):
 		if self.cfg.has_key(name):
 			return self.cfg[name]
 		return default
5a736f08
 
 class ConfigDumper(object):
 	def __init__(self, stream):
 		self.stream = stream
 
 	def dump(self, section, config):
 		self.stream.write("[%s]\n" % section)
 		for option in config.option_list():
 			self.stream.write("%s = %s\n" % (option, getattr(config, option)))