diff options
Diffstat (limited to 'subversion/svn/filesize.c')
-rw-r--r-- | subversion/svn/filesize.c | 221 |
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; +} |