summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJens Carl <j.carl43@gmx.de>2017-08-29 04:48:13 +0000
committerSamuel Mehrbrodt <Samuel.Mehrbrodt@cib.de>2017-09-26 08:53:22 +0200
commit423ee1020afe1bca896f2ecfc67ffbd49db5081c (patch)
tree862202456ea3b404427a21bb3dd756d13ea2f02b
parentloplugin:cstylecast (clang-cl) (diff)
downloadcore-423ee1020afe1bca896f2ecfc67ffbd49db5081c.tar.gz
core-423ee1020afe1bca896f2ecfc67ffbd49db5081c.zip
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>
-rw-r--r--postprocess/CustomTarget_images.mk2
-rwxr-xr-xsolenv/bin/pack_images.py590
-rwxr-xr-xsolenv/bin/packimages.pl550
3 files changed, 591 insertions, 551 deletions
diff --git a/postprocess/CustomTarget_images.mk b/postprocess/CustomTarget_images.mk
index 44899e6105a5..603c73d522d4 100644
--- a/postprocess/CustomTarget_images.mk
+++ b/postprocess/CustomTarget_images.mk
@@ -33,7 +33,7 @@ $(packimages_DIR)/%.zip : \
$(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),PRL,2)
$(call gb_Helper_abbreviate_dirs, \
ILSTFILE=$(call var2file,$(shell $(gb_MKTEMP)),100,$(filter %.ilst,$^)) && \
- $(PERL) $(SRCDIR)/solenv/bin/packimages.pl \
+ $(PYTHON) $(SRCDIR)/solenv/bin/pack_images.py \
$(if $(DEFAULT_THEME),\
-g $(packimages_DIR) -m $(packimages_DIR) -c $(packimages_DIR),\
-g $(SRCDIR)/icon-themes/$(subst images_,,$*) -m $(SRCDIR)/icon-themes/$(subst images_,,$*) -c $(SRCDIR)/icon-themes/$(subst images_,,$*) \
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};
- }
- }
-}