1 package fr.ifremer.quadrige3.core.dao.technical;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import com.google.common.collect.ImmutableList;
27 import fr.ifremer.quadrige3.core.ProgressionCoreModel;
28 import fr.ifremer.quadrige3.core.exception.QuadrigeTechnicalException;
29 import org.apache.commons.io.FileUtils;
30 import org.apache.commons.lang3.mutable.MutableLong;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.apache.commons.net.io.CopyStreamListener;
34 import org.apache.commons.net.io.Util;
35 import org.apache.commons.vfs2.FileObject;
36 import org.nuiton.jaxx.application.ApplicationIOUtil;
37
38 import java.io.File;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 import java.math.BigDecimal;
43 import java.math.BigInteger;
44 import java.math.RoundingMode;
45 import java.nio.ByteBuffer;
46 import java.nio.channels.FileChannel;
47 import java.nio.channels.SeekableByteChannel;
48 import java.nio.file.*;
49 import java.nio.file.attribute.BasicFileAttributes;
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.Objects;
53
54 import static java.nio.file.FileVisitResult.CONTINUE;
55 import static java.nio.file.FileVisitResult.SKIP_SUBTREE;
56 import static java.nio.file.StandardOpenOption.*;
57
58
59
60
61 public class Files {
62
63 private static final Log LOG = LogFactory.getLog(Files.class);
64 private static final int DEFAULT_IO_BUFFER_SIZE = 131072;
65 private static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_UP;
66
67 private Files() {
68
69 }
70
71 enum FileSize {
72 EXABYTE("EB", FileUtils.ONE_EB_BI),
73 PETABYTE("PB", FileUtils.ONE_PB_BI),
74 TERABYTE("TB", FileUtils.ONE_TB_BI),
75 GIGABYTE("GB", FileUtils.ONE_GB_BI),
76 MEGABYTE("MB", FileUtils.ONE_MB_BI),
77 KILOBYTE("KB", FileUtils.ONE_KB_BI),
78 BYTE("bytes", BigInteger.ONE);
79
80 private final String unit;
81 private final BigInteger byteCount;
82
83 FileSize(String unit, BigInteger byteCount) {
84 this.unit = unit;
85 this.byteCount = byteCount;
86 }
87
88 private String unit() {
89 return unit;
90 }
91
92 private BigInteger byteCount() {
93 return byteCount;
94 }
95
96 }
97
98
99
100
101
102
103
104 public static String byteCountToDisplaySize(final BigInteger fileSize) {
105
106 String unit = FileSize.BYTE.unit;
107 BigDecimal fileSizeInUnit = BigDecimal.ZERO;
108 String val;
109
110 for (FileSize fs : FileSize.values()) {
111 BigDecimal size_bd = new BigDecimal(fileSize);
112 fileSizeInUnit = size_bd.divide(new BigDecimal(fs.byteCount), 5, ROUNDING_MODE);
113 if (fileSizeInUnit.compareTo(BigDecimal.ONE) >= 0) {
114 unit = fs.unit;
115 break;
116 }
117 }
118
119
120 if (fileSizeInUnit.divide(BigDecimal.valueOf(100.0), BigDecimal.ROUND_DOWN).compareTo(BigDecimal.ONE) >= 0) {
121 val = fileSizeInUnit.setScale(0, ROUNDING_MODE).toString();
122 } else if (fileSizeInUnit.divide(BigDecimal.valueOf(10.0), BigDecimal.ROUND_DOWN).compareTo(BigDecimal.ONE) >= 0) {
123 val = fileSizeInUnit.setScale(1, ROUNDING_MODE).toString();
124 } else {
125 val = fileSizeInUnit.setScale(2, ROUNDING_MODE).toString();
126 }
127
128
129 if (val.endsWith(".00")) {
130 val = val.substring(0, val.length() - 3);
131 } else if (val.endsWith(".0")) {
132 val = val.substring(0, val.length() - 2);
133 }
134
135 return String.format("%s %s", val, unit);
136 }
137
138
139
140
141
142
143
144 public static String byteCountToDisplaySize(final long fileSize) {
145 return byteCountToDisplaySize(BigInteger.valueOf(fileSize));
146 }
147
148
149
150
151
152
153
154
155
156 @Deprecated
157 public static void copy(String sourceFileName, String destinationFileName, CopyStreamListener progressMonitor) throws IOException {
158
159 FileObject sourceFile = ApplicationIOUtil.resolveFile(sourceFileName, "Can't resolve " + sourceFileName);
160 FileObject destinationFile = ApplicationIOUtil.resolveFile(destinationFileName, "Can't resolve " + destinationFileName);
161
162 try (InputStream sourceFileIn = sourceFile.getContent().getInputStream();
163 OutputStream destinationFileOut = destinationFile.getContent().getOutputStream()) {
164 Util.copyStream(sourceFileIn, destinationFileOut, DEFAULT_IO_BUFFER_SIZE, sourceFile.getContent().getSize(), progressMonitor);
165 }
166 }
167
168 public static void copyFile(final Path source, final Path target) throws IOException {
169 copyFile(source, target, null);
170 }
171
172 public static void copyFile(final Path source, final Path target, final ProgressionCoreModel progressionModel) throws IOException {
173 Assert.notNull(source);
174 Assert.notNull(target);
175 Assert.isTrue(java.nio.file.Files.exists(source), "the source to copy must exists");
176 Assert.isTrue(java.nio.file.Files.isRegularFile(source), "the source to copy must be a regular file");
177 if (java.nio.file.Files.exists(target))
178 Assert.isTrue(!java.nio.file.Files.isSameFile(source, target), "the source and target can't be the same");
179 java.nio.file.Files.createDirectories(target.getParent());
180
181 if (progressionModel != null) {
182 progressionModel.adaptTotal(getSize(source));
183 }
184
185 try (SeekableByteChannel inputChannel = java.nio.file.Files.newByteChannel(source, READ);
186 SeekableByteChannel outputChannel = java.nio.file.Files.newByteChannel(target, CREATE, WRITE, TRUNCATE_EXISTING)) {
187 ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_IO_BUFFER_SIZE);
188 while (inputChannel.read(buffer) != -1) {
189 buffer.flip();
190 while (buffer.hasRemaining()) {
191 if (progressionModel != null) {
192 progressionModel.increments(buffer.remaining());
193 }
194 outputChannel.write(buffer);
195 }
196 buffer.clear();
197 }
198 }
199 }
200
201 public static void copyDirectory(final Path source, final Path target) throws IOException {
202 copyDirectory(source, target, null);
203 }
204
205 public static void copyDirectory(final Path source, final Path target, ProgressionCoreModel progressionModel) throws IOException {
206 Assert.notNull(source, "source is null");
207 Assert.notNull(target, "target is null");
208 Assert.isTrue(java.nio.file.Files.exists(source), "source must exists");
209 Assert.isTrue(java.nio.file.Files.isDirectory(source), "source must be a directory");
210 if (progressionModel != null) {
211 progressionModel.adaptTotal(getSize(source));
212 }
213 java.nio.file.Files.walkFileTree(source, new TreeCopier(source, target, progressionModel));
214 }
215
216 public static void moveDirectory(final Path source, final Path target, final ProgressionCoreModel progressionModel) throws IOException {
217 copyDirectory(source, target, progressionModel);
218 cleanDirectory(source, "Fail to clean directory " + source);
219 }
220
221 static class TreeCopier implements FileVisitor<Path> {
222 private final Path source;
223 private final Path target;
224 private final ProgressionCoreModel progressionModel;
225
226 TreeCopier(Path source, Path target, ProgressionCoreModel progressionModel) {
227 this.source = source;
228 this.target = target;
229 this.progressionModel = progressionModel;
230 }
231
232 @Override
233 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
234
235 Path newDir = target.resolve(source.relativize(dir));
236 try {
237 java.nio.file.Files.createDirectories(newDir);
238 } catch (IOException x) {
239 System.err.format("Unable to create: %s: %s%n", newDir, x);
240 return SKIP_SUBTREE;
241 }
242 return CONTINUE;
243 }
244
245 @Override
246 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
247
248 try {
249 copyFile(file, target.resolve(source.relativize(file)), progressionModel);
250 } catch (IOException e) {
251 System.err.format("Unable to copy: %s: %s%n", file, e);
252 }
253 return CONTINUE;
254 }
255
256 @Override
257 public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
258 return CONTINUE;
259 }
260
261 @Override
262 public FileVisitResult visitFileFailed(Path file, IOException exc) {
263 if (exc instanceof FileSystemLoopException) {
264 System.err.println("cycle detected: " + file);
265 } else {
266 System.err.format("Unable to copy: %s: %s%n", file, exc);
267 }
268 return CONTINUE;
269 }
270 }
271
272 public static void copyStream(final InputStream inputStream, final OutputStream outputStream) throws IOException {
273 copyStream(inputStream, outputStream, null);
274 }
275
276 public static void copyStream(final InputStream inputStream, final OutputStream outputStream, ProgressionCoreModel progressionModel) throws IOException {
277 byte[] buffer = new byte[DEFAULT_IO_BUFFER_SIZE];
278 int n;
279 while (-1 != (n = inputStream.read(buffer))) {
280 outputStream.write(buffer, 0, n);
281 if (progressionModel != null) {
282 progressionModel.increments(n);
283 }
284 }
285 }
286
287 public static void deleteQuietly(final List<Path> paths) {
288 if (paths == null) return;
289 paths.forEach(Files::deleteQuietly);
290 }
291
292 public static void deleteQuietly(final Path path) {
293 if (path == null) return;
294 try {
295 if (java.nio.file.Files.isDirectory(path)) {
296 cleanDirectory(path, "unable to clean " + path);
297 }
298 java.nio.file.Files.deleteIfExists(path);
299 } catch (IOException ignored) {
300 }
301 }
302
303
304
305
306
307
308
309 public static void cleanDirectory(final File directory, final String failMessage) {
310 cleanDirectory(directory.toPath(), failMessage);
311 }
312
313 public static void cleanDirectory(final Path directory, final String failMessage) {
314 cleanDirectory(directory, failMessage, null);
315 }
316
317 public static void cleanDirectory(final Path directory, final String failMessage, ProgressionCoreModel progressionModel) {
318
319 if (progressionModel != null) {
320 progressionModel.setTotal(getDirectoryFileCount(directory));
321 }
322
323 int nbAttempt = 0;
324 IOException lastException = null;
325 while (isDirectoryNotEmpty(directory) && nbAttempt < 10) {
326 nbAttempt++;
327 try {
328 java.nio.file.Files.walkFileTree(directory, new RecursiveDeleteFileVisitor(directory, progressionModel));
329 lastException = null;
330 } catch (NoSuchFileException ignored) {
331 } catch (AccessDeniedException ade) {
332 if (java.nio.file.Files.exists(Paths.get(ade.getFile()))) {
333 lastException = ade;
334 break;
335 }
336 } catch (IOException e) {
337 lastException = e;
338
339 try {
340 Thread.sleep(500);
341 } catch (InterruptedException ignored) {
342 }
343 }
344 }
345 if (lastException != null) {
346 throw new QuadrigeTechnicalException(failMessage, lastException);
347 }
348 if (LOG.isWarnEnabled() && nbAttempt > 1) {
349 LOG.warn(String.format("cleaning the directory '%s' successful after %d attempts", directory, nbAttempt));
350 }
351 }
352
353 private static boolean isDirectoryNotEmpty(final Path path) {
354 if (!java.nio.file.Files.isDirectory(path)) {
355 return false;
356 }
357 try {
358 try (DirectoryStream<Path> dirStream = java.nio.file.Files.newDirectoryStream(path)) {
359 return dirStream.iterator().hasNext();
360 }
361 } catch (IOException e) {
362 return false;
363 }
364 }
365
366 private static class RecursiveDeleteFileVisitor extends SimpleFileVisitor<Path> {
367
368 private final Path root;
369 private final ProgressionCoreModel progressionModel;
370
371 private RecursiveDeleteFileVisitor(Path root, ProgressionCoreModel progressionModel) {
372 this.root = root;
373 this.progressionModel = progressionModel;
374 }
375
376 @Override
377 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
378
379
380 java.nio.file.Files.deleteIfExists(file);
381 if (progressionModel != null)
382 progressionModel.increments(1);
383 return FileVisitResult.CONTINUE;
384 }
385
386 @Override
387 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
388
389
390 if (dir == root) {
391 return FileVisitResult.TERMINATE;
392 }
393
394
395 java.nio.file.Files.deleteIfExists(dir);
396 return FileVisitResult.CONTINUE;
397 }
398
399 }
400
401
402
403
404
405
406
407
408
409 public static List<Path> splitFile(final Path file, final long chunkSize) throws IOException {
410 long fileSize = java.nio.file.Files.size(file);
411 if (fileSize <= chunkSize) return ImmutableList.of(file);
412
413 int partCounter = 0;
414 long readPosition = 0;
415 long remainingSize = fileSize;
416 List<Path> files = new ArrayList<>();
417
418 try (FileChannel in = FileChannel.open(file, READ)) {
419 while (remainingSize > 0) {
420
421 String filePartName = String.format("%s.%03d", file.getFileName(), ++partCounter);
422 Path newFile = file.resolveSibling(filePartName);
423 try (FileChannel out = FileChannel.open(newFile, CREATE_NEW, WRITE)) {
424 for (long position = 0; position < (remainingSize < chunkSize ? remainingSize : chunkSize); ) {
425 position += in.transferTo(readPosition + position, chunkSize - position, out);
426 readPosition += position;
427 remainingSize -= position;
428 }
429 }
430 files.add(newFile);
431 }
432 }
433 return files;
434 }
435
436
437
438
439
440
441
442
443
444 public static void mergeFiles(final List<Path> files, final Path into) throws IOException {
445 try (FileChannel out = FileChannel.open(into, CREATE_NEW, WRITE)) {
446 for (Path file : files) {
447 try (FileChannel in = FileChannel.open(file, READ)) {
448 for (long position = 0, length = in.size(); position < length; ) {
449 position += in.transferTo(position, length - position, out);
450 }
451 }
452 }
453 }
454 }
455
456
457
458
459
460
461
462
463 public static List<Path> listOfFilesToMerge(final Path file) throws IOException {
464
465 List<Path> files = getDirectoryContent(file.getParent(), entry -> entry.getFileName().toString().matches(file.getFileName().toString() + "[.]\\d+"));
466 if (files == null) return null;
467 files.sort(Path::compareTo);
468 return files;
469 }
470
471 public static long getSize(final Path source) throws IOException {
472 if (source == null || !java.nio.file.Files.exists(source)) return 0;
473 if (java.nio.file.Files.isRegularFile(source)) return java.nio.file.Files.size(source);
474 MutableLong size = new MutableLong(0);
475 java.nio.file.Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
476 @Override
477 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
478 Objects.requireNonNull(file);
479 Objects.requireNonNull(attrs);
480 size.add(attrs.size());
481 return FileVisitResult.CONTINUE;
482 }
483 });
484 return size.getValue();
485 }
486
487 public static List<Path> getDirectoryContent(final Path directory) throws IOException {
488 if (directory == null || !java.nio.file.Files.isDirectory(directory)) return null;
489 List<Path> content = new ArrayList<>();
490 try (DirectoryStream<Path> directoryStream = java.nio.file.Files.newDirectoryStream(directory)) {
491 directoryStream.forEach(content::add);
492 }
493 return content;
494 }
495
496 public static List<Path> getDirectoryContent(final Path directory, final DirectoryStream.Filter<Path> filter) throws IOException {
497 if (directory == null || !java.nio.file.Files.isDirectory(directory)) return null;
498 List<Path> content = new ArrayList<>();
499 try (DirectoryStream<Path> directoryStream = java.nio.file.Files.newDirectoryStream(directory, filter)) {
500 directoryStream.forEach(content::add);
501 }
502 return content;
503 }
504
505 public static long getDirectoryFileCount(Path directory) {
506 if (directory == null || !java.nio.file.Files.exists(directory) || !java.nio.file.Files.isDirectory(directory))
507 return 0;
508 try {
509 return java.nio.file.Files.walk(directory).parallel().filter(path -> !java.nio.file.Files.isDirectory(path)).count();
510 } catch (IOException e) {
511
512 return 0;
513 }
514 }
515
516 }