mirror of
https://github.com/rozniak/xfce-winxp-tc.git
synced 2026-05-01 03:31:33 +00:00
605 lines
14 KiB
C
605 lines
14 KiB
C
#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 guint wintc_view_zip_get_unique_hash(
|
|
WinTCIShextView* view
|
|
);
|
|
|
|
static gboolean wintc_view_zip_has_parent(
|
|
WinTCIShextView* view
|
|
);
|
|
|
|
static guint zip_entry_hash(
|
|
const gchar* zip_file_path,
|
|
const gchar* rel_entry_path
|
|
);
|
|
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->get_unique_hash = wintc_view_zip_get_unique_hash;
|
|
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->is_leaf = !g_str_has_suffix(entry_name, G_DIR_SEPARATOR_S);
|
|
item->icon_name = item->is_leaf ? "empty" : "inode-directory";
|
|
item->hash = zip_entry_hash(path, entry_name);
|
|
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 guint wintc_view_zip_get_unique_hash(
|
|
WinTCIShextView* view
|
|
)
|
|
{
|
|
WinTCViewZip* view_zip = WINTC_VIEW_ZIP(view);
|
|
|
|
return
|
|
zip_entry_hash(
|
|
(view_zip->zip_uri + strlen("file://")),
|
|
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 guint zip_entry_hash(
|
|
const gchar* zip_file_path,
|
|
const gchar* rel_entry_path
|
|
)
|
|
{
|
|
guint hash = g_str_hash(zip_file_path);
|
|
|
|
if (rel_entry_path)
|
|
{
|
|
hash = hash * 33 + g_str_hash(rel_entry_path);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
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);
|
|
}
|