From afd1e3697065c1bd23fe9a1cacf43d8744d0bc9b Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Wed, 20 Jul 2011 19:44:39 +0200 Subject: [PATCH] Change GLib size units policy This commit changes GLib size units policy. We now prefer SI units and allow for use of proper IEC units where desired. g_format_size_for_display() which incorrectly mixed IEC units with SI suffixes is left unmodified, but has been deprecated. g_format_size() has been introduced which uses SI units and suffixes. g_format_size_full() has also been added which takes a flags argument to allow for use of IEC units (with correct suffixes). It also allows for a "long format" output which includes the total number of bytes. For example: "238.5 MB (238,472,938 bytes)". --- docs/reference/glib/glib-sections.txt | 5 + glib/gfileutils.c | 193 ++++++++++++++++++++++++++++++++++ glib/gfileutils.h | 13 +++ glib/glib.symbols | 2 + glib/tests/fileutils.c | 12 +++ 5 files changed, 225 insertions(+) diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 0c581d2..19290f0 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -1651,6 +1651,11 @@ g_build_filename g_build_filenamev g_build_path g_build_pathv + + +g_format_size +GFormatSizeFlags +g_format_size_full g_format_size_for_display diff --git a/glib/gfileutils.c b/glib/gfileutils.c index a3286c9..f3ecfe5 100644 --- a/glib/gfileutils.c +++ b/glib/gfileutils.c @@ -1767,6 +1767,13 @@ g_build_filename (const gchar *first_element, return str; } +#define KILOBYTE_FACTOR (G_GOFFSET_CONSTANT (1000)) +#define MEGABYTE_FACTOR (KILOBYTE_FACTOR * KILOBYTE_FACTOR) +#define GIGABYTE_FACTOR (MEGABYTE_FACTOR * KILOBYTE_FACTOR) +#define TERABYTE_FACTOR (GIGABYTE_FACTOR * KILOBYTE_FACTOR) +#define PETABYTE_FACTOR (TERABYTE_FACTOR * KILOBYTE_FACTOR) +#define EXABYTE_FACTOR (PETABYTE_FACTOR * KILOBYTE_FACTOR) + #define KIBIBYTE_FACTOR (G_GOFFSET_CONSTANT (1024)) #define MEBIBYTE_FACTOR (KIBIBYTE_FACTOR * KIBIBYTE_FACTOR) #define GIBIBYTE_FACTOR (MEBIBYTE_FACTOR * KIBIBYTE_FACTOR) @@ -1775,6 +1782,189 @@ g_build_filename (const gchar *first_element, #define EXBIBYTE_FACTOR (PEBIBYTE_FACTOR * KIBIBYTE_FACTOR) /** + * g_format_size: + * @size: a size in bytes + * + * Formats a size (for example the size of a file) into a human readable + * string. Sizes are rounded to the nearest size prefix (kB, MB, GB) + * and are displayed rounded to the nearest tenth. E.g. the file size + * 3292528 bytes will be converted into the string "3.2 MB". + * + * The prefix units base is 1000 (i.e. 1 kB is 1000 bytes). + * + * This string should be freed with g_free() when not needed any longer. + * + * See g_format_size_full() for more options about how the size might be + * formatted. + * + * Returns: a newly-allocated formatted string containing a human readable + * file size. + * + * Since: 2.30 + **/ +gchar * +g_format_size (guint64 size) +{ + return g_format_size_full (size, G_FORMAT_SIZE_DEFAULT); +} + +/** + * g_format_size_full: + * @size: a size in bytes + * @flags: #GFormatSizeFlags to modify the output + * + * Formats a size. + * + * This function is similar to g_format_size() but allows for flags that + * modify the output. See #GFormatSizeFlags. + * + * Returns: a newly-allocated formatted string containing a human + * readable file size. + * + * Since: 2.30 + **/ +/** + * GFormatSizeFlags: + * @G_FORMAT_SIZE_DEFAULT: behave the same as g_format_size() + * @G_FORMAT_SIZE_IEC_UNITS: use IEC (base 1024) units with "KiB"-style + * suffixes. IEC units should only be used + * for reporting things with a strong "power + * of 2" basis, like RAM sizes or RAID stripe + * sizes. Network and storage sizes should + * be reported in the normal SI units. + * @G_FORMAT_SIZE_LONG_FORMAT: include the exact number of bytes as part + * of the returned string. For example, + * "45.6 kB (45,612 bytes)". + * + * Flags to modify the format of the string returned by + * g_format_size_full(). + **/ +gchar * +g_format_size_full (guint64 size, + GFormatSizeFlags flags) +{ + /* Longest possibility for (2^64 - 1) is 42 characters: + * + * "16.0 EB (18 446 744 073 709 551 615 bytes)" + */ + gchar buffer[80]; + gsize i; + + if (flags & G_FORMAT_SIZE_IEC_UNITS) + { + if (size < KIBIBYTE_FACTOR) + { + i = snprintf (buffer, sizeof buffer, + g_dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes", (guint) size), + (guint) size); + flags &= ~G_FORMAT_SIZE_LONG_FORMAT; + } + + else if (size < MEBIBYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f KiB"), (gdouble) size / (gdouble) KIBIBYTE_FACTOR); + + else if (size < GIBIBYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f MiB"), (gdouble) size / (gdouble) MEBIBYTE_FACTOR); + + else if (size < TEBIBYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f GiB"), (gdouble) size / (gdouble) GIBIBYTE_FACTOR); + + else if (size < PEBIBYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f TiB"), (gdouble) size / (gdouble) TEBIBYTE_FACTOR); + + else if (size < EXBIBYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f PiB"), (gdouble) size / (gdouble) PEBIBYTE_FACTOR); + + else + i = snprintf (buffer, sizeof buffer, _("%.1f EiB"), (gdouble) size / (gdouble) EXBIBYTE_FACTOR); + } + else + { + if (size < KILOBYTE_FACTOR) + { + i = snprintf (buffer, sizeof buffer, + g_dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes", (guint) size), + (guint) size); + flags &= ~G_FORMAT_SIZE_LONG_FORMAT; + } + + else if (size < MEGABYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f kB"), (gdouble) size / (gdouble) KILOBYTE_FACTOR); + + else if (size < GIGABYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f MB"), (gdouble) size / (gdouble) MEGABYTE_FACTOR); + + else if (size < TERABYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f GB"), (gdouble) size / (gdouble) GIGABYTE_FACTOR); + + else if (size < PETABYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f TB"), (gdouble) size / (gdouble) TERABYTE_FACTOR); + + else if (size < EXABYTE_FACTOR) + i = snprintf (buffer, sizeof buffer, _("%.1f PB"), (gdouble) size / (gdouble) PETABYTE_FACTOR); + + else + i = snprintf (buffer, sizeof buffer, _("%.1f EB"), (gdouble) size / (gdouble) EXABYTE_FACTOR); + } + + if (flags & G_FORMAT_SIZE_LONG_FORMAT) + { + buffer[i++] = ' '; + buffer[i++] = '('; + + /* First problem: we need to use the number of bytes to decide on + * the plural form that is used for display, but the number of + * bytes potentially exceeds the size of a guint (which is what + * ngettext() takes). + * + * From a pragmatic standpoint, it seems that all known languages + * base plural forms on one or both of the following: + * + * - the lowest digits of the number + * + * - if the number if greater than some small value + * + * Here's how we fake it: Draw an arbitrary line at one thousand. + * If the number is below that, then fine. If it is above it, + * then we take the modulus of the number by one thousand (in + * order to keep the lowest digits) and add one thousand to that + * (in order to ensure that 1001 is not treated the same as 1). + */ + guint plural_form = size < 1000 ? size : size % 1000 + 1000; + + /* Second problem: we need to translate the string "%u byte" and + * "%u bytes" for pluralisation, but the correct number format to + * use for a gsize is different depending on which architecture + * we're on. + * + * Solution: format the number separately and use "%s bytes" on + * all platforms. + */ + gchar formatted_number[40]; + gint j; + + /* The "'" modifier is not available on Windows, so we'd better + * use g_snprintf(). + */ + j = g_snprintf (formatted_number, sizeof formatted_number, + "%'"G_GUINT64_FORMAT, size); + g_assert (j < sizeof formatted_number); + + /* Extra paranoia... */ + g_assert (i < sizeof buffer - 10); + i += snprintf (buffer + i, sizeof buffer - i, + g_dngettext(GETTEXT_PACKAGE, "%s byte", "%s bytes", plural_form), + formatted_number); + g_assert (i < sizeof buffer - 10); + buffer[i++] = ')'; + } + + buffer[i++] = '\0'; + + return g_memdup (buffer, i); +} + +/** * g_format_size_for_display: * @size: a size in bytes. * @@ -1790,6 +1980,9 @@ g_build_filename (const gchar *first_element, * Returns: a newly-allocated formatted string containing a human readable * file size. * + * Deprecated:2.30: This function is broken due to its use of SI + * suffixes to denote IEC units. Use g_format_size() + * instead. * Since: 2.16 **/ char * diff --git a/glib/gfileutils.h b/glib/gfileutils.h index a3c7029..a343270 100644 --- a/glib/gfileutils.h +++ b/glib/gfileutils.h @@ -110,7 +110,20 @@ gint g_file_open_tmp (const gchar *tmpl, gchar **name_used, GError **error); +typedef enum +{ + G_FORMAT_SIZE_DEFAULT, + G_FORMAT_SIZE_IEC_UNITS, + G_FORMAT_SIZE_LONG_FORMAT +} GFormatSizeFlags; + +char * g_format_size_full (guint64 size, + GFormatSizeFlags flags); +char * g_format_size (guint64 size); + +#ifndef G_DISABLE_DEPRECATED char *g_format_size_for_display (goffset size); +#endif gchar *g_build_path (const gchar *separator, const gchar *first_element, diff --git a/glib/glib.symbols b/glib/glib.symbols index dfb6748..808a83d 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -346,6 +346,8 @@ g_file_open_tmp PRIVATE g_file_test PRIVATE #endif g_file_read_link +g_format_size +g_format_size_full g_format_size_for_display #ifndef _WIN64 g_mkstemp PRIVATE diff --git a/glib/tests/fileutils.c b/glib/tests/fileutils.c index 225c6ce..5918a40 100644 --- a/glib/tests/fileutils.c +++ b/glib/tests/fileutils.c @@ -483,12 +483,24 @@ test_mkdir_with_parents (void) static void test_format_size_for_display (void) { + /* nobody called setlocale(), so we should get "C" behaviour... */ check_string (g_format_size_for_display (0), "0 bytes"); check_string (g_format_size_for_display (1), "1 byte"); check_string (g_format_size_for_display (2), "2 bytes"); check_string (g_format_size_for_display (1024), "1.0 KB"); check_string (g_format_size_for_display (1024 * 1024), "1.0 MB"); check_string (g_format_size_for_display (1024 * 1024 * 1024), "1.0 GB"); + + check_string (g_format_size (0), "0 bytes"); + check_string (g_format_size (1), "1 byte"); + check_string (g_format_size (2), "2 bytes"); + check_string (g_format_size (1000), "1.0 kB"); + check_string (g_format_size (1000 * 1000), "1.0 MB"); + check_string (g_format_size (1000 * 1000 * 1000), "1.0 GB"); + + check_string (g_format_size_full (238472938, G_FORMAT_SIZE_IEC_UNITS), "227.4 MiB"); + check_string (g_format_size_full (238472938, G_FORMAT_SIZE_DEFAULT), "238.5 MB"); + check_string (g_format_size_full (238472938, G_FORMAT_SIZE_LONG_FORMAT), "238.5 MB (238472938 bytes)"); } int -- 2.7.4