public class LXC {

    /**
     * The internal version id.
     * For automatic updates.
     */
    public static final int versionId = 170;
    /**
     * The external version id.
     */
    public static final String versionString = "v1.50";
    /**
     * Logger for core components
     */
    private final LXCLogger logger;
    /**
     * The Platform we are running on.
     */
    private final Platform platform;
    /**
     * Our GUI.
     */
    private final GuiInterface gui;
    /**
     * The networkmanager.
     */
    private final NetworkManager network;
    /**
     * The filemanager.
     */
    private final FileManager files;
    /**
     * The default target for downloads.
     */
    private File defaultDownloadTarget;
    /**
     * Use default target or ask.
     */
    private boolean askForDownloadTargetSupported;

    /**
     * Create a new instance of LXC with the given command-line options.
     * Should be called by the main-method only.
     *
     * @param platform the platform LXC is running on
     * @param args command-line args
     */
    public LXC(Platform platform, final String[] args) {
        logger = LXCLogBackend.getLogger("core");
        this.platform = platform;

        logger.info("This is LanXchange " + versionString + " (" + versionId + ") - Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Tobias Fleig - License GPLv3 or later");

        platform.readConfiguration(args);

        if (platform.hasAutoUpdates()) {
            platform.postUpdateStep(args);
            platform.checkAndPerformUpdates(args);
        }

        gui = platform.getGui(args);

        // set up listeners
        initListeners();

        gui.init();

        askForDownloadTargetSupported = platform.askForDownloadTargetSupported();
        if (platform.getDefaultDownloadTarget() != null) {
            defaultDownloadTarget = new File(platform.getDefaultDownloadTarget());
        }

        // create components
        files = new FileManager();

        // init networking
        network = new NetworkManager(new NetworkManagerListener() {
            @Override
            public void listReceived(TransFileList list, LXCInstance sender) {
                FileListChangeSet changes = files.computeFileList(list, sender);
                if (!changes.getRemovedFiles().isEmpty()) {
                    // no way of picking multiple files in larger set, must serialize
                    for (FileListChangeSet.FileModification modification : changes.getRemovedFiles()) {
                        gui.notifyFileChange(GuiInterface.UPDATE_ORIGIN_REMOTE, GuiInterface.UPDATE_OPERATION_REMOVE, modification.index, 1, Collections.singletonList(modification.file));
                    }
                }
                if (!changes.getAddedFiles().isEmpty()) {
                    // manual list copy. need Java 8 in this module soon...
                    List<LXCFile> addedLXCFiles = new ArrayList<LXCFile>();
                    for (FileListChangeSet.FileModification addition : changes.getAddedFiles()) {
                        addedLXCFiles.add(addition.file);
                    }
                    gui.notifyFileChange(GuiInterface.UPDATE_ORIGIN_REMOTE, GuiInterface.UPDATE_OPERATION_ADD, changes.getAddedFiles().get(0).index, changes.getAddedFiles().size(), addedLXCFiles);
                }
            }

            @Override
            public void refreshGui() {
                gui.update();
            }

            @Override
            public void notifyJobAdded(LXCFile file, int jobIndex) {
                gui.notifyJobChange(GuiInterface.UPDATE_OPERATION_ADD, file, jobIndex);
            }

            @Override
            public void notifyRemoveJob(LXCFile file, int jobIndex) {
                gui.notifyJobChange(GuiInterface.UPDATE_OPERATION_REMOVE, file, jobIndex);
            }

            @Override
            public void instanceRemoved(LXCInstance removedInstance) {
                FileListChangeSet removals = files.instanceRemoved(removedInstance);
                if (!removals.getRemovedFiles().isEmpty()) {
                    // no way of picking multiple files in larger set, must serialize
                    for (FileListChangeSet.FileModification modification : removals.getRemovedFiles()) {
                        gui.notifyFileChange(GuiInterface.UPDATE_ORIGIN_REMOTE, GuiInterface.UPDATE_OPERATION_REMOVE, modification.index, 1, Collections.singletonList(modification.file));
                    }
                }
            }

            @Override
            public void downloadComplete(LXCFile file, File targetFolder) {
                LXC.this.platform.downloadComplete(file, targetFolder);
            }

            @Override
            public void downloadFailedFileMissing() {
                gui.showError("At least one file could not be downloaded because it is no longer offered.", "");
            }

            @Override
            public void downloadFailedFileOk(String problemFilePath) {
                gui.showError("Download error, unable to create/write file:\n\"" + problemFilePath + "\"\nPossible reasons include missing write permissions and insufficient free space.", "");
            }

            @Override
            public void uploadFailedFileMissing(LXCFile file) {
                if (file.getFiles().size() == 1) {
                    gui.showError("Uploading \"" + file.getShownName() + "\" failed, LXC cannot locate this file anymore (did you move/delete it?)\n To avoid future errors, this file is no longer offered.", "");
                } else {
                    gui.showError("Uploading \"" + file.getShownName() + "\" failed, at least one file cannot be located anymore (did you move/delete it?)\n To avoid future errors, these files are no longer offered.", "");
                }
            }
        }, files, platform);

        // start networking
        if (!network.checkSingletonAndStart()) {
            // It is not possible to run multiple instances at the same time.
            // Warn user and exit.
            gui.showError("LXC is already running!", "");
            System.exit(1);
        }
        logger.info("My instance-id is " + LXCInstance.local.id);

        // startup completed, display gui
        gui.display();
    }

