// SPDX-License-Identifier: GPL-2.0-or-later /** @file * Inkscape::IO::HTTP - make internet requests using libsoup *//* * Authors: see git history * * Copyright (C) 2018 Authors * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ /* * How to use: * * #include "io/http.cpp" * void _async_test_call(Glib::ustring filename) { * g_warning("HTTP request saved to %s", filename.c_str()); * } * uint timeout = 20 * 60 * 60; // 20 hours * Glib::ustring filename = Inkscape::IO::HTTP::get_file("https://media.inkscape.org/media/messages.xml", timeout, _async_test_call); * */ #include #include #include #include #include "io/sys.h" #include "io/http.h" #include "io/resource.h" typedef std::function callback; namespace Resource = Inkscape::IO::Resource; namespace Inkscape { namespace IO { namespace HTTP { void _save_data_as_file(Glib::ustring filename, const char *result) { FILE *fileout = Inkscape::IO::fopen_utf8name(filename.c_str(), "wb"); if (!fileout) { g_warning("HTTP Cache: Can't open %s for write.", filename.c_str()); return; } fputs(result, fileout); fflush(fileout); if (ferror(fileout)) { g_warning("HTTP Cache: Error writing data to %s.", filename.c_str()); } fclose(fileout); } void _get_file_callback(SoupSession *session, SoupMessage *msg, gpointer user_data) { auto data = static_cast*>(user_data); data->first(data->second); delete data; } /* * Downloads a file, caches it in the local filesystem and then returns the * new filename of the cached file. * * Returns: filename where the file has been (blocking) or will be (async) stored. * * uri - The uri of the desired resource, the filename comes from this uri * timeout - Number of seconds to keep the cache, default is forever * func - An optional function, if given the function becomes asynchronous * the returned filename will be where the file 'hopes' to be saved * and this function will be called when the http request is complete. * * NOTE: If the cache file exists, then it's returned without any async request * your func will be called in a blocking way BEFORE this function returns. * */ Glib::ustring get_file(Glib::ustring uri, unsigned int timeout, callback func) { SoupURI *s_uri = soup_uri_new(uri.c_str()); std::string path = std::string(soup_uri_decode(soup_uri_get_path(s_uri))); std::string filepart; // Parse the url into a filename suitable for caching. if(path.back() == '/') { filepart = path.replace(path.begin(), path.end(), '/', '_'); filepart += ".url"; } else { filepart = path.substr(path.rfind("/") + 1); } const char *ret = get_path(Resource::CACHE, Resource::NONE, filepart.c_str()); Glib::ustring filename = Glib::ustring(ret); // We test first if the cache already exists if(file_test(filename.c_str(), G_FILE_TEST_EXISTS) && timeout > 0) { GStatBuf st; if(g_stat(filename.c_str(), &st) != -1) { time_t changed = st.st_mtime; time_t now = time(nullptr); // The cache hasn't timed out, so return the filename. if(now - changed < timeout) { if(func) { // Non-async func callback return, may block. func(filename); } return filename; } g_debug("HTTP Cache is stale: %s", filename.c_str()); } } // Only then do we get the http request SoupMessage *msg = soup_message_new_from_uri("GET", s_uri); SoupSession *session = soup_session_new(); #ifdef DEBUG_HTTP SoupLogger *logger; logger = soup_logger_new(SOUP_LOGGER_LOG_BODY, -1); soup_session_add_feature(session, SOUP_SESSION_FEATURE (logger)); g_object_unref (logger); #endif if(func) { auto *user_data = new std::pair(func, filename); soup_session_queue_message(session, msg, _get_file_callback, user_data); } else { guint status = soup_session_send_message (session, msg); if(status == SOUP_STATUS_OK) { g_debug("HTTP Cache saved to: %s", filename.c_str()); _save_data_as_file(filename, msg->response_body->data); } else { g_warning("Can't download %s", uri.c_str()); } } return filename; } } } } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :