/* 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); if (path == NULL) { GE_BREAK(ectx, 0); return SYSERR; } 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; char * fn; struct ECRS_MetaData * meta; struct stat buf; const char * f; if ( (list->is_directory != YES) || (list->searchList == NULL) || (list->searchViewRowReference == NULL) || (! gtk_tree_row_reference_valid(list->searchViewRowReference)) ) return; if (0 != stat(list->filename, &buf)) return; if (S_ISDIR(buf.st_mode)) { fn = MALLOC(strlen(list->filename) + strlen(GNUNET_DIRECTORY_EXT) + 1); strcpy(fn, list->filename); if (fn[strlen(fn)-1] == '/') fn[strlen(fn)-1] = '\0'; strcat(fn, GNUNET_DIRECTORY_EXT); if (0 != stat(list->filename, &buf)) { FREE(fn); return; } f = fn; } else { fn = NULL; f = list->filename; } size = buf.st_size; if (size == 0) { FREENONNULL(fn); return; } fd = disk_file_open(ectx, list->filename, O_RDONLY); if (fd == -1) { FREENONNULL(fn); 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", f); CLOSE(fd); FREENONNULL(fn); return; } FREENONNULL(fn); 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; GtkTreeIter piter; GtkTreePath * path; unsigned long long size; char * size_h; const char * sname; int progress; char * uri_name; gboolean valid; struct ECRS_URI * u; GtkTreeModel * model; /* setup visualization */ list = MALLOC(sizeof(DownloadList)); memset(list, 0, sizeof(DownloadList)); list->uri = ECRS_dupUri(fi->uri); list->filename = STRDUP(filename); if ( (dl_parent != NULL) && (NULL != (path = gtk_tree_row_reference_get_path(dl_parent->summaryViewRowReference) ) ) ) { valid = gtk_tree_model_get_iter(GTK_TREE_MODEL(download_summary), &piter, path); if (valid) { gtk_tree_store_append(download_summary, &iter, &piter); } else { gtk_tree_store_append(download_summary, &iter, NULL); } gtk_tree_path_free(path); } else { 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; list->searchViewRowReference = NULL; if (sl_parent != NULL) { model = GTK_TREE_MODEL(sl_parent->tree); if (dl_parent != NULL) { /* have parent, must be download from directory inside of search */ GE_BREAK(ectx, gtk_tree_row_reference_get_model(dl_parent->searchViewRowReference) == model); path = gtk_tree_row_reference_get_path(dl_parent->searchViewRowReference); if (path != NULL) { valid = gtk_tree_model_get_iter(model, &piter, path); GE_BREAK(ectx, valid == TRUE); if (valid == TRUE) { valid = gtk_tree_model_iter_children(model, &iter, &piter); GE_BREAK(ectx, valid == TRUE); } } else { GE_BREAK(ectx, 0); valid = FALSE; } } else { /* no download-parent, must be top-level entry in search */ valid = gtk_tree_model_get_iter_first(model, &iter); GE_BREAK(ectx, valid == TRUE); } while (valid == TRUE) { /* find matching entry */ gtk_tree_model_get(model, &iter, SEARCH_URI, &u, -1); if (ECRS_equalsUri(u, fi->uri)) { path = gtk_tree_model_get_path(model, &iter); list->searchViewRowReference = gtk_tree_row_reference_new(model, path); gtk_tree_path_free(path); gtk_tree_store_set(sl_parent->tree, &iter, SEARCH_CELL_BG_COLOR, "yellow", -1); break; } valid = gtk_tree_model_iter_next(model, &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; struct ECRS_MetaData * meta; path = gtk_tree_row_reference_get_path(list->summaryViewRowReference); if (path == NULL) { GE_BREAK(ectx, 0); return; } 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) ) { 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) { GtkTreeIter iter; GtkTreePath * path; if (downloadContext->searchViewRowReference != NULL) { path = gtk_tree_row_reference_get_path(downloadContext->searchViewRowReference); gtk_tree_model_get_iter(GTK_TREE_MODEL(downloadContext->searchList->tree), &iter, path); gtk_tree_path_free(path); gtk_tree_store_set(downloadContext->searchList->tree, &iter, SEARCH_CELL_BG_COLOR, "green", -1); } 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! */ 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; GtkTreeIter piter; GtkTreePath * path; DownloadList * prev; int valid; GtkTreeModel * model; path = gtk_tree_row_reference_get_path(list->summaryViewRowReference); if (path == NULL) { GE_BREAK(ectx, 0); } else { 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); } FREE(list->filename); ECRS_freeUri(list->uri); /* if we have child-results in view, remove them! */ if (list->searchList != NULL) { path = gtk_tree_row_reference_get_path(list->searchViewRowReference); if (path == NULL) { GE_BREAK(ectx, 0); } else { model = GTK_TREE_MODEL(list->searchList->tree); gtk_tree_model_get_iter(model, &piter, path); gtk_tree_path_free(path); valid = gtk_tree_model_iter_children(model, &iter, &piter); while (TRUE == valid) valid = gtk_tree_store_remove(GTK_TREE_STORE(model), &iter); } } if (list->searchViewRowReference != NULL) { gtk_tree_row_reference_free(list->searchViewRowReference); list->searchViewRowReference = NULL; } 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; } if (name != NULL) free(name); if (SYSERR == check_pending(filename, &iter)) return SYSERR; } while (gtk_tree_model_iter_next(model, &iter)); } return OK; } typedef struct { char * uri_name; struct ECRS_URI * idc_uri; struct ECRS_MetaData * idc_meta; char * idc_final_download_destination; SearchList * searchContext; DownloadList * parentContext; unsigned int anonymity; int recursive; } SDC; static void * init_download_helper(void * cls) { SDC * sdc = cls; FSUI_startDownload(ctx, sdc->anonymity, sdc->recursive, sdc->idc_uri, sdc->idc_meta, sdc->idc_final_download_destination, sdc->searchContext->fsui_list, (sdc->parentContext != NULL) ? sdc->parentContext->fsui_list : NULL); return NULL; } /** * The user clicked the download button. * Start the download of the selected entry. */ static void initiateDownload(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer unused) { SDC sdc; char * final_download_dir; GtkTreeIter iiter; char * tmp; char * cname; char * dname; GtkTreePath *dirTreePath; char *dirPath; unsigned int dirPathLen; char * idc_name; char * idc_mime; sdc.idc_uri = NULL; sdc.idc_meta = NULL; idc_name = NULL; idc_mime = NULL; sdc.searchContext = NULL; sdc.parentContext = NULL; gtk_tree_model_get(model, iter, SEARCH_NAME, &idc_name, SEARCH_URI, &sdc.idc_uri, SEARCH_META, &sdc.idc_meta, SEARCH_MIME, &idc_mime, SEARCH_INTERNAL, &sdc.searchContext, SEARCH_INTERNAL_PARENT, &sdc.parentContext, -1); if ( (sdc.idc_uri == NULL) || (! ECRS_isFileUri(sdc.idc_uri)) ) { GE_BREAK(ectx, 0); FREENONNULL(idc_name); FREENONNULL(idc_mime); return; } sdc.uri_name = ECRS_uriToString(sdc.idc_uri); if ( (sdc.uri_name == NULL) || (strlen(sdc.uri_name) < strlen(ECRS_URI_PREFIX) + strlen(ECRS_FILE_INFIX)) ) { GE_BREAK(ectx, 0); FREENONNULL(sdc.uri_name); FREENONNULL(idc_name); FREENONNULL(idc_mime); return; } if (idc_name == NULL) { #ifdef WINDOWS char * filehash; filehash = STRDUP(sdc.uri_name); filehash[16] = 0; idc_name = STRDUP(filehash); FREENONNULL(filehash); #else idc_name = STRDUP(sdc.uri_name); #endif } /* dname = directory portion of idc_name */ cname = idc_name; dname = STRDUP(idc_name); cname = &dname[strlen(dname)-1]; if (cname != dname) cname--; /* ignore tailing '/' */ while ( (cname != dname) && (*cname != DIR_SEPARATOR) ) cname--; if (*cname == DIR_SEPARATOR) { *cname = '\0'; FREE(idc_name); idc_name = STRDUP(cname + 1); } else { *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; /* 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) { char * dirname; char * newPath; 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; newPath = MALLOC(dirPathLen + 1); strcpy(newPath, dirname); if (newPath[strlen(newPath)-1] != DIR_SEPARATOR) strcat(newPath, DIR_SEPARATOR_STR); strcat(newPath, dirPath); FREE(dirPath); dirPath = newPath; free(dirname); } gtk_tree_path_free(dirTreePath); /* construct completed/directory/real-filename */ sdc.idc_final_download_destination = MALLOC(strlen(final_download_dir) + 2 + strlen(idc_name) + strlen(GNUNET_DIRECTORY_EXT) + strlen(dirPath)); strcpy(sdc.idc_final_download_destination, final_download_dir); if (sdc.idc_final_download_destination[strlen(sdc.idc_final_download_destination)-1] != DIR_SEPARATOR) strcat(sdc.idc_final_download_destination, DIR_SEPARATOR_STR); strcat(sdc.idc_final_download_destination, dirPath); strcat(sdc.idc_final_download_destination, idc_name); sdc.anonymity = getSpinButtonValue(sdc.searchContext->searchXML, "downloadAnonymitySpinButton"); sdc.recursive = getToggleButtonValue(sdc.searchContext->searchXML, "downloadRecursiveCheckButton"); if (OK == check_pending(idc_name, NULL)) { addLogEntry(_("Downloading `%s'"), idc_name); run_with_save_calls(&init_download_helper, &sdc); } else { addLogEntry(_("ERROR: already downloading `%s'"), idc_name); } FREE(sdc.uri_name); FREE(dirPath); FREE(sdc.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 user-specified filename * - enable button only if valid URI is entered */ void on_statusDownloadURIEntry_editing_done_fs(GtkWidget * entry, GtkWidget * downloadButton) { const char * uris; char * urid; char * final_download_dir; const char * dname; SDC sdc; uris = gtk_entry_get_text(GTK_ENTRY(entry)); urid = STRDUP(uris); gtk_entry_set_text(GTK_ENTRY(entry), ECRS_URI_PREFIX); sdc.idc_uri = ECRS_stringToUri(ectx, urid); if (sdc.idc_uri == NULL) { addLogEntry(_("Invalid URI `%s'"), urid); FREE(urid); return; } if (ECRS_isKeywordUri(sdc.idc_uri)) { addLogEntry(_("Please use the search function for keyword (KSK) URIs!")); FREE(urid); ECRS_freeUri(sdc.idc_uri); return; } else if (ECRS_isLocationUri(sdc.idc_uri)) { addLogEntry(_("Location URIs are not yet supported")); FREE(urid); ECRS_freeUri(sdc.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)]; sdc.idc_final_download_destination = MALLOC(strlen(final_download_dir) + strlen(dname) + 2); strcpy(sdc.idc_final_download_destination, final_download_dir); FREE(final_download_dir); if (sdc.idc_final_download_destination[strlen(sdc.idc_final_download_destination)] != DIR_SEPARATOR) strcat(sdc.idc_final_download_destination, DIR_SEPARATOR_STR); strcat(sdc.idc_final_download_destination, dname); addLogEntry(_("Downloading `%s'"), uris); sdc.idc_meta = ECRS_createMetaData(); sdc.anonymity = getSpinButtonValue(getMainXML(), "fsstatusAnonymitySpin"); sdc.recursive = NO; sdc.searchContext = NULL; sdc.parentContext = NULL; run_with_save_calls(&init_download_helper, &sdc); ECRS_freeMetaData(sdc.idc_meta); FREE(sdc.idc_final_download_destination); 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 void 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); } } void on_clearCompletedDownloadsButton_clicked_fs(void * unused, GtkWidget * clearButton) { ggc_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 */