Упаковка файлов в директории в один файл в Java


Моя маленькая программа (так называемый MrPackerUnpacker) принимает полный путь в качестве аргумента и создает один файл, который можно распаковать в папку, позже для выявления упакованных файлов.

Даны следующие 2 файла в папку с: /Пользователи/koraytugay/картинки:

a.txt
koray.txt

где содержимое файлов только "А" и "Корай", сохраненный в кодировке UTF-8, созданный файл будет иметь имя картинки.pckr и содержание:

0000 0000 0000 0005 0000 0000 0000 0001
612e 7478 7461 0000 0000 0000 0009 0000
0000 0000 0005 6b6f 7261 792e 7478 746b
6f72 6179 

где

0000 0000 0000 0005

представляет 5 байт, длина имени первого файла упакованные.

0000 0000 0000 0001

представляет 1 байт, длина содержимого первого файла упакованные.

612e 7478 74

представляет имя первого файла упакованные. (a.txt в данном случае).

61

представляет содержимое первого файла. (просто в этом случае).

Я думаю, вы получите представление о том, как я лепил "упакованный файл". Я еще не реализовал распаковка упакованного файла, я бы настоятельно целом, если вы можете просмотреть код для моей упаковки папку:

MrPackerUnpacker.java

import biz.tugay.mrpackerunpacker.PackerUnpacker;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class MrPackerUnpacker {

    public static void main(String[] args) {

        // Make sure the user is calling the application the right way.
        // Sample: java -jar MrPackerUnpacker pack /Users/koraytugay/Pictures
        if (args == null || args.length != 2) {
            printSampleUsageToUser();
            return;
        }

        // First argument must be a valid PackerUnpacker choice!
        final String packUnpackUserInput = args[0];
        if (!packUnpackUserInput.equals(PackerUnpacker.CHOICE_PACK)
                && !packUnpackUserInput.equals(PackerUnpacker.CHOICE_UNPACK)) {
            printSampleUsageToUser();
            return;
        }

        final Path path = Paths.get(args[1]);

        final PackerUnpacker packerUnpacker = new PackerUnpacker(packUnpackUserInput, path);
        try {
            packerUnpacker.packUnpack();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void printSampleUsageToUser() {
        final String sampleUsageDirective = "Sample usage: java -jar MrPackerUnpacker (" + PackerUnpacker.CHOICE_PACK + "|" + PackerUnpacker.CHOICE_UNPACK + ") $fullPath ";
        System.out.println(sampleUsageDirective);
    }
}

PackerUnpacker.java

package biz.tugay.mrpackerunpacker;

import biz.tugay.mrpackerunpacker.pack.Packer;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class PackerUnpacker {

    public static final String CHOICE_PACK = "pack";
    public static final String CHOICE_UNPACK = "unpack";
    public static final String EXTENSION = ".pckr";

    private final String packUnpackUserInput;
    private final Path path;

    public PackerUnpacker(final String packUnpackUserInput, final Path path) {
        this.packUnpackUserInput = packUnpackUserInput;
        this.path = path;
    }

    public void packUnpack() throws IOException {
        if (!Files.exists(path)) {
            throw new FileNotFoundException("The path you are trying to pack/unpack does not seem to exist!");
        }

        if (packUnpackUserInput.equals(CHOICE_PACK)) {
            packPath();
        }

        if (packUnpackUserInput.equals(CHOICE_UNPACK)) {
            unpackPath();
        }
    }

    private void packPath() throws IOException {
        final Packer packer = new Packer(path);
        packer.pack();
    }

    private void unpackPath() {
        // Not implemented yet...
    }

}

Packer.java

package biz.tugay.mrpackerunpacker.pack;

import biz.tugay.mrpackerunpacker.PackerUnpacker;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;

public class Packer {

    private final Path pathToPack;

    public Packer(final Path pathToPack) throws IOException {
        final boolean isPathToPackDirectory = Files.isDirectory(pathToPack);
        if (!isPathToPackDirectory) {
            throw new IOException("If you are trying to pack a path, it must be a directory!");
        }
        this.pathToPack = pathToPack;
    }

    public void pack() throws IOException {
        final PackFile packFile = new PackFile(pathToPack);

        final File output = new File(pathToPack.getFileName().toString() + PackerUnpacker.EXTENSION);

        final DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(output));

        for (PackedFileMeta packedFileMeta : packFile.getPackFileMetas()) {
            dataOutputStream.writeLong(packedFileMeta.getFilenameLength());
            dataOutputStream.writeLong(packedFileMeta.getFileLength());
            dataOutputStream.write(packedFileMeta.getFileNameUTF8Encoded());
            IOUtils.copyLarge(new FileInputStream(packedFileMeta.getFilePath().toFile()), dataOutputStream);
        }

        dataOutputStream.flush();
        dataOutputStream.close();
    }
}

PackFile.java

package biz.tugay.mrpackerunpacker.pack;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public class PackFile {

    private List<PackedFileMeta> packedFileMetas = new ArrayList<>();

    public PackFile(final Path pathToPack) throws IOException {
        DirectoryStream<Path> pathToPackDirectoryStream = Files.newDirectoryStream(pathToPack);
        for (Path path : pathToPackDirectoryStream) {
            if (Files.isDirectory(path)) {
                continue; // We do not recursivly pack folders, only files in a folder.
            }
            if (path.getFileName().toString().startsWith(".")) {
                continue; // Skip hidden files!
            }
            final PackedFileMeta packedFileMeta = new PackedFileMeta(path);
            packedFileMetas.add(packedFileMeta);
        }
    }

    public List<PackedFileMeta> getPackFileMetas() {
        return packedFileMetas;
    }
}

PackedFileMeta.java

package biz.tugay.mrpackerunpacker.pack;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class PackedFileMeta {

    private final Path filePath;
    private long filenameLength;
    private long fileLength;
    private byte[] fileNameUTF8Encoded;

    public PackedFileMeta(Path filePath) throws IOException {
        this.filePath = filePath;
        fileNameUTF8Encoded = filePath.getFileName().toString().getBytes(StandardCharsets.UTF_8);
        filenameLength = fileNameUTF8Encoded.length;
        fileLength = Files.size(filePath);
    }

    public Path getFilePath() {
        return filePath;
    }

    public long getFilenameLength() {
        return filenameLength;
    }

    public byte[] getFileNameUTF8Encoded() {
        return fileNameUTF8Encoded;
    }

    public long getFileLength() {
        return fileLength;
    }
}

Меня больше интересует читаемость моего кода.



89
2
задан 27 января 2018 в 07:01 Источник Поделиться
Комментарии
1 ответ

Во-первых, читабельность этого кода ОК. Существа достаточно хорошо структурирована и попытка соблюдения принципа СРП рассматривается.

Генеральное Проектирование

Однако, я бы предложил несколько переименований и изменения дизайна. В PackerUpacker класс напоминает фабрику, потому что он создает бизнес-логики сущностей (Packer или Unpacker). Но и он выбирает одну из них и вызывает основное действие: это слишком много для него, так что давайте разделять роли.

Мы можем представить интерфейс, который обеспечивает доступ к основному действию:

public interface PathProcessor {

void processPath(Path pathToProcess) throws IOException;

}

Он может также рассматриваться как абстрактный класс, потому что оба реализаторы могут делиться определенное сходство (например. проверка пути).

PathProcessor будут реализованы/расширены как Packer или Unpacker классов.

PathProcessorFactory (бывший PackerUnpacker) будет решать, в зависимости от входных аргументов, которые лицо, чтобы инстанцировать:

public static PathProcessor newPathProcessor(String userChoice) {
// instantiate and return `Packer` or `Unpacker` depending on the arg
}

Инструкции в методе Main теперь стало:

PathProcessor pathProcessor = PathProcessorFactory.newPathProcessor(packUnpackUserInput);
pathProcessor.processPath(path);

Другие

Packer

Потоки внутри pack() не завернутый в попытки с ресурсами, но должны быть, чтобы избежать отъездом незамкнутого потока в случае исключения.

PackFile

Идея разделить его на выделенный класс не понятно. (Кстати, это не хорошая практика, чтобы включать много логики в constuctors, ни бросить проверенные исключения из них). Его единственная роль обернуть packedFileMetasтак почему бы не выпускать их в служебный метод, экс. public static List<PackedFileMeta> collectFilesToPack(Path folderToPack)?

И там должны быть попытки с ресурсами упаковки инициализации pathToPackDirectoryStream.

Чтобы проверить, является ли файл скрытым или нет, path.getFileName().toString().startsWith(".") не надежный, потому что имя файла может начинаться с точки, без файла скрытые. Лучший способ проверить это Files.isHidden(Path).

PackedFileMeta

Все поля должны быть final.

Поскольку этот объект создается только в PackFile собирая последовательности и помещены в тот же пакет, я хотел бы предложить, чтобы уменьшить его видимость (и его методы) на уровне пакета, но это очень незначительная проблема.

1
ответ дан 27 января 2018 в 09:01 Источник Поделиться