    /**
     * Init listeners for all components.
     */
    private void initListeners() {
        gui.setGuiListener(new GuiListener() {
            @Override
            public void offerFile(LXCFile newFile) {
                int newIndex = files.addLocal(newFile);
                if (newIndex != -1) {
                    network.broadcastList();
                    gui.notifyFileChange(GuiInterface.UPDATE_ORIGIN_LOCAL, GuiInterface.UPDATE_OPERATION_ADD, newIndex, 1, Collections.singletonList(newFile));
                }
            }

            @Override
            public boolean shutdown(boolean force, boolean askUserOnTransfer, boolean block) {
                if (!force && files.transferRunning()) {
                    // always abort when askUser is false
                    if (!askUserOnTransfer || !gui.confirmCloseWithTransfersRunning()) {
                        return false;
                    }
                }
                if (block) {
                    // synchronous
                    LXC.this.shutdown();
                } else {
                    // asynchronous
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            LXC.this.shutdown();
                        }
                    }, "t_shutdown_helper").start();
                }
                return true;
            }

            @Override
            public void resetFile(LXCFile file) {
                files.resetAvailableFile(file);
            }

            @Override
            public void removeFile(LXCFile oldFile) {
                files.removeLocal(oldFile);
                network.broadcastList();
            }

            @Override
            public void downloadFile(LXCFile file, boolean chooseTarget) {
                File targetFolder = defaultDownloadTarget;
                if (askForDownloadTargetSupported && chooseTarget || defaultDownloadTarget == null) {
                    // let user choose a (different) target
                    targetFolder = gui.getFileTarget(file);
                    if (targetFolder == null) {
                        // abort
                        file.setLocked(false);
                        return;
                    }
                }
                if (!network.connectAndDownload(file, targetFolder)) {
                    gui.showError("Download failed, host unreachable.", "");
                }
            }

            @Override
            public void downloadFile(LXCFile file, File targetDir) {
                if (!network.connectAndDownload(file, targetDir)) {
                    gui.showError("Download failed, host unreachable.", "");
                }
            }

            @Override
            public void reloadConfiguration() {
                askForDownloadTargetSupported = platform.askForDownloadTargetSupported();
                if (platform.getDefaultDownloadTarget() != null) {
                    defaultDownloadTarget = new File(platform.getDefaultDownloadTarget());
                } else {
                    defaultDownloadTarget = null;
                }
            }

            @Override
            public String generateUniqueFileName(String base, String extension) {
                return files.generateUniqueFileName(base, extension);
            }

