aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/svn/filesize.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/filesize.c')
-rw-r--r--subversion/svn/filesize.c221
1 files changed, 221 insertions, 0 deletions
diff --git a/subversion/svn/filesize.c b/subversion/svn/filesize.c
new file mode 100644
index 000000000000..ba1c35626b61
--- /dev/null
+++ b/subversion/svn/filesize.c
@@ -0,0 +1,221 @@
+/*
+ * filesize.c -- Utilities for displaying file sizes
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+/*** Includes. ***/
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+
+#include <apr_strings.h>
+
+#include "cl.h"
+
+
+/*** Code. ***/
+
+/* The structure that describes the units and their magnitudes. */
+typedef struct filesize_order_t
+{
+ svn_filesize_t mask;
+ const char *suffix;
+ const char *short_suffix;
+} filesize_order_t;
+
+
+/* Get the index of the order of magnitude of the given SIZE.
+ The returned index will be within [0 .. order_size - 1]. */
+static apr_size_t
+get_order_index(svn_filesize_t abs_size,
+ const filesize_order_t *order,
+ apr_size_t order_size)
+{
+ /* It would be sexy to do a binary search here, but with only 7 elements
+ in the arrays ... we should ### FIXME: do the binary search anyway. */
+ apr_size_t index = order_size;
+ while (index > 0)
+ {
+ --index;
+ if (abs_size > order[index].mask)
+ break;
+ }
+ return index;
+}
+
+
+/* Format the adjusted size with the given units. */
+static const char *
+format_size(double human_readable_size,
+ svn_boolean_t long_units,
+ const filesize_order_t *order,
+ apr_size_t index,
+ apr_pool_t *result_pool)
+{
+ /* NOTE: We want to display a locale-specific decimal sepratator, but
+ APR's formatter completely ignores the locale. So we use the
+ good, old, standard, *dangerous* sprintf() to format the size.
+
+ But, on the bright side, we require that the number has no more
+ than 3 non-fractional digits. So the call to sprintf() here
+ should be safe. */
+ const double absolute_human_readable_size = fabs(human_readable_size);
+ const char *const suffix = (long_units ? order[index].suffix
+ : order[index].short_suffix);
+
+ /* 3 digits (or 2 digits and 1 decimal separator)
+ + 1 negative sign (which should not appear under normal circumstances)
+ + 1 nul terminator
+ ---
+ = 5 characters of space needed in the buffer. */
+ char buffer[8];
+
+ assert(absolute_human_readable_size < 1000.0);
+
+ /* When the adjusted size has only one significant digit left of the
+ decimal point, show tenths of a unit, too. */
+ sprintf(buffer, "%.*f",
+ absolute_human_readable_size < 10.0 ? 1 : 0,
+ human_readable_size);
+ return apr_pstrcat(result_pool, buffer, suffix, SVN_VA_NULL);
+}
+
+
+static const char *
+get_base2_unit_file_size(svn_filesize_t size,
+ svn_boolean_t long_units,
+ apr_pool_t *result_pool)
+{
+ static const filesize_order_t order[] =
+ {
+ {APR_INT64_C(0x0000000000000000), " B", "B"}, /* byte */
+ {APR_INT64_C(0x00000000000003FF), " KiB", "K"}, /* kibi */
+ {APR_INT64_C(0x00000000000FFFFF), " MiB", "M"}, /* mibi */
+ {APR_INT64_C(0x000000003FFFFFFF), " GiB", "G"}, /* gibi */
+ {APR_INT64_C(0x000000FFFFFFFFFF), " TiB", "T"}, /* tibi */
+ {APR_INT64_C(0x0003FFFFFFFFFFFF), " EiB", "E"}, /* exbi */
+ {APR_INT64_C(0x0FFFFFFFFFFFFFFF), " PiB", "P"} /* pibi */
+ };
+ static const apr_size_t order_size = sizeof(order) / sizeof(order[0]);
+
+ const svn_filesize_t abs_size = ((size < 0) ? -size : size);
+ apr_size_t index = get_order_index(abs_size, order, order_size);
+ double human_readable_size;
+
+ /* Adjust the size to the given order of magnitude.
+
+ This is division by (order[index].mask + 1), which is the base-2^10
+ magnitude of the size; and that is the same as an arithmetic right
+ shift by (index * 10) bits. But we split it into an integer and a
+ floating-point division, so that we don't overflow the mantissa at
+ very large file sizes. */
+ if ((abs_size >> 10 * index) > 999)
+ {
+ /* This assertion should never fail, because we only have 4 binary
+ digits in the petabyte (all right, "pibibyte") range and so the
+ number of petabytes can't be large enough to cause the program
+ flow to enter this conditional block. */
+ assert(index < order_size - 1);
+ ++index;
+ }
+ human_readable_size = (index == 0 ? (double)size
+ : (size >> 3 * index) / 128.0 / index);
+
+ return format_size(human_readable_size,
+ long_units, order, index, result_pool);
+}
+
+
+static const char *
+get_base10_unit_file_size(svn_filesize_t size,
+ svn_boolean_t long_units,
+ apr_pool_t *result_pool)
+{
+ static const filesize_order_t order[] =
+ {
+ {APR_INT64_C( 0), " B", "B"}, /* byte */
+ {APR_INT64_C( 999), " kB", "k"}, /* kilo */
+ {APR_INT64_C( 999999), " MB", "M"}, /* mega */
+ {APR_INT64_C( 999999999), " GB", "G"}, /* giga */
+ {APR_INT64_C( 999999999999), " TB", "T"}, /* tera */
+ {APR_INT64_C( 999999999999999), " EB", "E"}, /* exa */
+ {APR_INT64_C(999999999999999999), " PB", "P"} /* peta */
+ /* 18446744073709551615 is the maximum value. */
+ };
+ static const apr_size_t order_size = sizeof(order) / sizeof(order[0]);
+
+ const svn_filesize_t abs_size = ((size < 0) ? -size : size);
+ apr_size_t index = get_order_index(abs_size, order, order_size);
+ double human_readable_size;
+
+ /* Adjust the size to the given order of magnitude.
+
+ This is division by (order[index].mask + 1), which is the base-1000
+ magnitude of the size. For large file sizes, we split the operation
+ into an integer and a floating-point division, so that we don't
+ overflow the mantissa. */
+ if (index == 0)
+ human_readable_size = (double)size;
+ else if (index <= 3)
+ human_readable_size = (double)size / (order[index].mask + 1);
+ else
+ {
+ /* [ Keep integer division here! ] */
+ const double divisor = (double)((order[index].mask + 1) / 1000000);
+ human_readable_size = (size / 1000000) / divisor;
+ /* [ And here! ] */
+ }
+
+ return format_size(human_readable_size,
+ long_units, order, index, result_pool);
+}
+
+
+svn_error_t *
+svn_cl__format_file_size(const char **result,
+ svn_filesize_t size,
+ svn_cl__size_unit_t base,
+ svn_boolean_t long_units,
+ apr_pool_t *result_pool)
+{
+ switch (base)
+ {
+ case SVN_CL__SIZE_UNIT_NONE:
+ case SVN_CL__SIZE_UNIT_XML:
+ *result = apr_psprintf(result_pool, "%" SVN_FILESIZE_T_FMT, size);
+ break;
+
+ case SVN_CL__SIZE_UNIT_BASE_2:
+ *result = get_base2_unit_file_size(size, long_units, result_pool);
+ break;
+
+ case SVN_CL__SIZE_UNIT_BASE_10:
+ *result = get_base10_unit_file_size(size, long_units, result_pool);
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}