/* * Copyright 2008 Evenflow, Inc. * * caja-dropbox.c * Implements the Caja extension API for Dropbox. * * This file is part of caja-dropbox. * * caja-dropbox is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * caja-dropbox is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with caja-dropbox. If not, see . * */ #ifdef HAVE_CONFIG_H #include /* for GETTEXT_PACKAGE */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "g-util.h" #include "dropbox-command-client.h" #include "caja-dropbox.h" #include "caja-dropbox-hooks.h" static char *emblems[] = {"dropbox-uptodate", "dropbox-syncing", "dropbox-unsyncable"}; gchar *DEFAULT_EMBLEM_PATHS[2] = { EMBLEMDIR , NULL }; gboolean dropbox_use_operation_in_progress_workaround; static GType dropbox_type = 0; /* probably my favorite function */ static gchar * canonicalize_path(gchar *path) { int i, j = 0; gchar *toret, **cpy, **elts; g_assert(path != NULL); g_assert(path[0] == '/'); elts = g_strsplit(path, "/", 0); cpy = g_new(gchar *, g_strv_length(elts)+1); cpy[j++] = "/"; for (i = 0; elts[i] != NULL; i++) { if (strcmp(elts[i], "..") == 0) { j--; } else if (strcmp(elts[i], ".") != 0 && elts[i][0] != '\0') { cpy[j++] = elts[i]; } } cpy[j] = NULL; toret = g_build_filenamev(cpy); g_free(cpy); g_strfreev(elts); return toret; } static void reset_file(CajaFileInfo *file) { debug("resetting file %p", (void *) file); caja_file_info_invalidate_extension_info(file); } gboolean reset_all_files(CajaDropbox *cvs) { /* Only run this on the main loop or you'll cause problems. */ /* this works because you can call a function pointer with more arguments than it takes */ g_hash_table_foreach(cvs->obj2filename, (GHFunc) reset_file, NULL); return FALSE; } static void when_file_dies(CajaDropbox *cvs, CajaFileInfo *address) { gchar *filename; filename = g_hash_table_lookup(cvs->obj2filename, address); /* we never got a change to view this file */ if (filename == NULL) { return; } /* too chatty */ /* debug("removing %s <-> 0x%p", filename, address); */ g_hash_table_remove(cvs->filename2obj, filename); g_hash_table_remove(cvs->obj2filename, address); } static void changed_cb(CajaFileInfo *file, CajaDropbox *cvs) { /* check if this file's path has changed, if so update the hash and invalidate the file */ gchar *filename, *pfilename; gchar *filename2; gchar *uri; uri = caja_file_info_get_uri(file); pfilename = g_filename_from_uri(uri, NULL, NULL); filename = pfilename ? canonicalize_path(pfilename) : NULL; filename2 = g_hash_table_lookup(cvs->obj2filename, file); g_free(pfilename); g_free(uri); /* if filename2 is NULL we've never seen this file in update_file_info */ if (filename2 == NULL) { g_free(filename); return; } if (filename == NULL) { /* A file has moved to offline storage. Lets remove it from our tables. */ g_object_weak_unref(G_OBJECT(file), (GWeakNotify) when_file_dies, cvs); g_hash_table_remove(cvs->filename2obj, filename2); g_hash_table_remove(cvs->obj2filename, file); g_signal_handlers_disconnect_by_func(file, G_CALLBACK(changed_cb), cvs); reset_file(file); return; } /* this is a hack, because caja doesn't do this for us, for some reason the file's path has changed */ if (strcmp(filename, filename2) != 0) { debug("shifty old: %s, new %s", filename2, filename); /* gotta do this first, the call after this frees filename2 */ g_hash_table_remove(cvs->filename2obj, filename2); g_hash_table_replace(cvs->obj2filename, file, g_strdup(filename)); { CajaFileInfo *f2; /* we shouldn't have another mapping from filename to an object */ f2 = g_hash_table_lookup(cvs->filename2obj, filename); if (f2 != NULL) { /* lets fix it if it's true, just remove the mapping */ g_hash_table_remove(cvs->filename2obj, filename); g_hash_table_remove(cvs->obj2filename, f2); } } g_hash_table_insert(cvs->filename2obj, g_strdup(filename), file); reset_file(file); } g_free(filename); } static CajaOperationResult caja_dropbox_update_file_info(CajaInfoProvider *provider, CajaFileInfo *file, GClosure *update_complete, CajaOperationHandle **handle) { CajaDropbox *cvs; cvs = CAJA_DROPBOX(provider); /* this code adds this file object to our two-way hash of file objects so we can shell touch these files later */ { gchar *pfilename, *uri; uri = caja_file_info_get_uri(file); pfilename = g_filename_from_uri(uri, NULL, NULL); g_free(uri); if (pfilename == NULL) { return CAJA_OPERATION_COMPLETE; } else { int cmp = 0; gchar *stored_filename; gchar *filename; filename = canonicalize_path(pfilename); g_free(pfilename); stored_filename = g_hash_table_lookup(cvs->obj2filename, file); /* don't worry about the dup checks, gcc is smart enough to optimize this GCSE ftw */ if ((stored_filename != NULL && (cmp = strcmp(stored_filename, filename)) != 0) || stored_filename == NULL) { if (stored_filename != NULL && cmp != 0) { /* this happens when the filename changes name on a file obj but changed_cb isn't called */ g_object_weak_unref(G_OBJECT(file), (GWeakNotify) when_file_dies, cvs); g_hash_table_remove(cvs->obj2filename, file); g_hash_table_remove(cvs->filename2obj, stored_filename); g_signal_handlers_disconnect_by_func(file, G_CALLBACK(changed_cb), cvs); } else if (stored_filename == NULL) { CajaFileInfo *f2; if ((f2 = g_hash_table_lookup(cvs->filename2obj, filename)) != NULL) { /* if the filename exists in the filename2obj hash but the file obj doesn't exist in the obj2filename hash: this happens when caja allocates another file object for a filename without first deleting the original file object just remove the association to the older file object, it's obsolete */ g_object_weak_unref(G_OBJECT(f2), (GWeakNotify) when_file_dies, cvs); g_signal_handlers_disconnect_by_func(f2, G_CALLBACK(changed_cb), cvs); g_hash_table_remove(cvs->filename2obj, filename); g_hash_table_remove(cvs->obj2filename, f2); } } /* too chatty */ /* debug("adding %s <-> 0x%p", filename, file);*/ g_object_weak_ref(G_OBJECT(file), (GWeakNotify) when_file_dies, cvs); g_hash_table_insert(cvs->filename2obj, g_strdup(filename), file); g_hash_table_insert(cvs->obj2filename, file, g_strdup(filename)); g_signal_connect(file, "changed", G_CALLBACK(changed_cb), cvs); } g_free(filename); } } if (dropbox_client_is_connected(&(cvs->dc)) == FALSE || caja_file_info_is_gone(file)) { return CAJA_OPERATION_COMPLETE; } { DropboxFileInfoCommand *dfic = g_new0(DropboxFileInfoCommand, 1); dfic->cancelled = FALSE; dfic->provider = provider; dfic->dc.request_type = GET_FILE_INFO; dfic->update_complete = g_closure_ref(update_complete); dfic->file = g_object_ref(file); dropbox_command_client_request(&(cvs->dc.dcc), (DropboxCommand *) dfic); *handle = (CajaOperationHandle *) dfic; return dropbox_use_operation_in_progress_workaround ? CAJA_OPERATION_COMPLETE : CAJA_OPERATION_IN_PROGRESS; } } static void handle_shell_touch(GHashTable *args, CajaDropbox *cvs) { gchar **path; // debug_enter(); if ((path = g_hash_table_lookup(args, "path")) != NULL && path[0][0] == '/') { CajaFileInfo *file; gchar *filename; filename = canonicalize_path(path[0]); debug("shell touch for %s", filename); file = g_hash_table_lookup(cvs->filename2obj, filename); if (file != NULL) { debug("gonna reset %s", filename); reset_file(file); } g_free(filename); } return; } gboolean caja_dropbox_finish_file_info_command(DropboxFileInfoCommandResponse *dficr) { //debug_enter(); CajaOperationResult result = CAJA_OPERATION_FAILED; if (!dficr->dfic->cancelled) { gchar **status = NULL; gboolean isdir; isdir = caja_file_info_is_directory(dficr->dfic->file) ; /* if we have emblems just use them. */ if (dficr->emblems_response != NULL && (status = g_hash_table_lookup(dficr->emblems_response, "emblems")) != NULL) { int i; for ( i = 0; status[i] != NULL; i++) { if (status[i][0]) caja_file_info_add_emblem(dficr->dfic->file, status[i]); } result = CAJA_OPERATION_COMPLETE; } /* if the file status command went okay */ else if ((dficr->file_status_response != NULL && (status = g_hash_table_lookup(dficr->file_status_response, "status")) != NULL) && ((isdir == TRUE && dficr->folder_tag_response != NULL) || isdir == FALSE)) { gchar **tag = NULL; /* set the tag emblem */ if (isdir && (tag = g_hash_table_lookup(dficr->folder_tag_response, "tag")) != NULL) { if (strcmp("public", tag[0]) == 0) { caja_file_info_add_emblem(dficr->dfic->file, "web"); } else if (strcmp("shared", tag[0]) == 0) { caja_file_info_add_emblem(dficr->dfic->file, "people"); } else if (strcmp("photos", tag[0]) == 0) { caja_file_info_add_emblem(dficr->dfic->file, "photos"); } else if (strcmp("sandbox", tag[0]) == 0) { caja_file_info_add_emblem(dficr->dfic->file, "star"); } } /* set the status emblem */ { int emblem_code = 0; if (strcmp("up to date", status[0]) == 0) { emblem_code = 1; } else if (strcmp("syncing", status[0]) == 0) { emblem_code = 2; } else if (strcmp("unsyncable", status[0]) == 0) { emblem_code = 3; } if (emblem_code > 0) { /* debug("%s to %s", emblems[emblem_code-1], g_filename_from_uri(caja_file_info_get_uri(dficr->dfic->file), NULL, NULL)); */ caja_file_info_add_emblem(dficr->dfic->file, emblems[emblem_code-1]); } } result = CAJA_OPERATION_COMPLETE; } } /* complete the info request */ if (!dropbox_use_operation_in_progress_workaround) { caja_info_provider_update_complete_invoke(dficr->dfic->update_complete, dficr->dfic->provider, (CajaOperationHandle*) dficr->dfic, result); } /* destroy the objects we created */ if (dficr->file_status_response != NULL) g_hash_table_unref(dficr->file_status_response); if (dficr->folder_tag_response != NULL) g_hash_table_unref(dficr->folder_tag_response); if (dficr->emblems_response != NULL) g_hash_table_unref(dficr->emblems_response); /* unref the objects we didn't create */ g_closure_unref(dficr->dfic->update_complete); g_object_unref(dficr->dfic->file); /* now free the structs */ g_free(dficr->dfic); g_free(dficr); return FALSE; } static void caja_dropbox_cancel_update(CajaInfoProvider *provider, CajaOperationHandle *handle) { DropboxFileInfoCommand *dfic = (DropboxFileInfoCommand *) handle; dfic->cancelled = TRUE; return; } static void menu_item_cb(CajaMenuItem *item, CajaDropbox *cvs) { gchar *verb; GList *files; DropboxGeneralCommand *dcac; dcac = g_new(DropboxGeneralCommand, 1); /* maybe these would be better passed in a container struct used as the userdata pointer, oh well this is how dave camp does it */ files = g_object_get_data(G_OBJECT(item), "caja_dropbox_files"); verb = g_object_get_data(G_OBJECT(item), "caja_dropbox_verb"); dcac->dc.request_type = GENERAL_COMMAND; /* build the argument list */ dcac->command_args = g_hash_table_new_full((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_strfreev); { gchar **arglist; guint i; GList *li; arglist = g_new0(gchar *,g_list_length(files) + 1); for (li = files, i = 0; li != NULL; li = g_list_next(li)) { char *uri = caja_file_info_get_uri(CAJA_FILE_INFO(li->data)); char *path = g_filename_from_uri(uri, NULL, NULL); g_free(uri); if (!path) continue; arglist[i] = path; i++; } g_hash_table_insert(dcac->command_args, g_strdup("paths"), arglist); } { gchar **arglist; arglist = g_new(gchar *, 2); arglist[0] = g_strdup(verb); arglist[1] = NULL; g_hash_table_insert(dcac->command_args, g_strdup("verb"), arglist); } dcac->command_name = g_strdup("icon_overlay_context_action"); dcac->handler = NULL; dcac->handler_ud = NULL; dropbox_command_client_request(&(cvs->dc.dcc), (DropboxCommand *) dcac); } static char from_hex(gchar ch) { return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; } // decode in --> out, but dont fill more than n chars into out // returns len of out if thing went well, -1 if n wasn't big enough // can be used in place (whoa!) int GhettoURLDecode(gchar* out, gchar* in, int n) { char *out_initial; for(out_initial = out; out-out_initial < n && *in != '\0'; out++) { if (*in == '%') { *out = from_hex(in[1]) << 4 | from_hex(in[2]); in += 3; } else { *out = *in; in++; } } if (out-out_initial < n) { *out = '\0'; return out-out_initial; } return -1; } static int caja_dropbox_parse_menu(gchar **options, CajaMenu *menu, GString *old_action_string, GList *toret, CajaMenuProvider *provider, GList *files) { int ret = 0; int i; for ( i = 0; options[i] != NULL; i++) { gchar **option_info = g_strsplit(options[i], "~", 3); /* if this is a valid string */ if (option_info[0] == NULL || option_info[1] == NULL || option_info[2] == NULL || option_info[3] != NULL) { g_strfreev(option_info); continue; } gchar* item_name = option_info[0]; gchar* item_inner = option_info[1]; gchar* verb = option_info[2]; GhettoURLDecode(item_name, item_name, strlen(item_name)); GhettoURLDecode(verb, verb, strlen(verb)); GhettoURLDecode(item_inner, item_inner, strlen(item_inner)); // If the inner section has a menu in it then we create a submenu. The verb will be ignored. // Otherwise add the verb to our map and add the menu item to the list. if (strchr(item_inner, '~') != NULL) { GString *new_action_string = g_string_new(old_action_string->str); gchar **suboptions = g_strsplit(item_inner, "|", -1); CajaMenuItem *item; CajaMenu *submenu = caja_menu_new(); g_string_append(new_action_string, item_name); g_string_append(new_action_string, "::"); ret += caja_dropbox_parse_menu(suboptions, submenu, new_action_string, toret, provider, files); item = caja_menu_item_new(new_action_string->str, item_name, "", NULL); caja_menu_item_set_submenu(item, submenu); caja_menu_append_item(menu, item); g_strfreev(suboptions); g_object_unref(item); g_object_unref(submenu); g_string_free(new_action_string, TRUE); } else { CajaMenuItem *item; GString *new_action_string = g_string_new(old_action_string->str); gboolean grayed_out = FALSE; g_string_append(new_action_string, verb); if (item_name[0] == '!') { item_name++; grayed_out = TRUE; } item = caja_menu_item_new(new_action_string->str, item_name, item_inner, NULL); caja_menu_append_item(menu, item); /* add the file metadata to this item */ g_object_set_data_full (G_OBJECT(item), "caja_dropbox_files", caja_file_info_list_copy (files), (GDestroyNotify) caja_file_info_list_free); /* add the verb metadata */ g_object_set_data_full (G_OBJECT(item), "caja_dropbox_verb", g_strdup(verb), (GDestroyNotify) g_free); g_signal_connect (item, "activate", G_CALLBACK (menu_item_cb), provider); if (grayed_out) { GValue sensitive = { 0 }; g_value_init (&sensitive, G_TYPE_BOOLEAN); g_value_set_boolean (&sensitive, FALSE); g_object_set_property (G_OBJECT(item), "sensitive", &sensitive); } g_object_unref(item); g_string_free(new_action_string, TRUE); ret++; } g_strfreev(option_info); } return ret; } static void get_file_items_callback(GHashTable *response, gpointer ud) { GAsyncQueue *reply_queue = ud; /* queue_push doesn't accept NULL as a value so we create an empty hash table * if we got no response. */ g_async_queue_push(reply_queue, response ? g_hash_table_ref(response) : g_hash_table_new((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal)); g_async_queue_unref(reply_queue); } static GList * caja_dropbox_get_file_items(CajaMenuProvider *provider, GtkWidget *window, GList *files) { /* * 1. Convert files to filenames. */ int file_count = g_list_length(files); if (file_count < 1) return NULL; gchar **paths = g_new0(gchar *, file_count + 1); int i = 0; GList* elem; for (elem = files; elem; elem = elem->next, i++) { gchar *uri = caja_file_info_get_uri(elem->data); gchar *filename_un = uri ? g_filename_from_uri(uri, NULL, NULL) : NULL; gchar *filename = filename_un ? g_filename_to_utf8(filename_un, -1, NULL, NULL, NULL) : NULL; g_free(uri); g_free(filename_un); if (filename == NULL) { /* oooh, filename wasn't correctly encoded, or isn't a local file. */ g_strfreev(paths); return NULL; } paths[i] = filename; } GAsyncQueue *reply_queue = g_async_queue_new_full((GDestroyNotify)g_hash_table_unref); /* * 2. Create a DropboxGeneralCommand to call "icon_overlay_context_options" */ DropboxGeneralCommand *dgc = g_new0(DropboxGeneralCommand, 1); dgc->dc.request_type = GENERAL_COMMAND; dgc->command_name = g_strdup("icon_overlay_context_options"); dgc->command_args = g_hash_table_new_full((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_strfreev); g_hash_table_insert(dgc->command_args, g_strdup("paths"), paths); dgc->handler = get_file_items_callback; dgc->handler_ud = g_async_queue_ref(reply_queue); /* * 3. Queue it up for the helper thread to run it. */ CajaDropbox *cvs = CAJA_DROPBOX(provider); dropbox_command_client_request(&(cvs->dc.dcc), (DropboxCommand *) dgc); GTimeVal gtv; /* * 4. We have to block until it's done because caja expects a reply. But we will * only block for 50 ms for a reply. */ g_get_current_time(>v); g_time_val_add(>v, 50000); GHashTable *context_options_response = g_async_queue_timed_pop(reply_queue, >v); g_async_queue_unref(reply_queue); if (!context_options_response) { return NULL; } /* * 5. Parse the reply. */ char **options = g_hash_table_lookup(context_options_response, "options"); GList *toret = NULL; if (options && *options && **options) { /* build the menu */ CajaMenuItem *root_item; CajaMenu *root_menu; root_menu = caja_menu_new(); root_item = caja_menu_item_new("CajaDropbox::root_item", "Dropbox", "Dropbox Options", "dropbox"); toret = g_list_append(toret, root_item); GString *action_string = g_string_new("CajaDropbox::"); if (!caja_dropbox_parse_menu(options, root_menu, action_string, toret, provider, files)) { g_object_unref(toret); toret = NULL; } caja_menu_item_set_submenu(root_item, root_menu); g_string_free(action_string, TRUE); g_object_unref(root_menu); } g_hash_table_unref(context_options_response); return toret; } gboolean add_emblem_paths(GHashTable* emblem_paths_response) { /* Only run this on the main loop or you'll cause problems. */ if (!emblem_paths_response) return FALSE; gchar **emblem_paths_list; int i; GtkIconTheme *theme = gtk_icon_theme_get_default(); if (emblem_paths_response && (emblem_paths_list = g_hash_table_lookup(emblem_paths_response, "path"))) { for (i = 0; emblem_paths_list[i] != NULL; i++) { if (emblem_paths_list[i][0]) gtk_icon_theme_append_search_path(theme, emblem_paths_list[i]); } } g_hash_table_unref(emblem_paths_response); return FALSE; } gboolean remove_emblem_paths(GHashTable* emblem_paths_response) { /* Only run this on the main loop or you'll cause problems. */ if (!emblem_paths_response) return FALSE; gchar **emblem_paths_list = g_hash_table_lookup(emblem_paths_response, "path"); if (!emblem_paths_list) goto exit; // We need to remove the old paths. GtkIconTheme * icon_theme = gtk_icon_theme_get_default(); gchar ** paths; gint path_count; gtk_icon_theme_get_search_path(icon_theme, &paths, &path_count); gint i, j, out = 0; gboolean found = FALSE; for (i = 0; i < path_count; i++) { gboolean keep = TRUE; for (j = 0; emblem_paths_list[j] != NULL; j++) { if (emblem_paths_list[j][0]) { if (!g_strcmp0(paths[i], emblem_paths_list[j])) { found = TRUE; keep = FALSE; g_free(paths[i]); break; } } } if (keep) { paths[out] = paths[i]; out++; } } /* If we found one we need to reset the path to accomodate the changes */ if (found) { paths[out] = NULL; /* Clear the last one */ gtk_icon_theme_set_search_path(icon_theme, (const gchar **)paths, out); } g_strfreev(paths); exit: g_hash_table_unref(emblem_paths_response); return FALSE; } void get_emblem_paths_cb(GHashTable *emblem_paths_response, CajaDropbox *cvs) { if (!emblem_paths_response) { emblem_paths_response = g_hash_table_new((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal); g_hash_table_insert(emblem_paths_response, "path", DEFAULT_EMBLEM_PATHS); } else { /* Increase the ref so that finish_general_command doesn't delete it. */ g_hash_table_ref(emblem_paths_response); } g_mutex_lock(cvs->emblem_paths_mutex); if (cvs->emblem_paths) { g_idle_add((GSourceFunc) remove_emblem_paths, cvs->emblem_paths); cvs->emblem_paths = NULL; } cvs->emblem_paths = emblem_paths_response; g_mutex_unlock(cvs->emblem_paths_mutex); g_idle_add((GSourceFunc) add_emblem_paths, g_hash_table_ref(emblem_paths_response)); g_idle_add((GSourceFunc) reset_all_files, cvs); } static void on_connect(CajaDropbox *cvs) { reset_all_files(cvs); dropbox_command_client_send_command(&(cvs->dc.dcc), (CajaDropboxCommandResponseHandler) get_emblem_paths_cb, cvs, "get_emblem_paths", NULL); } static void on_disconnect(CajaDropbox *cvs) { reset_all_files(cvs); g_mutex_lock(cvs->emblem_paths_mutex); /* This call will free the data too. */ g_idle_add((GSourceFunc) remove_emblem_paths, cvs->emblem_paths); cvs->emblem_paths = NULL; g_mutex_unlock(cvs->emblem_paths_mutex); } static void caja_dropbox_menu_provider_iface_init (CajaMenuProviderIface *iface) { iface->get_file_items = caja_dropbox_get_file_items; return; } static void caja_dropbox_info_provider_iface_init (CajaInfoProviderIface *iface) { iface->update_file_info = caja_dropbox_update_file_info; iface->cancel_update = caja_dropbox_cancel_update; return; } static void caja_dropbox_instance_init (CajaDropbox *cvs) { cvs->filename2obj = g_hash_table_new_full((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) NULL); cvs->obj2filename = g_hash_table_new_full((GHashFunc) g_direct_hash, (GEqualFunc) g_direct_equal, (GDestroyNotify) NULL, (GDestroyNotify) g_free); cvs->emblem_paths_mutex = g_mutex_new(); cvs->emblem_paths = NULL; /* setup the connection obj*/ dropbox_client_setup(&(cvs->dc)); /* our hooks */ caja_dropbox_hooks_add(&(cvs->dc.hookserv), "shell_touch", (DropboxUpdateHook) handle_shell_touch, cvs); /* add connection handlers */ dropbox_client_add_on_connect_hook(&(cvs->dc), (DropboxClientConnectHook) on_connect, cvs); dropbox_client_add_on_disconnect_hook(&(cvs->dc), (DropboxClientConnectHook) on_disconnect, cvs); /* now start the connection */ debug("about to start client connection"); dropbox_client_start(&(cvs->dc)); return; } static void caja_dropbox_class_init (CajaDropboxClass *class) { } static void caja_dropbox_class_finalize (CajaDropboxClass *class) { debug("just checking"); /* kill threads here? */ } GType caja_dropbox_get_type (void) { return dropbox_type; } void caja_dropbox_register_type (GTypeModule *module) { static const GTypeInfo info = { sizeof (CajaDropboxClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) caja_dropbox_class_init, (GClassFinalizeFunc) caja_dropbox_class_finalize, NULL, sizeof (CajaDropbox), 0, (GInstanceInitFunc) caja_dropbox_instance_init, }; static const GInterfaceInfo menu_provider_iface_info = { (GInterfaceInitFunc) caja_dropbox_menu_provider_iface_init, NULL, NULL }; static const GInterfaceInfo info_provider_iface_info = { (GInterfaceInitFunc) caja_dropbox_info_provider_iface_init, NULL, NULL }; dropbox_type = g_type_module_register_type(module, G_TYPE_OBJECT, "CajaDropbox", &info, 0); g_type_module_add_interface (module, dropbox_type, CAJA_TYPE_MENU_PROVIDER, &menu_provider_iface_info); g_type_module_add_interface (module, dropbox_type, CAJA_TYPE_INFO_PROVIDER, &info_provider_iface_info); }