Reads all files in the source folder that match any of the files to include and copies them to the destination, creating necessary folders in the process
Signed-off-by: Florian Eder others.meder@gmail.com --- Does not yet break at a certain depth, so this will copy as deep as possible Wildcards are already supported, so the "file" * will copy all files in any subdirectories --- programs/robocopy/Makefile.in | 2 +- programs/robocopy/main.c | 160 ++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 10 ++- programs/robocopy/robocopy.rc | 2 + 4 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index 3f16d00c0a8..b81d799b75a 100644 --- a/programs/robocopy/Makefile.in +++ b/programs/robocopy/Makefile.in @@ -1,5 +1,5 @@ MODULE = robocopy.exe -IMPORTS = kernelbase +IMPORTS = kernelbase shlwapi
EXTRADLLFLAGS = -mconsole -municode -mno-cygwin
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 97b961a5d0d..a28b008a8fa 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -23,6 +23,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy); #include <windows.h> #include <stdlib.h> #include <pathcch.h> +#include <shlwapi.h> +#include <wine/list.h> #include "robocopy.h"
struct robocopy_options options; @@ -147,6 +149,162 @@ static void parse_arguments(int argc, WCHAR *argv[]) } }
+static BOOL matches_array_entry(WCHAR *name, struct path_array *excluded_names) +{ + int i; + for (i = 0; i < excluded_names->size; i++) + { + if (PathMatchSpecW(name, excluded_names->array[i])) return TRUE; + } + return FALSE; +} + +static WCHAR *get_combined_path(const WCHAR* path_prefix, const WCHAR* path_suffix) +{ + WCHAR *combined_path; + if (path_prefix[wcslen(path_prefix) - 1] == L'\' || !path_prefix[0]) + { + /* path_prefix ends in a backslash (or is empty) */ + combined_path = calloc(wcslen(path_prefix) + wcslen(path_suffix) + 1, sizeof(WCHAR)); + wcscpy(combined_path, path_prefix); + wcscpy(&(combined_path[wcslen(path_prefix)]), path_suffix); + } + else + { + /* path_prefix ends not in a backslash, we have to add one between the strings */ + combined_path = calloc(wcslen(path_prefix) + wcslen(path_suffix) + 2, sizeof(WCHAR)); + wcscpy(combined_path, path_prefix); + wcscpy(&(combined_path[wcslen(path_prefix) + 1]), path_suffix); + combined_path[wcslen(path_prefix)] = L'\'; + } + return combined_path; +} + +static BOOL create_directory_path(WCHAR *path) +{ + WCHAR *pointer, *current_folder; + current_folder = calloc(wcslen(path) + 1, sizeof(WCHAR)); + /* ignore the "\?" prefix, so that those backslashes are not matched */ + pointer = wcschr(strip_path_prefix(path), L'\'); + while (pointer != NULL) + { + if (!lstrcpynW(current_folder, path, pointer - path + 2)) return FALSE; + /* try to create the folder, ignoring any failure due to ERROR_ALREADY_EXISTS */ + if (!CreateDirectoryW(current_folder, NULL)) + { + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + WINE_FIXME("error create directory %S %d", current_folder, GetLastError()); + return FALSE; + } + } + else + output_message(STRING_CREATE_DIRECTORY, strip_path_prefix(current_folder)); + pointer = wcschr(pointer + 1, L'\'); + } + return TRUE; +} + +static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths) +{ + HANDLE temp_handle; + struct path *new_path, *current_path; + WIN32_FIND_DATAW entry_data; + WCHAR *parent_absolute_path, *current_relative_path, *current_absolute_path, *current_search_path; + + list_init(paths); + + /* initialize list with a empty relative path */ + new_path = calloc(1, sizeof(struct path)); + new_path->name = calloc(2, sizeof(WCHAR)); + list_add_tail(paths, &new_path->entry); + + LIST_FOR_EACH_ENTRY(current_path, paths, struct path, entry) + { + /* append relative path to the (prefix) directory path */ + parent_absolute_path = get_combined_path(directory_path, current_path->name); + + /* ignore files, only search in directories (with files or subdirectories in them) */ + if (!PathIsDirectoryW(parent_absolute_path) || PathIsDirectoryEmptyW(parent_absolute_path)) continue; + + /* append * to recieve every file / subdirectory in this directory */ + current_search_path = get_combined_path(parent_absolute_path, L"*"); + /* walk through all files / directories in this directory */ + temp_handle = FindFirstFileExW(current_search_path, FindExInfoStandard, &entry_data, FindExSearchNameMatch, NULL, 0); + if (temp_handle != INVALID_HANDLE_VALUE) + { + do + { + /* Ignore . and .. entries */ + if (!wcscmp(L".", entry_data.cFileName) || !wcscmp(L"..", entry_data.cFileName)) continue; + + current_relative_path = get_combined_path(current_path->name, entry_data.cFileName); + current_absolute_path = get_combined_path(directory_path, current_relative_path); + + /* If this entry is a matching file or empty directory, add it to the list of results */ + if ((!PathIsDirectoryW(current_absolute_path) && matches_array_entry(entry_data.cFileName, options.files)) || + (PathIsDirectoryW(current_absolute_path))) + { + new_path = calloc(1, sizeof(struct path)); + new_path->name = wcsdup(current_relative_path); + list_add_tail(paths, &new_path->entry); + } + } + while (FindNextFileW(temp_handle, &entry_data) != 0); + } + } +} + +static BOOL perform_copy(void) +{ + struct list paths_source; + struct path *current_path; + WCHAR *current_absolute_path, *target_path; + + list_init(&paths_source); + + if (!PathIsDirectoryW(options.source)) + { + WINE_FIXME("error read directory %S %d", options.source, GetLastError()); + return FALSE; + } + + /* create destination folder if it does not yet exist */ + create_directory_path(options.destination); + + /* get files in the destination folder and source folder */ + get_file_paths_in_folder(options.source, &paths_source); + + /* get files in the source folder */ + LIST_FOR_EACH_ENTRY(current_path, &paths_source, struct path, entry) + { + /* append the relative path to the source to get the absolute path of the source file / directory */ + current_absolute_path = get_combined_path(options.source, current_path->name); + + /* append the relative path to the destination to get the target path */ + target_path = get_combined_path(options.destination, current_path->name); + + if (PathIsDirectoryW(current_absolute_path)) + { + /* Create the directory path and then create the directory itself */ + if (!create_directory_path(target_path)) + WINE_FIXME("error write directory %S %d", target_path, GetLastError()); + } + else + { + if (!CopyFileW(current_absolute_path, target_path, FALSE)) + WINE_FIXME("error write file %S %d", target_path, GetLastError()); + else + { + output_message(STRING_CREATE_FILE, strip_path_prefix(target_path)); + } + } + + } + + return TRUE; +} + static void print_header(void) { UINT i; @@ -170,6 +328,8 @@ int __cdecl wmain(int argc, WCHAR *argv[])
print_header();
+ perform_copy(); + WINE_FIXME("robocopy stub"); return 0; } \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 3be51b460a7..22c7406e0ea 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -18,6 +18,12 @@
#define WIN32_LEAN_AND_MEAN #include <windows.h> +#include <wine/list.h> + +struct path { + struct list entry; + WCHAR *name; +};
struct path_array { UINT size; @@ -35,4 +41,6 @@ struct robocopy_options { #define STRING_SOURCE 1003 #define STRING_DESTINATION 1004 #define STRING_FILES 1005 -#define STRING_ADDITIONAL_INFO 1008 \ No newline at end of file +#define STRING_ADDITIONAL_INFO 1008 +#define STRING_CREATE_DIRECTORY 1019 +#define STRING_CREATE_FILE 1022 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index e00d9fc0227..92f3b8efe63 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -30,6 +30,8 @@ STRINGTABLE STRING_DESTINATION, " Destination: %1\n\n" STRING_FILES, " Files: %1\n" STRING_ADDITIONAL_INFO, " %1\n" + STRING_CREATE_DIRECTORY, " Created Dir: %1\n" + STRING_CREATE_FILE, " Copied File: %1\n" }
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL