[glib] Change GLib size units policy



commit afd1e3697065c1bd23fe9a1cacf43d8744d0bc9b
Author: Ryan Lortie <desrt desrt ca>
Date:   Wed Jul 20 19:44:39 2011 +0200

    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(+), 0 deletions(-)
---
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
+
+<SUBSECTION>
+g_format_size
+GFormatSizeFlags
+g_format_size_full
 g_format_size_for_display
 
 <SUBSECTION>
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



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]