diff options
author | Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de> | 2017-09-26 09:10:57 +0200 |
---|---|---|
committer | Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de> | 2017-09-26 09:11:08 +0200 |
commit | 138c1252787256a20807caacc9df2af7b19b245e (patch) | |
tree | 22a6caca051a42194e57bc6d741cdc8b8203f752 /solenv | |
parent | 07fd83987a38edb21fc7a8b7d7efe44e0a25e347 (diff) |
Revert "tdf#106894 Rewrite packimages.pl in Python (pack_images.py)"
This broke the build: https://ci.libreoffice.org/job/lo_tb_master_win/14994/console
This reverts commit 423ee1020afe1bca896f2ecfc67ffbd49db5081c.
Change-Id: I05e6fd68f2bbec189236cbe265f6510731327997
Reviewed-on: https://gerrit.libreoffice.org/42778
Reviewed-by: Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de>
Tested-by: Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de>
Diffstat (limited to 'solenv')
-rwxr-xr-x | solenv/bin/pack_images.py | 590 | ||||
-rwxr-xr-x | solenv/bin/packimages.pl | 550 |
2 files changed, 550 insertions, 590 deletions
diff --git a/solenv/bin/pack_images.py b/solenv/bin/pack_images.py deleted file mode 100755 index 2e43c1815de4..000000000000 --- a/solenv/bin/pack_images.py +++ /dev/null @@ -1,590 +0,0 @@ -#!/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 new file mode 100755 index 000000000000..96a31de3b2fd --- /dev/null +++ b/solenv/bin/packimages.pl @@ -0,0 +1,550 @@ +: +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}; + } + } +} |