Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Redo last commit

  • Loading branch information...
commit 16e95e2ebbbdc104fd9f278c6bdd4374b548e949 1 parent b25522a
@logandk authored
View
3  build/.gitignore
@@ -0,0 +1,3 @@
+*
+!.gitignore
+
View
203 include/windows_helpers.h
@@ -0,0 +1,203 @@
+#ifndef UNFURL_WINDOWS_HELPERS_H
+#define UNFURL_WINDOWS_HELPERS_H
+
+#include <windows.h>
+#include <shlwapi.h>
+#include <shlobj.h>
+#include <stdexcept>
+
+bool file_exists(const std::string &path)
+{
+ DWORD attr = GetFileAttributes(path.c_str());
+
+ return (attr != INVALID_FILE_ATTRIBUTES &&
+ !(attr & FILE_ATTRIBUTE_DIRECTORY));
+}
+
+bool dir_exists(const std::string &path)
+{
+ DWORD attr = GetFileAttributes(path.c_str());
+
+ return (attr != INVALID_FILE_ATTRIBUTES &&
+ (attr & FILE_ATTRIBUTE_DIRECTORY));
+}
+
+std::string current_path()
+{
+ char path[MAX_PATH] = {0};
+ GetCurrentDirectory(MAX_PATH, path);
+ return path;
+}
+
+std::string application_path()
+{
+ char path[MAX_PATH] = {0};
+ GetModuleFileName(NULL, path, MAX_PATH);
+ PathRemoveFileSpec(path);
+ return path;
+}
+
+std::string appdata_path()
+{
+ char path[MAX_PATH] = {0};
+ SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path);
+ return path;
+}
+
+std::string join_path(const std::string &a, const std::string &b)
+{
+ char path[MAX_PATH] = {0};
+ strcpy(path, a.c_str());
+ PathAppend(path, b.c_str());
+ if (dir_exists(path)) PathAddBackslash(path);
+ return path;
+}
+
+std::string parent_path(const std::string &child_path)
+{
+ char path[MAX_PATH] = {0};
+ strcpy(path, child_path.c_str());
+ PathRemoveBackslash(path);
+ PathRemoveFileSpec(path);
+ return path;
+}
+
+void create_path(const std::string &path)
+{
+ if (!dir_exists(parent_path(path)))
+ {
+ create_path(parent_path(path));
+ }
+
+ if (!dir_exists(path))
+ {
+ CreateDirectory(path.c_str(), NULL);
+ }
+}
+
+void delete_file(const std::string &path)
+{
+ DeleteFile(path.c_str());
+}
+
+void delete_dir(const std::string &path)
+{
+ RemoveDirectory(path.c_str());
+}
+
+void delete_path(const std::string &path)
+{
+ if (file_exists(path))
+ {
+ delete_file(path);
+ }
+ else
+ {
+ WIN32_FIND_DATA fd;
+ HANDLE h;
+
+ h = FindFirstFile(join_path(path, "*").c_str(), &fd);
+
+ if (h == INVALID_HANDLE_VALUE)
+ {
+ throw std::runtime_error("delete_path: invalid path: " + path);
+ }
+
+ do
+ {
+ if (std::string(fd.cFileName) == "." || std::string(fd.cFileName) == "..")
+ {
+ continue;
+ }
+
+ if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ delete_path(join_path(path, fd.cFileName));
+ }
+ else
+ {
+ delete_file(join_path(path, fd.cFileName));
+ }
+ }
+ while (FindNextFile(h, &fd) != 0);
+
+ FindClose(h);
+
+ delete_dir(path);
+ }
+}
+
+unsigned long create_process(const std::string &command)
+{
+ DWORD exit_code;
+ char path[MAX_PATH] = {0};
+
+ strcpy(path, command.c_str());
+
+ // Set up members of the PROCESS_INFORMATION structure.
+ PROCESS_INFORMATION proc_info;
+ ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION));
+
+ // Set up members of the STARTUPINFO structure.
+ // This structure specifies the STDIN and STDOUT handles for redirection.
+ STARTUPINFO start_info;
+ ZeroMemory(&start_info, sizeof(STARTUPINFO));
+ start_info.cb = sizeof(STARTUPINFO);
+ start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ start_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ start_info.dwFlags |= STARTF_USESTDHANDLES;
+
+ // Create the child process.
+ bool success = CreateProcess(NULL,
+ path, // command line
+ NULL, // process security attributes
+ NULL, // primary thread security attributes
+ true, // handles are inherited
+ 0, // creation flags
+ NULL, // use parent's environment
+ NULL, // use parent's current directory
+ &start_info, // STARTUPINFO pointer
+ &proc_info); // receives PROCESS_INFORMATION
+
+ // If an error occurs, exit the application.
+ if (!success)
+ {
+ throw std::runtime_error("create_process: unable to create process: " + command);
+ }
+
+ // Successfully created the process. Wait for it to finish.
+ WaitForSingleObject(proc_info.hProcess, INFINITE);
+
+ // Get the exit code.
+ GetExitCodeProcess(proc_info.hProcess, &exit_code);
+
+ // Close handles to the child process and its primary thread.
+ CloseHandle(proc_info.hProcess);
+ CloseHandle(proc_info.hThread);
+
+ return exit_code;
+}
+
+void show_info(const std::string &title, const std::string &message)
+{
+ MessageBox(
+ NULL,
+ message.c_str(),
+ title.c_str(),
+ MB_ICONINFORMATION | MB_OK
+ );
+}
+
+void show_error(const std::string &title, const std::string &message)
+{
+ MessageBox(
+ NULL,
+ message.c_str(),
+ title.c_str(),
+ MB_ICONERROR | MB_OK
+ );
+}
+
+#endif
+
View
128 include/zip_helpers.h
@@ -0,0 +1,128 @@
+#ifndef UNFURL_ZIP_HELPERS_H
+#define UNFURL_ZIP_HELPERS_H
+
+#include <unzip.h>
+#include <iowin32.h>
+#include <string>
+#include <stdexcept>
+#include "windows_helpers.h"
+
+#define UNZIP_BUFFER_SIZE 8192
+
+void unzip(const std::string &archive, const std::string &destination)
+{
+ // Open the zip file
+ unzFile zipfile = unzOpen64(archive.c_str());
+
+ if (zipfile == NULL)
+ {
+ throw std::runtime_error("unzOpen64: package not found: " + archive);
+ }
+
+ // Get info about the zip file
+ unz_global_info64 global_info;
+
+ if (unzGetGlobalInfo64(zipfile, &global_info) != UNZ_OK)
+ {
+ unzClose(zipfile);
+ throw std::runtime_error("unzGetGlobalInfo64: package corrupt: " + archive);
+ }
+
+ // Buffer to hold data read from the zip file.
+ char read_buffer[UNZIP_BUFFER_SIZE];
+
+ // Loop to extract all files
+ uLong i;
+
+ for (i = 0; i < global_info.number_entry; ++i)
+ {
+ // Get info about current file.
+ unz_file_info64 file_info;
+ char filename[MAX_PATH];
+
+ if (unzGetCurrentFileInfo64(zipfile, &file_info, filename, MAX_PATH, NULL, 0, NULL, 0) != UNZ_OK)
+ {
+ unzClose(zipfile);
+ throw std::runtime_error("unzGetCurrentFileInfo64: package corrupt: " + archive);
+ }
+
+ // Check if this entry is a directory or file.
+ const size_t filename_length = strlen(filename);
+
+ if (filename[filename_length - 1] == '\\' || filename[filename_length - 1] == '/')
+ {
+ // Entry is a directory, so create it.
+ create_path(join_path(destination, filename));
+ }
+ else
+ {
+ // Entry is a file, so extract it.
+ if (unzOpenCurrentFile(zipfile) != UNZ_OK)
+ {
+ unzClose(zipfile);
+ throw std::runtime_error("unzOpenCurrentFile: cannot open file: " +
+ std::string(filename));
+ }
+
+ // Open a file to write out the data.
+ FILE *out = fopen(join_path(destination, filename).c_str(), "wb");
+
+ if (out == NULL)
+ {
+ unzCloseCurrentFile(zipfile);
+ unzClose(zipfile);
+ throw std::runtime_error("fopen: cannot open output file: " +
+ join_path(destination, filename));
+ }
+
+ int error = UNZ_OK;
+
+ do
+ {
+ error = unzReadCurrentFile(zipfile, read_buffer, UNZIP_BUFFER_SIZE);
+
+ if (error < 0)
+ {
+ fclose(out);
+ unzCloseCurrentFile(zipfile);
+ unzClose(zipfile);
+ throw std::runtime_error("unzReadCurrentFile: error while reading file: " +
+ std::string(filename));
+ }
+
+ // Write data to file.
+ if (error > 0)
+ {
+ if (fwrite(read_buffer, error, 1, out) != 1)
+ {
+ fclose(out);
+ unzCloseCurrentFile(zipfile);
+ unzClose(zipfile);
+ throw std::runtime_error("fwrite: error while writing to file: " +
+ join_path(destination, filename));
+ }
+ }
+ }
+ while (error > 0);
+
+ fclose(out);
+ }
+
+ unzCloseCurrentFile(zipfile);
+
+ // Go the the next entry listed in the zip file.
+ if ((i + 1) < global_info.number_entry)
+ {
+ if (unzGoToNextFile(zipfile) != UNZ_OK)
+ {
+ unzClose(zipfile);
+ throw std::runtime_error("unzGoToNextFile: error while reading next file");
+ }
+ }
+ }
+
+ unzClose(zipfile);
+}
+
+#endif
+
View
143 src/main.cpp
@@ -0,0 +1,143 @@
+#include <sstream>
+#include <string>
+#include <pugixml.hpp>
+#include "windows_helpers.h"
+#include "zip_helpers.h"
+
+using namespace std;
+using namespace pugi;
+
+void show_help(const string &name)
+{
+ stringstream s;
+
+ s << "Usage: unfurl manifest [options]..." << endl << endl
+ << "Deploy and launch the latest version of the application " << endl
+ << "described in the supplied manifest file." << endl
+ << "Any additional options will be forwarded to the " << endl
+ << "target application." << endl << endl
+ << "Example manifest file:" << endl << endl
+ << "---" << endl << endl
+ << " <?xml version=\"1.0\" ?>" << endl
+ << " <unfurl>" << endl
+ << " <binary>myapp.exe</binary>" << endl
+ << " <repository>P:\\deploy</repository>" << endl
+ << " <identifier>myapp</identifier>" << endl
+ << " <current-version>1.0</current-version>" << endl
+ << " </unfurl>" << endl << endl
+ << "---" << endl << endl
+ << "http://github.com/logandk/unfurl" << endl;
+
+ show_info("unfurl: usage", s.str());
+}
+
+int main(int argc, char *argv[])
+{
+ try
+ {
+ if (argc < 2 || string(argv[1]) == "-h" || string(argv[1]) == "--help")
+ {
+ show_help(argv[0]);
+
+ return 1;
+ }
+
+ string manifest_path = argv[1];
+
+ // Case 1: Path is absolute
+ if (!file_exists(manifest_path))
+ {
+ if (file_exists(join_path(current_path(), manifest_path)))
+ {
+ // Case 2: Path is relative to working directory with extension
+ manifest_path = join_path(current_path(), manifest_path);
+ }
+ else if (file_exists(join_path(current_path(), manifest_path + ".xml")))
+ {
+ // Case 3: Path is relative to working directory without extension
+ manifest_path = join_path(current_path(), manifest_path + ".xml");
+ }
+ else if (file_exists(join_path(application_path(), manifest_path)))
+ {
+ // Case 4: Path is relative to unfurl directory with extension
+ manifest_path = join_path(application_path(), manifest_path);
+ }
+ else if (file_exists(join_path(application_path(), manifest_path + ".xml")))
+ {
+ // Case 5: Path is relative to unfurl directory without extension
+ manifest_path = join_path(application_path(), manifest_path + ".xml");
+ }
+ }
+
+ // Check if a manifest file was found
+ if (!file_exists(manifest_path))
+ {
+ throw runtime_error("manifest file not found");
+ }
+
+ // Parse manifest file
+ xml_document doc;
+ xml_parse_result result = doc.load_file(manifest_path.c_str());
+
+ if (!result)
+ {
+ throw runtime_error(result.description());
+ }
+
+ xml_node root = doc.child("unfurl");
+
+ string binary = root.child_value("binary");
+ string repository = root.child_value("repository");
+ string identifier = root.child_value("identifier");
+ string current_version = root.child_value("current-version");
+
+ // Check if installation is required
+ bool install_required = true;
+
+ string data_path = join_path(appdata_path(), "unfurl");
+ string app_path = join_path(data_path, identifier);
+ string version_path = join_path(app_path, current_version);
+ string binary_path = join_path(version_path, binary);
+
+ if (file_exists(binary_path))
+ {
+ install_required = false;
+ }
+
+ // Unzip package to destination
+ if (install_required)
+ {
+ string package_name = identifier + "-" + current_version + ".zip";
+ string package_path = join_path(repository, package_name);
+
+ create_path(version_path);
+
+ try
+ {
+ unzip(package_path, version_path);
+ }
+ catch (...)
+ {
+ delete_path(version_path);
+ throw;
+ }
+ }
+
+ // Launch application
+ string command = binary_path;
+
+ for (int i = 2; i < argc; i++)
+ {
+ command += " ";
+ command += argv[i];
+ }
+
+ return create_process(command);
+ }
+ catch (exception &e)
+ {
+ show_error("unfurl: error", e.what());
+ }
+
+ return 1;
+}
View
5 vendor/.gitignore
@@ -0,0 +1,5 @@
+*
+!pugixml
+!zlib
+!.gitignore
+!CMakeLists.txt
View
20 vendor/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 2.6)
+project(vendor)
+include(ExternalProject)
+
+ExternalProject_Add(
+ pugixml
+ URL http://pugixml.googlecode.com/files/pugixml-1.2.zip
+ PREFIX pugixml
+ PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/pugixml/patch.cmake
+ CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/pugixml
+)
+
+ExternalProject_Add(
+ zlib
+ URL http://zlib.net/zlib-1.2.8.tar.gz
+ PREFIX zlib
+ CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/zlib
+)
+
+
View
3  vendor/pugixml/.gitignore
@@ -0,0 +1,3 @@
+*
+!.gitignore
+!patch.cmake
View
3  vendor/pugixml/patch.cmake
@@ -0,0 +1,3 @@
+FILE(READ ${CMAKE_CURRENT_SOURCE_DIR}/scripts/CMakeLists.txt FILE_CONTENT)
+STRING(REPLACE "../" "" MODIFIED_FILE_CONTENT "${FILE_CONTENT}")
+FILE(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt "${MODIFIED_FILE_CONTENT}")
View
2  vendor/zlib/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
Please sign in to comment.
Something went wrong with that request. Please try again.