#!/usr/bin/env python
#
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# Check MD5 and SHA1 signatures of files, using md5sums and/or
# sha1sums as manifests.  Replaces the 'md5sum' and 'sha1sum' commands
# on systems that do not have them, such as Mac OS X or Windows.
#
# Usage: checksums.py [manifest]
#   where "os.path.basename(manifest)" is either "md5sums" or "sha1sums"
#
# Tested with the following Python versions:
#        2.4   2.5   2.6   2.7   3.2


import os
import shutil
import sys

try:
    from hashlib import md5
    from hashlib import sha1
except ImportError:
    from md5 import md5
    from sha import sha as sha1


class Digester(object):
    BUFFER_SIZE = 1024*1024

    def __init__(self, factory):
        self.factory = factory
        self.digest_size = factory().digest_size
        self.hashfunc = None

    def reset(self):
        self.hashfunc = self.factory()

    def write(self, data):
        return self.hashfunc.update(data)

    def hexdigest(self):
        return self.hashfunc.hexdigest()


def main(manipath):
    basedir, manifest = os.path.split(manipath)

    if manifest == 'md5sums':
        sink = Digester(md5)
    elif manifest == 'sha1sums':
        sink = Digester(sha1)
    else:
        raise ValueError('The name of the digest manifest must be '
                         "'md5sums' or 'sha1sums', not '%s'" % manifest)

    # No 'with' statement in Python 2.4 ...
    stream = None
    try:
        stream = open(manipath, 'r')
        for line in stream:
            sink.reset()
            parse_digest(basedir, line.rstrip(), sink)
    finally:
        if stream is not None:
            stream.close()


def parse_digest(basedir, entry, sink):
    length = 2 * sink.digest_size
    expected = entry[:length].lower()
    filename = entry[length + 2:]

    # Still no 'with' statement in Python 2.4 ...
    source = None
    try:
        source = open(os.path.join(basedir, filename), 'rb')
        shutil.copyfileobj(source, sink, sink.BUFFER_SIZE)
        actual = sink.hexdigest().lower()
    finally:
        if source is not None:
            source.close()

    if expected != actual:
        raise ValueError('Mismatch: expected %s, actual %s:  %s'
                         % (expected, actual, filename))
    print('ok: %s  %s' % (actual, filename))


if __name__ == '__main__':
    main(sys.argv[1])