            @Override
            public List<LXCFile> getFileList() {
                return files.getList();
            }
        });
    }

    /**
     * Stops LanXchange.
     */
    private void shutdown() {
        network.stop();

        // write configuration
        platform.writeConfiguration();

        // done, exit
        logger.info("LXC done. Thank you.");
    }
} public class LXCFile implements Serializable {

    static final long serialVersionUID = 2812501218020544733L;
    /**
     * The user wants to share a single file.
     */
    public static final int TYPE_FILE = 1;
    /**
     * The user wants to share a single folder.
     */
    public static final int TYPE_FOLDER = 2;
    /**
     * The user wants to share multiple files (and/or folders).
     */
    public static final int TYPE_MULTI = 3;

    private static final LXCLogger logger = LXCLogBackend.getLogger("file");

    /**
     * Identifier, chosen at random, required for equals.
     */
    public final long id;
    /**
     * If true, the file has been downloaded successfully.
     * Only valid for remote files.
     */
    private transient boolean isAvailable = false;
    /**
     * The LXCInstance that created/offers this file.
     * This is transient, because each running instance of LXC has its own representations of remote instances.
     */
    private transient LXCInstance instance;
    /**
     * If true, this file is locked and cannot be downloaded.
     * This is used to prevent further attempts to download a file while one is still connecting.
     */
    private transient boolean locked = false;
    /**
     * The list of files that the user wants to share.
     * Including directorys, but not the files within them.
     * Can be shared with other classes, because it is unmodifiable.
     */
    private transient List<VirtualFile> files;
    /**
     * The jobs currently assigned to this file.
     */
    private transient List<LXCJob> jobs = new ArrayList<LXCJob>();
    /**
     * The textual representation that is displayed.
     */
    private final String shownName;
    /**
     * The total size in bytes.
     * Is calculated by the constructor.
     * Includes all files in subfolders.
     * Directorys count as zero bytes.
     */
    private long fileSize;
    /**
     * The type of this file.
     * One of TYPE_FILE, TYPE_FOLDER or TYPE_MULTI
     */
    private final int type;
    /**
     * Transport protocol version number.
     */
    private int lxcTransVersion = 1;
    /**
     * The maximum version number supported by this client.
     */
    private final transient int lxcTransVersionMax = 1;
    /**
     * Number of successful transfers.
     */
    private transient int numberOfTransfers = 0;

    /**
     * The new constructor, creates a LXCFile with the given parameters.
     * May take a long time to calculate the size if many files are involved.
     *
     * @param fileList a list of VirtualFiles
     * @param shownN   the name that is shown
     */
    public LXCFile(List<VirtualFile> fileList, String shownN) {
        id = Double.doubleToLongBits(Math.random());
        // create a copy:
        this.files = new ArrayList<VirtualFile>(fileList);
        // Create name
        if (fileList.size() > 1) {
            shownName = shownN.concat(" + " + (fileList.size() - 1));
        } else {
            shownName = shownN;
        }

        // set type
        if (files.size() > 1) {
            type = LXCFile.TYPE_MULTI;
        } else if (files.get(0).isDirectory()) {
            type = LXCFile.TYPE_FOLDER;
        } else {
            type = LXCFile.TYPE_FILE;
        }

        // this constructor only creates local files
        instance = LXCInstance.local;

        // calculate size
        for (VirtualFile file : fileList) {
            fileSize += calcSize(file);
        }
    }

    /**
     * Adds a job to this LXCFile.
     *
     * @param job the new job
     */
    public void addJob(LXCJob job) {
        if (jobs == null) {
            jobs = new ArrayList<LXCJob>();
        }
        jobs.add(job);
    }

    /**
     * Removes a certain job from this LXCFile.
     *
     * @param job     the job to be removed
     * @param success true if transfer was successful
     */
    public void removeJob(LXCJob job, boolean success) {
        jobs.remove(job);
        if (success) {
            numberOfTransfers++;
        }
    }

    /**
     * Returns a List containing all LXCJobs added to this LXCFile.
     *
     * @return a List with all LXCJobs
     */
    public List<LXCJob> getJobs() {
        if (jobs == null) {
            jobs = new ArrayList<LXCJob>();
        }
        return jobs;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        LXCFile lxcFile = (LXCFile) o;

        return id == lxcFile.id;
    }

    @Override
    public int hashCode() {
        return (int) (id ^ (id >>> 32));
    }

    /**
     * Sets the available-flag.
     *
     * @param available new state of the flag
     */
    public void setAvailable(boolean available) {
        isAvailable = available;
    }

    /**
     * Computes the size of a given VirtualFile.
     * Recursively scans directorys.
     * Only counts files. Directorys count as zero bytes.
     *
     * @param startFile a file to scan
     * @return the size of the file/dir+subs in bytes
     */
    private long calcSize(VirtualFile startFile) {
        long size = 0;
        LinkedList<VirtualFile> queue = new LinkedList<VirtualFile>();
        // init queue
        queue.addLast(startFile);
        // add everything up
        while (!queue.isEmpty()) {
            VirtualFile file = queue.pollFirst();
            if (file.isDirectory()) {
                queue.addAll(file.children());
            } else {
                size += file.size();
            }
        }
        return size;
    }

    /**
     * Returns the available-flag.
     *
     * @return the available-flag
     */
    public boolean isAvailable() {
        return isAvailable;
    }

    /**
     * Returns the shown name.
     *
     * @return the shown name
     */
    public String getShownName() {
        return shownName;
    }

    /**
     * Returns the type.
     *
     * @return the type
     */
    public int getType() {
        return type;
    }

    /**
     * Returns the size of this file.
     *
     * @return the size of this file
     */
    public long getFileSize() {
        return fileSize;
    }

    /**
     * Returns the LXCInstance that created/offers this file.
     *
     * @return the instance
     */
    public LXCInstance getInstance() {
        return instance;
    }

    /**
     * Sets the LXCInstance that offers this file.
     *
     * @param instance the instance to set
     */
    public void setInstance(LXCInstance instance) {
        this.instance = instance;
    }

    /**
     * Returns the locked-flag.
     *
     * @return the locked-flag
     */
    public boolean isLocked() {
        return locked;
    }

    /**
     * Sets the locked-flag.
     *
     * @param locked the new state
     */
    public void setLocked(boolean locked) {
        this.locked = locked;
    }

    /**
     * Returns the list of files the user wants to share.
     *
     * @return the file-list
     */
    public List<VirtualFile> getFiles() {
        return files;
    }

    /**
     * Sets the list of base files.
     * This is required after downloads.
     *
     * @param baseFiles the base file list
     */
    public void setBaseFiles(List<VirtualFile> baseFiles) {
        this.files = baseFiles;
    }

    /**
     * Returns the current set protocol version.
     *
     * @return the lxcTransVersion
     */
    public int getLxcTransVersion() {
        return lxcTransVersion;
    }

    /**
     * Limits the protocol version to the maximum supported by this local client.
     */
    public void limitTransVersion() {
        if (lxcTransVersion > lxcTransVersionMax) {
            lxcTransVersion = lxcTransVersionMax;
        }
    }

    /**
     * Returns true, if this file is offered by this local instance.
     * Returns false, if received from a remote instance.
     *
     * @return true, if local
     */
    public boolean isLocal() {
        return instance.id == LXCInstance.local.id;
    }

    /**
     * Returns the number of (completed) transfers.
     *
     * @return number of transfers
     */
    public int getNumberOfTransfers() {
        return numberOfTransfers;
    }

    /**
     * Converts a list of real files to a List of VirtualFiles.
     * Determines the common toplevel node, if any, and creates file accordingly.
     *
     * @param files a list of real (java.io.File) files
     * @return a list of VirtualFiles
     */
    public static List<VirtualFile> convertToVirtual(List<File> files) {
        // Search toplevel node(s):
        LinkedList<File> topLevelNodes = new LinkedList<File>();
        outer:
        for (File file : files) {
            // Check if this file is below any toplevel node
            for (File topLevelNode : topLevelNodes) {
                if (file.getAbsolutePath().startsWith(topLevelNode.getAbsolutePath() + File.separator)) {
                    // below - this means file is not a toplevel node
                    continue outer;
                }
            }
            // This file is a toplevel node - check if this makes other toplevel nodes obsolete
            for (int i = 0; i < topLevelNodes.size(); i++) {
                File topLevelNode = topLevelNodes.get(i);
                if (topLevelNode.getAbsolutePath().startsWith(file.getAbsolutePath() + File.separator)) {
                    // the new file is a parent of this toplevel node
                    topLevelNodes.remove(i);
                }
            }
            // Insert the new toplevel node
            topLevelNodes.add(file);
        }

        ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
        // Each of these files now either has a parent in topLevelNodes or is a toplevel node itself.
        for (File file : files) {
            if (topLevelNodes.contains(file)) {
                // toplevel
                result.add(new RealFile(file));
            } else {
                // search topLevelNode
                for (File topLevelNode : topLevelNodes) {
                    if (file.getAbsolutePath().startsWith(topLevelNode.getAbsolutePath() + File.separator)) {
                        // found toplevel node
                        result.add(new RealFile(file, topLevelNode));
                        break;
                    }
                }
                // this should never be reached
                logger.warn("Cannot find TopLevel node for " + file.getAbsolutePath());
                // just assume this is toplevel as well
                result.add(new RealFile(file));
            }
        }
        return result;
    }

    /**
     * Computes a human-readable String which approximates the value passed to this method.
     *
     * @param fSize the fileSize in bytes
     * @return a human-readable String containing the size (like 12,3 MiB)
     */
    public static String getFormattedSize(long fSize) {
        DecimalFormat form = new DecimalFormat("0.0");
        if (fSize < 1024) {
            return fSize + " B";
        } else if (fSize < 1048576) {
            String size = form.format(fSize / 1024d);
            return size + " KiB";
        } else if (fSize < 1073741824) {
            String size = form.format(fSize / 1048576d);
            return size + " MiB";
        } else if (fSize < 1099511627776L) {
            String size = form.format(fSize / 1073741824d);
            return size + " GiB";
        } else if (fSize < 1125899906842625L) {
            String size = form.format(fSize / 1099511627776d);
            return size + " TiB";
        } else {
            return "unknown";
        }
    }
} public interface GuiInterface {

    int UPDATE_ORIGIN_LOCAL = 1;
    int UPDATE_ORIGIN_REMOTE = 2;
    int UPDATE_OPERATION_ADD = 3;
    int UPDATE_OPERATION_REMOVE = 4;

    /**
     * Initialize the user-inferface.
     *
     */
    public void init();

    /**
     * Displays the gui.
     * Must be called after init
     */
    public void display();

    /**
     * Displays an error.
     *
     * @param error the message to show
     * @param cancelText text on "cancel" button. Will only be shown if non-empty
     *
     * @return true if normal action was taken, false if cancel was chosen
     */
    public boolean showError(String error, String cancelText);

    /**
     * Sets the listener.
     * Must be called before display().
     *
     * @param guiListener
     */
    public void setGuiListener(GuiListener guiListener);

    /**
     * Updates the gui.
     * must be called after changes (modification of own/av/alllist, file-transfers etc)
     * may use interal scheduling to limit framerate/cpu usage
     *
     * If more specific info is available, one can also call notifyFileChange().
     */
    public void update();

    /**
     * More specific way of updating the gui.
     * Gives the gui the opportunity to selectively update the view and/or animate the changes.
     * This can be called instead of update()
     *
     * @param fileOrigin one of UPDATE_ORIGIN_LOCAL, UPDATE_ORIGIN_REMOTE
     * @param operation one of UPDATE_OPERATION_ADD, UPDATE_OPERATION_REMOVE
     * @param firstIndex index of first modified file (index of first removed or future index of first new)
     * @param numberOfFiles number of files that were added/removed in this batch
     * @param affectedFiles the changed files
     */
    public void notifyFileChange(int fileOrigin, int operation, int firstIndex, int numberOfFiles, List<LXCFile> affectedFiles);

    /**
     * Fired to notify the gui of new/removed jobs.
     * Gives the gui the opportunity ot selectively update the view and/or animate the changes.
     * This can be called instead of update()
     *
     * @param operation one of UPDATE_OPERATION_ADD, UPDATE_OPERATION_REMOVE
     * @param file the transferred LXCFile
     * @param index the index of the modified job (withing the file) - (index of removed or index of new)
     */
    public void notifyJobChange(int operation, LXCFile file, int index);

    /**
     * Finds out where to save the given file.
     * optional, only required if the corresponding guiListener ever calls downloadFile with chooseTarget=true
     *
     * @param file the file that is to be downloaded
     * @return the folder to save this file in
     */
    public File getFileTarget(LXCFile file);

    /**
     * Asks "are you sure" if a user tries to quit LXC while transfers are running.
     *
     * @return true, if user really wants to quit and cancel all running transfers
     */
    public boolean confirmCloseWithTransfersRunning();
} public interface GuiListener {

    /**
     * Called within gui.display().
     * Must return a (non-modifiable) list of all managed files.
     * This method is only called once, so the list must be backed be the real, internal state of FileManager.
     *
     * @return a List containing all managed files.
     */
    public List<LXCFile> getFileList();

    /**
     * Called when the user wants to offer a new LXCFile.
     * Must not be called before the size of the file is fully calculated.
     * GUI-Implementations may want to provide some sort of feedback during calculation,
     * as this may require quite some time for large folders.
     *
     * @param newFile the new LXCFile
     */
    public void offerFile(LXCFile newFile);

    /**
     * Called when the user no longer wants to offer a certain LXCFile.
     *
     * @param oldFile the old LXCFile
     */
    public void removeFile(LXCFile oldFile);

    /**
     * Called when the user wants to download a certain file.
     *
     * @param file the file the user wants to download
     * @param chooseTarget true, if the user wants to choose a file-target even if a default is set
     */
    public void downloadFile(LXCFile file, boolean chooseTarget);

    /**
     * Called when the user wants to download a certain file.
     *
     * @param file the file the user wants to download
     * @param targetDir the directory to put the file in
     */
    public void downloadFile(LXCFile file, File targetDir);

    /**
     * Unsets the "downloaded" (available) flag for a given file.
     * If the file is still available, it can be downloaded again.
     * Otherwise it will disappear.
     *
     * @param file the downloaded, non-local file to reset
     */
    public void resetFile(LXCFile file);

    /**
     * The user wants to quit LXC.
     * Returns true, if and only if the shutdown was successful.
     * To be successful, the shutdown must not be aborted by running transfers.
     *
     * If force is true, the shutdown is always successful and the user is never asked.
     *
     * Otherwise, the parameter askUserOnTransfer configures the behaviour when there are still transfers running.
     * If true, the gui is called to display a dialog with abort and quit anyway.
     * If false the user will not be asked and LXC will never shut down with transfers running.
     *
     * @param block if true, method blocks until core shutdown is complete
     */
    public boolean shutdown(boolean force, boolean askUserOnTransfer, boolean block);

    /**
     * The user changed important settings.
     */
    public void reloadConfiguration();

    /**
     * Generates a new file name that is currently unused.
     * Collisions are resolved by adding a number after the base name
     * @param base base name, like "image"
     * @param extension extension, without the dot, like "png"
     * @return (probably) unique name like "image05.png"
     */
    public String generateUniqueFileName(String base, String extension);
} public class StartCommand extends BackendCommand {

    /**
     * Whether the backend was started for this command.
     * (If not, the backend prints a "already running" message.
     */
    private final boolean backendLaunched;

    public StartCommand(boolean backendLaunched) {
        super(BackendCommandType.START);
        this.backendLaunched = backendLaunched;
    }

    public boolean isBackendLaunched() {
        return backendLaunched;
    }
} public class StopCommand extends BackendCommand {

    private final boolean force;

    public StopCommand(boolean force) {
        super(BackendCommandType.STOP);
        this.force = force;
    }

    public boolean isForce() {
        return force;
    }

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

    @Override
    public void onFrontendResult(boolean deliveredToBackend) {
        if (!deliveredToBackend) {
            CLITools.out.println("Not running.");
        }
    }
} public final class CLITools {

    /**
     * This is the original System.out out.
     * LanXchange may change System.out for logging purposes, but the cli gui must still be able to print to stdout.
     */
    public static final PrintStream out = new PrintStream(new FileOutputStream(FileDescriptor.out));
} public class CLIUpdaterGui implements UpdaterGui {

    private final LXCLogger logger;

    /**
     * Cached version title
     */
    private String versionTitle = "";

    public CLIUpdaterGui() {
        logger = LXCLogBackend.getLogger("cli-updater-gui");
    }

    @Override
    public void setVersionTitle(String title) {
        this.versionTitle = title;
    }

    @Override
    public void setStatusToVerify() {
        CLITools.out.println("[UPDATER]: Verifying update package...");
    }

    @Override
    public void setStatusToInstall() {
        CLITools.out.println("[UPDATER]: Installing update...");
    }

    @Override
    public void setStatusToRestart() {
        CLITools.out.println("[UPDATER]: Update complete, restart required.");
    }

    @Override
    public void setStatusToError() {
        CLITools.out.println("[UPDATER]: Error! Update failed.");
    }

    @Override
    public void setRestartTime(int i, boolean manual) {
        if (manual) {
            // user fiddled with startup script or bypassed it if this happens
            logger.error("In CLI mode, but not restartable/managed ?!?");
        } else {
            CLITools.out.println("[UPDATER]: Restarting in " + i + "...");
        }
    }

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

    @Override
    public void toProgressView() {
        CLITools.out.println("[UPDATER]: Downloading update...");
    }

    @Override
    public void finish() {
        // nop
    }
} public class ListPrinter {

    private static final String TEMPLATE_FILENAME = "                              "; // 30 spaces
    private static final String TEMPLATE_ID = "    "; // 4 spaces

    private final LXCLogger logger;

    private LocalFileIDManager idManager;

    public ListPrinter(LocalFileIDManager idManager) {
        logger = LXCLogBackend.getLogger("cli-listprinter");
        this.idManager = idManager;
    }

    public void printList(List<LXCFile> files) {
        List<LXCFile> ownFiles = files.stream().filter(f -> f.isLocal()).collect(Collectors.toList());
        List<LXCFile> remoteFiles = files.stream().filter(f -> !f.isLocal()).collect(Collectors.toList());

        if (ownFiles.isEmpty()) {
            CLITools.out.println("Not sharing any files from this machine.");
        } else {
            CLITools.out.println("Shared files:");
            printHeader();
            ownFiles.forEach(f -> {
                CLITools.out.print("  ");
                CLITools.out.print(formatId(f));
                CLITools.out.print("  ");
                CLITools.out.print(formatName(f.getShownName()));
                CLITools.out.print("  ");
                CLITools.out.print(LXCFile.getFormattedSize(f.getFileSize()));
                CLITools.out.println();
            });
        }

        if (remoteFiles.isEmpty()) {
            CLITools.out.println("No remote files found (yet).");
        } else {
            CLITools.out.println("Available files:");
            printHeader();
            remoteFiles.forEach(f -> {
                CLITools.out.print("  ");
                CLITools.out.print(formatId(f));
                CLITools.out.print("  ");
                CLITools.out.print(formatName(f.getShownName()));
                CLITools.out.print("  ");
                CLITools.out.print(LXCFile.getFormattedSize(f.getFileSize()));
                CLITools.out.println();
            });
        }
    }

    /**
     * Creates a constant-length string from the given file id
     */
    private String formatId(LXCFile file) {
        int id = idManager.getId(file);
        if (id < 0) {
            // this should never ever happen
            logger.wtf("Error: No ID for file " + file);
            id = -1;
        }
        String idString = String.valueOf(id);
        // set length to 4, fill with whitespace
        return (idString + TEMPLATE_ID).substring(0, 4);
    }

    /**
     * Creates a constant-length string from the given file name
     */
    private String formatName(String name) {
        // ensure 30 chars length
        String result = (name + TEMPLATE_FILENAME).substring(0, 30);
        // insert "..." if name is too long
        if (name.length() > 30) {
            result = result.substring(0, 27) + "...";
        }
        return result;
    }

    private void printHeader() {
        CLITools.out.println("  -ID-  -----------FILENAME-----------  ---SIZE---");
    }
} public abstract class PCPlatform implements Platform {

    private static final int MAX_LOG_SIZE_CHARS = 134217728; // ~128MiB
    private static final int LOG_ROTATION_SIZE = 3;

    /**
     * path to cfg-file.
     */
    private static final String CONFIG_PATH = "lxc.cfg";

    protected final LXCLogger logger;

    public PCPlatform(String[] args) {
        // check permission for own folder
        try {
            File.createTempFile("testacl", null, new File(".")).delete();
            // Can write
        } catch (IOException ex) {
            // Cannot write
            System.err.println("ERROR: Cannot write to my directory ("
                    + new File(".").getAbsolutePath()
                    + "). Try running LXC in your home directory.");
            getGui(args).showError("LXC is not allowed to create/modify files in the folder it is located. Please move to your home directory or start as administrator.", "");
            System.exit(1);
        }
        // init logging
        boolean debug = false;
        for (String s : args) {
            if (s.equals("-nolog")) {
                debug = true;
                break;
            }
        }
        LXCLogBackend.init(new File("."), MAX_LOG_SIZE_CHARS, LOG_ROTATION_SIZE, debug);
        logger = LXCLogBackend.getLogger("platform");
    }

    @Override
    public boolean hasAutoUpdates() {
        return true;
    }

    @Override
    public void postUpdateStep(String[] args) {
        // default is just file cleanup
        LXCUpdater.cleanup();
    }

    @Override
    public void checkAndPerformUpdates(String[] args) {
        LXCUpdater.Options options = new LXCUpdater.Options();
        // Find out if updating is allowed, requested, forced and/or if the
        // verification should be disabled.
        boolean checkForUpdates = false;
        if (Configuration.containsKey("allowupdates")) {
            String result = Configuration.getStringSetting("allowupdates");
            if ("yes".equals(result.toLowerCase())
                    || "true".equals(result.toLowerCase())) {
                checkForUpdates = true;
            }
        } else {
            // not set yet, default to true (yes)
            checkForUpdates = true;
            Configuration.putStringSetting("allowupdates", "true");
        }
        if (Configuration.containsKey("beta")) {
            String result = Configuration.getStringSetting("beta");
            if ("yes".equals(result.toLowerCase())
                    || "true".equals(result.toLowerCase())) {
                options.beta = true;
            }
        }
        // special settings
        for (int i = 0; i < args.length; i++) {
            String s = args[i];
            if (s.equals("-update")) {
                checkForUpdates = true;
            } else if (s.equals("-forceupdate")) {
                options.forceUpdate = true;
            } else if (s.equals("-overrideVerification")) {
                options.overrideVerification = true;
            }  else if (s.equals("-allowDowngrade")) {
                options.allowDowngrade = true;
            } else if (s.equals("-managed")) {
                // whoever launched LXC tells us he is able to restart us in
                // case of an update
                options.restartable = true;
            } else if (s.equals("-beta")) {
                options.beta = true;
            } else if (s.equals("-unsafe_updates")) {
                options.unsafe = true;
            } else if (s.equals("-unsafe_url_override") && i + 1 < args.length) {
                options.unsafeUrlOverride = args[i + 1];
                i++;
            } else if (s.equals("-unsafe_disable_tls")) {
                options.unsafeDisableTLS = true;
            } else if (s.equals("-unsafe_internal_version_override") && i + 1 < args.length) {
                options.unsafeOverrideInternalVersion = Integer.parseInt(args[i + 1]);
                i++;
            } else if (s.equals("-unsafe_update_sig_pubkey") && i + 1 < args.length) {
                options.unsafePublicKey = args[i + 1];
                i++;
            }
        }
        if (checkForUpdates || options.forceUpdate) {
            logger.info("Checking for Updates...");
            // check in separate thread
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    try {
                        LXCUpdater.checkAndPerformUpdate(getUpdaterGui(), options);
                    } catch (Exception ex) {
                        logger.error("Updater crashed", ex);
                    }
                }
            };

            Thread t = new Thread(r);
            t.setName("update checker");
            t.setDaemon(true);
            t.start();
        } else {
            logger.info("Not checking for updates. (disabled via lxc.cfg)");
        }
    }

    @Override
    public void readConfiguration(String[] args) {
        BufferedReader reader = null;
        try {
            File cfgFile = new File(CONFIG_PATH);
            // create on first start
            if (!cfgFile.exists()) {
                cfgFile.createNewFile();
            }
            reader = new BufferedReader(new FileReader(cfgFile));
            String line;
            int i = 0; // line number
            while ((line = reader.readLine()) != null) {
                // read line after line, add content to Configuration
                int equalSignIndex = line.indexOf('='); // search for "="
                if (equalSignIndex == -1) {
                } else {
                    String v1 = line.substring(0, equalSignIndex); // prefix (before "=")
                    String v2 = line.substring(equalSignIndex + 1); // suffix (after "=")
                    Configuration.putStringSetting(v1, v2);
                }
            }
        } catch (IOException ex) {
            // this is serious. exit
            logger.error("Cannot read/write configfile (cfg.txt)", ex);
            // display warning (gui not available yet)
            showEarlyError("Cannot write to own folder. Please move to your home directory or start as administrator.");
            System.exit(1);
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException ex) {
                // ignore
            }
        }
    }

    @Override
    public void writeConfiguration() {
        FileWriter writer = null;
        try {
            writer = new FileWriter(CONFIG_PATH);
            Iterator<String> iter = Configuration.getKeyIterator();
            while (iter.hasNext()) {
                String s = iter.next();
                writer.append(s);
                writer.append("=");
                writer.append(Configuration.getStringSetting(s));
                writer.append('\n');
            }
        } catch (IOException ex) {
            logger.error("Cannot write config file.", ex);
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException ex) {
                // ignore
            }
        }
    }

    @Override
    public boolean askForDownloadTargetSupported() {
        return true;
    }

    @Override
    public String getDefaultDownloadTarget() {
        // depends on configuration
        if (!Configuration.containsKey("defaulttarget")) {
            return null; // default = always ask
        } else {
            // check if valid
            File file = new File(
                    Configuration.getStringSetting("defaulttarget"));
            if (file.isDirectory() && file.canWrite()) {
                return file.getAbsolutePath();
            }
        }
        return null; // invalid path = ask
    }

    @Override
    public String[] getRequiredMulticastHelpers() {
        return new String[0];
    }

    /**
     * Display critical errors that occurred during startup.
     * @param error the message
     */
    public abstract void showEarlyError(String error);

    /**
     * Returns the updater gui.
     * @return the updater gui
     */
    public abstract UpdaterGui getUpdaterGui();
} public interface UpdaterGui {

    void setVersionTitle(String title);
    void setStatusToVerify();
    void setStatusToInstall();
    void setStatusToRestart();
    void setStatusToError();
    void setRestartTime(int i, boolean manual);
    boolean prompt();
    void toProgressView();
    void finish();
}
import de.tobifleig.lxc.LXC;
import de.tobifleig.lxc.data.LXCFile;
import de.tobifleig.lxc.plaf.GuiInterface;
import de.tobifleig.lxc.plaf.GuiListener;
import de.tobifleig.lxc.plaf.cli.cmds.StartCommand;
import de.tobifleig.lxc.plaf.cli.cmds.StopCommand;
import de.tobifleig.lxc.plaf.cli.ui.CLITools;
import de.tobifleig.lxc.plaf.cli.ui.CLIUpdaterGui;
import de.tobifleig.lxc.plaf.cli.ui.ListPrinter;
import de.tobifleig.lxc.plaf.pc.PCPlatform;
import de.tobifleig.lxc.plaf.pc.UpdaterGui;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
/*
 * Copyright 2017 Tobias Fleig (tobifleig gmail com)
 * All rights reserved.
 *
 * Usage of this source code is governed by the GNU GENERAL PUBLIC LICENSE version 3 or (at your option) any later version.
 * For the full license text see the file COPYING or https://github.com/tfg13/LanXchange/blob/master/COPYING
 */
