# or implied, of GRNET S.A.
from image_creator.util import get_command
-from image_creator.util import warn, progress, success, output, FatalError
+from image_creator.util import FatalError
from image_creator.gpt import GPTPartitionTable
import stat
import os
the Linux kernel.
"""
- def __init__(self, source):
+ def __init__(self, source, output):
"""Create a new Disk instance out of a source media. The source
media can be an image file, a block device or a directory."""
self._cleanup_jobs = []
self._devices = []
self.source = source
+ self.out = output
def _add_cleanup(self, job, *args):
self._cleanup_jobs.append((job, args))
instance.
"""
- output("Examining source media `%s'..." % self.source, False)
+ self.out.output("Examining source media `%s'..." % self.source, False)
sourcedev = self.source
mode = os.stat(self.source).st_mode
if stat.S_ISDIR(mode):
raise ValueError("Invalid media source. Only block devices, "
"regular files and directories are supported.")
else:
- success('looks like a block device')
+ self.out.success('looks like a block device')
# Take a snapshot and return it to the user
- output("Snapshotting media source...", False)
+ self.out.output("Snapshotting media source...", False)
size = blockdev('--getsize', sourcedev)
cowfd, cow = tempfile.mkstemp()
os.close(cowfd)
finally:
os.unlink(table)
- success('done')
+ self.out.success('done')
return "/dev/mapper/%s" % snapshot
def get_device(self, media):
"""Returns a newly created DiskDevice instance."""
- new_device = DiskDevice(media)
+ new_device = DiskDevice(media, self.out)
self._devices.append(new_device)
new_device.enable()
return new_device
as created by the device-mapper.
"""
- def __init__(self, device, bootable=True):
+ def __init__(self, device, output, bootable=True):
"""Create a new DiskDevice."""
self.real_device = device
+ self.out = output
self.bootable = bootable
self.progress_bar = None
self.guestfs_device = None
def enable(self):
"""Enable a newly created DiskDevice"""
- self.progressbar = progress("Launching helper VM: ", "percent")
+ self.progressbar = self.out.Progress("Launching helper VM", "percent")
self.progressbar.max = 100
self.progressbar.goto(1)
eh = self.g.set_event_callback(self.progress_callback,
self.g.launch()
self.guestfs_enabled = True
self.g.delete_event_callback(eh)
- if self.progressbar is not None:
- output("\rLaunching helper VM...\033[K", False)
- success("done")
- self.progressbar = None
+ self.progressbar.success('done')
+ self.progressbar = None
- output('Inspecting Operating System...', False)
+ self.out.output('Inspecting Operating System...', False)
roots = self.g.inspect_os()
if len(roots) == 0:
raise FatalError("No operating system found")
self.ostype = self.g.inspect_get_type(self.root)
self.distro = self.g.inspect_get_distro(self.root)
- success('found a(n) %s system' % self.distro)
+ self.out.success('found a(n) %s system' % self.distro)
def destroy(self):
"""Destroy this DiskDevice instance."""
def mount(self):
"""Mount all disk partitions in a correct order."""
- output("Mounting image...", False)
+ self.out.output("Mounting image...", False)
mps = self.g.inspect_get_mountpoints(self.root)
# Sort the keys to mount the fs in a correct order.
try:
self.g.mount(dev, mp)
except RuntimeError as msg:
- warn("%s (ignored)" % msg)
- success("done")
+ self.out.warn("%s (ignored)" % msg)
+ self.out.success("done")
def umount(self):
"""Umount all mounted filesystems."""
MB = 2 ** 20
- output("Shrinking image (this may take a while)...", False)
+ self.out.output("Shrinking image (this may take a while)...", False)
last_part = None
fstype = None
break
if not re.match("ext[234]", fstype):
- warn("Don't know how to resize %s partitions." % fstype)
+ self.out.warn("Don't know how to resize %s partitions." % fstype)
return self.meta['SIZE']
part_dev = "%s%d" % (self.guestfs_device, last_part['part_num'])
part_set_id(last_part['part_num'], last_part['id'])
new_size = (end + 1) * sector_size
- success("new size is %dMB" % ((new_size + MB - 1) // MB))
+ self.out.success("new size is %dMB" % ((new_size + MB - 1) // MB))
if self.meta['PARTITION_TABLE'] == 'gpt':
ptable = GPTPartitionTable(self.real_device)
blocksize = 4 * MB # 4MB
size = self.meta['SIZE']
progress_size = (size + MB - 1) // MB # in MB
- progressbar = progress("Dumping image file: ", 'mb')
+ progressbar = self.out.Progress("Dumping image file", 'mb')
progressbar.max = progress_size
with open(self.real_device, 'r') as src:
offset += sent
left -= sent
progressbar.goto((size - left) // MB)
- output("\rDumping image file...\033[K", False)
- success('image file %s was successfully created' % outfile)
+ progressbar.success('image file %s was successfully created' % outfile)
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
from kamaki.clients.pithos import PithosClient
from progress.bar import Bar
-from image_creator.util import FatalError, output, success
+from image_creator.util import FatalError
+from image_creator.output import output, warn
CONTAINER = "images"
-def progress(message):
-
- MSG_LENGTH = 30
-
- def progress_gen(n):
- msg = "%s:" % message
-
- progressbar = Bar(msg.ljust(MSG_LENGTH))
- progressbar.max = n
- progressbar.fill = '#'
- progressbar.bar_prefix = ' ['
- progressbar.bar_suffix = '] '
-
- for _ in range(n):
- yield
- progressbar.next()
- output("\r%s...\033[K" % message, False)
- success("done")
- yield
- return progress_gen
-
-
-class Kamaki:
- def __init__(self, account, token):
+class Kamaki(object):
+ def __init__(self, account, token, output):
self.account = account
self.token = token
+ self.out = output
config = Config()
raise FatalError("Pithos client: %d %s" % \
(e.status, e.message))
try:
- hash_cb = progress(hp) if hp is not None else None
- upload_cb = progress(up) if up is not None else None
+ hash_cb = self.out.progress_gen(hp) if hp is not None else None
+ upload_cb = self.out.progress_gen(up) if up is not None else None
self.pithos_client.create_object(remote_path, file_obj, size,
hash_cb, upload_cb)
return "pithos://%s/%s/%s" % \
from image_creator import __version__ as version
from image_creator import util
from image_creator.disk import Disk
-from image_creator.util import get_command, error, success, output, \
- FatalError, progress, md5
+from image_creator.util import get_command, FatalError, MD5
+from image_creator.output import Output, Output_with_progress, Silent, error
from image_creator.os_type import get_os_class
from image_creator.kamaki_wrapper import Kamaki
import sys
def image_creator():
options = parse_options(sys.argv[1:])
- if options.silent:
- util.silent = True
-
if options.outfile is None and not options.upload \
and not options.print_sysprep:
raise FatalError("At least one of `-o', `-u' or `--print-sysprep' " \
"must be set")
+ if options.silent:
+ out = Silent()
+ else:
+ out = Output_with_progress()
+
title = 'snf-image-creator %s' % version
- output(title)
- output('=' * len(title))
+ out.output(title)
+ out.output('=' * len(title))
if os.geteuid() != 0:
raise FatalError("You must run %s as root" \
raise FatalError("Output file %s exists "
"(use --force to overwrite it)." % filename)
- disk = Disk(options.source)
+ disk = Disk(options.source, out)
try:
snapshot = disk.snapshot()
dev.mount()
osclass = get_os_class(dev.distro, dev.ostype)
- image_os = osclass(dev.root, dev.g)
- output()
+ image_os = osclass(dev.root, dev.g, out)
+ out.output()
for sysprep in options.disabled_syspreps:
image_os.disable_sysprep(sysprep)
if options.print_sysprep:
image_os.print_syspreps()
- output()
+ out.output()
if options.outfile is None and not options.upload:
return 0
metadata = image_os.meta
dev.umount()
- size = options.shrink and dev.shrink() or dev.size
+ size = options.shrink and dev.shrink() or dev.meta['SIZE']
metadata.update(dev.meta)
# Add command line metadata to the collected ones...
metadata.update(options.metadata)
- checksum = md5(snapshot, size)
+ md5 = MD5(out)
+ checksum = md5.compute(snapshot, size)
metastring = '\n'.join(
['%s=%s' % (key, value) for (key, value) in metadata.items()])
if options.outfile is not None:
dev.dump(options.outfile)
- output('Dumping metadata file...', False)
+ out.output('Dumping metadata file...', False)
with open('%s.%s' % (options.outfile, 'meta'), 'w') as f:
f.write(metastring)
- success('done')
+ out.success('done')
- output('Dumping md5sum file...', False)
+ out.output('Dumping md5sum file...', False)
with open('%s.%s' % (options.outfile, 'md5sum'), 'w') as f:
f.write('%s %s\n' % (checksum, \
os.path.basename(options.outfile)))
- success('done')
+ out.success('done')
# Destroy the device. We only need the snapshot from now on
disk.destroy_device(dev)
- output()
+ out.output()
uploaded_obj = ""
if options.upload:
- output("Uploading image to pithos:")
- kamaki = Kamaki(options.account, options.token)
+ out.output("Uploading image to pithos:")
+ kamaki = Kamaki(options.account, options.token, out)
with open(snapshot) as f:
uploaded_obj = kamaki.upload(f, size, options.upload,
"(1/4) Calculating block hashes",
"(2/4) Uploading missing blocks")
- output("(3/4) Uploading metadata file...", False)
+ out.output("(3/4) Uploading metadata file...", False)
kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
remote_path="%s.%s" % (options.upload, 'meta'))
- success('done')
- output("(4/4) Uploading md5sum file...", False)
+ out.success('done')
+ out.output("(4/4) Uploading md5sum file...", False)
md5sumstr = '%s %s\n' % (
checksum, os.path.basename(options.upload))
kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr),
remote_path="%s.%s" % (options.upload, 'md5sum'))
- success('done')
- output()
+ out.success('done')
+ out.output()
if options.register:
- output('Registring image to ~okeanos...', False)
+ out.output('Registring image to ~okeanos...', False)
kamaki.register(options.register, uploaded_obj, metadata)
- success('done')
- output()
+ out.success('done')
+ out.output()
finally:
- output('cleaning up...')
+ out.output('cleaning up...')
disk.cleanup()
- success("snf-image-creator exited without errors")
+ out.success("snf-image-creator exited without errors")
return 0
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
-from image_creator.util import output, FatalError
+from image_creator.util import FatalError
import textwrap
import re
class OSBase(object):
"""Basic operating system class"""
- def __init__(self, rootdev, ghandler):
+ def __init__(self, rootdev, ghandler, output):
self.root = rootdev
self.g = ghandler
+ self.out = output
# Collect metadata about the OS
self.meta = {}
wrapper.initial_indent = '\t'
wrapper.width = 72
- output("Enabled system preperation operations:")
+ self.out.output("Enabled system preperation operations:")
if len(enabled) == 0:
- output("(none)")
+ self.out.output("(none)")
else:
for sysprep in enabled:
name = sysprep.__name__.replace('_', '-')
descr = wrapper.fill(textwrap.dedent(sysprep.__doc__))
- output(' %s:\n%s\n' % (name, descr))
+ self.out.output(' %s:\n%s\n' % (name, descr))
- output("Disabled system preperation operations:")
+ self.out.output("Disabled system preperation operations:")
if len(disabled) == 0:
- output("(none)")
+ self.out.output("(none)")
else:
for sysprep in disabled:
name = sysprep.__name__.replace('_', '-')
descr = wrapper.fill(textwrap.dedent(sysprep.__doc__))
- output(' %s:\n%s\n' % (name, descr))
+ self.out.output(' %s:\n%s\n' % (name, descr))
@add_prefix
def ls(self, directory):
def do_sysprep(self):
"""Prepere system for image creation."""
- output('Preparing system for image creation:')
+ self.out.output('Preparing system for image creation:')
tasks, _ = self.list_syspreps()
size = len(tasks)
cnt = 0
for task in tasks:
cnt += 1
- output(('(%d/%d)' % (cnt, size)).ljust(7), False)
+ self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
task()
- output()
+ self.out.output()
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
# or implied, of GRNET S.A.
from image_creator.os_type.unix import Unix, sysprep
-from image_creator.util import warn, output
import re
import time
class Linux(Unix):
- def __init__(self, rootdev, ghandler):
- super(Linux, self).__init__(rootdev, ghandler)
+ def __init__(self, rootdev, ghandler, output):
+ super(Linux, self).__init__(rootdev, ghandler, output)
self._uuid = dict()
self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*')
"""
if print_header:
- output('Fixing acpid powerdown action')
+ self.out.output('Fixing acpid powerdown action')
powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
'shutdown -h now \"Power button pressed\"\n'
events_dir = '/etc/acpi/events'
if not self.g.is_dir(events_dir):
- warn("No acpid event directory found")
+ self.out.warn("No acpid event directory found")
return
event_exp = re.compile('event=(.+)', re.I)
if event.strip() == "button[ /]power":
if action:
if not self.g.is_file(action):
- warn("Acpid action file: %s does not exist" % action)
+ self.out.warn("Acpid action file: %s does not exist" %
+ action)
return
self.g.copy_file_to_file(action, \
"%s.orig.snf-image-creator-%d" % (action, time.time()))
self.g.write(action, powerbtn_action)
return
else:
- warn("Acpid event file %s does not contain and action")
+ self.out.warn(
+ "Acpid event file %s does not contain and action")
return
elif event.strip() == ".*":
- warn("Found action `.*'. Don't know how to handle this." \
- " Please edit \%s' image file manually to make the " \
+ self.out.warn(
+ "Found action `.*'. Don't know how to handle this. " \
+ "Please edit \%s' image file manually to make the " \
"system immediatelly shutdown when an power button acpi " \
"event occures" % action)
return
"""
if print_header:
- output('Removing persistent network interface names')
+ self.out.output('Removing persistent network interface names')
rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
if self.g.is_file(rule_file):
"""
if print_header:
- output('Removing swap entry from fstab')
+ self.out.output('Removing swap entry from fstab')
new_fstab = ""
fstab = self.g.cat('/etc/fstab')
"""
if print_header:
- output('Replacing fstab & grub non-persistent device appearences')
+ self.out.output(
+ 'Replacing fstab & grub non-persistent device appearences')
# convert all devices in fstab to persistent
persistent_root = self._persistent_fstab()
entry = line.split()
if len(entry) != 6:
- warn("Detected abnormal entry in fstab")
+ self.out.warn("Detected abnormal entry in fstab")
return orig, "", ""
dev = entry[0]
class Ubuntu(Linux):
- def __init__(self, rootdev, ghandler):
- super(Ubuntu, self).__init__(rootdev, ghandler)
+ def __init__(self, rootdev, ghandler, output):
+ super(Ubuntu, self).__init__(rootdev, ghandler, output)
apps = self.g.inspect_list_applications(self.root)
for app in apps:
import sys
from image_creator.os_type import OSBase, sysprep
-from image_creator.util import warn, output
class Unix(OSBase):
'.thunderbird'
]
- def __init__(self, rootdev, ghandler):
- super(Unix, self).__init__(rootdev, ghandler)
+ def __init__(self, rootdev, ghandler, output):
+ super(Unix, self).__init__(rootdev, ghandler, output)
self.meta["USERS"] = " ".join(self._get_passworded_users())
"""Remove all user accounts with id greater than 1000"""
if print_header:
- output('Removing all user accounts with id greater than 1000')
+ self.out.output(
+ 'Removing all user accounts with id greater than 1000')
# Remove users from /etc/passwd
passwd = []
"""Remove all passwords and lock all user accounts"""
if print_header:
- output('Cleaning up passwords & locking all user accounts')
+ self.out.output(
+ 'Cleaning up passwords & locking all user accounts')
shadow = []
"""Remove all regular files under /var/cache"""
if print_header:
- output('Removing files under /var/cache')
+ self.out.output('Removing files under /var/cache')
self.foreach_file('/var/cache', self.g.rm, ftype='r')
"""Remove all files under /tmp and /var/tmp"""
if print_header:
- output('Removing files under /tmp and /var/tmp')
+ self.out.output('Removing files under /tmp and /var/tmp')
self.foreach_file('/tmp', self.g.rm_rf, maxdepth=1)
self.foreach_file('/var/tmp', self.g.rm_rf, maxdepth=1)
"""Empty all files under /var/log"""
if print_header:
- output('Emptying all files under /var/log')
+ self.out.output('Emptying all files under /var/log')
self.foreach_file('/var/log', self.g.truncate, ftype='r')
"""Remove all files under /var/mail and /var/spool/mail"""
if print_header:
- output('Removing files under /var/mail and /var/spool/mail')
+ self.out.output('Removing files under /var/mail & /var/spool/mail')
self.foreach_file('/var/spool/mail', self.g.rm_rf, maxdepth=1)
self.foreach_file('/var/mail', self.g.rm_rf, maxdepth=1)
homedirs = ['/root'] + self.ls('/home/')
if print_header:
- output('Removing sensitive user data under %s' % " ".
+ self.out.output('Removing sensitive user data under %s' % " ".
join(homedirs))
for homedir in homedirs:
--- /dev/null
+# Copyright 2012 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+import sys
+from progress.bar import Bar
+from colors import red, green, yellow
+
+
+def error(msg, new_line=True):
+ nl = "\n" if new_line else ''
+ sys.stderr.write(red('Error: %s' % msg) + nl)
+
+
+def warn(msg, new_line=True):
+ nl = "\n" if new_line else ''
+ sys.stderr.write(yellow("Warning: %s" % msg) + nl)
+
+
+def success(msg, new_line=True):
+ nl = "\n" if new_line else ''
+ sys.stdout.write(green(msg) + nl)
+ if not nl:
+ sys.stdout.flush()
+
+
+def output(msg='', new_line=True):
+ nl = "\n" if new_line else ''
+ sys.stdout.write(msg + nl)
+ if not nl:
+ sys.stdout.flush()
+
+
+class Output(object):
+ def error(self, msg, new_line=True):
+ error(msg, new_line)
+
+ def warn(self, msg, new_line=True):
+ warn(msg, new_line)
+
+ def success(self, msg, new_line=True):
+ success(msg, new_line)
+
+ def output(self, msg='', new_line=True):
+ output(msg, new_line)
+
+ class Progress(object):
+ def __init__(self, title, bar_type='default'):
+ output("%s..." % title, False)
+
+ def goto(self, dest):
+ pass
+
+ def next(self):
+ pass
+
+ def success(self, result):
+ sucess(result)
+
+ def progress_gen(self, message):
+
+ progress = getattr(self, 'Progress')
+
+ def generator(n):
+ progressbar = progress(message, 'default')
+
+ for _ in range(n):
+ yield
+ progressbar.next()
+
+ progressbar.success('done')
+ yield
+ return generator
+
+
+class Output_with_progress(Output):
+ class Progress(Bar):
+ MESSAGE_LENGTH = 30
+
+ template = {
+ 'default': '%(index)d/%(max)d',
+ 'percent': '%(percent)d%%',
+ 'b': '%(index)d/%(max)d B',
+ 'kb': '%(index)d/%(max)d KB',
+ 'mb': '%(index)d/%(max)d MB'
+ }
+
+ def __init__(self, title, bar_type='default'):
+ super(Output_with_progress.Progress, self).__init__()
+ self.title = title
+ self.fill = '#'
+ self.bar_prefix = ' ['
+ self.bar_suffix = '] '
+ self.message = ("%s:" % self.title).ljust(self.MESSAGE_LENGTH)
+ self.suffix = self.template[bar_type]
+
+ def success(self, result):
+ output("\r%s... \033[K" % self.title, False)
+ success(result)
+
+
+class Silent(Output):
+ def warn(self, msg, new_line=True):
+ pass
+
+ def success(self, msg, new_line=True):
+ pass
+
+ def output(self, msg='', new_line=True):
+ pass
+
+ class Progress(Output.Progress):
+ def __init__(self, title, bar_type):
+ pass
+
+ def success(self, result):
+ pass
+
+# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
import sys
import pbs
import hashlib
-from colors import red, green, yellow
-from progress.bar import Bar
class FatalError(Exception):
pass
-silent = False
-
-
def get_command(command):
def find_sbin_command(command, exception):
search_paths = ['/usr/local/sbin', '/usr/sbin', '/sbin']
return find_sbin_command(command, e)
-def error(msg, new_line=True):
- nl = "\n" if new_line else ''
- sys.stderr.write(red('Error: %s' % msg) + nl)
-
-
-def warn(msg, new_line=True):
- if not silent:
- nl = "\n" if new_line else ''
- sys.stderr.write(yellow("Warning: %s" % msg) + nl)
-
-
-def success(msg, new_line=True):
- if not silent:
- nl = "\n" if new_line else ''
- sys.stdout.write(green(msg) + nl)
- if not nl:
- sys.stdout.flush()
-
-
-def output(msg="", new_line=True):
- if not silent:
- nl = "\n" if new_line else ''
- sys.stdout.write(msg + nl)
- if not nl:
- sys.stdout.flush()
-
-
-def progress(message='', bar_type="default"):
-
- MESSAGE_LENGTH = 30
-
- suffix = {
- 'default': '%(index)d/%(max)d',
- 'percent': '%(percent)d%%',
- 'b': '%(index)d/%(max)d B',
- 'kb': '%(index)d/%(max)d KB',
- 'mb': '%(index)d/%(max)d MB'
- }
-
- bar = Bar()
- bar.message = message.ljust(MESSAGE_LENGTH)
- bar.fill = '#'
- bar.suffix = suffix[bar_type]
- bar.bar_prefix = ' ['
- bar.bar_suffix = '] '
-
- return bar
-
+class MD5:
+ def __init__(self, output):
+ self.out = output
-def md5(filename, size):
+ def compute(self, filename, size):
- BLOCKSIZE = 2 ** 22 # 4MB
+ BLOCKSIZE = 2 ** 22 # 4MB
- progressbar = progress("Calculating md5sum:", 'mb')
- progressbar.max = ((size + 2 ** 20 - 1) // (2 ** 20))
- md5 = hashlib.md5()
- with open(filename, "r") as src:
- left = size
- while left > 0:
- length = min(left, BLOCKSIZE)
- data = src.read(length)
- md5.update(data)
- left -= length
- progressbar.goto((size - left) // (2 ** 20))
+ progressbar = self.out.Progress("Calculating md5sum:", 'mb')
+ progressbar.max = ((size + 2 ** 20 - 1) // (2 ** 20))
+ md5 = hashlib.md5()
+ with open(filename, "r") as src:
+ left = size
+ while left > 0:
+ length = min(left, BLOCKSIZE)
+ data = src.read(length)
+ md5.update(data)
+ left -= length
+ progressbar.goto((size - left) // (2 ** 20))
- checksum = md5.hexdigest()
- output("\rCalculating md5sum...\033[K", False)
- success(checksum)
+ checksum = md5.hexdigest()
+ progressbar.success(checksum)
- return checksum
+ return checksum
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :