diff --git a/RELEASE b/RELEASE index 50727f0..b78d5aa 100644 --- a/RELEASE +++ b/RELEASE @@ -9,12 +9,6 @@ appstream-util appdata-to-news ../data/org.freedesktop.Passim.metainfo.xml > NEW Update translations: ninja-build passim-pot -cd .. -tx push --source -tx pull --all --force --minimum-perc=5 -cd build -ninja-build fix-translations -git add ../po/*.po # MAKE SURE THIS IS CORRECT export release_ver="0.1.6" diff --git a/libpassim/passim-client.c b/libpassim/passim-client.c index 9d67078..e4efccc 100644 --- a/libpassim/passim-client.c +++ b/libpassim/passim-client.c @@ -30,6 +30,8 @@ typedef struct { gchar *version; gchar *uri; PassimStatus status; + guint64 download_saving; + gdouble carbon_saving; } PassimClientPrivate; G_DEFINE_TYPE_WITH_PRIVATE(PassimClient, passim_client, G_TYPE_OBJECT) @@ -89,10 +91,48 @@ passim_client_get_status(PassimClient *self) return priv->status; } +/** + * passim_client_get_download_saving: + * @self: a #PassimClient + * + * Gets the total number of bytes saved from using this project. + * + * Returns: bytes + * + * Since: 0.1.6 + **/ +guint64 +passim_client_get_download_saving(PassimClient *self) +{ + PassimClientPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(PASSIM_IS_CLIENT(self), G_MAXUINT64); + return priv->download_saving; +} + +/** + * passim_client_get_carbon_saving: + * @self: a #PassimClient + * + * Gets the carbon saving from using this project. + * + * Returns: kgs of CO₂e + * + * Since: 0.1.6 + **/ +gdouble +passim_client_get_carbon_saving(PassimClient *self) +{ + PassimClientPrivate *priv = GET_PRIVATE(self); + g_return_val_if_fail(PASSIM_IS_CLIENT(self), PASSIM_STATUS_UNKNOWN); + return priv->carbon_saving; +} + static void passim_client_load_proxy_properties(PassimClient *self) { PassimClientPrivate *priv = GET_PRIVATE(self); + g_autoptr(GVariant) download_saving = NULL; + g_autoptr(GVariant) carbon_saving = NULL; g_autoptr(GVariant) status = NULL; g_autoptr(GVariant) version = NULL; g_autoptr(GVariant) uri = NULL; @@ -110,6 +150,12 @@ passim_client_load_proxy_properties(PassimClient *self) status = g_dbus_proxy_get_cached_property(priv->proxy, "Status"); if (status != NULL) priv->status = g_variant_get_uint32(status); + download_saving = g_dbus_proxy_get_cached_property(priv->proxy, "DownloadSaving"); + if (download_saving != NULL) + priv->download_saving = g_variant_get_uint64(download_saving); + carbon_saving = g_dbus_proxy_get_cached_property(priv->proxy, "CarbonSaving"); + if (carbon_saving != NULL) + priv->carbon_saving = g_variant_get_double(carbon_saving); } static void diff --git a/libpassim/passim-client.h b/libpassim/passim-client.h index c81aa9b..455885c 100644 --- a/libpassim/passim-client.h +++ b/libpassim/passim-client.h @@ -45,6 +45,10 @@ const gchar * passim_client_get_uri(PassimClient *self); PassimStatus passim_client_get_status(PassimClient *self); +guint64 +passim_client_get_download_saving(PassimClient *self); +gdouble +passim_client_get_carbon_saving(PassimClient *self); gboolean passim_client_load(PassimClient *self, GError **error); GPtrArray * diff --git a/libpassim/passim.map b/libpassim/passim.map index 5aacf1e..53eb47a 100644 --- a/libpassim/passim.map +++ b/libpassim/passim.map @@ -63,6 +63,8 @@ LIBPASSIM_0.1.5 { LIBPASSIM_0.1.6 { global: + passim_client_get_carbon_saving; + passim_client_get_download_saving; passim_client_get_uri; local: *; } LIBPASSIM_0.1.5; diff --git a/po/passim.pot b/po/passim.pot index 82419b2..096876f 100644 --- a/po/passim.pot +++ b/po/passim.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: passim\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-19 15:36+0000\n" +"POT-Creation-Date: 2024-04-14 18:42+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -58,104 +58,114 @@ msgid "Size" msgstr "" #. TRANSLATORS: daemon is starting up -#: src/passim-cli.c:267 +#: src/passim-cli.c:269 msgid "Loading…" msgstr "" #. TRANSLATORS: daemon is scared to publish files -#: src/passim-cli.c:270 +#: src/passim-cli.c:272 msgid "Disabled (metered network)" msgstr "" #. TRANSLATORS: daemon is offering files like normal -#: src/passim-cli.c:273 +#: src/passim-cli.c:275 msgid "Running" msgstr "" -#: src/passim-cli.c:277 +#: src/passim-cli.c:279 msgid "Status" msgstr "" +#. TRANSLATORS: how many bytes we did not download from the internet +#: src/passim-cli.c:287 +msgid "Network Saving" +msgstr "" + +#. TRANSLATORS: how much carbon we did not *burn* by using local data +#: src/passim-cli.c:294 +msgid "Carbon Saving" +msgstr "" + #. TRANSLATORS: full https://whatever of the daemon -#: src/passim-cli.c:284 +#: src/passim-cli.c:302 msgid "URI" msgstr "" #. TRANSLATORS: user mistyped the command -#: src/passim-cli.c:323 src/passim-cli.c:353 +#: src/passim-cli.c:341 src/passim-cli.c:371 msgid "Invalid arguments" msgstr "" #. TRANSLATORS: now sharing to the world -#: src/passim-cli.c:340 +#: src/passim-cli.c:358 msgid "Published" msgstr "" #. TRANSLATORS: no longer sharing with the world -#: src/passim-cli.c:360 +#: src/passim-cli.c:378 msgid "Unpublished" msgstr "" #. TRANSLATORS: --version -#: src/passim-cli.c:375 +#: src/passim-cli.c:393 msgid "Show project version" msgstr "" -#: src/passim-cli.c:382 +#: src/passim-cli.c:400 msgid "Next reboot" msgstr "" #. TRANSLATORS: CLI action description -#: src/passim-cli.c:397 +#: src/passim-cli.c:415 msgid "Show daemon status" msgstr "" #. TRANSLATORS: CLI option example -#: src/passim-cli.c:402 +#: src/passim-cli.c:420 msgid "FILENAME [MAX-AGE] [MAX-SHARE]" msgstr "" #. TRANSLATORS: CLI action description -#: src/passim-cli.c:404 +#: src/passim-cli.c:422 msgid "Publish an additional file" msgstr "" #. TRANSLATORS: CLI option example -#: src/passim-cli.c:409 +#: src/passim-cli.c:427 msgid "HASH" msgstr "" #. TRANSLATORS: CLI action description -#: src/passim-cli.c:411 +#: src/passim-cli.c:429 msgid "Unpublish an existing file" msgstr "" #. TRANSLATORS: CLI tool description -#: src/passim-cli.c:418 +#: src/passim-cli.c:436 msgid "Interact with the local passimd process." msgstr "" #. TRANSLATORS: CLI tool name -#: src/passim-cli.c:420 +#: src/passim-cli.c:438 msgid "Passim CLI" msgstr "" #. TRANSLATORS: we don't know what to do -#: src/passim-cli.c:424 +#: src/passim-cli.c:442 msgid "Failed to parse arguments" msgstr "" #. TRANSLATORS: daemon failed to start -#: src/passim-cli.c:432 +#: src/passim-cli.c:450 msgid "Failed to connect to daemon" msgstr "" #. TRANSLATORS: CLI tool -#: src/passim-cli.c:439 +#: src/passim-cli.c:457 msgid "client version" msgstr "" #. TRANSLATORS: server -#: src/passim-cli.c:441 +#: src/passim-cli.c:459 msgid "daemon version" msgstr "" diff --git a/src/org.freedesktop.Passim.xml b/src/org.freedesktop.Passim.xml index be13cd4..ca142a1 100644 --- a/src/org.freedesktop.Passim.xml +++ b/src/org.freedesktop.Passim.xml @@ -37,6 +37,24 @@ + + + + + The total number of bytes saved by using this project. + + + + + + + + + The carbon saving in kg CO₂e by using this project. + + + + diff --git a/src/passim-cli.c b/src/passim-cli.c index 3c4a379..18ee69f 100644 --- a/src/passim-cli.c +++ b/src/passim-cli.c @@ -258,6 +258,8 @@ passim_cli_status(PassimCli *self, gchar **values, GError **error) { PassimStatus status = passim_client_get_status(self->client); const gchar *status_value; + gdouble download_saving = passim_client_get_download_saving(self->client); + gdouble carbon_saving = passim_client_get_carbon_saving(self->client); g_autofree gchar *status_str = NULL; g_autoptr(GPtrArray) items = NULL; @@ -277,6 +279,22 @@ passim_cli_status(PassimCli *self, gchar **values, GError **error) status_str = passim_cli_align_indent(_("Status"), status_value, PASSIM_CLI_VALIGN); g_print("%s\n", status_str); + /* this is important enough to show */ + if (download_saving > 0) { + g_autofree gchar *download_value = g_format_size(download_saving); + g_autofree gchar *download_str = + /* TRANSLATORS: how many bytes we did not download from the internet */ + passim_cli_align_indent(_("Network Saving"), download_value, PASSIM_CLI_VALIGN); + g_print("%s\n", download_str); + } + if (carbon_saving > 0.001) { + g_autofree gchar *carbon_value = g_strdup_printf("%.02lf kg CO₂e", carbon_saving); + g_autofree gchar *carbon_str = + /* TRANSLATORS: how much carbon we did not *burn* by using local data */ + passim_cli_align_indent(_("Carbon Saving"), carbon_value, PASSIM_CLI_VALIGN); + g_print("%s\n", carbon_str); + } + /* show location of the web console */ if (passim_client_get_uri(self->client) != NULL) { g_autofree gchar *uri_str = diff --git a/src/passim-common.c b/src/passim-common.c index 8c5c364..8c76d91 100644 --- a/src/passim-common.c +++ b/src/passim-common.c @@ -14,6 +14,7 @@ #define PASSIM_CONFIG_PORT "Port" #define PASSIM_CONFIG_PATH "Path" #define PASSIM_CONFIG_MAX_ITEM_SIZE "MaxItemSize" +#define PASSIM_CONFIG_CARBON_COST "CarbonCost" const gchar * passim_status_to_string(PassimStatus status) @@ -71,6 +72,21 @@ passim_config_get_max_item_size(GKeyFile *kf) return g_key_file_get_uint64(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_MAX_ITEM_SIZE, NULL); } +gdouble +passim_config_get_carbon_cost(GKeyFile *kf) +{ + gdouble carbon_cost = + g_key_file_get_double(kf, PASSIM_CONFIG_GROUP, PASSIM_CONFIG_CARBON_COST, NULL); + if (carbon_cost < 0.00001) { + /* using + * https://www.carbonbrief.org/factcheck-what-is-the-carbon-footprint-of-streaming-video-on-netflix/ + * we can see that 0.018 kg CO2e for 30 mins, where 3 GB/hr -- so this gives a + * kg/GB of ~0.018 kg x (3h / 2) */ + carbon_cost = 0.026367; + } + return carbon_cost; +} + gchar * passim_config_get_path(GKeyFile *kf) { diff --git a/src/passim-common.h b/src/passim-common.h index ca4bcc5..87e2e5d 100644 --- a/src/passim-common.h +++ b/src/passim-common.h @@ -16,6 +16,8 @@ guint16 passim_config_get_port(GKeyFile *kf); gsize passim_config_get_max_item_size(GKeyFile *kf); +gdouble +passim_config_get_carbon_cost(GKeyFile *kf); gchar * passim_config_get_path(GKeyFile *kf); gboolean diff --git a/src/passim-server.c b/src/passim-server.c index 500ca9f..7341869 100644 --- a/src/passim-server.c +++ b/src/passim-server.c @@ -33,6 +33,7 @@ typedef struct { guint owner_id; guint poll_item_age_id; guint timed_exit_id; + guint64 download_saving; PassimStatus status; } PassimServer; @@ -164,10 +165,88 @@ passim_server_avahi_register(PassimServer *self, GError **error) return TRUE; } +static gchar * +passim_server_get_logdir(void) +{ + const gchar *logs_directory = g_getenv("LOGS_DIRECTORY"); + if (logs_directory != NULL) + return g_strdup(logs_directory); + return g_build_filename(PACKAGE_LOCALSTATEDIR, "log", PACKAGE_NAME, NULL); +} + +static gboolean +passim_server_update_download_saving_arg(PassimServer *self, const gchar *arg, GError **error) +{ + g_auto(GStrv) kvs = g_strsplit(arg, "=", -1); + if (g_strcmp0(kvs[0], "size") == 0) { + guint64 value = g_ascii_strtoull(kvs[1], NULL, 10); + if (value != G_MAXUINT64 && value != 0) + self->download_saving += value; + } + return TRUE; +} + +static gboolean +passim_server_update_download_saving_args(PassimServer *self, const gchar *args, GError **error) +{ + g_auto(GStrv) sections = g_strsplit(args, ",", -1); + for (guint i = 0; sections[i] != NULL; i++) { + if (!passim_server_update_download_saving_arg(self, sections[i], error)) + return FALSE; + } + return TRUE; +} + +static gboolean +passim_server_update_download_saving(PassimServer *self, GError **error) +{ + g_autofree gchar *filename = NULL; + g_autofree gchar *path = NULL; + g_autoptr(GDataInputStream) dstream = NULL; + g_autoptr(GError) error_local = NULL; + g_autoptr(GFile) file = NULL; + g_autoptr(GFileInputStream) istream = NULL; + + /* open file for reading */ + path = passim_server_get_logdir(); + filename = g_build_filename(path, "audit.log", NULL); + file = g_file_new_for_path(filename); + istream = g_file_read(file, NULL, &error_local); + if (istream == NULL) { + if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + return TRUE; + g_propagate_error(error, g_steal_pointer(&error_local)); + return FALSE; + } + dstream = g_data_input_stream_new(G_INPUT_STREAM(istream)); + + /* read each line */ + do { + g_autofree gchar *str = NULL; + g_autoptr(GError) error_line = NULL; + g_auto(GStrv) sections = NULL; + + str = g_data_input_stream_read_line_utf8(dstream, NULL, NULL, &error_line); + if (str == NULL && error_line != NULL) { + g_propagate_error(error, g_steal_pointer(&error_line)); + return FALSE; + } + if (str == NULL) + break; + sections = g_strsplit(str, " ", -1); + if (g_strv_length(sections) >= 3 && g_strcmp0(sections[1], "SHARE") == 0) { + if (!passim_server_update_download_saving_args(self, sections[2], error)) + return FALSE; + } + } while (1); + + /* success */ + return TRUE; +} + static gboolean passim_server_eventlog(PassimServer *self, const gchar *type, const gchar *value, GError **error) { - const gchar *logs_directory = g_getenv("LOGS_DIRECTORY"); g_autofree gchar *filename = NULL; g_autofree gchar *line = NULL; g_autofree gchar *path = NULL; @@ -177,11 +256,7 @@ passim_server_eventlog(PassimServer *self, const gchar *type, const gchar *value g_autoptr(GFileOutputStream) ostream = NULL; /* create logdir */ - if (logs_directory == NULL) { - path = g_build_filename(PACKAGE_LOCALSTATEDIR, "log", PACKAGE_NAME, NULL); - } else { - path = g_strdup(logs_directory); - } + path = passim_server_get_logdir(); if (!passim_mkdir(path, error)) return FALSE; @@ -902,6 +977,9 @@ passim_server_handler_cb(SoupServer *server, } passim_server_msg_send_item(self, msg, item); + /* update counter */ + self->download_saving += passim_item_get_size(item); + /* log */ passim_server_append_str(event_msg, "hash", passim_item_get_hash(item)); passim_server_append_str(event_msg, "basename", passim_item_get_basename(item)); @@ -1280,6 +1358,15 @@ passim_server_method_call(GDBusConnection *connection, method_name); } +static gdouble +passim_server_get_carbon_saving(PassimServer *self) +{ + gdouble val = self->download_saving; + /* convert both to MB */ + val *= passim_config_get_carbon_cost(self->kf) / 1024; + return val / 0x100000L; +} + static GVariant * passim_server_get_property(GDBusConnection *connection_, const gchar *sender, @@ -1295,6 +1382,10 @@ passim_server_get_property(GDBusConnection *connection_, return g_variant_new_string(SOURCE_VERSION); if (g_strcmp0(property_name, "Status") == 0) return g_variant_new_uint32(self->status); + if (g_strcmp0(property_name, "DownloadSaving") == 0) + return g_variant_new_uint64(self->download_saving); + if (g_strcmp0(property_name, "CarbonSaving") == 0) + return g_variant_new_double(passim_server_get_carbon_saving(self)); if (g_strcmp0(property_name, "Uri") == 0) { g_autofree gchar *uri = g_strdup_printf("https://localhost:%u/", self->port); return g_variant_new_string(uri); @@ -1569,6 +1660,12 @@ main(int argc, char *argv[]) } self->status = PASSIM_STATUS_LOADING; + /* update total shared so far */ + if (!passim_server_update_download_saving(self, &error)) { + g_warning("failed to read log: %s", error->message); + return 1; + } + /* register objects with Avahi */ if (!passim_server_avahi_register(self, &error)) { g_warning("failed to register: %s", error->message);