2013-10-11 23:07:35 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2012 - Juan Ferrer Toribio
|
|
|
|
*
|
|
|
|
* This program 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.
|
|
|
|
*
|
|
|
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "db-file-loader.h"
|
|
|
|
#include "gio/gio.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
#define CACHE_CLEAN_PERIOD 600
|
|
|
|
#define CACHE_DEFAULT_SIZE 100000000
|
|
|
|
#define CACHE_CLEAN_MARGIN (CACHE_DEFAULT_SIZE*20)/100
|
|
|
|
|
|
|
|
/**
|
|
|
|
* SECTION: db-file-loader
|
|
|
|
* @Short_description: utility to download/upload resources from an URL
|
|
|
|
* @Title: DbFileLoader
|
|
|
|
*
|
|
|
|
* #DbFileLoader can download and upload files from a remote or local location by
|
|
|
|
* its URL. Both operations will allways be made asynchronously, to retrieve the
|
|
|
|
* results you must pass a #DbFileLoaderCallbackFunc.
|
|
|
|
*
|
|
|
|
* By default, #DbFileLoader uses a local cache on disk to store the downloaded
|
|
|
|
* files. To avoid the use of this feature, create the #DbFileLoader using
|
|
|
|
* db_file_loader_new_simple(). The default cache directory is 'hedera', under
|
|
|
|
* g_get_user_cache_dir(). To use another directory as cache or set its size,
|
|
|
|
* use g_object_set().
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct _DbFileLoaderPrivate
|
|
|
|
{
|
|
|
|
gchar * dir;
|
|
|
|
gchar * cache;
|
|
|
|
goffset size;
|
|
|
|
gchar * host;
|
|
|
|
gchar * path;
|
|
|
|
GInetSocketAddress * addr;
|
|
|
|
GThreadPool * pool;
|
|
|
|
GMutex * mutex;
|
|
|
|
GMutex * cache_mutex;
|
|
|
|
GHashTable * downloading;
|
|
|
|
guint src;
|
|
|
|
};
|
|
|
|
|
|
|
|
G_DEFINE_TYPE (DbFileLoader, db_file_loader, G_TYPE_OBJECT)
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
DbFileLoader * obj;
|
|
|
|
gchar * name;
|
|
|
|
GBytes * data;
|
|
|
|
GError * error;
|
|
|
|
DbFileLoaderCallbackFunc func;
|
|
|
|
gpointer user_data;
|
|
|
|
GCancellable * cancel;
|
|
|
|
}
|
|
|
|
File;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
guint64 atime;
|
|
|
|
goffset size;
|
|
|
|
gchar * path;
|
|
|
|
}
|
|
|
|
CacheFile;
|
|
|
|
|
|
|
|
static const gchar * WDAY[] = {"Err",
|
|
|
|
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
|
|
|
|
|
|
|
static const gchar * MONTH[] = {"Err",
|
|
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
|
|
|
|
|
|
static const gchar * format_request = {
|
|
|
|
"GET /%s/%s HTTP/1.1\r\n" // Path to the file
|
|
|
|
"Host: %s\r\n" // Hostname
|
|
|
|
"%s" // If-modified-since: date\r\n
|
|
|
|
"\r\n"
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* db_file_loader_new:
|
|
|
|
* @host: the host where the files are stored
|
|
|
|
* @path: the base directory to find the loaded resources
|
|
|
|
*
|
|
|
|
* Creates a new #DbFileLoader pointing to @path in @host. The cache is set
|
|
|
|
* to default.
|
|
|
|
*
|
|
|
|
* Return value: a #DbFileLoader
|
|
|
|
**/
|
|
|
|
DbFileLoader * db_file_loader_new (const gchar * host, const gchar * path)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (host, NULL);
|
|
|
|
g_return_val_if_fail (path, NULL);
|
|
|
|
|
|
|
|
return g_object_new
|
|
|
|
(DB_TYPE_FILE_LOADER, "host", host, "path", path, "cache", NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* db_file_loader_new_simple:
|
|
|
|
* @host: the host where the files are stored
|
|
|
|
* @path: the base directory to find the loaded resources
|
|
|
|
*
|
|
|
|
* Creates a new #DbFileLoader pointing to @path in @host. To use a cache,
|
|
|
|
* call db_file_loader_new().
|
|
|
|
*
|
|
|
|
* Return value: a #DbFileLoader
|
|
|
|
**/
|
|
|
|
DbFileLoader * db_file_loader_new_simple (const gchar * host, const gchar * path)
|
|
|
|
{
|
|
|
|
DbFileLoader * fl;
|
|
|
|
|
|
|
|
g_return_val_if_fail (host, NULL);
|
|
|
|
g_return_val_if_fail (path, NULL);
|
|
|
|
|
|
|
|
fl = g_object_new (DB_TYPE_FILE_LOADER, "host", host, "path", path, NULL);
|
|
|
|
|
|
|
|
if (fl->priv->cache)
|
|
|
|
{
|
|
|
|
g_free (fl->priv->cache);
|
|
|
|
fl->priv->cache = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* db_file_loader_new_full:
|
|
|
|
* @host: the host where the files are stored
|
|
|
|
* @path: the base directory to find the loaded resources
|
|
|
|
* @cache: (allow-none): the base directory for the local cache or %NULL
|
|
|
|
* @size: maximal size for the cache
|
|
|
|
*
|
|
|
|
* Creates a new #DbFileLoader pointing to @path in @host. Note that the user
|
|
|
|
* needs permission to read and write in @cache. If @cache is %NULL, it will be
|
|
|
|
* set to default, if @max_cache is 0, the cache has no maximal size.
|
|
|
|
*
|
|
|
|
* Return value: a #DbFileLoader or %NULL if @cache is an invalid directory
|
|
|
|
**/
|
|
|
|
DbFileLoader * db_file_loader_new_full
|
|
|
|
(const gchar * host, const gchar * path, const gchar * cache, goffset size)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (host, NULL);
|
|
|
|
g_return_val_if_fail (path, NULL);
|
|
|
|
|
|
|
|
// Comprobar que cache existe y se puede escribir, comprobar tamaño disponible
|
|
|
|
return g_object_new (DB_TYPE_FILE_LOADER,
|
|
|
|
"host", host, "path", path,
|
|
|
|
"cache", cache, "size", size, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Methods
|
|
|
|
|
|
|
|
static File * file_new (DbFileLoader * obj, const gchar * path, const GBytes * data,
|
|
|
|
DbFileLoaderCallbackFunc func, gpointer user_data, gboolean async)
|
|
|
|
{
|
|
|
|
File * file = g_new (File, 1);
|
|
|
|
|
|
|
|
file->obj = g_object_ref (obj);
|
|
|
|
file->name = g_strdup (path);
|
|
|
|
file->data = data ? g_boxed_copy (G_TYPE_BYTES, data) : NULL;
|
|
|
|
file->error = NULL;
|
|
|
|
file->func = func;
|
|
|
|
file-> user_data = user_data;
|
|
|
|
file->cancel = async ? g_cancellable_new () : NULL;
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void file_free (File * file)
|
|
|
|
{
|
|
|
|
if (file)
|
|
|
|
{
|
|
|
|
g_free (file->name);
|
|
|
|
|
|
|
|
if (file->error)
|
|
|
|
g_error_free (file->error);
|
|
|
|
|
|
|
|
if (file->data)
|
|
|
|
g_bytes_unref (file->data);
|
|
|
|
|
|
|
|
g_object_unref (file->obj);
|
|
|
|
g_free (file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Connection management and usage
|
|
|
|
*/
|
|
|
|
static GFile * db_file_loader_get_cache_file (DbFileLoader * obj, const gchar * subpath)
|
|
|
|
{
|
|
|
|
gchar * path = g_strconcat (obj->priv->cache, "/", subpath, NULL);
|
|
|
|
GFile * file = g_file_new_for_path (path);
|
|
|
|
g_free (path);
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean db_file_loader_callback (File * file)
|
|
|
|
{
|
|
|
|
if (file->cancel && !g_cancellable_is_cancelled (file->cancel))
|
|
|
|
file->func (file->obj, file->data, file->error, file->user_data);
|
|
|
|
|
|
|
|
g_hash_table_remove (file->obj->priv->downloading, file);
|
|
|
|
|
2014-02-11 12:16:39 +00:00
|
|
|
return G_SOURCE_REMOVE;
|
2013-10-11 23:07:35 +00:00
|
|
|
}
|
|
|
|
|
2013-10-23 08:58:06 +00:00
|
|
|
static gboolean db_file_loader_resolve_host (DbFileLoader * obj,
|
|
|
|
GCancellable * cancel, GError ** error)
|
2013-10-11 23:07:35 +00:00
|
|
|
{
|
|
|
|
gboolean ret = FALSE;
|
|
|
|
GResolver * r = g_resolver_get_default ();
|
|
|
|
GList * ads = g_resolver_lookup_by_name (r, obj->priv->host, cancel, error);
|
|
|
|
|
|
|
|
if (ads)
|
|
|
|
{
|
|
|
|
GInetAddress * a = g_list_nth_data (ads, 0);// TODO? Use the entire list
|
|
|
|
// TODO Add the option to set the connection port!!
|
|
|
|
obj->priv->addr = G_INET_SOCKET_ADDRESS (g_inet_socket_address_new (a, 80));
|
|
|
|
g_resolver_free_addresses (ads);
|
|
|
|
ret = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_object_unref (r);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GIOStream * db_file_loader_connect (DbFileLoader * obj, GCancellable * cancel, GError ** error)
|
|
|
|
{
|
|
|
|
GSocketClient * client = g_socket_client_new ();
|
|
|
|
GIOStream * connection = G_IO_STREAM (g_socket_client_connect
|
|
|
|
(client, G_SOCKET_CONNECTABLE (obj->priv->addr), cancel, error));
|
|
|
|
g_object_unref (client);
|
|
|
|
return connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gchar * db_file_loader_create_request (DbFileLoader * obj, const gchar * subpath)
|
|
|
|
{
|
|
|
|
gchar * ifmod;
|
|
|
|
gchar * request;
|
|
|
|
gchar * date = NULL;
|
|
|
|
|
|
|
|
if (obj->priv->cache)
|
|
|
|
{
|
|
|
|
GFileInfo * info;
|
|
|
|
GFile * file = db_file_loader_get_cache_file (obj, subpath);
|
|
|
|
|
|
|
|
info = g_file_query_info
|
|
|
|
(file
|
|
|
|
,G_FILE_ATTRIBUTE_TIME_CHANGED
|
|
|
|
,G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS
|
|
|
|
,NULL, NULL);
|
|
|
|
|
|
|
|
g_object_unref (file);
|
|
|
|
|
|
|
|
if (info)
|
|
|
|
{
|
|
|
|
guint64 date_attr = g_file_info_get_attribute_uint64
|
|
|
|
(info, G_FILE_ATTRIBUTE_TIME_CHANGED);
|
|
|
|
GDateTime * dt = g_date_time_new_from_unix_utc (date_attr);
|
|
|
|
gchar * f_year_time = g_date_time_format (dt, "%Y %T GMT");
|
|
|
|
gchar * f_month_day = g_date_time_format (dt, "%d");
|
|
|
|
date = g_strdup_printf ("%s, %s %s %s"
|
|
|
|
,WDAY[g_date_time_get_day_of_week (dt)]
|
|
|
|
,f_month_day
|
|
|
|
,MONTH[g_date_time_get_month (dt)]
|
|
|
|
,f_year_time
|
|
|
|
);
|
|
|
|
|
|
|
|
g_free (f_month_day);
|
|
|
|
g_free (f_year_time);
|
|
|
|
g_date_time_unref (dt);
|
|
|
|
g_object_unref (info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ifmod = date ? g_strconcat ("If-modified-since: ", date, "\r\n", NULL) : "";
|
|
|
|
request = g_strdup_printf (format_request,
|
|
|
|
obj->priv->path, subpath, obj->priv->host, ifmod);
|
|
|
|
|
|
|
|
if (date)
|
|
|
|
{
|
|
|
|
g_free (date);
|
|
|
|
g_free (ifmod);
|
|
|
|
}
|
|
|
|
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
2013-10-23 08:58:06 +00:00
|
|
|
static gboolean db_file_loader_store (DbFileLoader * obj,
|
|
|
|
const gchar * subpath, const gchar * data, gsize len, GError ** error)
|
2013-10-11 23:07:35 +00:00
|
|
|
{
|
|
|
|
gsize dir_len;
|
|
|
|
gboolean ret = FALSE;
|
|
|
|
gchar * path = g_strconcat (obj->priv->cache, "/",subpath, NULL);
|
|
|
|
GFile * file = g_file_new_for_path (path);
|
|
|
|
gchar * name = g_file_get_basename (file);
|
|
|
|
|
|
|
|
dir_len = strlen (path) - strlen (name);
|
|
|
|
gchar dir[dir_len];
|
|
|
|
g_strlcpy (dir, path, dir_len);
|
|
|
|
g_free (name);
|
|
|
|
|
|
|
|
g_mutex_lock (obj->priv->cache_mutex);
|
|
|
|
|
|
|
|
if (g_mkdir_with_parents (dir, 00700) >= 0)
|
|
|
|
{
|
|
|
|
if (!g_file_query_exists (file, NULL)
|
|
|
|
&& g_file_set_contents (path, data, len, error))
|
|
|
|
ret = TRUE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
g_set_error (error, DB_FILE_LOADER_LOG_DOMAIN,
|
|
|
|
g_file_error_from_errno (errno), _("%s not cached"), subpath);
|
|
|
|
|
|
|
|
g_mutex_unlock (obj->priv->cache_mutex);
|
|
|
|
|
|
|
|
g_free (path);
|
|
|
|
g_object_unref (file);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-10-23 08:58:06 +00:00
|
|
|
static gchar * db_file_loader_load_from_cache (DbFileLoader * obj,
|
|
|
|
const gchar * subpath, gsize * len, GError ** error)
|
2013-10-11 23:07:35 +00:00
|
|
|
{
|
|
|
|
gchar * data;
|
|
|
|
GFile * file = db_file_loader_get_cache_file (obj, subpath);
|
|
|
|
|
|
|
|
g_file_load_contents (file, NULL, &data, len, NULL, error);
|
|
|
|
g_object_unref (file);
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2013-10-23 08:58:06 +00:00
|
|
|
static void db_file_loader_thread_download (File * file, DbFileLoader * obj)
|
2013-10-11 23:07:35 +00:00
|
|
|
{
|
|
|
|
gsize len;
|
|
|
|
gchar ** split;
|
|
|
|
gchar * status_line = NULL;
|
|
|
|
gchar * request = NULL;
|
|
|
|
gchar * data = NULL;
|
|
|
|
GError * error = NULL;
|
|
|
|
GIOStream * connection = NULL;
|
|
|
|
GDataInputStream * receive_stream = NULL;
|
2013-10-23 08:58:06 +00:00
|
|
|
GOutputStream * send_stream;
|
|
|
|
GCancellable * cancel = file->cancel;
|
2013-10-11 23:07:35 +00:00
|
|
|
|
|
|
|
g_mutex_lock (obj->priv->mutex);
|
|
|
|
|
|
|
|
if (!obj->priv->addr && !db_file_loader_resolve_host (obj, cancel, &error))
|
|
|
|
{
|
|
|
|
g_mutex_unlock (obj->priv->mutex);
|
2014-06-10 10:48:03 +00:00
|
|
|
goto exit;
|
2013-10-11 23:07:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
g_mutex_unlock (obj->priv->mutex);
|
|
|
|
|
|
|
|
if (!(connection = db_file_loader_connect (obj, cancel, &error)))
|
2014-06-10 10:48:03 +00:00
|
|
|
goto exit;
|
2013-10-11 23:07:35 +00:00
|
|
|
|
|
|
|
request = db_file_loader_create_request (obj, file->name);
|
|
|
|
send_stream = g_io_stream_get_output_stream (connection);
|
|
|
|
|
|
|
|
if (0 > g_output_stream_write (send_stream, request, strlen (request), cancel, &error)
|
|
|
|
|| !g_output_stream_close (send_stream, cancel, &error))
|
2014-06-10 10:48:03 +00:00
|
|
|
goto exit;
|
2013-10-11 23:07:35 +00:00
|
|
|
|
|
|
|
receive_stream = g_data_input_stream_new (g_io_stream_get_input_stream (connection));
|
|
|
|
g_data_input_stream_set_newline_type (receive_stream, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
|
|
|
|
status_line = g_data_input_stream_read_line (receive_stream, &len, cancel, &error);
|
|
|
|
|
|
|
|
if (!status_line)
|
2014-06-10 10:48:03 +00:00
|
|
|
goto exit;
|
2013-10-11 23:07:35 +00:00
|
|
|
|
|
|
|
split = g_strsplit (status_line, " ", -1);
|
|
|
|
guint status = g_strv_length (split) >= 2 ? atoi (split[1]) : 0;
|
|
|
|
g_strfreev (split);
|
|
|
|
|
|
|
|
switch (status)
|
|
|
|
{
|
|
|
|
gchar * line;
|
|
|
|
|
|
|
|
case 200:
|
|
|
|
{
|
|
|
|
gboolean content_known;
|
|
|
|
guint i, nbytes = 0;
|
|
|
|
line = NULL;
|
|
|
|
|
|
|
|
do
|
|
|
|
{// Read header fields
|
|
|
|
g_free (line);
|
|
|
|
line = g_data_input_stream_read_line
|
|
|
|
(receive_stream, &len, cancel, &error);
|
|
|
|
|
|
|
|
if (g_str_has_prefix (line, "Content-Length: "))
|
|
|
|
{
|
|
|
|
nbytes = atoi (line + 16);
|
|
|
|
content_known = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (line && len && !error);
|
|
|
|
|
|
|
|
g_free (line);
|
|
|
|
|
|
|
|
if (!error)
|
|
|
|
{
|
|
|
|
if (!content_known)
|
|
|
|
g_set_error (&error, DB_FILE_LOADER_LOG_DOMAIN, status,
|
|
|
|
_("Unknown content length of file %s"), file->name);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
len = nbytes;
|
|
|
|
data = g_new (gchar, len);
|
|
|
|
|
|
|
|
for (i = 0; !error && i < len; i++)
|
|
|
|
data[i] = g_data_input_stream_read_byte
|
|
|
|
(receive_stream, cancel, &error);
|
|
|
|
|
|
|
|
if (!error && data)
|
|
|
|
db_file_loader_store (obj, file->name, data, len, &error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 304:
|
|
|
|
{
|
|
|
|
data = db_file_loader_load_from_cache (obj, file->name, &len, &error);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if (status_line && !error)
|
|
|
|
g_set_error (&error, DB_FILE_LOADER_LOG_DOMAIN, status,
|
|
|
|
"%s: '%s'", status_line, file->name);
|
|
|
|
}
|
|
|
|
|
2014-04-15 09:36:14 +00:00
|
|
|
if (data)
|
|
|
|
file->data = g_bytes_new_take (data, len);
|
2013-10-11 23:07:35 +00:00
|
|
|
|
2014-06-10 10:48:03 +00:00
|
|
|
exit:
|
2013-10-11 23:07:35 +00:00
|
|
|
g_free (request);
|
|
|
|
g_free (status_line);
|
|
|
|
|
|
|
|
if (receive_stream)
|
|
|
|
{
|
|
|
|
g_input_stream_close (G_INPUT_STREAM (receive_stream), NULL,
|
|
|
|
error ? NULL : &error);
|
|
|
|
g_object_unref (receive_stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (connection)
|
|
|
|
{
|
|
|
|
g_io_stream_close (connection, NULL, error ? NULL : &error);
|
|
|
|
g_object_unref (connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
file->error = error;
|
|
|
|
|
|
|
|
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE
|
|
|
|
,(GSourceFunc) db_file_loader_callback, file
|
|
|
|
,(GDestroyNotify) file_free
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* db_file_loader_download:
|
|
|
|
* @obj: a #DbFileLoader
|
|
|
|
* @path: the path to the file from @obj's path
|
|
|
|
* @func: (scope async): the #DbFileLoaderCallbackFunc to call after downloading
|
|
|
|
* or in case of error
|
|
|
|
* @user_data: (closure): data to pass to @func
|
|
|
|
*
|
|
|
|
* Downloads a file from @file, which is a relative path to the file from the
|
|
|
|
* base URL of the #DbFileLoader. The result will be availble in @func.
|
|
|
|
**/
|
2013-10-23 08:58:06 +00:00
|
|
|
void db_file_loader_download (DbFileLoader * obj,
|
|
|
|
const gchar * path, DbFileLoaderCallbackFunc func, gpointer user_data)
|
2013-10-11 23:07:35 +00:00
|
|
|
{
|
|
|
|
File * file;
|
|
|
|
|
|
|
|
g_return_if_fail (DB_IS_FILE_LOADER (obj));
|
|
|
|
g_return_if_fail (path);
|
|
|
|
|
|
|
|
if (!obj->priv->pool)
|
2013-10-23 08:58:06 +00:00
|
|
|
obj->priv->pool = g_thread_pool_new ((GFunc) db_file_loader_thread_download,
|
2013-10-29 10:10:31 +00:00
|
|
|
obj, 2, FALSE, NULL);
|
2013-10-11 23:07:35 +00:00
|
|
|
|
|
|
|
file = file_new (obj, path, NULL, func, user_data, TRUE);
|
|
|
|
g_hash_table_add (obj->priv->downloading, file);
|
|
|
|
|
|
|
|
g_thread_pool_push (obj->priv->pool, file, NULL);
|
|
|
|
}
|
|
|
|
|
2013-10-23 08:58:06 +00:00
|
|
|
static void db_file_loader_thread_upload (DbFileLoader * obj, File * file)
|
2013-10-11 23:07:35 +00:00
|
|
|
{
|
|
|
|
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
|
|
|
|
(GSourceFunc) db_file_loader_callback, file,
|
|
|
|
(GDestroyNotify) file_free);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* db_file_loader_upload:
|
|
|
|
* @obj: a #DbFileLoader
|
|
|
|
* @data: a #GBytes with the data to upload
|
|
|
|
* @path: a string with the path to the file
|
|
|
|
* @func: (scope async): the #DbFileLoaderCallbackFunc to call after uploading
|
|
|
|
* or in case of error
|
|
|
|
* @user_data: (closure): data to pass to @func
|
|
|
|
*
|
|
|
|
* Uploads @data to @file, which is a relative path to the file from the base
|
|
|
|
* URL of the #DbFileLoader. To retrieve the result, connect to the
|
|
|
|
* "downloaded" signal and to the "error" signal to manage errors.
|
|
|
|
**/
|
2013-10-23 08:58:06 +00:00
|
|
|
void db_file_loader_upload (DbFileLoader * obj,
|
|
|
|
GBytes * data, const gchar * path,
|
|
|
|
DbFileLoaderCallbackFunc func, gpointer user_data)
|
2013-10-11 23:07:35 +00:00
|
|
|
{
|
|
|
|
File * file;
|
|
|
|
|
|
|
|
g_return_if_fail (DB_IS_FILE_LOADER (obj));
|
|
|
|
g_return_if_fail (data);
|
|
|
|
g_return_if_fail (path);
|
|
|
|
//TODO? create a pool only for uploads
|
|
|
|
if (!obj->priv->pool)
|
2013-10-23 08:58:06 +00:00
|
|
|
obj->priv->pool = g_thread_pool_new ((GFunc) db_file_loader_thread_upload,
|
2013-10-29 10:10:31 +00:00
|
|
|
obj, 2, TRUE, NULL);
|
2013-10-11 23:07:35 +00:00
|
|
|
|
|
|
|
file = file_new (obj, path, data, func, user_data, TRUE);
|
|
|
|
|
|
|
|
g_thread_pool_push (obj->priv->pool, file, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void db_file_loader_cancel (DbFileLoader * obj, const gchar * name)
|
|
|
|
{
|
|
|
|
GHashTableIter iter;
|
|
|
|
File * file;
|
|
|
|
|
|
|
|
g_return_if_fail (DB_IS_FILE_LOADER (obj));
|
|
|
|
if (!obj->priv->downloading) return;
|
|
|
|
|
|
|
|
g_hash_table_iter_init (&iter, obj->priv->downloading);
|
|
|
|
|
2013-10-14 12:08:06 +00:00
|
|
|
while (g_hash_table_iter_next (&iter, (gpointer *) &file, NULL))
|
2013-10-11 23:07:35 +00:00
|
|
|
if (!name || !g_strcmp0 (file->name, name))
|
|
|
|
{
|
|
|
|
g_cancellable_cancel (file->cancel);
|
|
|
|
g_hash_table_iter_steal (&iter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* db_file_loader_cancel_all:
|
|
|
|
* @obj: a #DbFileLoader
|
|
|
|
*
|
|
|
|
* Cancels all downloads pending or in progress. Note that after cancelling a
|
|
|
|
* task it may delay until it returns to the main loop. For the cause of this,
|
|
|
|
* see g_cancellable_cancel().
|
|
|
|
**/
|
|
|
|
void db_file_loader_cancel_all (DbFileLoader * obj)
|
|
|
|
{
|
|
|
|
db_file_loader_cancel (obj, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* db_file_loader_cancel_by_name:
|
|
|
|
* @obj: a #DbFileLoader
|
|
|
|
* @name: an string with a filename
|
|
|
|
*
|
|
|
|
* Cancels the download of the file with name @name, that may be pending or in
|
|
|
|
* progress. See db_file_loader_cancel_all() for more information.
|
|
|
|
**/
|
|
|
|
void db_file_loader_cancel_by_name (DbFileLoader * obj, const gchar * name)
|
|
|
|
{
|
|
|
|
if (name)
|
|
|
|
db_file_loader_cancel (obj, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
HOST_PROP = 1
|
|
|
|
,PATH_PROP
|
|
|
|
,CACHE_PROP
|
|
|
|
,SIZE_PROP
|
|
|
|
};
|
|
|
|
|
|
|
|
static void db_file_loader_set_property (DbFileLoader * obj, guint id,
|
|
|
|
const GValue * value, GParamSpec * pspec)
|
|
|
|
{
|
|
|
|
switch (id)
|
|
|
|
{
|
|
|
|
case HOST_PROP:
|
|
|
|
{
|
|
|
|
g_free (obj->priv->host);
|
|
|
|
obj->priv->host = g_value_dup_string (value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case PATH_PROP:
|
|
|
|
{
|
|
|
|
g_free (obj->priv->path);
|
|
|
|
obj->priv->path = g_value_dup_string (value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CACHE_PROP:
|
|
|
|
{
|
|
|
|
const gchar * cache = g_value_get_string (value);
|
|
|
|
g_free (obj->priv->cache);
|
|
|
|
obj->priv->cache = g_build_filename (
|
|
|
|
(cache ? cache : g_get_user_cache_dir ())
|
|
|
|
,"hedera"
|
|
|
|
,obj->priv->host
|
|
|
|
,obj->priv->path
|
|
|
|
,NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SIZE_PROP:
|
|
|
|
{
|
|
|
|
obj->priv->size = g_value_get_int64 (value);
|
|
|
|
obj->priv->size = obj->priv->size <= 0 ? 0 : obj->priv->size;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void db_file_loader_get_property (DbFileLoader * obj, guint id,
|
|
|
|
GValue * value, GParamSpec * pspec)
|
|
|
|
{
|
|
|
|
switch (id)
|
|
|
|
{
|
|
|
|
case HOST_PROP:
|
|
|
|
g_value_set_string (value, obj->priv->host);
|
|
|
|
break;
|
|
|
|
case PATH_PROP:
|
|
|
|
g_value_set_string (value, obj->priv->path);
|
|
|
|
break;
|
|
|
|
case CACHE_PROP:
|
|
|
|
g_value_set_string (value, obj->priv->cache);
|
|
|
|
case SIZE_PROP:
|
|
|
|
g_value_set_int64 (value, obj->priv->size);
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class
|
|
|
|
|
|
|
|
static void db_file_loader_init (DbFileLoader * obj)
|
|
|
|
{
|
|
|
|
obj->priv = G_TYPE_INSTANCE_GET_PRIVATE
|
|
|
|
(obj, DB_TYPE_FILE_LOADER, DbFileLoaderPrivate);
|
|
|
|
obj->priv->host = NULL;
|
|
|
|
obj->priv->path = NULL;
|
|
|
|
obj->priv->cache = NULL;
|
|
|
|
obj->priv->size = CACHE_DEFAULT_SIZE;
|
|
|
|
obj->priv->addr = NULL;
|
|
|
|
obj->priv->pool = NULL;
|
|
|
|
obj->priv->mutex = g_new (GMutex, 1);
|
|
|
|
g_mutex_init (obj->priv->mutex);
|
|
|
|
obj->priv->cache_mutex = g_new (GMutex, 1);
|
|
|
|
g_mutex_init (obj->priv->cache_mutex);
|
|
|
|
obj->priv->downloading = g_hash_table_new_full
|
|
|
|
((GHashFunc) g_direct_hash, (GEqualFunc) g_direct_equal, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void db_file_loader_finalize (DbFileLoader * obj)
|
|
|
|
{
|
|
|
|
G_OBJECT_CLASS (db_file_loader_parent_class)->finalize (G_OBJECT (obj));
|
|
|
|
|
|
|
|
g_free (obj->priv->host);
|
|
|
|
g_free (obj->priv->path);
|
|
|
|
g_free (obj->priv->cache);
|
|
|
|
|
|
|
|
g_clear_object (&obj->priv->addr);
|
|
|
|
|
|
|
|
if (obj->priv->pool)
|
|
|
|
g_thread_pool_free (obj->priv->pool, TRUE, TRUE);
|
|
|
|
|
|
|
|
g_mutex_clear (obj->priv->cache_mutex);
|
|
|
|
g_free (obj->priv->cache_mutex);
|
|
|
|
g_mutex_clear (obj->priv->mutex);
|
|
|
|
g_free (obj->priv->mutex);
|
|
|
|
|
|
|
|
g_hash_table_destroy (obj->priv->downloading);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void db_file_loader_class_init (DbFileLoaderClass * klass)
|
|
|
|
{
|
|
|
|
GObjectClass * k = G_OBJECT_CLASS (klass);
|
|
|
|
k->finalize = (GObjectFinalizeFunc) db_file_loader_finalize;
|
|
|
|
k->set_property = (GObjectSetPropertyFunc) db_file_loader_set_property;
|
|
|
|
k->get_property = (GObjectGetPropertyFunc) db_file_loader_get_property;
|
|
|
|
g_type_class_add_private (klass, sizeof (DbFileLoaderPrivate));
|
|
|
|
|
|
|
|
g_object_class_install_property (k, HOST_PROP,
|
|
|
|
g_param_spec_string ("host"
|
|
|
|
,_("Host")
|
|
|
|
,_("The host web server name to get the images")
|
|
|
|
,NULL
|
|
|
|
,G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
|
|
|
|
));
|
|
|
|
|
|
|
|
g_object_class_install_property (k, PATH_PROP,
|
|
|
|
g_param_spec_string ("path"
|
|
|
|
,_("Path")
|
|
|
|
,_("The path of the directory to interact with")
|
|
|
|
,NULL
|
|
|
|
,G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
|
|
|
|
));
|
|
|
|
|
|
|
|
g_object_class_install_property (k, CACHE_PROP,
|
|
|
|
g_param_spec_string ("cache"
|
|
|
|
,_("Cache directory")
|
|
|
|
,_("The local directory where the downloaded files will be stored. "
|
|
|
|
"The default cache directory is 'hedera', under g_get_user_cache_dir().")
|
|
|
|
,NULL
|
|
|
|
,G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
|
|
|
|
));
|
|
|
|
|
|
|
|
g_object_class_install_property (k, SIZE_PROP,
|
|
|
|
g_param_spec_int64 ("size"
|
|
|
|
,_("Maximal cache size")
|
|
|
|
,_("The maximal size for the contents of the cache directory")
|
|
|
|
,0
|
|
|
|
,G_MAXOFFSET
|
|
|
|
,CACHE_DEFAULT_SIZE
|
|
|
|
,G_PARAM_READWRITE
|
|
|
|
));
|
|
|
|
}
|