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