package de.tobifleig.lxc.plaf.cli;



/**
 * The CLI is non-interactive.
 * This backend hosts the LanXchange core, runs in the background and awaits commands.
 */
public class CLIBackend extends PCPlatform {

    /**
     * Port used for frontend backend communication
     */
    public static final int LANXCHANGE_DAEMON_PORT = 27718;

    /**
     * If a stop command (without force parameter) is
     * received twice within this time limit, auto force.
     */
    private static final int DOUBLE_STOP_AUTOFORCE_THRESHOLD = 10000;

    private GuiListener listener;

    /**
     * Prints the file lists.
     */
    private ListPrinter listPrinter;

    /**
     * Used to display constant, simple, human-usable ids for files.
     */
    private LocalFileIDManager idManager = new LocalFileIDManager();

    private CLIUpdaterGui updaterGui = new CLIUpdaterGui();

    /**
     * Flag that indicates if the server mainloop must quit.
     */
    private boolean stopServer = false;

    private long lastStopTime = 0;

    public CLIBackend(String[] args) {
        super(args);

        listPrinter = new ListPrinter(idManager);
        new LXC(this, args);

        receiveLoop();
    }

    public static void main(String[] args) {
        // this should only be called by lxcc (the cli launch script)
        if (args.length == 0 || !args[0].equals("-cli")) {
            CLITools.out.println("Please use the script \"lxcc\" to interact with the CLI version of LanXchange. " +
                    "If you don't know what this means, you probably want to run \"lxc\"");
            return;
        }

        // replace "-cli" with "-managed" for core/the updater
        String[] argsCopy = new String[args.length];
        System.arraycopy(args, 0, argsCopy, 0, args.length);
        argsCopy[0] = "-managed";
        new CLIBackend(argsCopy);
    }

    @Override
    public GuiInterface getGui(String[] args) {