Enhancement: Fixes #324, ZIP archive shell extension

This commit is contained in:
Rory Fewell
2024-06-30 20:06:04 +01:00
parent 52980c3c66
commit 85f9a55288
25 changed files with 1016 additions and 97 deletions

View File

@@ -30,7 +30,6 @@ wintc_resolve_library(wintc-shellext WINTC_SHELLEXT)
add_library(
libwintc-cpl-printers
src/shext.c
src/shext.h
src/vwprntrs.c
src/vwprntrs.h
)

View File

@@ -3,16 +3,16 @@
#include <wintc/shcommon.h>
#include <wintc/shellext.h>
#include "shext.h"
#include "vwprntrs.h"
//
// FORWARD DECLARATIONS
//
static WinTCIShextView* factory_view_cpl_printers(
WinTCShextViewAssoc assoc,
const gchar* assoc_str,
const gchar* url
WinTCShextHost* shext_host,
WinTCShextViewAssoc assoc,
const gchar* assoc_str,
const WinTCShextPathInfo* path_info
);
//
@@ -38,9 +38,10 @@ gboolean shext_init(
// CALLBACKS
//
static WinTCIShextView* factory_view_cpl_printers(
WINTC_UNUSED(WinTCShextViewAssoc assoc),
WINTC_UNUSED(const gchar* assoc_str),
WINTC_UNUSED(const gchar* url)
WINTC_UNUSED(WinTCShextHost* shext_host),
WINTC_UNUSED(WinTCShextViewAssoc assoc),
WINTC_UNUSED(const gchar* assoc_str),
WINTC_UNUSED(const WinTCShextPathInfo* path_info)
)
{
WINTC_LOG_DEBUG("%s", "cpl-prntrs: create new cpl printers view");

View File

@@ -1,14 +0,0 @@
#ifndef __SHEXT_H__
#define __SHEXT_H__
#include <glib.h>
#include <wintc/shellext.h>
//
// PUBLIC FUNCTIONS
//
gboolean shext_init(
WinTCShextHost* shext_host
);
#endif

View File

@@ -204,7 +204,10 @@ static void on_explorer_wnd_location_changed(
&path_info
);
if (path_info.extended_path)
if (
path_info.extended_path &&
strlen(path_info.extended_path) > 0
)
{
gtk_entry_set_text(
GTK_ENTRY(entry),

5
shell/shext/README.MD Normal file
View File

@@ -0,0 +1,5 @@
# Shell Extensions
This directory contains source code for the built-in shell extensions.
## Structure
The structure under each directory is fairly typical for that of a CMake project library under `/shared`, only with some modifications for deploying a shell extension (aka, putting the library in the right place, so it gets loaded at runtime).

View File

@@ -0,0 +1,78 @@
cmake_minimum_required(VERSION 3.5)
project(
libwintc-shext-zip
VERSION 1.0
DESCRIPTION "Windows Total Conversion ZIP file shell extension."
LANGUAGES C
)
set(PROJECT_ANYARCH false)
set(PROJECT_FREESTATUS true)
set(PROJECT_MAINTAINER "Rory Fewell <roryf@oddmatics.uk>")
set(PROJECT_ROOT ${CMAKE_CURRENT_LIST_DIR})
include(GNUInstallDirs)
include(../../../packaging/cmake-inc/common/CMakeLists.txt)
include(../../../packaging/cmake-inc/libraries/CMakeLists.txt)
include(../../../packaging/cmake-inc/linking/CMakeLists.txt)
include(../../../packaging/cmake-inc/packaging/CMakeLists.txt)
wintc_resolve_library(glib-2.0 GLIB)
wintc_resolve_library(gtk+-3.0 GTK3)
wintc_resolve_library(wintc-comgtk WINTC_COMGTK)
wintc_resolve_library(wintc-shellext WINTC_SHELLEXT)
wintc_resolve_library(libzip ZIP)
add_library(
libwintc-shext-zip
src/shext.c
src/vwzip.c
src/vwzip.h
)
target_compile_options(
libwintc-shext-zip
PRIVATE ${WINTC_COMPILE_OPTIONS}
)
target_include_directories(
libwintc-shext-zip
SYSTEM
BEFORE
PRIVATE ${GLIB_INCLUDE_DIRS}
PRIVATE ${GTK3_INCLUDE_DIRS}
PRIVATE ${WINTC_COMGTK_INCLUDE_DIRS}
PRIVATE ${WINTC_SHELLEXT_INCLUDE_DIRS}
PRIVATE ${ZIP_INCLUDE_DIRS}
)
target_link_directories(
libwintc-shext-zip
PRIVATE ${GLIB_LIBRARY_DIRS}
PRIVATE ${GTK3_LIBRARY_DIRS}
PRIVATE ${WINTC_COMGTK_LIBRARY_DIRS}
PRIVATE ${WINTC_SHELLEXT_LIBRARY_DIRS}
PRIVATE ${ZIP_LIBRARY_DIRS}
)
target_link_libraries(
libwintc-shext-zip
PRIVATE ${GLIB_LIBRARIES}
PRIVATE ${GTK3_LIBRARIES}
PRIVATE ${WINTC_COMGTK_LIBRARIES}
PRIVATE ${WINTC_SHELLEXT_LIBRARIES}
PRIVATE ${ZIP_LIBRARIES}
)
#
# Installation
#
wintc_configure_and_install_packaging()
install(
TARGETS libwintc-shext-zip
DESTINATION ${LIB_DIR}/wintc/shext
)

View File

@@ -0,0 +1,2 @@
# libwintc-shext-zip
This directory contains the source code for the ZIP files shell extension, to allow ZIP file management within Explorer.

5
shell/shext/zip/deps Normal file
View File

@@ -0,0 +1,5 @@
bt,rt:glib2
bt,rt:gtk3
bt,rt:wintc-comgtk
bt,rt:wintc-shellext
bt,rt:zip

View File

@@ -0,0 +1,52 @@
#include <glib.h>
#include <wintc/comgtk.h>
#include <wintc/shellext.h>
#include "vwzip.h"
//
// FORWARD DECLARATIONS
//
static WinTCIShextView* factory_view_zip(
WinTCShextHost* shext_host,
WinTCShextViewAssoc assoc,
const gchar* assoc_str,
const WinTCShextPathInfo* path_info
);
//
// PUBLIC FUNCTIONS
//
gboolean shext_init(
WinTCShextHost* shext_host
)
{
WINTC_RETURN_VAL_IF_FAIL(
wintc_shext_host_use_view_for_mime(
shext_host,
"application/zip",
factory_view_zip
),
FALSE
);
return TRUE;
}
//
// CALLBACKS
//
static WinTCIShextView* factory_view_zip(
WINTC_UNUSED(WinTCShextHost* shext_host),
WINTC_UNUSED(WinTCShextViewAssoc assoc),
WINTC_UNUSED(const gchar* assoc_str),
const WinTCShextPathInfo* path_info
)
{
WINTC_LOG_DEBUG("%s", "shext-zip: create new zip view");
return wintc_view_zip_new(
path_info->base_path,
path_info->extended_path
);
}

566
shell/shext/zip/src/vwzip.c Normal file
View File

@@ -0,0 +1,566 @@
#include <glib.h>
#include <wintc/comgtk.h>
#include <wintc/shellext.h>
#include <string.h>
#include <zip.h>
#include "vwzip.h"
//
// PRIVATE ENUMS
//
enum
{
PROP_PATH = 1,
PROP_REL_PATH
};
//
// FORWARD DECLARATIONS
//
static void wintc_view_zip_ishext_view_interface_init(
WinTCIShextViewInterface* iface
);
static void wintc_view_zip_finalize(
GObject* object
);
static void wintc_view_zip_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
);
static gboolean wintc_view_zip_activate_item(
WinTCIShextView* view,
WinTCShextViewItem* item,
WinTCShextPathInfo* path_info,
GError** error
);
static void wintc_view_zip_refresh_items(
WinTCIShextView* view
);
static void wintc_view_zip_get_actions_for_item(
WinTCIShextView* view,
WinTCShextViewItem* item
);
static void wintc_view_zip_get_actions_for_view(
WinTCIShextView* view
);
static const gchar* wintc_view_zip_get_display_name(
WinTCIShextView* view
);
static void wintc_view_zip_get_parent_path(
WinTCIShextView* view,
WinTCShextPathInfo* path_info
);
static void wintc_view_zip_get_path(
WinTCIShextView* view,
WinTCShextPathInfo* path_info
);
static gboolean wintc_view_zip_has_parent(
WinTCIShextView* view
);
static gboolean zip_entry_is_in_dir(
const gchar* this_dir,
const gchar* entry,
gint* name_offset
);
static void clear_view_item(
WinTCShextViewItem* item
);
//
// GLIB OOP/CLASS INSTANCE DEFINITIONS
//
struct _WinTCViewZipClass
{
GObjectClass __parent__;
};
struct _WinTCViewZip
{
GObject __parent__;
// ZIP state
//
GArray* items;
gchar* parent_path;
gchar* rel_path;
gchar* zip_uri;
};
//
// GLIB TYPE DEFINITIONS & CTORS
//
G_DEFINE_TYPE_WITH_CODE(
WinTCViewZip,
wintc_view_zip,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(
WINTC_TYPE_ISHEXT_VIEW,
wintc_view_zip_ishext_view_interface_init
)
)
static void wintc_view_zip_class_init(
WinTCViewZipClass* klass
)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
object_class->finalize = wintc_view_zip_finalize;
object_class->set_property = wintc_view_zip_set_property;
g_object_class_install_property(
object_class,
PROP_PATH,
g_param_spec_string(
"path",
"Path",
"The path of the ZIP file to open.",
NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY
)
);
g_object_class_install_property(
object_class,
PROP_REL_PATH,
g_param_spec_string(
"relative-path",
"RelativePath",
"The relative path within the ZIP file to view.",
NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY
)
);
}
static void wintc_view_zip_init(
WinTCViewZip* self
)
{
self->items = g_array_new(FALSE, TRUE, sizeof (WinTCShextViewItem));
g_array_set_clear_func(
self->items,
(GDestroyNotify) clear_view_item
);
}
static void wintc_view_zip_ishext_view_interface_init(
WinTCIShextViewInterface* iface
)
{
iface->activate_item = wintc_view_zip_activate_item;
iface->refresh_items = wintc_view_zip_refresh_items;
iface->get_actions_for_item = wintc_view_zip_get_actions_for_item;
iface->get_actions_for_view = wintc_view_zip_get_actions_for_view;
iface->get_display_name = wintc_view_zip_get_display_name;
iface->get_parent_path = wintc_view_zip_get_parent_path;
iface->get_path = wintc_view_zip_get_path;
iface->has_parent = wintc_view_zip_has_parent;
}
//
// CLASS VIRTUAL METHODS
//
static void wintc_view_zip_finalize(
GObject* object
)
{
WinTCViewZip* view_zip = WINTC_VIEW_ZIP(object);
g_array_free(view_zip->items, TRUE);
g_free(view_zip->parent_path);
g_free(view_zip->rel_path);
g_free(view_zip->zip_uri);
(G_OBJECT_CLASS(wintc_view_zip_parent_class))->finalize(object);
}
static void wintc_view_zip_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
)
{
WinTCViewZip* view_zip = WINTC_VIEW_ZIP(object);
switch (prop_id)
{
case PROP_PATH:
view_zip->zip_uri = g_value_dup_string(value);
view_zip->parent_path = g_path_get_dirname(view_zip->zip_uri);
break;
case PROP_REL_PATH:
view_zip->rel_path = g_value_dup_string(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
//
// INTERFACE METHODS
//
static gboolean wintc_view_zip_activate_item(
WinTCIShextView* view,
WinTCShextViewItem* item,
WinTCShextPathInfo* path_info,
GError** error
)
{
WinTCViewZip* view_zip = WINTC_VIEW_ZIP(view);
WINTC_SAFE_REF_CLEAR(error);
gboolean is_dir = g_str_has_suffix(item->priv, G_DIR_SEPARATOR_S);
if (is_dir)
{
path_info->base_path = g_strdup(view_zip->zip_uri);
path_info->extended_path = g_strdup(item->priv);
return TRUE;
}
// TODO: We'll need to extract files first to tmp to be able to open
// them - deal with in a separate issue
//
g_set_error(
error,
WINTC_GENERAL_ERROR,
WINTC_GENERAL_ERROR_NOTIMPL,
"ZIP file extraction not implemented yet."
);
return FALSE;
}
static void wintc_view_zip_refresh_items(
WinTCIShextView* view
)
{
WinTCViewZip* view_zip = WINTC_VIEW_ZIP(view);
WINTC_LOG_DEBUG("%s", "shext-zip: refresh zip view");
// Open the archive
//
const gchar* path = view_zip->zip_uri + strlen("file://");
gint zip_error = 0;
zip_t* zip_file = zip_open(path, 0, &zip_error);
if (!zip_file)
{
zip_error_t zip_error_st;
zip_error_init_with_code(&zip_error_st, zip_error);
// FIXME: Need a proper way of returning error to caller!
g_critical(
"shext-zip: can't open %s , %s",
path,
zip_error_strerror(&zip_error_st)
);
zip_error_fini(&zip_error_st);
return;
}
// Read through the entries
// FIXME: Probably want to cap max num of entries? Check zip bombs to see
// what happens - don't want to get nuked by a crazy zip archive
//
gint64 n_entries = zip_get_num_entries(zip_file, 0);
gint64 n_local_entries = 0;
g_array_remove_range(
view_zip->items,
0,
view_zip->items->len
);
g_array_set_size(
view_zip->items,
n_entries
);
for (gint64 i = 0; i < n_entries; i++)
{
const gchar* entry_name = zip_get_name(zip_file, (guint64) i, 0);
// Only want to add the entries in the current relative dir
//
gint name_offset = 0;
if (!zip_entry_is_in_dir(view_zip->rel_path, entry_name, &name_offset))
{
continue;
}
gchar* entry_copy = g_strdup(entry_name);
WinTCShextViewItem* item = &g_array_index(
view_zip->items,
WinTCShextViewItem,
n_local_entries
);
item->display_name = entry_copy + name_offset;
item->icon_name = g_str_has_suffix(entry_name, G_DIR_SEPARATOR_S) ?
"inode-directory" : "empty";
item->priv = entry_copy;
n_local_entries++;
}
zip_close(zip_file);
// Emit the entries
//
WinTCShextViewItemsAddedData update = { 0 };
g_array_set_size(
view_zip->items,
n_local_entries
);
update.items = &g_array_index(
view_zip->items,
WinTCShextViewItem,
0
);
update.num_items = n_local_entries;
update.done = TRUE;
_wintc_ishext_view_items_added(view, &update);
}
static void wintc_view_zip_get_actions_for_item(
WINTC_UNUSED(WinTCIShextView* view),
WINTC_UNUSED(WinTCShextViewItem* item)
)
{
g_critical("%s Not Implemented", __func__);
}
static void wintc_view_zip_get_actions_for_view(
WINTC_UNUSED(WinTCIShextView* view)
)
{
g_critical("%s Not Implemented", __func__);
}
static const gchar* wintc_view_zip_get_display_name(
WinTCIShextView* view
)
{
WinTCViewZip* view_zip = WINTC_VIEW_ZIP(view);
// FIXME: Like FS view in shell, this could be broken maybe if there's
// an escaped dir separator in the filename?
//
if (view_zip->rel_path)
{
// This deals with the fact that the ZIP relative paths are essentially
// a partial path, dirs are like this:
// somedir/
// somedir/another/
//
// This screws with the usual path APIs which assume absolute paths, or
// at the very least they don't like trailing slashes - here we get the
// last component by searching for the second-to-last slash, and return
// everything after it
//
// Of course if the path is only a single component like the first
// example, then next == end will be true on the very first iteration,
// in which case we can just return the path as-is
//
const gchar* end = strrchr(view_zip->rel_path, G_DIR_SEPARATOR);
const gchar* last = view_zip->rel_path;
const gchar* next = strchr(view_zip->rel_path, G_DIR_SEPARATOR);
while (next != end)
{
next++;
last = next;
next = strchr(next, G_DIR_SEPARATOR);
}
return last;
}
else
{
return strrchr(view_zip->zip_uri, G_DIR_SEPARATOR) + 1;
}
}
static void wintc_view_zip_get_parent_path(
WinTCIShextView* view,
WinTCShextPathInfo* path_info
)
{
WinTCViewZip* view_zip = WINTC_VIEW_ZIP(view);
// If we have a relative path, then use the relative path parent... unless
// the relative path parent is the root of the zip, in which case just
// return the zip's path only
//
if (view_zip->rel_path)
{
// A bit like above, we can't just use g_path_get_dirname because it
// doesn't like the trailing slash - so here we will deal with
// retrieving the position of the second-to-last slash, and return
// everything before it
//
// Have a look at the example paths in the above comment and this
// logic should make sense to you
//
const gchar* end = strrchr(view_zip->rel_path, G_DIR_SEPARATOR);
const gchar* last = view_zip->rel_path;
const gchar* next = strchr(view_zip->rel_path, G_DIR_SEPARATOR);
while (next != end)
{
next++;
last = next;
next = strchr(next, G_DIR_SEPARATOR);
}
path_info->base_path = g_strdup(view_zip->zip_uri);
if (last != view_zip->rel_path)
{
gint len = last - view_zip->rel_path;
gchar* rel_parent = g_malloc0((sizeof(gchar) * len) + 1);
strncpy(rel_parent, view_zip->rel_path, len);
path_info->extended_path = rel_parent;
}
return;
}
path_info->base_path = g_strdup(view_zip->parent_path);
}
static void wintc_view_zip_get_path(
WinTCIShextView* view,
WinTCShextPathInfo* path_info
)
{
WinTCViewZip* view_zip = WINTC_VIEW_ZIP(view);
path_info->base_path = g_strdup(view_zip->zip_uri);
path_info->extended_path = g_strdup(view_zip->rel_path);
}
static gboolean wintc_view_zip_has_parent(
WINTC_UNUSED(WinTCIShextView* view)
)
{
return TRUE;
}
//
// PUBLIC FUNCTIONS
//
WinTCIShextView* wintc_view_zip_new(
const gchar* path,
const gchar* rel_path
)
{
return WINTC_ISHEXT_VIEW(
g_object_new(
WINTC_TYPE_VIEW_ZIP,
"path", path,
"relative-path", rel_path,
NULL
)
);
}
//
// PRIVATE FUNCTIONS
//
static gboolean zip_entry_is_in_dir(
const gchar* this_dir,
const gchar* entry,
gint* name_offset
)
{
const gchar* next_ds;
*name_offset = 0;
// If we're at the root of the zip, then we just scrub off any file that is
// in a subdir, nothing else needed
//
if (!this_dir)
{
next_ds = strchr(entry, G_DIR_SEPARATOR);
return !next_ds || next_ds == (entry + strlen(entry) - 1);
}
// Exclude the dir itself
//
if (g_strcmp0(entry, this_dir) == 0)
{
return FALSE;
}
// We're in a subdir, so any file not in this subdir is obviously binned
//
if (!g_str_has_prefix(entry, this_dir))
{
return FALSE;
}
// Finally, check that there are no more directory components after our
// path
//
const gchar* entry_in_dir = entry + strlen(this_dir);
next_ds = strchr(entry_in_dir, G_DIR_SEPARATOR);
if (!next_ds || next_ds == (entry + strlen(entry) - 1))
{
*name_offset = entry_in_dir - entry;
return TRUE;
}
return FALSE;
}
//
// CALLBACKS
//
static void clear_view_item(
WinTCShextViewItem* item
)
{
// Don't need to bin display_name as it's an offset into priv which
// contains the full path in the zip
//
g_clear_pointer(&(item->priv), g_free);
}

View File

@@ -0,0 +1,30 @@
#ifndef __VWZIP_H__
#define __VWZIP_H__
#include <glib.h>
#include <wintc/shellext.h>
//
// GTK OOP BOILERPLATE
//
typedef struct _WinTCViewZipClass WinTCViewZipClass;
typedef struct _WinTCViewZip WinTCViewZip;
#define WINTC_TYPE_VIEW_ZIP (wintc_view_zip_get_type())
#define WINTC_VIEW_ZIP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WINTC_TYPE_VIEW_ZIP, WinTCViewZip))
#define WINTC_VIEW_ZIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WINTC_TYPE_VIEW_ZIP, WinTCViewZipClass))
#define IS_WINTC_VIEW_ZIP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WINTC_TYPE_VIEW_ZIP))
#define IS_WINTC_VIEW_ZIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), WINTC_TYPE_VIEW_ZIP))
#define WINTC_VIEW_ZIP_GET_CLASS(obj) (G_TYPE_INSANCE_GET_CLASS((obj), WINTC_TYPE_VIEW_ZIP, WinTCViewZip))
GType wintc_view_zip_get_type(void) G_GNUC_CONST;
//
// PUBLIC FUNCTIONS
//
WinTCIShextView* wintc_view_zip_new(
const gchar* path,
const gchar* rel_path
);
#endif