org.fim.internal.hash.FileHasher.java Source code

Java tutorial

Introduction

Here is the source code for org.fim.internal.hash.FileHasher.java

Source

/*
 * This file is part of Fim - File Integrity Manager
 *
 * Copyright (C) 2015  Etienne Vrignaud
 *
 * Fim is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fim is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Fim.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fim.internal.hash;

import static org.fim.model.Constants.NO_HASH;
import static org.fim.model.HashMode.dontHash;

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermissions;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.SystemUtils;
import org.fim.model.Attribute;
import org.fim.model.Context;
import org.fim.model.FileAttribute;
import org.fim.model.FileHash;
import org.fim.model.FileState;
import org.fim.model.HashMode;
import org.fim.model.Range;
import org.fim.util.Console;
import org.fim.util.DosFilePermissions;
import org.fim.util.FileUtil;
import org.fim.util.Logger;
import org.fim.util.SELinux;
import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;

public class FileHasher implements Runnable {
    private final HashProgress hashProgress;
    private final BlockingDeque<Path> filesToHashQueue;
    private final String rootDir;

    private final List<FileState> fileStates;

    private final FrontHasher frontHasher;
    private Context context;

    public FileHasher(Context context, HashProgress hashProgress, BlockingDeque<Path> filesToHashQueue,
            String rootDir) throws NoSuchAlgorithmException {
        this.context = context;
        this.hashProgress = hashProgress;
        this.filesToHashQueue = filesToHashQueue;
        this.rootDir = rootDir;

        this.fileStates = new ArrayList<>();

        HashMode hashMode = hashProgress.getContext().getHashMode();
        frontHasher = new FrontHasher(hashMode);
    }

    public List<FileState> getFileStates() {
        return fileStates;
    }

    public long getTotalBytesHashed() {
        return frontHasher.getTotalBytesHashed();
    }

    protected FrontHasher getFrontHasher() {
        return frontHasher;
    }

    @Override
    public void run() {
        try {
            Path file;
            while ((file = filesToHashQueue.poll(500, TimeUnit.MILLISECONDS)) != null) {
                try {
                    BasicFileAttributes attributes;
                    List<Attribute> fileAttributes = null;

                    if (SystemUtils.IS_OS_WINDOWS) {
                        DosFileAttributes dosFileAttributes = Files.readAttributes(file, DosFileAttributes.class);
                        fileAttributes = addAttribute(fileAttributes, FileAttribute.DosFilePermissions,
                                DosFilePermissions.toString(dosFileAttributes));
                        attributes = dosFileAttributes;
                    } else {
                        PosixFileAttributes posixFileAttributes = Files.readAttributes(file,
                                PosixFileAttributes.class);
                        fileAttributes = addAttribute(fileAttributes, FileAttribute.PosixFilePermissions,
                                PosixFilePermissions.toString(posixFileAttributes.permissions()));
                        if (SELinux.ENABLED) {
                            fileAttributes = addAttribute(fileAttributes, FileAttribute.SELinuxLabel,
                                    SELinux.getLabel(context, file));
                        }
                        attributes = posixFileAttributes;
                    }

                    hashProgress.updateOutput(attributes.size());

                    FileHash fileHash = hashFile(file, attributes.size());
                    String normalizedFileName = FileUtil.getNormalizedFileName(file);
                    String relativeFileName = FileUtil.getRelativeFileName(rootDir, normalizedFileName);

                    fileStates.add(new FileState(relativeFileName, attributes, fileHash, fileAttributes));
                } catch (Exception ex) {
                    Console.newLine();
                    Logger.error("Skipping - Error hashing file '" + file + "'", ex, context.isDisplayStackTrace());
                }
            }
        } catch (InterruptedException ex) {
            Logger.error("Exception while hashing", ex, context.isDisplayStackTrace());
        }
    }

    private List<Attribute> addAttribute(List<Attribute> attributes, FileAttribute attribute, String value) {
        if (value == null) {
            return attributes;
        }

        List<Attribute> newAttributes = attributes;
        if (newAttributes == null) {
            newAttributes = new ArrayList<>();
        }

        newAttributes.add(new Attribute(attribute.name(), value));

        return newAttributes;
    }

    protected FileHash hashFile(Path file, long fileSize) throws IOException {
        HashMode hashMode = hashProgress.getContext().getHashMode();

        if (hashMode == dontHash) {
            return new FileHash(NO_HASH, NO_HASH, NO_HASH);
        }

        frontHasher.reset(fileSize);

        long filePosition = 0;
        long blockSize;
        int bufferSize;

        try (final FileChannel channel = FileChannel.open(file)) {
            while (filePosition < fileSize) {
                Range nextRange = frontHasher.getNextRange(filePosition);
                if (nextRange == null) {
                    break;
                }

                filePosition = nextRange.getFrom();
                blockSize = nextRange.getTo() - nextRange.getFrom();
                bufferSize = hashBuffer(channel, filePosition, blockSize);
                filePosition += bufferSize;
            }
        }

        if (false == frontHasher.hashComplete()) {
            throw new RuntimeException(String.format(
                    "Fim is not working correctly for file '%s' (size=%d). Some Hasher have not completed: small=%s, medium=%s, full=%s",
                    file, fileSize, frontHasher.getSmallBlockHasher().hashComplete(),
                    frontHasher.getMediumBlockHasher().hashComplete(), frontHasher.getFullHasher().hashComplete()));
        }

        return frontHasher.getFileHash();
    }

    private int hashBuffer(FileChannel channel, long filePosition, long size) throws IOException {
        MappedByteBuffer buffer = null;
        try {
            buffer = channel.map(FileChannel.MapMode.READ_ONLY, filePosition, size);
            int bufferSize = buffer.remaining();

            frontHasher.update(filePosition, buffer);

            return bufferSize;
        } finally {
            unmap(buffer);
        }
    }

    /**
     * Comes from here: http://stackoverflow.com/questions/8553158/prevent-outofmemory-when-using-java-nio-mappedbytebuffer
     */
    private void unmap(MappedByteBuffer bb) {
        if (bb == null) {
            return;
        }
        Cleaner cleaner = ((DirectBuffer) bb).cleaner();
        if (cleaner != null) {
            cleaner.clean();
        }
    }
}