/* This file is part of GNUnet. (C) 2005, 2006 Christian Grothoff (and other contributing authors) GNUnet 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 2, or (at your option) any later version. GNUnet 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 GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file src/plugins/fs/download.c * @brief code for downloading with gnunet-gtk * @author Christian Grothoff */ #include "fs.h" #include "search.h" #include "meta.h" #include "platform.h" /* ****************** FSUI download events ****************** */ /** * We are iterating over the contents of a * directory. Add the list of entries to * the search page at the position indicated * by the download list. */ static int addFilesToDirectory(const ECRS_FileInfo * fi, const HashCode512 * key, int isRoot, void * closure) { DownloadList * list = closure; GtkTreeIter iter; GtkTreeIter child; int i; GtkTreePath * path; GtkTreeModel * model; if (isRoot == YES) return OK; if (! gtk_tree_row_reference_valid(list->searchViewRowReference)) return SYSERR; model = GTK_TREE_MODEL(list->searchList->tree); path = gtk_tree_row_reference_get_path(list->searchViewRowReference); gtk_tree_model_get_iter(model, &iter, path); gtk_tree_path_free(path); /* check for existing entry -- this function maybe called multiple times for the same directory entry */ for (i=gtk_tree_model_iter_n_children(model, &iter)-1;i>=0;i--) { if (TRUE == gtk_tree_model_iter_nth_child(model, &child, &iter, i)) { struct ECRS_URI * uri; uri = NULL; gtk_tree_model_get(model, &child, SEARCH_URI, &uri, -1); if ( (uri != NULL) && (ECRS_equalsUri(uri, fi->uri)) ) return OK; } } gtk_tree_store_append(GTK_TREE_STORE(model), &child, &iter); addEntryToSearchTree(list->searchList, list, fi, &child); return OK; } static void refreshDirectoryViewFromDisk(DownloadList * list) { unsigned long long size; char * data; int fd; struct ECRS_MetaData * meta; if ( (list->is_directory != YES) || (list->searchList == NULL) || (list->searchViewRowReference == NULL) || (! gtk_tree_row_reference_valid(list->searchViewRowReference)) ) return; if (OK != disk_file_size(ectx, list->filename, &size, YES)) return; if (size == 0) return; fd = disk_file_open(ectx, list->filename, O_RDONLY); if (fd == -1) return; data = MMAP(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if ( (data == MAP_FAILED) || (data == NULL) ) { GE_LOG_STRERROR_FILE(ectx, GE_ERROR | GE_ADMIN | GE_BULK, "mmap", list->filename); CLOSE(fd); return; } meta = NULL; ECRS_listDirectory(ectx, data, size, &meta, &addFilesToDirectory, list); MUNMAP(data, size); CLOSE(fd); if (meta != NULL) ECRS_freeMetaData(meta); } /** * A download has been started. Add an entry * to the search tree view (if applicable) and * the download summary. */ DownloadList * fs_download_started(struct FSUI_DownloadList * fsui_dl, DownloadList * dl_parent, SearchList * sl_parent, unsigned long long total, unsigned int anonymityLevel, const ECRS_FileInfo * fi, const char * filename, unsigned long long completed, cron_t eta, FSUI_State state) { DownloadList * list; GtkTreeIter iter; GtkTreePath * path; unsigned long long size; char * size_h; const char * sname; int progress; char * uri_name; gboolean valid; struct ECRS_URI * u; /* setup visualization */ list = MALLOC(sizeof(DownloadList)); memset(list, 0, sizeof(DownloadList)); list->uri = ECRS_dupUri(fi->uri); list->filename = STRDUP(filename); /* FIXME: if we have dl_parent, we may not want to just append! */ gtk_tree_store_append(download_summary, &iter, NULL); size = ECRS_fileSize(fi->uri); size_h = string_get_fancy_byte_size(size); sname = &filename[strlen(filename)-1]; while ( (sname > filename) && (sname[-1] != '/') && (sname[-1] != '\\') ) sname--; if (size != 0) progress = completed * 100 / size; else progress = 100; uri_name = ECRS_uriToString(fi->uri); gtk_tree_store_set(download_summary, &iter, DOWNLOAD_FILENAME, filename, DOWNLOAD_SHORTNAME, sname, DOWNLOAD_SIZE, size, DOWNLOAD_HSIZE, size_h, DOWNLOAD_PROGRESS, progress, DOWNLOAD_URISTRING, uri_name, DOWNLOAD_INTERNAL, list, -1); FREE(uri_name); FREE(size_h); path = gtk_tree_model_get_path(GTK_TREE_MODEL(download_summary), &iter); list->summaryViewRowReference = gtk_tree_row_reference_new(GTK_TREE_MODEL(download_summary), path); gtk_tree_path_free(path); list->searchList = sl_parent; if (sl_parent != NULL) { if (dl_parent != NULL) { GtkTreeIter piter; /* have parent, must be download from directory inside of search */ path = gtk_tree_row_reference_get_path(dl_parent->searchViewRowReference); valid = gtk_tree_model_get_iter(GTK_TREE_MODEL(sl_parent->tree), &piter, path); if (valid == TRUE) valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(sl_parent->tree), &iter, &piter); } else { /* must be top-level entry in search */ valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(sl_parent->tree), &iter); } if (valid == TRUE) { valid = FALSE; /* find matching entry */ do { gtk_tree_model_get(GTK_TREE_MODEL(sl_parent->tree), &iter, SEARCH_URI, &u, -1); if (ECRS_equalsUri(u, fi->uri)) { valid = TRUE; path = gtk_tree_model_get_path(GTK_TREE_MODEL(sl_parent->tree), &iter); list->searchViewRowReference = gtk_tree_row_reference_new(GTK_TREE_MODEL(sl_parent->tree), path); gtk_tree_path_free(path); /* TODO: extend search model with status; start to indicate active download! */ break; } } while (TRUE == gtk_tree_model_iter_next(GTK_TREE_MODEL(sl_parent->tree), &iter)); } if (valid == FALSE) { /* did not find matching entry in search list -- bug! Continue without adding to to search list! */ GE_BREAK(ectx, 0); list->searchList = NULL; } } list->fsui_list = fsui_dl; list->total = total; list->is_directory = ECRS_isDirectory(fi->meta); list->has_terminated = ( (state != FSUI_ACTIVE) && (state != FSUI_PENDING) ); list->next = download_head; download_head = list; if ( (list->is_directory == YES) && (completed != 0) ) refreshDirectoryViewFromDisk(list); return list; } /** * The download has progressed. Update the * summary and the preview of the directory * contents in the search page (if applicable). */ void fs_download_update(DownloadList * list, unsigned long long completed, const char * data, unsigned int size) { GtkTreeIter iter; GtkTreePath * path; unsigned int val; path = gtk_tree_row_reference_get_path(list->summaryViewRowReference); gtk_tree_model_get_iter(GTK_TREE_MODEL(download_summary), &iter, path); gtk_tree_path_free(path); if (list->total != 0) val = completed * 100 / list->total; else val = 100; gtk_tree_store_set(download_summary, &iter, DOWNLOAD_PROGRESS, val, -1); if ( (list->is_directory == YES) && (list->searchList != NULL) && (list->searchViewRowReference != NULL) ) { struct ECRS_MetaData * meta; meta = NULL; ECRS_listDirectory(ectx, data, size, &meta, &addFilesToDirectory, list); if (meta != NULL) ECRS_freeMetaData(meta); } } /** * A download has terminated successfully. Update summary and * possibly refresh directory listing. */ void fs_download_completed(DownloadList * downloadContext) { /* FIXME: update summary? / search list status entry (once added) */ downloadContext->has_terminated = YES; refreshDirectoryViewFromDisk(downloadContext); } /** * A download has been aborted. Update summary and * possibly refresh directory listing. */ void fs_download_aborted(DownloadList * downloadContext) { /* FIXME: update summary? / search list status entry (once added) */ downloadContext->has_terminated = YES; refreshDirectoryViewFromDisk(downloadContext); } /** * A download has been stopped. Remove from summary * and free associated resources. */ void fs_download_stopped(DownloadList * list) { GtkTreeIter iter; GtkTreePath * path; DownloadList * prev; path = gtk_tree_row_reference_get_path(list->summaryViewRowReference); gtk_tree_model_get_iter(GTK_TREE_MODEL(download_summary), &iter, path); gtk_tree_path_free(path); gtk_tree_row_reference_free(list->summaryViewRowReference); list->summaryViewRowReference = NULL; gtk_tree_store_remove(download_summary, &iter); if (list->searchViewRowReference != NULL) { gtk_tree_row_reference_free(list->searchViewRowReference); list->searchViewRowReference = NULL; } FREE(list->filename); ECRS_freeUri(list->uri); if (download_head == list) download_head = list->next; else { prev = download_head; while ( (prev != NULL) && (prev->next != list) ) prev = prev->next; if (prev != NULL) prev->next = list->next; else GE_BREAK(ectx, 0); } FREE(list); } /* **************** user download events ******************** */ /** * Check if a download for the given filename is * already running. * * @return OK if no download is pending, SYSERR if * such a download is already active. */ static int check_pending(const char * filename, GtkTreeIter * parent) { GtkTreeModel * model; GtkTreeIter iter; char * name; model = GTK_TREE_MODEL(download_summary); if (gtk_tree_model_iter_children(model, &iter, parent)) { do { gtk_tree_model_get(model, &iter, DOWNLOAD_FILENAME, &name, -1); if ( (name != NULL) && (0 == strcmp(name, filename)) ) { FREE(name); return SYSERR; } FREENONNULL(name); if (SYSERR == check_pending(filename, &iter)) return SYSERR; } while (gtk_tree_model_iter_next(model, &iter)); } return OK; } /** * The user clicked the download button. * Start the download of the selected entry. */ static void initiateDownload(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer unused) { char * uri_name; char * final_download_dir; GtkTreeIter iiter; char * tmp; char * cname; char * dname; GtkTreePath *dirTreePath; char *dirPath; unsigned int dirPathLen; struct ECRS_URI * idc_uri; struct ECRS_MetaData * idc_meta; char * idc_name; char * idc_mime; char * idc_final_download_destination; SearchList * searchContext; DownloadList * parentContext; DEBUG_BEGIN(); idc_uri = NULL; idc_meta = NULL; idc_name = NULL; idc_mime = NULL; searchContext = NULL; parentContext = NULL; gtk_tree_model_get(model, iter, SEARCH_NAME, &idc_name, SEARCH_URI, &idc_uri, SEARCH_META, &idc_meta, SEARCH_MIME, &idc_mime, SEARCH_INTERNAL, &searchContext, SEARCH_INTERNAL_PARENT, &parentContext, -1); if ( (idc_uri == NULL) || (! ECRS_isFileUri(idc_uri)) ) { GE_BREAK(ectx, 0); FREENONNULL(idc_name); FREENONNULL(idc_mime); return; } uri_name = ECRS_uriToString(idc_uri); if ( (uri_name == NULL) || (strlen(uri_name) < strlen(ECRS_URI_PREFIX) + strlen(ECRS_FILE_INFIX)) ) { GE_BREAK(ectx, 0); FREENONNULL(uri_name); FREENONNULL(idc_name); FREENONNULL(idc_mime); return; } if (idc_name == NULL) { #ifdef WINDOWS char * filehash; filehash = STRDUP(uri_name); filehash[16] = 0; idc_name = STRDUP(filehash); FREENONNULL(filehash); #else idc_name = STRDUP(uri_name); #endif } /* dname = directory portion of idc_name */ cname = idc_name; dname = STRDUP(idc_name); cname = &dname[strlen(dname)-1]; while ( (cname != dname) && (*cname != DIR_SEPARATOR) ) cname--; *cname = '\0'; cname = NULL; GC_get_configuration_value_filename(cfg, "FS", "INCOMINGDIR", "$HOME/gnunet-downloads/", &final_download_dir); if (strlen(dname) > 0) { tmp = MALLOC(strlen(final_download_dir) + strlen(dname) + 2); strcpy(tmp, final_download_dir); if (tmp[strlen(tmp)] != DIR_SEPARATOR) strcat(tmp, DIR_SEPARATOR_STR); if (dname[0] == DIR_SEPARATOR) strcat(tmp, &dname[1]); else strcat(tmp, dname); FREE(final_download_dir); final_download_dir = tmp; } FREE(dname); dname = NULL; disk_directory_create(ectx, final_download_dir); /* If file is inside a directory, get the full path */ dirTreePath = gtk_tree_path_copy(path); dirPath = MALLOC(1); dirPath[0] = '\0'; dirPathLen = 0; while (gtk_tree_path_get_depth(dirTreePath) > 1) { const char * dirname; char * new; if (! gtk_tree_path_up(dirTreePath)) break; if (!gtk_tree_model_get_iter(model, &iiter, dirTreePath)) break; gtk_tree_model_get(model, &iiter, SEARCH_NAME, &dirname, -1); dirPathLen = strlen(dirPath) + strlen(dirname) + strlen(DIR_SEPARATOR_STR) + 1; new = MALLOC(dirPathLen + 1); strcpy(new, dirname); if (new[strlen(new)-1] != DIR_SEPARATOR) strcat(new, DIR_SEPARATOR_STR); strcat(new, dirPath); FREE(dirPath); dirPath = new; } gtk_tree_path_free(dirTreePath); /* construct completed/directory/real-filename */ idc_final_download_destination = MALLOC(strlen(final_download_dir) + 2 + strlen(idc_name) + strlen(GNUNET_DIRECTORY_EXT) + strlen(dirPath)); strcpy(idc_final_download_destination, final_download_dir); if (idc_final_download_destination[strlen(idc_final_download_destination)-1] != DIR_SEPARATOR) strcat(idc_final_download_destination, DIR_SEPARATOR_STR); strcat(idc_final_download_destination, dirPath); disk_directory_create(ectx, idc_final_download_destination); strcat(idc_final_download_destination, idc_name); if ( (idc_final_download_destination[strlen(idc_final_download_destination) - 1] == '/') || (idc_final_download_destination[strlen(idc_final_download_destination) - 1] == '\\') ) idc_final_download_destination[strlen(idc_final_download_destination) - 1] = '\0'; /* append ".gnd" if needed (== directory and .gnd not present) */ if ( (idc_mime != NULL) && (0 == strcmp(idc_mime, GNUNET_DIRECTORY_MIME)) && ( (strlen(idc_final_download_destination) < strlen(GNUNET_DIRECTORY_EXT)) || (0 != strcmp(&idc_final_download_destination[strlen(idc_final_download_destination) - strlen(GNUNET_DIRECTORY_EXT)], GNUNET_DIRECTORY_EXT)) ) ) strcat(idc_final_download_destination, GNUNET_DIRECTORY_EXT); /* FIXME: check that there is no pending download for idc_name! */ if (OK == check_pending(idc_name, NULL)) { addLogEntry(_("Downloading `%s'"), idc_name); FSUI_startDownload(ctx, getSpinButtonValue(searchContext->searchXML, "downloadAnonymitySpinButton"), NO, /* FIXME: isRecursive */ idc_uri, idc_meta, idc_final_download_destination, searchContext->fsui_list, (parentContext != NULL) ? parentContext->fsui_list : NULL); } else { addLogEntry(_("ERROR: already downloading `%s'"), idc_name); } FREE(uri_name); FREE(dirPath); FREE(idc_final_download_destination); FREENONNULL(final_download_dir); FREENONNULL(idc_name); FREENONNULL(idc_mime); } /** * The download button in the search dialog was * clicked. Download all selected entries. */ void on_downloadButton_clicked_fs(GtkWidget * treeview, GtkWidget * downloadButton) { GtkTreeSelection * selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); ggc_tree_selection_selected_foreach (selection, &initiateDownload, NULL); } /** * User used the URI download entry. Start download * that is NOT rooted within a search or directory. * * TODO: * - support for recursive downloads * - support for showing directories (if downloaded like this) * - support for user-specified filename */ void on_statusDownloadURIEntry_editing_done_fs(GtkWidget * entry, GtkWidget * downloadButton) { struct ECRS_URI * idc_uri; struct ECRS_MetaData * idc_meta; char * idc_final_download_destination; const char * uris; char * urid; char * final_download_dir; const char * dname; uris = gtk_entry_get_text(GTK_ENTRY(entry)); urid = STRDUP(uris); gtk_entry_set_text(GTK_ENTRY(entry), ECRS_URI_PREFIX); idc_uri = ECRS_stringToUri(ectx, urid); if (idc_uri == NULL) { addLogEntry(_("Invalid URI `%s'"), urid); FREE(urid); return; } if (ECRS_isKeywordUri(idc_uri)) { addLogEntry(_("Please use the search function for keyword (KSK) URIs!")); FREE(urid); ECRS_freeUri(idc_uri); return; } else if (ECRS_isLocationUri(idc_uri)) { addLogEntry(_("Location URIs are not yet supported")); FREE(urid); ECRS_freeUri(idc_uri); return; } GC_get_configuration_value_filename(cfg, "FS", "INCOMINGDIR", "$HOME/gnunet-downloads/", &final_download_dir); disk_directory_create(ectx, final_download_dir); dname = &uris[strlen(ECRS_URI_PREFIX) + strlen(ECRS_FILE_INFIX)]; idc_final_download_destination = MALLOC(strlen(final_download_dir) + strlen(dname) + 2); strcpy(idc_final_download_destination, final_download_dir); FREE(final_download_dir); if (idc_final_download_destination[strlen(idc_final_download_destination)] != DIR_SEPARATOR) strcat(idc_final_download_destination, DIR_SEPARATOR_STR); strcat(idc_final_download_destination, dname); addLogEntry(_("Downloading `%s'"), uris); idc_meta = ECRS_createMetaData(); FSUI_startDownload(ctx, getSpinButtonValue(getMainXML(), "fsstatusAnonymitySpin"), NO, /* FIXME: isRecursive */ idc_uri, idc_meta, idc_final_download_destination, NULL, NULL); ECRS_freeMetaData(idc_meta); FREE(urid); } struct FCBC { int (*method)(struct FSUI_Context * ctx, struct FSUI_DownloadList * list); struct FSUI_DownloadList * argument; }; static void * fsui_callback(void * cls) { struct FCBC * fcbc = cls; fcbc->method(ctx, fcbc->argument); return NULL; } static gboolean clearCompletedDownloadCallback(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer unused) { DownloadList * dl; struct FCBC fcbc; GE_ASSERT(ectx, model == GTK_TREE_MODEL(download_summary)); gtk_tree_model_get(model, iter, DOWNLOAD_INTERNAL, &dl, -1); if (dl->has_terminated) { fcbc.method = &FSUI_stopDownload; fcbc.argument = dl->fsui_list; run_with_save_calls(&fsui_callback, &fcbc); } return FALSE; } void on_clearCompletedDownloadsButton_clicked_fs(void * unused, GtkWidget * clearButton) { gtk_tree_model_foreach(GTK_TREE_MODEL(download_summary), &clearCompletedDownloadCallback, NULL); } static void fsuiCallDownloadCallback(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer fsui_call) { DownloadList * dl; struct FCBC fcbc; GE_ASSERT(ectx, model == GTK_TREE_MODEL(download_summary)); gtk_tree_model_get(model, iter, DOWNLOAD_INTERNAL, &dl, -1); fcbc.method = fsui_call; fcbc.argument = dl->fsui_list; run_with_save_calls(&fsui_callback, &fcbc); } void on_abortDownloadButton_clicked_fs(void * unused, GtkWidget * abortButton) { GtkTreeSelection * selection; GtkWidget * downloadList; downloadList = glade_xml_get_widget(getMainXML(), "activeDownloadsList"); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(downloadList)); ggc_tree_selection_selected_foreach (selection, &fsuiCallDownloadCallback, &FSUI_abortDownload); } void on_stopDownloadButton_clicked_fs(void * unused, GtkWidget * stopButton) { GtkTreeSelection * selection; GtkWidget * downloadList; downloadList = glade_xml_get_widget(getMainXML(), "activeDownloadsList"); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(downloadList)); ggc_tree_selection_selected_foreach (selection, &fsuiCallDownloadCallback, &FSUI_abortDownload); ggc_tree_selection_selected_foreach (selection, &fsuiCallDownloadCallback, &FSUI_stopDownload); } /* end of download.c */