at.beris.virtualfile.shell.Shell.java Source code

Java tutorial

Introduction

Here is the source code for at.beris.virtualfile.shell.Shell.java

Source

/*
 * This file is part of VirtualFile.
 *
 * Copyright 2016 by Bernd Riedl <bernd.riedl@gmail.com>
 *
 * Licensed under GNU Lesser General Public License 3.0 or later.
 * Some rights reserved. See COPYING, AUTHORS.
 */

package at.beris.virtualfile.shell;

import at.beris.virtualfile.File;
import at.beris.virtualfile.FileContext;
import at.beris.virtualfile.FileModel;
import at.beris.virtualfile.attribute.PosixFilePermission;
import at.beris.virtualfile.provider.operation.CopyListener;
import at.beris.virtualfile.util.FileUtils;
import at.beris.virtualfile.util.UrlUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URL;
import java.nio.file.NoSuchFileException;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.UserPrincipal;
import java.text.DateFormat;
import java.util.*;

import static at.beris.virtualfile.util.UrlUtils.maskedUrlString;

public class Shell {
    private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(Shell.class);
    private final static String NOT_CONNECTED_MSG = "You're not connected. use con.";
    private final static String CANT_GO_UP_FROM_ROOT_MSG = "You can't change up from the root directory.";
    private final static DateFormat DATE_FORMATTER = DateFormat.getDateInstance();
    private final static DateFormat TIME_FORMATTER = DateFormat.getTimeInstance();

    private FileContext fileContext;
    private File localFile;
    private File workingFile;
    private Scanner scanner;

    public Shell() throws IOException {
        fileContext = new FileContext();
        localFile = fileContext.newLocalFile(System.getProperty("user.dir"));
        scanner = new Scanner(System.in);
        scanner.useDelimiter("\n");
    }

    public static void main(String args[]) {
        try {
            new Shell().run();
        } catch (IOException e) {
            logException(e);
        }
    }

    public void run() {
        System.out.println("VirtualFile Shell");
        String line;
        Pair<Command, List<String>> cmd = null;

        do {
            try {
                System.out.print("> ");
                line = StringUtils.trim(scanner.next());
                cmd = parseCommandLine(line);
                if (commandArgumentsValid(cmd))
                    processCommand(cmd);
            } catch (IOException e) {
                logException(e);
                System.out.println("Error: " + e.getMessage());
            }

        } while (cmd != null && cmd.getLeft() != Command.QUIT);
    }

    private Pair<Command, List<String>> parseCommandLine(String line) {
        List<String> lineParts = new LinkedList<>(Arrays.asList(line.split(" ")));

        for (Command availCmd : Command.values()) {
            if (availCmd.toString().equals(lineParts.get(0).toUpperCase())) {
                lineParts.remove(0);
                return Pair.of(availCmd, lineParts);
            }
        }
        return Pair.of(Command.UNKNOWN, Collections.<String>emptyList());
    }

    private void processCommand(Pair<Command, List<String>> cmd) throws IOException {
        switch (cmd.getLeft()) {
        case CD:
            change(cmd.getRight().get(0), false);
            break;
        case CON:
            connect(new URL(cmd.getRight().get(0)));
            break;
        case DIS:
            disconnect();
            break;
        case GET:
            get(cmd.getRight().get(0));
            break;
        case HELP:
            displayHelp();
            break;
        case LCD:
            change(cmd.getRight().get(0), true);
            break;
        case LPWD:
            System.out.println(localFile.getPath());
            break;
        case LLS:
            list(true);
            break;
        case LRM:
            remove(true, cmd.getRight().get(0));
            break;
        case LS:
            list(false);
            break;
        case PUT:
            put(cmd.getRight().get(0));
            break;
        case PWD:
            System.out.println(workingFile != null ? maskedUrlString(workingFile.getUrl()) : NOT_CONNECTED_MSG);
            break;
        case RM:
            remove(false, cmd.getRight().get(0));
            break;
        case STAT:
            displayStatistics();
            break;
        case QUIT:
            quit();
            break;
        default:
            System.out.println("Unknown command.");
        }
    }

    private void disconnect() {
        if (workingFile == null)
            System.out.println(NOT_CONNECTED_MSG);
        else
            workingFile = null;
    }

    private boolean commandArgumentsValid(Pair<Command, List<String>> cmd) {
        StringBuilder sb = new StringBuilder();
        Command cmdOp = cmd.getLeft();
        int[] expectedArgCounts = cmdOp.getArgumentCounts();
        List<String> actualArgs = cmd.getRight();

        sb.append("Wrong number of arguments for Command ").append(cmdOp.toString().toLowerCase())
                .append(". Expected ");

        boolean argCountMatches = false;
        for (int expectedArgCount : expectedArgCounts) {
            if (expectedArgCount == actualArgs.size())
                argCountMatches = true;
            sb.append(expectedArgCount).append(" or ");
        }
        sb.delete(sb.length() - 4, sb.length()).append(".");

        if (!argCountMatches)
            System.out.println(sb.toString());

        return argCountMatches;
    }

