From 88f80019efc331fc2f0c3842d825f6e3b61e0466 Mon Sep 17 00:00:00 2001 From: Matteo Paonessa Date: Fri, 10 Mar 2017 11:46:41 +0100 Subject: [PATCH] All features available --- CMakeLists.txt | 2 +- INSTALL | 40 ++++--------------- README.md | 16 +++++--- src/CMakeLists.txt | 1 - src/error.c | 45 +++++++++++++++++++++ src/error.h | 8 ++++ src/helper.c | 97 +++++++++++++++++++++++++++++++++------------- src/helper.h | 1 + src/main.c | 21 +++++----- src/utils.c | 16 ++++---- 10 files changed, 164 insertions(+), 83 deletions(-) create mode 100644 src/error.c create mode 100644 src/error.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d1016d3..1e88212 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(caesiumclt) # The version number. set(VERSION_MAJOR 0) set(VERSION_MINOR 10) -set(VERSION_PATCH 0) +set(VERSION_PATCH 1) configure_file( "src/config.h.in" diff --git a/INSTALL b/INSTALL index 4227007..98c647c 100644 --- a/INSTALL +++ b/INSTALL @@ -1,35 +1,10 @@ -Installation Instructions +Compile instructions ************************* Requirements ================== -You will need both mozjpeg (libjpeg-turbo will work too) and zopflipng compiled as libraries to be able to -compile CaesiumCLT. Refer to their own documentation for the detailed -instructions. - -This is the step-by-step sequence tested on Ubuntu 16.04 LTS: - - mozjpeg - - $ git clone https://github.com/mozilla/mozjpeg - $ cd mozjpeg/ - $ autoreconf -fiv - $ mkdir build && cd build - $ ../configure - $ make && sudo make install - -Note that on some systems you have to run $ autoreconf -fiv twice. - - zopflipng - - $ git clone https://github.com/google/zopfli.git - $ cd zopfli - $ make libzopflipng - $ sudo cp libzopflipng.so.1.0.0 /usr/lib - $ sudo ln -s libzopflipng.so.1.0.0 /usr/lib/libzopflipng.so - $ sudo ln -s libzopflipng.so.1.0.0 /usr/lib/libzopflipng.so.1 - $ sudo mkdir /usr/include/zopflipng - $ sudo cp src/zopflipng/zopflipng_lib.h /usr/include/zopflipng +CaesiumCLT is based on libcaesium (https://github.com/Lymphatus/libcaesium) and requires it to be compiled. +Please refer to its own documentation. Basic Installation ================== @@ -39,17 +14,16 @@ Unpack the archive (if not cloned from GIT), enter the directory and compile the program. The sequence should be something like: - $ tar xfv caesiumclt-*.tar.gz - $ cd caesiumclt-* + $ tar xfv caesium-clt-*.tar.gz + $ cd caesium-clt-* $ cmake . $ make $ sudo make install Cloning from git ================== - $ git clone https://github.com/Lymphatus/CaesiumCLT - $ cd CaesiumCLT - $ autoreconf -fiv + $ git clone https://github.com/Lymphatus/caesium-clt + $ cd caesium-clt $ cmake . $ make $ sudo make install diff --git a/README.md b/README.md index 918e4c3..0820507 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ## Caesium CommandLineTools -##### caesium-clt - v0.10.0-beta (build 20170307) - Copyright © Matteo Paonessa, 2017. All Rights Reserved. +##### caesium-clt - v0.10.1-beta (build 20170310) - Copyright © Matteo Paonessa, 2017. All Rights Reserved. ---------- @@ -13,13 +13,13 @@ ---------- ###### TESTED PLATFORMS -* Mac OS X Sierra (v10.12.1) -* Arch Linux +* Mac OS X Sierra (v10.12.3) +* Ubuntu 16.04 * Windows 10 ---------- -###### INSTALLATION +###### COMPILATION See INSTALL for more details. ---------- @@ -46,15 +46,21 @@ Losslessly compress ```Pictures``` folder and subfolders, located in the ```home $ caesiumclt -q 0 -R -o ~/output/ ~/Pictures ``` +Losslessly compress ```Pictures``` folder and subfolders, located in the ```home``` directory, into a folder called ```output``` retaining the input folder structure +``` +$ caesiumclt -q 0 -RS-o ~/output/ ~/Pictures +``` + ---------- ###### TODO * Code cleaning -* Keep folder structure +* Deeper error handling ---------- ###### CHANGELOG +* 0.10.1-beta - All features are available * 0.10.0-beta - Switched to cmake build system and libcaesium * 0.9.1-beta - Initial development stage diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1192ef..433836c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,6 @@ FILE(GLOB CHeaders *.h) add_executable(caesiumclt ${CSources} ${CHeaders}) -find_library(caesium caesium /usr/local/lib) target_link_libraries(caesiumclt LINK_PUBLIC caesium) install(TARGETS caesiumclt DESTINATION bin) \ No newline at end of file diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..68348d0 --- /dev/null +++ b/src/error.c @@ -0,0 +1,45 @@ +#include +#include + +#include "error.h" + +void display_error(error_level level, int code) +{ + char *error_level = ((level) ? "[WARNING]" : "[ERROR]"); + fprintf(stderr, "%s %d: %s\n", + error_level, + code, + get_error_message(code)); +} + +const char *get_error_message(int code) +{ + switch (code) { + //Generic errors + case 1: + return "Invalid quality value. Must be between [0-100]."; + case 2: + return "Unrecognized option."; + case 3: + return "Empty input folder."; + case 4: + return "Cannot keep folder structure providing multiple input files."; + case 5: + return "Cannot create output folder."; + case 6: + return "Cannot check if is a directory."; + case 7: + return "Cannot calculate file size"; + case 8: + return "Input folder provided. Skipping all other inputs."; + case 9: + return "Input files provided. Cannot mix them with a folder."; + case 10: + return "-R is useless on files."; + case 11: + return "-S is useless without -R."; + + default: + return "Unrecognized error."; + } +} \ No newline at end of file diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..1413a72 --- /dev/null +++ b/src/error.h @@ -0,0 +1,8 @@ +#ifndef CAESIUMCLT_ERROR_H +#define CAESIUMCLT_ERROR_H + +void display_error(error_level level, int code); + +const char *get_error_message(int code); + +#endif //CAESIUMCLT_ERROR_H diff --git a/src/helper.c b/src/helper.c index 37fdfce..7c48495 100644 --- a/src/helper.c +++ b/src/helper.c @@ -5,12 +5,13 @@ #include "optparse.h" #include "utils.h" #include "config.h" +#include "error.h" cclt_options parse_arguments(char **argv, cs_image_pars *options) { struct optparse opts; //Initialize application options - cclt_options result = {NULL, NULL, false, false, 0, 0, 0}; + cclt_options parameters = {NULL, NULL, false, false, 0, 0, 0}; //Parse command line args optparse_init(&opts, argv); @@ -31,7 +32,7 @@ cclt_options parse_arguments(char **argv, cs_image_pars *options) case 'q': options->jpeg.quality = atoi(opts.optarg); if (options->jpeg.quality < 0 || options->jpeg.quality > 100) { - //TODO Trigger a error + display_error(ERROR, 1); exit(EXIT_FAILURE); } break; @@ -40,18 +41,18 @@ cclt_options parse_arguments(char **argv, cs_image_pars *options) break; case 'o': if (opts.optarg[strlen(opts.optarg) - 1] == '/') { - result.output_folder = malloc((strlen(opts.optarg) + 1) * sizeof(char)); - snprintf(result.output_folder, strlen(opts.optarg) + 1, "%s", opts.optarg); + parameters.output_folder = malloc((strlen(opts.optarg) + 1) * sizeof(char)); + snprintf(parameters.output_folder, strlen(opts.optarg) + 1, "%s", opts.optarg); } else { - result.output_folder = malloc((strlen(opts.optarg) + 2) * sizeof(char)); - snprintf(result.output_folder, strlen(opts.optarg) + 2, "%s/", opts.optarg); + parameters.output_folder = malloc((strlen(opts.optarg) + 2) * sizeof(char)); + snprintf(parameters.output_folder, strlen(opts.optarg) + 2, "%s/", opts.optarg); } break; case 'R': - result.recursive = true; + parameters.recursive = true; break; case 'S': - result.keep_structure = true; + parameters.keep_structure = true; break; case 'v': fprintf(stdout, "%d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); @@ -62,6 +63,7 @@ cclt_options parse_arguments(char **argv, cs_image_pars *options) case '?': default: fprintf(stderr, "%s: %s\n", argv[0], opts.errmsg); + display_error(ERROR, 2); exit(EXIT_FAILURE); } } @@ -70,27 +72,48 @@ cclt_options parse_arguments(char **argv, cs_image_pars *options) char *arg; bool files_flag = false, folders_flag = false; while ((arg = optparse_arg(&opts))) { + if (folders_flag) { + display_error(WARNING, 8); + continue; + } //Check if it's a directory and add its content if (is_directory(arg)) { - int count = 0; - count = scan_folder(arg, &result, result.recursive); - if (count == 0) { - //TODO Trigger a warning + if (!files_flag) { + folders_flag = true; + parameters.input_folder = strdup(arg); + int count = 0; + count = scan_folder(arg, ¶meters, parameters.recursive); + if (count == 0) { + display_error(WARNING, 3); + } + } else { + display_error(WARNING, 9); } - } else { - result.input_files = realloc(result.input_files, (result.files_count + 1) * sizeof(char *)); - result.input_files[result.files_count] = malloc((strlen(arg) + 1) * sizeof(char)); - snprintf(result.input_files[result.files_count], strlen(arg) + 1, "%s", arg); - result.files_count++; + files_flag = true; + parameters.input_folder = NULL; + parameters.input_files = realloc(parameters.input_files, (parameters.files_count + 1) * sizeof(char *)); + parameters.input_files[parameters.files_count] = malloc((strlen(arg) + 1) * sizeof(char)); + snprintf(parameters.input_files[parameters.files_count], strlen(arg) + 1, "%s", arg); + parameters.files_count++; } } //If there're files and folders, we cannot keep the structure - //TODO Trigger a warning - result.keep_structure = !(files_flag && folders_flag); + if (parameters.recursive && !folders_flag) { + display_error(WARNING, 10); + parameters.recursive = false; + } + if (!parameters.recursive && parameters.keep_structure) { + display_error(WARNING, 11); + parameters.keep_structure = false; + } + if (parameters.keep_structure && (!folders_flag && parameters.files_count > 1)) { + display_error(WARNING, 4); + parameters.keep_structure = false; + } - return result; + return parameters; } int start_compression(cclt_options *options, cs_image_pars *parameters) @@ -99,17 +122,36 @@ int start_compression(cclt_options *options, cs_image_pars *parameters) off_t input_file_size = 0; off_t output_file_size = 0; //TODO Support folder structure - //Create the output folder if does not exists if (mkpath(options->output_folder, 0777) == -1) { - //TODO Error + display_error(ERROR, 5); exit(EXIT_FAILURE); } for (int i = 0; i < options->files_count; i++) { char *filename = get_filename(options->input_files[i]); - char *output_full_path = malloc((strlen(filename) + strlen(options->output_folder) + 1) * sizeof(char)); - snprintf(output_full_path, (strlen(filename) + strlen(options->output_folder) + 1), "%s%s", options->output_folder, filename); + char *output_full_path; + //If we don't need to keep the structure, we put all the files in one folder by just the filename + if (!options->keep_structure) { + output_full_path = malloc((strlen(filename) + strlen(options->output_folder) + 1) * sizeof(char)); + snprintf(output_full_path, (strlen(filename) + strlen(options->output_folder) + 1), "%s%s", + options->output_folder, filename); + } else { + /* + * Otherwise, we nee to compute the whole directory structure + * We are sure we have a folder only as input, so that's the root + * Just compute the subfolders without the filename, make them and append the filename + * A piece of cake <3 + */ + + size_t index = strspn(options->input_folder, options->input_files[i]) + 1; + size_t size = strlen(options->input_files[i]) - index - strlen(filename); + char output_full_folder[strlen(options->output_folder) + size + 1]; + snprintf(output_full_folder, strlen(options->output_folder) + size + 1, "%s%s", options->output_folder, &options->input_files[i][index]); + output_full_path = malloc((strlen(output_full_folder) + strlen(filename) + 1) * sizeof(char)); + snprintf(output_full_path, strlen(output_full_folder) + strlen(filename) + 1, "%s%s", output_full_folder, filename); + mkpath(output_full_folder, 0777); + } fprintf(stdout, "(%d/%d) %s -> %s\n", i + 1, @@ -124,9 +166,12 @@ int start_compression(cclt_options *options, cs_image_pars *parameters) output_file_size = get_file_size(output_full_path); options->output_total_size += output_file_size; + char *human_input_size = get_human_size(input_file_size); + char *human_output_size = get_human_size(output_file_size); + fprintf(stdout, "%s -> %s [%.2f%%]\n", - get_human_size(input_file_size), - get_human_size(output_file_size), + human_input_size, + human_output_size, ((float) output_file_size - input_file_size) * 100 / input_file_size); } else { options->input_total_size -= get_file_size(options->input_files[i]); diff --git a/src/helper.h b/src/helper.h index 2f5689f..28accbe 100644 --- a/src/helper.h +++ b/src/helper.h @@ -6,6 +6,7 @@ typedef struct cclt_options { char **input_files; + char *input_folder; char *output_folder; bool recursive; bool keep_structure; diff --git a/src/main.c b/src/main.c index eb916ea..46e3e92 100755 --- a/src/main.c +++ b/src/main.c @@ -1,21 +1,21 @@ #include #include #include -#include #include #include "utils.h" -#include "helper.h" -int main(int argc, char* argv[]) { - errno = 0; - long execution_ms = 0; +int main(int argc, char *argv[]) +{ + long compression_time = 0; cs_image_pars compress_options; cclt_options options; //Initialize the default parameters compress_options = initialize_parameters(); + //Set them according to command line parameters options = parse_arguments(argv, &compress_options); + //Start a timer before calling the compression clock_t start = clock(), diff; @@ -24,18 +24,19 @@ int main(int argc, char* argv[]) { //Cleanup the two memory allocated objects free(options.output_folder); free(options.input_files); + free(options.input_folder); //Get the difference diff = clock() - start; - execution_ms = diff * 1000 / CLOCKS_PER_SEC; + compression_time = diff * 1000 / CLOCKS_PER_SEC; //Output the compression results - - fprintf(stdout, "-------------------------------\nCompression completed in " + fprintf(stdout, "-------------------------------\n" + "Compression completed in " "%lum%lus%lums\n%s -> %s [%.2f%% | %s]\n", - execution_ms / 1000 / 60, execution_ms / 1000 % 60, execution_ms % 1000, + compression_time / 1000 / 60, compression_time / 1000 % 60, compression_time % 1000, get_human_size(options.input_total_size), get_human_size(options.output_total_size), - ((float)options.output_total_size - options.input_total_size) * 100 / options.input_total_size, + ((float) options.output_total_size - options.input_total_size) * 100 / options.input_total_size, get_human_size((options.output_total_size - options.input_total_size))); diff --git a/src/utils.c b/src/utils.c index a061136..e3c40d2 100644 --- a/src/utils.c +++ b/src/utils.c @@ -6,6 +6,7 @@ #include #include "utils.h" #include "tinydir.h" +#include "error.h" void print_help() @@ -32,7 +33,7 @@ bool is_directory(const char *path) tinydir_file file; if (tinydir_file_open(&file, path) == -1) { - //TODO Error + display_error(ERROR, 6); exit(EXIT_FAILURE); } @@ -99,7 +100,7 @@ char *get_filename(char *full_path) tofree = strdup(full_path); //TODO change to strncpy strcpy(tofree, full_path); - //TODO Change on Windows + //TODO Windows? while ((token = strsep(&tofree, "/")) != NULL) { if (tofree == NULL) { break; @@ -116,20 +117,21 @@ off_t get_file_size(const char *path) tinydir_file file; if (tinydir_file_open(&file, path) == -1) { - //TODO Error + display_error(ERROR, 7); exit(EXIT_FAILURE); } return file._s.st_size; } -char* get_human_size(off_t size) { +char *get_human_size(off_t size) +{ //We should not get more than TB images - char* unit[5] = {"B", "KB", "MB", "GB", "TB"}; + char *unit[5] = {"B", "KB", "MB", "GB", "TB"}; //Index of the array containing the correct unit double order = floor(log2(labs(size)) / 10); //Alloc enough size for the final string - char* final = (char*) malloc(((int) (floor(log10(labs(size))) + 4)) * sizeof(char)); + char *final = (char *) malloc(((int) (floor(log10(labs(size))) + 4)) * sizeof(char)); //If the order exceeds 4, something is fishy if (order > 4) { @@ -137,7 +139,7 @@ char* get_human_size(off_t size) { } //Copy the formatted string into the buffer - sprintf(final, "%.2f %s", size / (pow(1024, order)), unit[(int)order]); + sprintf(final, "%.2f %s", size / (pow(1024, order)), unit[(int) order]); //And return it return final; }