diff options
author | Jens Carl <j.carl43@gmx.de> | 2017-08-29 04:48:13 +0000 |
---|---|---|
committer | Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de> | 2017-09-26 08:53:22 +0200 |
commit | 423ee1020afe1bca896f2ecfc67ffbd49db5081c (patch) | |
tree | 862202456ea3b404427a21bb3dd756d13ea2f02b /solenv/bin | |
parent | a385dcd02001fc9ce26bf994956db0a704d5764b (diff) |
tdf#106894 Rewrite packimages.pl in Python (pack_images.py)
Change-Id: Id627d9295edc77e561f15e0886fdcf9fb64fe68d
Reviewed-on: https://gerrit.libreoffice.org/41667
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de>
Diffstat (limited to 'solenv/bin')
-rwxr-xr-x | solenv/bin/pack_images.py | 590 | ||||
-rwxr-xr-x | solenv/bin/packimages.pl | 550 |
2 files changed, 590 insertions, 550 deletions
diff --git a/solenv/bin/pack_images.py b/solenv/bin/pack_images.py new file mode 100755 index 000000000000..2e43c1815de4 --- /dev/null +++ b/solenv/bin/pack_images.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +""" Pack images into archives. """ + +from __future__ import with_statement + +import argparse +from collections import OrderedDict +import logging +import os +import shutil +import sys +import tempfile +import zipfile + + +logging.basicConfig(format='%(message)s') +LOGGER = logging.getLogger() + + +def main(args): + """ Main function. """ + + tmp_output_file = "%s.%d.tmp" % (args.output_file, os.getpid()) + + # Sanity checks + if not os.path.exists(args.imagelist_file): + LOGGER.error("imagelist_file '%s' doesn't exists", args.imagelist_file) + sys.exit(2) + + out_path = os.path.dirname(args.output_file) + for path in (out_path, args.global_path, args.module_path): + if not os.path.exists(path): + LOGGER.error("Path '%s' doesn't exists", path) + sys.exit(2) + if not os.access(path, os.X_OK): + LOGGER.error("Unable to search path %s", path) + sys.exit(2) + + if not os.access(out_path, os.W_OK): + LOGGER.error("Unable to write into path %s", out_path) + + custom_paths = [] + for path in args.custom_path: + if not os.path.exists(path): + LOGGER.warning("Skipping non-existing path: %s", path) + continue + elif not os.access(path, os.X_OK): + LOGGER.error("Unable to search path %s", path) + sys.exit(2) + continue + + custom_paths.append(path) + + imagelist_filenames = get_imagelist_filenames(args.imagelist_file) + global_image_list, module_image_list = parse_image_list(imagelist_filenames) + custom_image_list = find_custom(custom_paths) + + links = {} + read_links(links, ARGS.global_path) + for path in custom_paths: + read_links(links, path) + check_links(links) + + zip_list = create_zip_list(global_image_list, module_image_list, custom_image_list, + args.global_path, args.module_path) + remove_links_from_zip_list(zip_list, links) + + if check_rebuild(args.output_file, imagelist_filenames, custom_paths, zip_list): + tmp_dir = copy_images(zip_list) + create_zip_archive(zip_list, links, tmp_dir, tmp_output_file, args.sort_file) + + replace_zip_file(tmp_output_file, args.output_file) + try: + LOGGER.info("Remove temporary directory %s", tmp_dir) + shutil.rmtree(tmp_dir) + except Exception as e: + LOGGER.error("Unable to remove temporary directory %s", tmp_dir) + sys.exit(2) + else: + LOGGER.info("No rebuild needed. %s is up to date.", args.output_file) + + +def check_rebuild(zip_file, imagelist_filenames, custom_paths, zip_list): + """ Check if a rebuild is needed. + + :type zip_file: str + :param zip_file: File to check against (use st_mtime). + + :type imagelist_filenames: dict + :param imagelist_filenames: List of imagelist filename. + + :type custom_paths: list + :param custom_paths: List of paths to use with links.txt files. + + :type zip_list: dict + :param zip_list: List of filenames to create the zip archive. + + :rtype: bool + :return: True if rebuild is needed and False if not. + """ + + if not os.path.exists(zip_file): + return True + + zip_file_stat = os.stat(zip_file) + + def compare_modtime(filenames): + """ Check if modification time of zip archive is older the provided + list of files. + + :type filenames: dict + :param filesnames: List of filenames to check against. + + :rtype: bool + :return: True if zip archive is older and False if not. + """ + for path, filename in filenames.iteritems(): + filename = os.path.join(path, filename) if filename else path + if zip_file_stat.st_mtime < os.stat(filename).st_mtime: + return True + return False + + if compare_modtime(imagelist_filenames): + return True + if compare_modtime(zip_list): + return True + for path in custom_paths: + link_file = os.path.join(path, 'links.txt') + if os.path.exists(link_file): + if zip_file_stat.st_mtime < os.stat(link_file).st_mtime: + return True + + return False + + +def replace_zip_file(src, dst): + """ Replace the old archive file with the newly created one. + + :type src: str + :param src: Source file name. + + :type dst: str + :param dst: Destination file name. + """ + LOGGER.info("Replace old zip archive with new archive") + if os.path.exists(dst): + try: + os.unlink(dst) + except OSError as e: + if os.path.exists(src): + os.unlink(src) + + LOGGER.error("Unable to unlink old archive '%s'", dst) + LOGGER.error(str(e)) + sys.exit(1) + + try: + LOGGER.info("Copy archive '%s' to '%s'", src, dst) + shutil.copyfile(src, dst) + except (shutil.SameFileError, OSError) as e: + os.unlink(src) + LOGGER.error("Cannot copy file '%s' to %s: %s", src, dst, str(e)) + sys.exit(1) + + +def optimize_zip_layout(zip_list, sort_file=None): + """ Optimzie the zip layout by ordering the list of filename alphabetically + or with provided sort file (can be partly). + + :type zip_list: dict + :param zip_list: List of files to include in the zip archive. + + :type sort_file: str + :param sort_file: Path/Filename to file with sort order. + + :rtype: OrderedDict + :return: Dict with right sort order. + """ + if sort_file is None: + LOGGER.info("No sort file provided") + LOGGER.info("Sorting entries alphabetically") + + return OrderedDict(sorted(zip_list.items(), key=lambda t: t[0])) + + LOGGER.info("Sort entries from file '%s'", sort_file) + sorted_zip_list = OrderedDict() + try: + fh = open(sort_file) + except IOError: + LOGGER.error("Cannot open file %s", sort_file) + sys.exit(1) + else: + with fh: + for line in fh: + line = line.strip() + if line == '' or line.startswith('#'): + continue + + if line in zip_list: + sorted_zip_list[line] = '' + else: + LOGGER.warning("Unknown file '%s'", line) + + for key in sorted(zip_list.keys()): + if key not in sorted_zip_list: + sorted_zip_list[key] = '' + + return sorted_zip_list + + +def create_zip_archive(zip_list, links, tmp_dir, tmp_zip_file, sort_file=None): + """ Create the zip archive. + + :type zip_list: dict + :param zip_list: All filenames to be included in the archive. + + :type links: dict + :param links: All filenames to create links.txt file. + + :type tmp_dir: str + :param tmp_dir: Path to tempory saved files. + + :type tmp_zip_file: str + :param tmp_zip_file: Filename/Path of temporary zip archive. + + :type sort_file: str|None + :param sort_file: Optional filename with sort order to apply. + """ + LOGGER.info("Creating image archive") + + old_pwd = os.getcwd() + os.chdir(tmp_dir) + + ordered_zip_list = optimize_zip_layout(zip_list, sort_file) + + with zipfile.ZipFile(tmp_zip_file, 'w') as tmp_zip: + if links.keys(): + LOGGER.info("Add file 'links.txt' to zip archive") + create_links_file(tmp_dir, links) + tmp_zip.write('links.txt') + + for link in ordered_zip_list: + LOGGER.info("Add file '%s' from path '%s' to zip archive", link, tmp_dir) + try: + tmp_zip.write(link) + except OSError: + LOGGER.warning("Unable to add file '%s' to zip archive", link) + + os.chdir(old_pwd) + + +def create_links_file(path, links): + """ Create file links.txt. Contains all links. + + :type path: str + :param path: Path where to create the file. + + :type links: dict + :param links: Dict with links (source -> target). + """ + LOGGER.info("Create file links.txt") + + try: + filename = os.path.join(path, 'links.txt') + fh = open(filename, 'w') + except IOError: + LOGGER.error("Cannot open file %s", filename) + sys.exit(1) + else: + with fh: + for key in sorted(links.keys()): + fh.write("%s %s\n" % (key, links[key])) + + +def copy_images(zip_list): + """ Create a temporary directory and copy images to that directory. + + :type zip_list: dict + :param zip_list: Dict with all files. + + :rtype: str + :return: Path of the temporary directory. + """ + LOGGER.info("Copy image files to temporary directory") + + tmp_dir = tempfile.mkdtemp() + for key, value in zip_list.items(): + path = os.path.join(value, key) + out_path = os.path.join(tmp_dir, key) + + LOGGER.debug("Copying '%s' to '%s'", path, out_path) + if os.path.exists(path): + dirname = os.path.dirname(out_path) + if not os.path.exists(dirname): + os.makedirs(dirname) + + try: + shutil.copyfile(path, out_path) + except (shutil.SameFileError, OSError) as e: + LOGGER.error("Cannot add file '%s' to image dir: %s", path, str(e)) + sys.exit(1) + + LOGGER.debug("Temporary directory '%s'", tmp_dir) + return tmp_dir + + +def remove_links_from_zip_list(zip_list, links): + """ Remove any files from zip list that are linked. + + :type zip_list: dict + :param zip_list: Files to include in the zip archive. + + :type links: dict + :param links: Images which are linked. + """ + LOGGER.info("Remove linked files from zip list") + + for link in links.keys(): + if link in zip_list: + del zip_list[link] + + LOGGER.debug("Cleaned zip list:\n%s", "\n".join(zip_list)) + + +def create_zip_list(global_image_list, module_image_list, custom_image_list, global_path, module_path): + """ Create list of images for the zip archive. + + :type global_image_list: dict + :param global_image_list: Global images. + + :type module_image_list: dict + :param module_image_list: Module images. + + :type custom_image_list: dict + :param custom_image_list: Custom images. + + :type global_path: str + :param global_path: Global path (use when no custom path is available). + + :type module_path: str + :param module_path: Module path (use when no custom path is available). + + :rtype: dict + :return List of images to include in zip archive. + """ + LOGGER.info("Assemble image list") + + zip_list = {} + duplicates = [] + + for key in global_image_list.keys(): + if key in module_image_list: + duplicates.append(key) + continue + + if key in custom_image_list: + zip_list[key] = custom_image_list[key] + continue + + zip_list[key] = global_path + + for key in module_image_list.keys(): + if key in custom_image_list: + zip_list[key] = custom_image_list[key] + continue + + zip_list[key] = module_path + + if duplicates: + LOGGER.warning("Found duplicate entries in 'global' and 'module' list") + LOGGER.warning("\n".join(duplicates)) + + LOGGER.debug("Assembled image list:\n%s", "\n".join(zip_list)) + return zip_list + + +def check_links(links): + """ Check that no link points to another link. + + :type links: dict + :param links: Links to icons + """ + + stop = False + + for link, target in links.items(): + if target in links.keys(): + LOGGER.error("Link %s -> %s -> %s", link, target, links[target]) + stop = True + + if stop: + LOGGER.error("Some icons in links.txt were found to link to other linked icons") + sys.exit(1) + + +def read_links(links, path): + """ Read links from file. + + :type links: dict + :param links: Hash to store all links + + :type path: str + :param path: Path to use + """ + + filename = os.path.join(path, "links.txt") + LOGGER.info("Read links from file '%s'", filename) + if not os.path.isfile(filename): + LOGGER.info("No file to read") + return + + try: + fh = open(filename) + except IOError: + LOGGER.error("Cannot open file %s", filename) + sys.exit(1) + else: + with fh: + for line in fh: + line = line.strip() + if line == '' or line.startswith('#'): + continue + + tmp = line.split(' ') + if len(tmp) == 2: + links[tmp[0]] = tmp[1] + else: + LOGGER.error("Malformed links line '%s' in file %s", line, filename) + sys.exit(1) + + LOGGER.debug("Read links:\n%s", "\n".join(links)) + + +def find_custom(custom_paths): + """ Find all custom images. + + :type custom_paths: list + :param custom_paths: List of custom paths to search within. + + :rtype: dict + :return: List of all custom images. + """ + LOGGER.info("Find all images in custom paths") + + custom_image_list = {} + for path in custom_paths: + # find all png files under the path including subdirs + custom_files = [val for sublist in [ + [os.path.join(i[0], j) for j in i[2] + if j.endswith('.png') and os.path.isfile(os.path.join(i[0], j))] + for i in os.walk(path)] for val in sublist] + + for filename in custom_files: + if filename.startswith(path): + key = filename.replace(os.path.join(path, ''), '') + if key not in custom_image_list: + custom_image_list[key] = path + + LOGGER.debug("Custom image list:\n%s", "\n".join(custom_image_list.keys())) + return custom_image_list + + +def parse_image_list(imagelist_filenames): + """ Parse and extract filename from the imagelist files. + + :type imagelist_filenames: list + :param imagelist_filenames: List of imagelist files. + + :rtype: tuple + :return: Tuple with dicts containing the global or module image list. + """ + + global_image_list = {} + module_image_list = {} + for imagelist_filename in imagelist_filenames.keys(): + LOGGER.info("Parsing '%s'", imagelist_filename) + + try: + fh = open(imagelist_filename) + except IOError as e: + LOGGER.error("Cannot open imagelist file %s", imagelist_filename) + sys.exit(2) + else: + line_count = 0 + with fh: + for line in fh: + line_count += 1 + line = line.strip() + if line == '' or line.startswith('#'): + continue + # clean up backslashes and double slashes + line = line.replace('\\', '/') + line = line.replace('//', '/') + + if line.startswith('%GLOBALRES%'): + key = "res/%s" % line.replace('%GLOBALRES%/', '') + if key in global_image_list: + global_image_list[key] += 1 + else: + global_image_list[key] = 0 + continue + + if line.startswith('%MODULE%'): + key = line.replace('%MODULE%/', '') + if key in global_image_list: + module_image_list[key] += 1 + else: + module_image_list[key] = 0 + continue + + LOGGER.error("Cannot parse line %s:%d", imagelist_filename, line_count) + sys.exit(1) + + LOGGER.debug("Global image list:\n%s", "\n".join(global_image_list)) + LOGGER.debug("Module image list:\n%s", "\n".join(module_image_list)) + return global_image_list, module_image_list + + +def get_imagelist_filenames(filename): + """ Extract a list of imagelist filenames. + + :type filename: str + :param filename: Name of file from extracting the list. + + :rtype: dict + :return: List of imagelist filenames. + """ + LOGGER.info("Extract the imagelist filenames") + + imagelist_filenames = {} + try: + fh = open(filename) + except IOError: + LOGGER.error("Cannot open imagelist file %s", filename) + sys.exit(1) + else: + with fh: + for line in fh: + line = line.strip() + if not line or line == '': + continue + + for line_split in line.split(' '): + line_split.strip() + imagelist_filenames[line_split] = '' + + LOGGER.debug("Extraced imagelist filenames:\n%s", "\n".join(imagelist_filenames.keys())) + return imagelist_filenames + + +if __name__ == '__main__': + parser = argparse.ArgumentParser("Pack images into archives") + parser.add_argument('-o', '--output-file', dest='output_file', + action='store', required=True, + help='path to output archive') + parser.add_argument('-l', '--imagelist-file', dest='imagelist_file', + action='store', required=True, + help='file containing list of image list file') + parser.add_argument('-s', '--sort-file', dest='sort_file', + action='store', required=True, default=None, + help='image sort order file') + parser.add_argument('-c', '--custom-path', dest='custom_path', + action='append', required=True, + help='path to custom path directory') + parser.add_argument('-g', '--global-path', dest='global_path', + action='store', required=True, + help='path to global images directory') + parser.add_argument('-m', '--module-path', dest='module_path', + action='store', required=True, + help='path to module images directory') + parser.add_argument('-v', '--verbose', dest='verbose', + action='count', default=0, + help='set the verbosity (can be used multiple times)') + + ARGS = parser.parse_args() + LOGGER.setLevel(logging.ERROR - (10 * ARGS.verbose if ARGS.verbose <= 3 else 3)) + + main(ARGS) + sys.exit(0) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/solenv/bin/packimages.pl b/solenv/bin/packimages.pl deleted file mode 100755 index 96a31de3b2fd..000000000000 --- a/solenv/bin/packimages.pl +++ /dev/null @@ -1,550 +0,0 @@ -: -eval 'exec perl -wS $0 ${1+"$@"}' - if 0; -# -# This file is part of the LibreOffice project. -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This file incorporates work covered by the following license notice: -# -# 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 . -# - -# -# packimages.pl - pack images into archives -# - -use strict; -use Getopt::Long; -use File::Find; -use File::Basename; -use File::Copy qw(copy); -use File::Path qw(make_path rmtree); -require File::Temp; -use File::Temp qw(tempdir); - -#### globals #### - -my $img_global = '%GLOBALRES%'; # 'global' image prefix -my $img_module = '%MODULE%'; # 'module' image prefix - -my $out_file; # path to output archive -my $tmp_out_file; # path to temporary output file -my $global_path; # path to global images directory -my $module_path; # path to module images directory -my $sort_file; # path to file containing sorting data -my @custom_path; # path to custom images directory -my $imagelist_file; # file containing list of image list files -my $verbose; # be verbose -my $extra_verbose; # be extra verbose -my $do_rebuild = 0; # is rebuilding zipfile required? - -my @custom_list; -#### script id ##### - -( my $script_name = $0 ) =~ s/^.*\b(\w+)\.pl$/$1/; - -print "$script_name -- version: 1.17\n" if $verbose; - -#### main ##### - -parse_options(); -my $image_lists_ref = get_image_lists(); -my %image_lists_hash; -foreach ( @{$image_lists_ref} ) { - $image_lists_hash{$_}=""; -} -$do_rebuild = is_file_newer(\%image_lists_hash) if $do_rebuild == 0; -my ($global_hash_ref, $module_hash_ref, $custom_hash_ref) = iterate_image_lists($image_lists_ref); -# custom_hash filled from filesystem lookup -find_custom($custom_hash_ref); - -# build a consolidated set of links -my %links; -read_links(\%links, $global_path); -for my $path (@custom_path) { - read_links(\%links, $path); -} -check_links(\%links); - -# rebuild if links.txt has been modified -for my $path (@custom_path) { - my $links_file = $path."/links.txt"; - if ((-e $links_file ) && ( -e $out_file )){ - if ((stat($out_file))[9] < (stat($links_file))[9]){ - $do_rebuild = 1; - print_message("$links_file has been modified.") if $verbose; - } - } -} - -my $zip_hash_ref = create_zip_list($global_hash_ref, $module_hash_ref, $custom_hash_ref); - -remove_links_from_zip_list($zip_hash_ref, \%links); - -$do_rebuild = is_file_newer($zip_hash_ref) if $do_rebuild == 0; -if ( $do_rebuild == 1 ) { - my $tmpdir = copy_images($zip_hash_ref); - create_zip_archive($zip_hash_ref, \%links, $tmpdir); - replace_file($tmp_out_file, $out_file); - print_message("packing $out_file finished.") if $verbose; - - rmtree($tmpdir); - print_error("failed to delete $tmpdir") if -e $tmpdir; -} else { - print_message("$out_file up to date. nothing to do.") if $verbose; -} - -exit(0); - -#### subroutines #### - -sub parse_options -{ - my $opt_help; - my $p = Getopt::Long::Parser->new(); - my @custom_path_list; - my $success =$p->getoptions( - '-h' => \$opt_help, - '-o=s' => \$out_file, - '-g=s' => \$global_path, - '-s=s' => \$sort_file, - '-m=s' => \$module_path, - '-c=s' => \@custom_path_list, - '-l=s' => \$imagelist_file, - '-v' => \$verbose, - '-vv' => \$extra_verbose - ); - if ( $opt_help || !$success || !$out_file || !$global_path - || !$module_path || !@custom_path_list || !$imagelist_file ) - { - usage(); - exit(1); - } - #define intermediate output file - $tmp_out_file="$out_file"."$$".".tmp"; - # Sanity checks. - - # Check if out_file can be written. - my $out_dir = dirname($out_file); - - # Check paths. - print_error("no such file '$_'", 2) if ! -f $imagelist_file; - - my @check_directories = ($out_dir, $global_path, $module_path); - - foreach (@check_directories) { - print_error("no such directory: '$_'", 2) if ! -d $_; - print_error("can't search directory: '$_'", 2) if ! -x $_; - } - print_error("directory is not writable: '$out_dir'", 2) if ! -w $out_dir; - - # Use just the working paths - @custom_path = (); - foreach (@custom_path_list) { - if ( ! -d $_ ) { - print_warning("skipping non-existing directory: '$_'", 2); - } - elsif ( ! -x $_ ) { - print_error("can't search directory: '$_'", 2); - } - else { - push @custom_path, $_; - } - } -} - -sub get_image_lists -{ - my @image_lists; - - open (my $fh, $imagelist_file) or die "cannot open imagelist file $imagelist_file\n"; - while (<$fh>) { - chomp; - next if /^\s*$/; - my @ilsts = split ' '; - push @image_lists, @ilsts; - } - close $fh; - - return wantarray ? @image_lists : \@image_lists; -} - -sub iterate_image_lists -{ - my $image_lists_ref = shift; - - my %global_hash; - my %module_hash; - my %custom_hash; - my %help_hash; - - foreach my $i ( @{$image_lists_ref} ) { - parse_image_list($i, \%global_hash, \%module_hash, \%custom_hash, \%help_hash); - } - - return (\%global_hash, \%module_hash, \%custom_hash, \%help_hash); -} - -sub parse_image_list -{ - my $image_list = shift; - my $global_hash_ref = shift; - my $module_hash_ref = shift; - my $custom_hash_ref = shift; - - print_message("parsing '$image_list' ...") if $verbose; - my $linecount = 0; - open(IMAGE_LIST, "< $image_list") or die "ERROR: can't open $image_list: $!"; - while ( <IMAGE_LIST> ) { - $linecount++; - next if /^\s*#/; - next if /^\s*$/; - # clean up trailing whitespace - tr/\r\n//d; - s/\s+$//; - # clean up backslashes and double slashes - tr{\\}{/}s; - tr{/}{}s; - # hack "res" back into globals - if ( /^\Q$img_global\E\/(.*)$/o ) { - $global_hash_ref->{"res/".$1}++; - next; - } - if ( /^\Q$img_module\E\/(.*)$/o ) { - $module_hash_ref->{$1}++; - next; - } - # parse failed if we reach this point, bail out - close(IMAGE_LIST); - print_error("can't parse line $linecount from file '$image_list'", 4); - } - close(IMAGE_LIST); - - return ($global_hash_ref, $module_hash_ref, $custom_hash_ref); -} - -sub find_custom -{ - my $custom_hash_ref = shift; - my $keep_back; - for my $path (@custom_path) { - find({ wanted => \&wanted, no_chdir => 0 }, $path); - foreach ( @custom_list ) { - if ( /^\Q$path\E\/(.*)$/ ) { - $keep_back=$1; - if (!defined $custom_hash_ref->{$keep_back}) { - $custom_hash_ref->{$keep_back} = $path; - } - } - } - } -} - -sub wanted -{ - my $file = $_; - - if ( $file =~ /.*\.png$/ && -f $file ) { - push @custom_list, $File::Find::name; - } -} - -sub create_zip_list -{ - my $global_hash_ref = shift; - my $module_hash_ref = shift; - my $custom_hash_ref = shift; - - my %zip_hash; - my @warn_list; - - print_message("assemble image list ...") if $verbose; - foreach ( keys %{$global_hash_ref} ) { - # check if in 'global' and in 'module' list and add to warn list - if ( exists $module_hash_ref->{$_} ) { - push(@warn_list, $_); - next; - } - if ( exists $custom_hash_ref->{$_} ) { - $zip_hash{$_} = $custom_hash_ref->{$_}; - next; - } - # it's neither in 'module' nor 'custom', record it in zip hash - $zip_hash{$_} = $global_path; - } - foreach ( keys %{$module_hash_ref} ) { - if ( exists $custom_hash_ref->{$_} ) { - $zip_hash{$_} = $custom_hash_ref->{$_}; - next; - } - # it's not in 'custom', record it in zip hash - $zip_hash{$_} = $module_path; - } - - if ( @warn_list ) { - foreach ( @warn_list ) { - print_warning("$_ is duplicated in 'global' and 'module' list"); - } - } - - return \%zip_hash -} - -sub is_file_newer -{ - my $test_hash_ref = shift; - my $reference_stamp = 0; - - print_message("checking timestamps ...") if $verbose; - if ( -e $out_file ) { - $reference_stamp = (stat($out_file))[9]; - print_message("found $out_file with $reference_stamp ...") if $verbose; - } - return 1 if $reference_stamp == 0; - - foreach ( sort keys %{$test_hash_ref} ) { - my $path = $test_hash_ref->{$_}; - $path .= "/" if "$path" ne ""; - $path .= "$_"; - print_message("checking '$path' ...") if $extra_verbose; - my $mtime = (stat($path))[9]; - return 1 if $reference_stamp < $mtime; - } - return 0; -} - -sub optimize_zip_layout($) -{ - my $zip_hash_ref = shift; - - if (!defined $sort_file) { - print_message("no sort file - sorting alphabetically ...") if $verbose; - return sort keys %{$zip_hash_ref}; - } - print_message("sorting from $sort_file ...") if $verbose; - - my $orderh; - my %included; - my @sorted; - open ($orderh, $sort_file) || die "Can't open $sort_file: $!"; - while (<$orderh>) { - /^\#.*/ && next; # comments - s/[\r\n]*$//; - /^\s*$/ && next; - my $file = $_; - if (!defined $zip_hash_ref->{$file}) { - print "unknown file '$file'\n" if ($extra_verbose); - } else { - push @sorted, $file; - $included{$file} = 1; - } - } - close ($orderh); - - for my $img (sort keys %{$zip_hash_ref}) { - push @sorted, $img if (!$included{$img}); - } - - print_message("done sort ...") if $verbose; - - return @sorted; -} - -sub copy_images($) -{ - my ($zip_hash_ref) = @_; - my $dir = tempdir(); - foreach (keys %$zip_hash_ref) { - my $path = $zip_hash_ref->{$_} . "/$_"; - my $outpath = $dir . "/$_"; - print_message("copying '$path' to '$outpath' ...") if $extra_verbose; - if ( -e $path) { - my $dirname = dirname($outpath); - if (!-d $dirname) { - make_path($dirname); - } - copy($path, $outpath) - or print_error("can't add file '$path' to image dir: $!", 5); - } - } - return $dir; -} - -sub create_zip_archive($$$) -{ - my ($zip_hash_ref, $links_hash_ref, $image_dir_ref) = @_; - - print_message("creating image archive ...") if $verbose; - - chdir $image_dir_ref; - - if (keys %{$links_hash_ref}) { - write_links($links_hash_ref, $image_dir_ref); - system "zip $tmp_out_file links.txt"; - # print_error("failed to add links file: $!", 5); - } - - my @sorted_list = optimize_zip_layout($zip_hash_ref); - my $sorted_file = File::Temp->new(); - foreach my $item (@sorted_list) { - print $sorted_file "$item\n"; - } - binmode $sorted_file; # flush - - system "cat $sorted_file | zip -0 -@ $tmp_out_file"; - # print_error("write image zip archive '$tmp_out_file' failed. Reason: $!", 6); - chdir; # just go out of the temp dir -} - -sub replace_file -{ - my $source_file = shift; - my $dest_file = shift; - my $result = 0; - - $result = unlink($dest_file) if -f $dest_file; - if ( $result != 1 && -f $dest_file ) { - unlink $source_file; - print_error("couldn't remove '$dest_file'",1); - } else { - if ( !rename($source_file, $dest_file)) { - unlink $source_file; - print_error("couldn't rename '$source_file'",1); - } - } - return; -} - -sub usage -{ - print STDERR "Usage: packimages.pl [-h] -o out_file -g g_path -m m_path -c c_path -l imagelist_file\n"; - print STDERR "Creates archive of images\n"; - print STDERR "Options:\n"; - print STDERR " -h print this help\n"; - print STDERR " -o out_file path to output archive\n"; - print STDERR " -g g_path path to global images directory\n"; - print STDERR " -m m_path path to module images directory\n"; - print STDERR " -c c_path path to custom images directory\n"; - print STDERR " -s sort_file path to image sort order file\n"; - print STDERR " -l imagelist_file file containing list of image list files\n"; - print STDERR " -v verbose\n"; - print STDERR " -vv very verbose\n"; -} - -sub print_message -{ - my $message = shift; - - print "$script_name: "; - print "$message\n"; - return; -} - -sub print_warning -{ - my $message = shift; - - print STDERR "$script_name: "; - print STDERR "WARNING $message\n"; - return; -} - -sub print_error -{ - my $message = shift; - my $error_code = shift; - - print STDERR "$script_name: "; - print STDERR "ERROR: $message\n"; - - if ( $error_code ) { - print STDERR "\nFAILURE: $script_name aborted.\n"; - exit($error_code); - } - return; -} - -sub read_links($$) -{ - my $links = shift; - my $path = shift; - - my $fname = "$path/links.txt"; - if (!-f "$fname") { - return; - } - - my $fh; - open ($fh, $fname) || die "Can't open: $fname: $!"; - # Syntax of links file: - # # comment - # missing-image image-to-load-instead - while (<$fh>) { - my $line = $_; - $line =~ s/\r//g; # DOS line-feeds - $line =~ s/\#.*$//; # kill comments - $line =~ m/^\s*$/ && next; # blank lines - if ($line =~ m/^([^\s]+)\s+(.*)$/) { - my ($missing, $replace) = ($1, $2); - # enter into hash, and overwrite previous layer if necessary - $links->{$1} = $2; - } else { - die "Malformed links line: '$line'\n"; - } - } - close ($fh); -} - -# write out the links -sub write_links($$) -{ - my ($links, $out_dir_ref) = @_; - open (my $fh, ">", "$out_dir_ref/links.txt") - || die "can't create links.txt"; - for my $missing (sort keys %{$links}) { - my $line = $missing . " " . $links->{$missing} . "\n"; - print $fh $line; - } - close $fh; -} - -# Ensure that no link points to another link -sub check_links($) -{ - my $links = shift; - my $stop_die = 0; - - for my $link (keys %{$links}) { - my $value = $links->{$link}; - if (defined $links->{$value}) { - print STDERR "\nLink: $link -> $value -> " . $links->{$value}; - $stop_die = 1; - } - } - if ( $stop_die ) { - die "\nSome icons in links.txt were found to link to other linked icons.\n\n"; - } - -} - -# remove any files from our zip list that are linked -sub remove_links_from_zip_list($$) -{ - my $zip_hash_ref = shift; - my $links = shift; - for my $link (keys %{$links}) { - if (defined $zip_hash_ref->{$link}) { - delete $zip_hash_ref->{$link}; - } - } -} |