    private void displayHelp() {
        String helpFormatStr = "%-10s - %s";
        for (Command cmd : Command.values()) {
            if (cmd == Command.UNKNOWN)
                continue;
            System.out.println(String.format(helpFormatStr, cmd.toString().toLowerCase(), cmd.getDescription()));
        }
    }

    private void displayStatistics() {
        Runtime runtime = Runtime.getRuntime();
        System.out.println("** Heap utilization statistics [KB] **");
        System.out
                .println(String.format("Used Memory: %,d", (runtime.totalMemory() - runtime.freeMemory()) / 1024));
        System.out.println(String.format("Free Memory: %,d", runtime.freeMemory() / 1024));
        System.out.println(String.format("Total Memory: %,d", runtime.totalMemory() / 1024));
        System.out.println(String.format("Max Memory: %,d", runtime.maxMemory() / 1024));
    }

    private void quit() throws IOException {
        fileContext.dispose();
    }

    private void connect(URL url) throws IOException {
        workingFile = fileContext.newFile(url);
    }

    private void list(boolean local) throws IOException {
        File file = local ? localFile : workingFile;

        if (file == null && !local) {
            System.out.println(NOT_CONNECTED_MSG);
            return;
        }

        int maxLengthOwner = 0, maxLengthGroup = 0, maxLengthSize = 0;
        int maxLengthDateStr = 0, maxLengthTimeStr = 0;

        StringBuilder sb = new StringBuilder();
        List<ExtFileModel> fileModelList = new ArrayList<>();
        for (File childFile : file.list()) {
            ExtFileModel model = new ExtFileModel();
            model.setUrl(childFile.getUrl());
            model.setAttributes(childFile.getAttributes());
            model.setDirectory(childFile.isDirectory());
            model.setOwner(childFile.getOwner());
            model.setGroup(childFile.getGroup());
            model.setSize(childFile.getSize());
            model.setLastModifiedTime(childFile.getLastModifiedTime());

            UserPrincipal owner = model.getOwner();
            if (owner != null && maxLengthOwner < owner.getName().length())
                maxLengthOwner = model.getOwner().getName().length();
            GroupPrincipal group = model.getGroup();
            if (group != null && maxLengthGroup < group.getName().length())
                maxLengthGroup = model.getGroup().getName().length();
            String sizeString = String.valueOf(model.getSize());
            if (maxLengthSize < sizeString.length())
                maxLengthSize = sizeString.length();

            if (model.getLastModifiedTime() != null) {
                Date lastModifiedDate = new Date(model.getLastModifiedTime().toMillis());
                model.dateStr = DATE_FORMATTER.format(lastModifiedDate);
                if (maxLengthDateStr < model.dateStr.length())
                    maxLengthDateStr = model.dateStr.length();
                model.timeStr = TIME_FORMATTER.format(lastModifiedDate);
                if (maxLengthTimeStr < model.timeStr.length())
                    maxLengthTimeStr = model.timeStr.length();
            } else {
                model.dateStr = "";
                model.timeStr = "";
            }

            fileModelList.add(model);
        }

        for (ExtFileModel model : fileModelList) {
            sb.setLength(0);
            sb.append(model.isDirectory() ? 'd' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.OWNER_READ) ? 'r' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.OWNER_WRITE) ? 'w' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.OWNER_EXECUTE) ? 'x' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.GROUP_READ) ? 'r' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.GROUP_WRITE) ? 'w' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.GROUP_EXECUTE) ? 'x' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.OTHERS_READ) ? 'r' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.OTHERS_WRITE) ? 'w' : '-');
            sb.append(model.getAttributes().contains(PosixFilePermission.OTHERS_EXECUTE) ? 'x' : '-').append(' ');
            sb.append(StringUtils.rightPad(model.getOwner().getName(), maxLengthOwner, ' ')).append(' ');
            sb.append(StringUtils.rightPad(model.getGroup().getName(), maxLengthGroup, ' ')).append(' ');
            sb.append(StringUtils.leftPad(String.valueOf(model.getSize()), maxLengthSize, ' ')).append(' ');
            sb.append(StringUtils.leftPad(model.dateStr, maxLengthDateStr, ' ')).append(' ');
            sb.append(StringUtils.leftPad(model.timeStr, maxLengthTimeStr, ' ')).append(' ');
            sb.append(FileUtils.getName(model.getUrl().toString()));
            System.out.println(sb.toString());
        }

        for (FileModel fileModel : fileModelList)
            fileModel.clear();
        fileModelList.clear();
    }

    private void change(String directoryName, boolean local) throws IOException {
        if (!local && workingFile == null) {
            System.out.println(NOT_CONNECTED_MSG);
            return;
        }

        File file = local ? localFile : workingFile;

        if ("/".equals(file.getPath()) && "..".equals(directoryName)) {
            System.out.println(CANT_GO_UP_FROM_ROOT_MSG);
            return;
        }

        URL newUrl;
        if (directoryName.startsWith("/"))
            newUrl = UrlUtils.normalizeUrl(UrlUtils.newUrlReplacePath(file.getUrl(),
                    directoryName + (directoryName.endsWith("/") ? "" : "/")));
        else {
            newUrl = UrlUtils.normalizeUrl(UrlUtils
                    .newUrl(file.getUrl().toString() + directoryName + (directoryName.endsWith("/") ? "" : "/")));
        }

        File actionFile = fileContext.newFile(newUrl);

        if (actionFile.isSymbolicLink())
            actionFile = fileContext.newFile(actionFile.getLinkTarget());

        if (actionFile.exists()) {
            if (local)
                localFile = actionFile;
            else
                workingFile = actionFile;
        } else
            throw new NoSuchFileException(actionFile.getUrl().toString());
    }

    private void get(String fileName) throws IOException {
        if (workingFile == null) {
            System.out.println(NOT_CONNECTED_MSG);
            return;
        }

        URL actionUrl = UrlUtils.newUrl(workingFile.getUrl(), fileName);
        File actionFile = fileContext.newFile(actionUrl);
        actionFile.copy(localFile, new CustomCopyListener());
        System.out.println("");
    }

    private void put(String fileName) throws IOException {
        if (workingFile == null) {
            System.out.println(NOT_CONNECTED_MSG);
            return;
        }

        URL sourceUrl = UrlUtils.normalizeUrl(UrlUtils.newUrl(localFile.getUrl(), fileName));
        File sourceFile = fileContext.newFile(sourceUrl);
        URL targetUrl = UrlUtils.normalizeUrl(UrlUtils.newUrl(workingFile.getUrl(), fileName));
        File targetFile = fileContext.newFile(targetUrl);

        sourceFile.copy(targetFile, new CustomCopyListener());
        System.out.println("");
    }

    private void remove(boolean local, String fileName) throws IOException {
        if (!local && workingFile == null) {
            System.out.println(NOT_CONNECTED_MSG);
            return;
        }

        URL actionUrl = UrlUtils
                .normalizeUrl(UrlUtils.newUrl((local ? localFile : workingFile).getUrl(), fileName));
        fileContext.newFile(actionUrl).delete();
    }

    private static void logException(Exception e) {
        LOGGER.error("Exception occured.", e);
    }

    private class CustomCopyListener implements CopyListener {
        private final static char PROGRESS_CHAR = '*';
        private final static int NUM_PROGRESS_CHARS_100_PERC = 80;

        private int progressCharsPrinted;

        @Override
        public void startFile(File file, long currentFileNumber) {
            try {
                System.out.println(
                        String.format("Copying %s [%,d KB]", file.getPath(), Math.round(file.getSize() / 1024)));
                progressCharsPrinted = 0;
            } catch (IOException e) {
                logException(e);
            }
        }

        @Override
        public void finishedFile(File file) {
            if (NUM_PROGRESS_CHARS_100_PERC - progressCharsPrinted > 0)
                System.out.print(
                        StringUtils.repeat(PROGRESS_CHAR, (NUM_PROGRESS_CHARS_100_PERC - progressCharsPrinted)));
            System.out.println("");
        }

        @Override
        public void afterBlockCopied(long fileSize, long bytesCopiedBlock, long bytesCopiedTotal) {
            int percent = (int) ((bytesCopiedTotal * 100) / fileSize);
            //            int currentNumProgressCharsToPrint = percent / (100 / NUM_PROGRESS_CHARS_100_PERC);
            int currentNumProgressCharsToPrint = (int) Math
                    .floor(percent / (100 / (float) NUM_PROGRESS_CHARS_100_PERC));

            if (progressCharsPrinted < currentNumProgressCharsToPrint) {
                System.out.print(PROGRESS_CHAR);
                System.out.flush();
                progressCharsPrinted++;
            }
        }

        @Override
        public boolean interrupt() {
            return false;
        }

        @Override
        public boolean fileExists(File file) {
            try {
                System.out.println(file.getPath() + " already exists. Overwrite? (y/n)");
                String next = scanner.next();
                if ("y".equals(next.toLowerCase())) {
                    file.delete();
                    return true;
                }
            } catch (IOException e) {
                logException(e);
            }
            return false;
        }
    }

    private class ExtFileModel extends FileModel {
        public String dateStr;
        public String timeStr;
    }
}