summaryrefslogtreecommitdiffstats
path: root/src/io/http.cpp
blob: 6f28db7e4bbaae5b0bf85ca9c3b93353cd8d2294 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// 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 <glib/gstdio.h>
#include <libsoup/soup.h>
#include <string>
#include <ctime>

#include "io/sys.h"
#include "io/http.h"
#include "io/resource.h"

typedef std::function<void(Glib::ustring)> 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<std::pair<callback, Glib::ustring>*>(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<callback, Glib::ustring>(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 :