001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.internal.impl;
020
021import javax.inject.Named;
022import javax.inject.Singleton;
023
024import java.io.BufferedInputStream;
025import java.io.BufferedOutputStream;
026import java.io.File;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.OutputStream;
030import java.io.UncheckedIOException;
031import java.nio.ByteBuffer;
032import java.nio.charset.StandardCharsets;
033import java.nio.file.Files;
034import java.nio.file.StandardCopyOption;
035
036import org.eclipse.aether.spi.io.FileProcessor;
037import org.eclipse.aether.util.ChecksumUtils;
038import org.eclipse.aether.util.FileUtils;
039
040/**
041 * A utility class helping with file-based operations.
042 */
043@Singleton
044@Named
045public class DefaultFileProcessor implements FileProcessor {
046
047    /**
048     * Thread-safe variant of {@link File#mkdirs()}. Creates the directory named by the given abstract pathname,
049     * including any necessary but nonexistent parent directories. Note that if this operation fails it may have
050     * succeeded in creating some of the necessary parent directories.
051     *
052     * @param directory The directory to create, may be {@code null}.
053     * @return {@code true} if and only if the directory was created, along with all necessary parent directories;
054     * {@code false} otherwise
055     */
056    public boolean mkdirs(File directory) {
057        if (directory == null) {
058            return false;
059        }
060
061        if (directory.exists()) {
062            return false;
063        }
064        if (directory.mkdir()) {
065            return true;
066        }
067
068        File canonDir;
069        try {
070            canonDir = directory.getCanonicalFile();
071        } catch (IOException e) {
072            throw new UncheckedIOException(e);
073        }
074
075        File parentDir = canonDir.getParentFile();
076        return (parentDir != null && (mkdirs(parentDir) || parentDir.exists()) && canonDir.mkdir());
077    }
078
079    public void write(File target, String data) throws IOException {
080        FileUtils.writeFile(target.toPath(), p -> Files.write(p, data.getBytes(StandardCharsets.UTF_8)));
081    }
082
083    public void write(File target, InputStream source) throws IOException {
084        FileUtils.writeFile(target.toPath(), p -> Files.copy(source, p, StandardCopyOption.REPLACE_EXISTING));
085    }
086
087    public void copy(File source, File target) throws IOException {
088        copy(source, target, null);
089    }
090
091    public long copy(File source, File target, ProgressListener listener) throws IOException {
092        try (InputStream in = new BufferedInputStream(Files.newInputStream(source.toPath()));
093                FileUtils.CollocatedTempFile tempTarget = FileUtils.newTempFile(target.toPath());
094                OutputStream out = new BufferedOutputStream(Files.newOutputStream(tempTarget.getPath()))) {
095            long result = copy(out, in, listener);
096            tempTarget.move();
097            return result;
098        }
099    }
100
101    private long copy(OutputStream os, InputStream is, ProgressListener listener) throws IOException {
102        long total = 0L;
103        byte[] buffer = new byte[1024 * 32];
104        while (true) {
105            int bytes = is.read(buffer);
106            if (bytes < 0) {
107                break;
108            }
109
110            os.write(buffer, 0, bytes);
111
112            total += bytes;
113
114            if (listener != null && bytes > 0) {
115                try {
116                    listener.progressed(ByteBuffer.wrap(buffer, 0, bytes));
117                } catch (Exception e) {
118                    // too bad
119                }
120            }
121        }
122
123        return total;
124    }
125
126    public void move(File source, File target) throws IOException {
127        if (!source.renameTo(target)) {
128            copy(source, target);
129
130            target.setLastModified(source.lastModified());
131
132            source.delete();
133        }
134    }
135
136    @Override
137    public String readChecksum(final File checksumFile) throws IOException {
138        // for now do exactly same as happened before, but FileProcessor is a component and can be replaced
139        return ChecksumUtils.read(checksumFile);
140    }
141
142    @Override
143    public void writeChecksum(final File checksumFile, final String checksum) throws IOException {
144        // for now do exactly same as happened before, but FileProcessor is a component and can be replaced
145        write(checksumFile, checksum);
146    }
147}