1 |
/* |
2 |
* filesize.c -- Utilities for displaying file sizes |
3 |
* |
4 |
* ==================================================================== |
5 |
* Licensed to the Apache Software Foundation (ASF) under one |
6 |
* or more contributor license agreements. See the NOTICE file |
7 |
* distributed with this work for additional information |
8 |
* regarding copyright ownership. The ASF licenses this file |
9 |
* to you under the Apache License, Version 2.0 (the |
10 |
* "License"); you may not use this file except in compliance |
11 |
* with the License. You may obtain a copy of the License at |
12 |
* |
13 |
* http://www.apache.org/licenses/LICENSE-2.0 |
14 |
* |
15 |
* Unless required by applicable law or agreed to in writing, |
16 |
* software distributed under the License is distributed on an |
17 |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
18 |
* KIND, either express or implied. See the License for the |
19 |
* specific language governing permissions and limitations |
20 |
* under the License. |
21 |
* ==================================================================== |
22 |
*/ |
23 |
|
24 |
|
25 |
/*** Includes. ***/ |
26 |
|
27 |
#include <assert.h> |
28 |
#include <math.h> |
29 |
#include <stdio.h> |
30 |
|
31 |
#include <apr_strings.h> |
32 |
|
33 |
#include "cl.h" |
34 |
|
35 |
|
36 |
/*** Code. ***/ |
37 |
|
38 |
/* The structure that describes the units and their magnitudes. */ |
39 |
typedef struct filesize_order_t |
40 |
{ |
41 |
svn_filesize_t mask; |
42 |
const char *suffix; |
43 |
const char *short_suffix; |
44 |
} filesize_order_t; |
45 |
|
46 |
|
47 |
/* Get the index of the order of magnitude of the given SIZE. |
48 |
The returned index will be within [0 .. order_size - 1]. */ |
49 |
static apr_size_t |
50 |
get_order_index(svn_filesize_t abs_size, |
51 |
const filesize_order_t *order, |
52 |
apr_size_t order_size) |
53 |
{ |
54 |
/* It would be sexy to do a binary search here, but with only 7 elements |
55 |
in the arrays ... we should ### FIXME: do the binary search anyway. */ |
56 |
apr_size_t index = order_size; |
57 |
while (index > 0) |
58 |
{ |
59 |
--index; |
60 |
if (abs_size > order[index].mask) |
61 |
break; |
62 |
} |
63 |
return index; |
64 |
} |
65 |
|
66 |
|
67 |
/* Format the adjusted size with the given units. */ |
68 |
static const char * |
69 |
format_size(double human_readable_size, |
70 |
svn_boolean_t long_units, |
71 |
const filesize_order_t *order, |
72 |
apr_size_t index, |
73 |
apr_pool_t *result_pool) |
74 |
{ |
75 |
/* NOTE: We want to display a locale-specific decimal sepratator, but |
76 |
APR's formatter completely ignores the locale. So we use the |
77 |
good, old, standard, *dangerous* sprintf() to format the size. |
78 |
|
79 |
But, on the bright side, we require that the number has no more |
80 |
than 3 non-fractional digits. So the call to sprintf() here |
81 |
should be safe. */ |
82 |
const double absolute_human_readable_size = fabs(human_readable_size); |
83 |
const char *const suffix = (long_units ? order[index].suffix |
84 |
: order[index].short_suffix); |
85 |
|
86 |
/* 3 digits (or 2 digits and 1 decimal separator) |
87 |
+ 1 negative sign (which should not appear under normal circumstances) |
88 |
+ 1 nul terminator |
89 |
--- |
90 |
= 5 characters of space needed in the buffer. */ |
91 |
char buffer[64]; |
92 |
|
93 |
assert(absolute_human_readable_size < 1000); |
94 |
|
95 |
/* When the adjusted size has only one significant digit left of |
96 |
the decimal point, show tenths of a unit, too. Except when |
97 |
the absolute size is actually a single-digit number, because |
98 |
files can't have fractional byte sizes. */ |
99 |
if (absolute_human_readable_size >= 10) |
100 |
sprintf(buffer, "%.0f", human_readable_size); |
101 |
else |
102 |
{ |
103 |
double integral; |
104 |
const double frac = modf(absolute_human_readable_size, &integral); |
105 |
const int decimals = (index > 0 && (integral < 9 || frac <= .949999999)); |
106 |
sprintf(buffer, "%.*f", decimals, human_readable_size); |
107 |
} |
108 |
|
109 |
return apr_pstrcat(result_pool, buffer, suffix, SVN_VA_NULL); |
110 |
} |
111 |
|
112 |
|
113 |
static const char * |
114 |
get_base2_unit_file_size(svn_filesize_t size, |
115 |
svn_boolean_t long_units, |
116 |
apr_pool_t *result_pool) |
117 |
{ |
118 |
static const filesize_order_t order[] = |
119 |
{ |
120 |
{APR_INT64_C(0x0000000000000000), " B", "B"}, /* byte */ |
121 |
{APR_INT64_C(0x00000000000003FF), " KiB", "K"}, /* kibi */ |
122 |
{APR_INT64_C(0x00000000000FFFFF), " MiB", "M"}, /* mibi */ |
123 |
{APR_INT64_C(0x000000003FFFFFFF), " GiB", "G"}, /* gibi */ |
124 |
{APR_INT64_C(0x000000FFFFFFFFFF), " TiB", "T"}, /* tibi */ |
125 |
{APR_INT64_C(0x0003FFFFFFFFFFFF), " EiB", "E"}, /* exbi */ |
126 |
{APR_INT64_C(0x0FFFFFFFFFFFFFFF), " PiB", "P"} /* pibi */ |
127 |
}; |
128 |
static const apr_size_t order_size = sizeof(order) / sizeof(order[0]); |
129 |
|
130 |
const svn_filesize_t abs_size = ((size < 0) ? -size : size); |
131 |
apr_size_t index = get_order_index(abs_size, order, order_size); |
132 |
double human_readable_size; |
133 |
|
134 |
/* Adjust the size to the given order of magnitude. |
135 |
|
136 |
This is division by (order[index].mask + 1), which is the base-2^10 |
137 |
magnitude of the size; and that is the same as an arithmetic right |
138 |
shift by (index * 10) bits. But we split it into an integer and a |
139 |
floating-point division, so that we don't overflow the mantissa at |
140 |
very large file sizes. */ |
141 |
if ((abs_size >> 10 * index) > 999) |
142 |
{ |
143 |
/* This assertion should never fail, because we only have 4 binary |
144 |
digits in the petabyte (all right, "pibibyte") range and so the |
145 |
number of petabytes can't be large enough to cause the program |
146 |
flow to enter this conditional block. */ |
147 |
assert(index < order_size - 1); |
148 |
++index; |
149 |
} |
150 |
|
151 |
human_readable_size = (index == 0 ? (double)size |
152 |
: (size >> (10 * index - 10)) / 1024.0); |
153 |
|
154 |
return format_size(human_readable_size, |
155 |
long_units, order, index, result_pool); |
156 |
} |
157 |
|
158 |
|
159 |
static const char * |
160 |
get_base10_unit_file_size(svn_filesize_t size, |
161 |
svn_boolean_t long_units, |
162 |
apr_pool_t *result_pool) |
163 |
{ |
164 |
static const filesize_order_t order[] = |
165 |
{ |
166 |
{APR_INT64_C( 0), " B", "B"}, /* byte */ |
167 |
{APR_INT64_C( 999), " kB", "k"}, /* kilo */ |
168 |
{APR_INT64_C( 999999), " MB", "M"}, /* mega */ |
169 |
{APR_INT64_C( 999999999), " GB", "G"}, /* giga */ |
170 |
{APR_INT64_C( 999999999999), " TB", "T"}, /* tera */ |
171 |
{APR_INT64_C( 999999999999999), " EB", "E"}, /* exa */ |
172 |
{APR_INT64_C(999999999999999999), " PB", "P"} /* peta */ |
173 |
/* 9223372036854775807 is the maximum value. */ |
174 |
}; |
175 |
static const apr_size_t order_size = sizeof(order) / sizeof(order[0]); |
176 |
|
177 |
const svn_filesize_t abs_size = ((size < 0) ? -size : size); |
178 |
apr_size_t index = get_order_index(abs_size, order, order_size); |
179 |
double human_readable_size; |
180 |
|
181 |
/* Adjust the size to the given order of magnitude. |
182 |
|
183 |
This is division by (order[index].mask + 1), which is the |
184 |
base-1000 magnitude of the size. We split the operation into an |
185 |
integer and a floating-point division, so that we don't |
186 |
overflow the mantissa. */ |
187 |
if (index == 0) |
188 |
human_readable_size = (double)size; |
189 |
else |
190 |
{ |
191 |
const svn_filesize_t divisor = (order[index - 1].mask + 1); |
192 |
/* [Keep integer arithmetic here!] */ |
193 |
human_readable_size = (size / divisor) / 1000.0; |
194 |
} |
195 |
|
196 |
/* Adjust index and number for rounding. */ |
197 |
if (human_readable_size >= 999.5) |
198 |
{ |
199 |
/* This assertion should never fail, because we only have one |
200 |
decimal digit in the petabyte range and so the number of |
201 |
petabytes can't be large enough to cause the program flow |
202 |
to enter this conditional block. */ |
203 |
assert(index < order_size - 1); |
204 |
human_readable_size /= 1000.0; |
205 |
++index; |
206 |
} |
207 |
|
208 |
return format_size(human_readable_size, |
209 |
long_units, order, index, result_pool); |
210 |
} |
211 |
|
212 |
|
213 |
svn_error_t * |
214 |
svn_cl__format_file_size(const char **result, |
215 |
svn_filesize_t size, |
216 |
svn_cl__size_unit_t base, |
217 |
svn_boolean_t long_units, |
218 |
apr_pool_t *result_pool) |
219 |
{ |
220 |
switch (base) |
221 |
{ |
222 |
case SVN_CL__SIZE_UNIT_NONE: |
223 |
case SVN_CL__SIZE_UNIT_XML: |
224 |
*result = apr_psprintf(result_pool, "%" SVN_FILESIZE_T_FMT, size); |
225 |
break; |
226 |
|
227 |
case SVN_CL__SIZE_UNIT_BASE_2: |
228 |
*result = get_base2_unit_file_size(size, long_units, result_pool); |
229 |
break; |
230 |
|
231 |
case SVN_CL__SIZE_UNIT_BASE_10: |
232 |
*result = get_base10_unit_file_size(size, long_units, result_pool); |
233 |
break; |
234 |
|
235 |
default: |
236 |
SVN_ERR_MALFUNCTION(); |
237 |
} |
238 |
|
239 |
return SVN_NO_ERROR; |
240 |
} |