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;
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
046        implements FileProcessor
047{
048
049    /**
050     * Thread-safe variant of {@link File#mkdirs()}. Creates the directory named by the given abstract pathname,
051     * including any necessary but nonexistent parent directories. Note that if this operation fails it may have
052     * succeeded in creating some of the necessary parent directories.
053     *
054     * @param directory The directory to create, may be {@code null}.
055     * @return {@code true} if and only if the directory was created, along with all necessary parent directories;
056     * {@code false} otherwise
057     */
058    public boolean mkdirs( File directory )
059    {
060        if ( directory == null )
061        {
062            return false;
063        }
064
065        if ( directory.exists() )
066        {
067            return false;
068        }
069        if ( directory.mkdir() )
070        {
071            return true;
072        }
073
074        File canonDir;
075        try
076        {
077            canonDir = directory.getCanonicalFile();
078        }
079        catch ( IOException e )
080        {
081            throw new UncheckedIOException( e );
082        }
083
084        File parentDir = canonDir.getParentFile();
085        return ( parentDir != null && ( mkdirs( parentDir ) || parentDir.exists() ) && canonDir.mkdir() );
086    }
087
088    public void write( File target, String data )
089            throws IOException
090    {
091        FileUtils.writeFile( target.toPath(), p -> Files.write( p, data.getBytes( StandardCharsets.UTF_8 ) ) );
092    }
093
094    public void write( File target, InputStream source )
095            throws IOException
096    {
097        FileUtils.writeFile( target.toPath(), p -> Files.copy( source, p ) );
098    }
099
100    public void copy( File source, File target )
101            throws IOException
102    {
103        copy( source, target, null );
104    }
105
106    public long copy( File source, File target, ProgressListener listener )
107            throws IOException
108    {
109        try ( InputStream in = new BufferedInputStream( Files.newInputStream( source.toPath() ) );
110              FileUtils.CollocatedTempFile tempTarget = FileUtils.newTempFile( target.toPath() );
111              OutputStream out = new BufferedOutputStream( Files.newOutputStream( tempTarget.getPath() ) ) )
112        {
113            long result = copy( out, in, listener );
114            tempTarget.move();
115            return result;
116        }
117    }
118
119    private long copy( OutputStream os, InputStream is, ProgressListener listener )
120            throws IOException
121    {
122        long total = 0L;
123        byte[] buffer = new byte[1024 * 32];
124        while ( true )
125        {
126            int bytes = is.read( buffer );
127            if ( bytes < 0 )
128            {
129                break;
130            }
131
132            os.write( buffer, 0, bytes );
133
134            total += bytes;
135
136            if ( listener != null && bytes > 0 )
137            {
138                try
139                {
140                    listener.progressed( ByteBuffer.wrap( buffer, 0, bytes ) );
141                }
142                catch ( Exception e )
143                {
144                    // too bad
145                }
146            }
147        }
148
149        return total;
150    }
151
152    public void move( File source, File target )
153            throws IOException
154    {
155        if ( !source.renameTo( target ) )
156        {
157            copy( source, target );
158
159            target.setLastModified( source.lastModified() );
160
161            source.delete();
162        }
163    }
164
165    @Override
166    public String readChecksum( final File checksumFile ) throws IOException
167    {
168        // for now do exactly same as happened before, but FileProcessor is a component and can be replaced
169        return ChecksumUtils.read( checksumFile );
170    }
171
172    @Override
173    public void writeChecksum( final File checksumFile, final String checksum ) throws IOException
174    {
175        // for now do exactly same as happened before, but FileProcessor is a component and can be replaced
176        write( checksumFile, checksum );
177    }
178}