/*
* Copyright (c) 2015 Jonas Danielsson
*
* GNOME Maps 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 of the License, or (at your
* option) any later version.
*
* GNOME Maps 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 GNOME Maps; if not, see .
*
* Author: Jonas Danielsson
*/
#include
#include
#include
#include
#include
#include
#include "mapsintl.h"
#include "maps-file-data-source.h"
#define MAPS_FILE_DATA_SOURCE_ERROR maps_file_data_source_error_quark ()
GQuark
maps_file_data_source_error_quark (void)
{
return g_quark_from_static_string ("maps-file-data-source-error");
}
enum {
PROP_0,
PROP_PATH,
PROP_MAX_ZOOM,
PROP_MIN_ZOOM
};
struct _MapsFileDataSourcePrivate
{
gchar *path;
gchar *extension;
gint max_zoom;
gint min_zoom;
long min_x;
long min_y;
long max_x;
long max_y;
};
G_DEFINE_TYPE_WITH_PRIVATE (MapsFileDataSource, maps_file_data_source, SHUMATE_TYPE_DATA_SOURCE)
static void
maps_file_data_source_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MapsFileDataSource *data_source = MAPS_FILE_DATA_SOURCE (object);
switch (prop_id)
{
case PROP_PATH:
data_source->priv->path = g_strdup ((char *) g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
maps_file_data_source_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MapsFileDataSource *data_source = MAPS_FILE_DATA_SOURCE (object);
switch (prop_id)
{
case PROP_PATH:
g_value_set_string (value, data_source->priv->path);
break;
case PROP_MIN_ZOOM:
g_value_set_uint (value, data_source->priv->min_zoom);
break;
case PROP_MAX_ZOOM:
g_value_set_uint (value, data_source->priv->max_zoom);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
maps_file_data_source_dispose (GObject *object)
{
G_OBJECT_CLASS (maps_file_data_source_parent_class)->dispose (object);
}
static void
maps_file_data_source_finalize (GObject *object)
{
MapsFileDataSource *data_source = MAPS_FILE_DATA_SOURCE (object);
if (data_source->priv->path)
g_free (data_source->priv->path);
if (data_source->priv->extension)
g_free (data_source->priv->extension);
G_OBJECT_CLASS (maps_file_data_source_parent_class)->finalize (object);
}
static void
get_tile_data_async (ShumateDataSource *source,
int x,
int y,
int zoom_level,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
static void
maps_file_data_source_class_init (MapsFileDataSourceClass *klass)
{
ShumateDataSourceClass *data_source_class = SHUMATE_DATA_SOURCE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
object_class->finalize = maps_file_data_source_finalize;
object_class->dispose = maps_file_data_source_dispose;
object_class->get_property = maps_file_data_source_get_property;
object_class->set_property = maps_file_data_source_set_property;
data_source_class->get_tile_data_async = get_tile_data_async;
/**
* MapsFileDataSource:path:
*
* The path to the tile source.
*
*/
pspec = g_param_spec_string ("path",
"Path",
"The path to the tile source",
"",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_property (object_class, PROP_PATH, pspec);
/**
* MapsFileDataSource:min-zoom:
*
* The minimum zoom level of the tile source.
*
*/
pspec = g_param_spec_uint ("min-zoom",
"Minimum zoom",
"The minimum zoom level of the tile source",
0,
20,
2,
G_PARAM_READABLE);
g_object_class_install_property (object_class, PROP_MIN_ZOOM, pspec);
/**
* MapsFileDataSource:max-zoom:
*
* The maximum zoom level of the tile source.
*
*/
pspec = g_param_spec_uint ("max-zoom",
"Maximum zoom",
"The maximum zoom level of the tile source",
0,
20,
2,
G_PARAM_READABLE);
g_object_class_install_property (object_class, PROP_MAX_ZOOM, pspec);
}
static void
maps_file_data_source_init (MapsFileDataSource *data_source)
{
data_source->priv = maps_file_data_source_get_instance_private (data_source);
data_source->priv->path = NULL;
data_source->priv->extension = NULL;
data_source->priv->max_zoom = -1;
data_source->priv->min_zoom = 21;
data_source->priv->min_x = G_MAXLONG;
data_source->priv->min_y = G_MAXLONG;
data_source->priv->max_x = 0;
data_source->priv->max_y = 0;
}
static gboolean
get_zoom_levels (MapsFileDataSource *data_source,
GError **error)
{
GFile *file;
GFileEnumerator *enumerator;
gboolean ret = TRUE;
long orig_min = data_source->priv->min_zoom;
long orig_max = data_source->priv->max_zoom;
file = g_file_new_for_path (data_source->priv->path);
enumerator = g_file_enumerate_children (file, "standard::*",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL,
error);
if (!enumerator)
return FALSE;
while (TRUE)
{
GFileInfo *info;
const char *name;
char *endptr;
long val;
if (!g_file_enumerator_iterate (enumerator, &info, NULL, NULL, error)) {
ret = FALSE;
goto out;
}
if (!info)
break;
if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
continue;
name = g_file_info_get_name (info);
val = strtol (name, &endptr, 0);
if (endptr == name || *endptr != '\0')
continue;
if (val > data_source->priv->max_zoom)
data_source->priv->max_zoom = val;
if (val < data_source->priv->min_zoom)
data_source->priv->min_zoom = val;
}
if (data_source->priv->min_zoom == orig_min ||
data_source->priv->max_zoom == orig_max) {
ret = FALSE;
if (error)
{
*error = g_error_new_literal (MAPS_FILE_DATA_SOURCE_ERROR, 0,
_("Failed to find tile structure in directory"));
}
}
out:
g_object_unref (file);
g_object_unref (enumerator);
return ret;
}
static gboolean
get_y_bounds (MapsFileDataSource *data_source,
const char *path,
GError **error)
{
GFileEnumerator *enumerator;
GFile *file;
gboolean ret = TRUE;
gboolean found = FALSE;
file = g_file_new_for_path (path);
enumerator = g_file_enumerate_children (file, "standard::*",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL,
error);
if (!enumerator)
return FALSE;
while (TRUE)
{
GFileInfo *info;
char **names;
char *endptr;
long y;
if (!g_file_enumerator_iterate (enumerator, &info,
NULL, NULL, error)) {
ret = FALSE;
goto out;
}
if (!info)
break;
if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
continue;
names = g_strsplit (g_file_info_get_name (info), ".", 2);
if (!data_source->priv->extension)
data_source->priv->extension = g_strdup (names[1]);
y = strtol (names[0], &endptr, 0);
if (endptr == names[0] || *endptr != '\0') {
g_strfreev (names);
continue;
}
if (!found)
found = TRUE;
g_strfreev (names);
if (y > data_source->priv->max_y)
data_source->priv->max_y = y;
if (y < data_source->priv->min_y)
data_source->priv->min_y = y;
}
if (!found)
{
ret = FALSE;
if (error)
{
*error = g_error_new_literal (MAPS_FILE_DATA_SOURCE_ERROR, 0,
_("Failed to find tile structure in directory"));
}
}
out:
g_object_unref (file);
g_object_unref (enumerator);
return ret;
}
static gboolean
get_bounds (MapsFileDataSource *data_source,
GError **error)
{
GFileEnumerator *enumerator;
GFile *file;
char *path;
gboolean ret = TRUE;
char min_zoom[3];
gboolean found = FALSE;
sprintf (min_zoom, "%u", data_source->priv->min_zoom);
path = g_build_filename (data_source->priv->path, min_zoom, NULL);
file = g_file_new_for_path (path);
enumerator = g_file_enumerate_children (file, "standard::*",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL,
error);
if (!enumerator)
return FALSE;
while (TRUE)
{
char *y_path;
GFileInfo *info;
const char *name;
char *endptr;
long x;
if (!g_file_enumerator_iterate (enumerator, &info, NULL, NULL, error)) {
ret = FALSE;
goto out;
}
if (!info)
break;
if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
continue;
name = g_file_info_get_name (info);
x = strtol (name, &endptr, 0);
if (endptr == name || *endptr != '\0')
continue;
if (!found)
found = TRUE;
if (x > data_source->priv->max_x)
data_source->priv->max_x = x;
if (x < data_source->priv->min_x)
data_source->priv->min_x = x;
y_path = g_build_filename (path, name, NULL);
if (!get_y_bounds (data_source, y_path, error)) {
g_free (y_path);
ret = FALSE;
goto out;
}
g_free (y_path);
}
if (!found)
{
ret = FALSE;
if (error)
{
*error = g_error_new_literal (MAPS_FILE_DATA_SOURCE_ERROR, 0,
_("Failed to find tile structure in directory"));
}
}
out:
g_free (path);
g_object_unref (file);
g_object_unref (enumerator);
return ret;
}
gboolean
maps_file_data_source_prepare (MapsFileDataSource *data_source,
GError **error)
{
g_return_val_if_fail (MAPS_IS_FILE_DATA_SOURCE (data_source), FALSE);
g_return_val_if_fail (data_source->priv->path != NULL, FALSE);
if (!get_zoom_levels (data_source, error)) {
return FALSE;
}
if (!get_bounds (data_source, error)) {
return FALSE;
}
return TRUE;
}
typedef struct {
MapsFileDataSource *self;
int x;
int y;
int z;
GBytes *bytes;
GFile *file;
} FillTileData;
static void
fill_tile_data_free (FillTileData *data)
{
g_clear_object (&data->self);
g_clear_pointer (&data->file, g_object_unref);
g_free (data);
}
static void
on_file_load (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GTask) task = user_data;
g_autoptr(GError) error = NULL;
FillTileData *data = g_task_get_task_data (task);
char *contents;
gsize length;
g_file_load_contents_finish (data->file, res, &contents, &length, NULL, &error);
if (error)
{
g_warning ("Failed to load file: %s", error->message);
return;
}
if (contents != NULL)
{
data->bytes = g_bytes_new_take (contents, length);
g_signal_emit_by_name (data->self, "received-data", data->x, data->y, data->z, data->bytes);
g_task_return_pointer (task, g_steal_pointer (&data->bytes), (GDestroyNotify)g_bytes_unref);
}
}
static void
get_tile_data_async (ShumateDataSource *source,
int x,
int y,
int zoom_level,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (MAPS_IS_FILE_DATA_SOURCE (source));
MapsFileDataSource *data_source = MAPS_FILE_DATA_SOURCE (source);
GFile *file;
gchar *path = NULL;
g_autoptr(GTask) task = NULL;
FillTileData *data;
path = g_strdup_printf("%s/%d/%d/%d.%s",
data_source->priv->path,
zoom_level,
x,
y,
data_source->priv->extension);
file = g_file_new_for_path (path);
task = g_task_new (source, cancellable, callback, user_data);
g_task_set_source_tag (task, get_tile_data_async);
data = g_new0 (FillTileData, 1);
data->self = g_object_ref (data_source);
data->x = x;
data->y = y;
data->z = zoom_level;
data->file = g_object_ref (file);
g_task_set_task_data (task, data, (GDestroyNotify) fill_tile_data_free);
if (g_file_query_exists(file, NULL))
{
g_file_load_contents_async (file, cancellable,
on_file_load,
g_object_ref (task));
}
g_object_unref (file);
g_free (path);
}