xyz.dogboy.paperplane.PaperPlane.java Source code

Java tutorial

Introduction

Here is the source code for xyz.dogboy.paperplane.PaperPlane.java

Source

/*
 * PaperPlane
 * Copyright (C) 2018 Dogboy21
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package xyz.dogboy.paperplane;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.apache.http.entity.ContentType;
import xyz.dogboy.paperplane.api.uploading.UploadCallback;
import xyz.dogboy.paperplane.api.uploading.UploadService;
import xyz.dogboy.paperplane.api.uploading.UploadSettingType;
import xyz.dogboy.paperplane.gui.AppFrame;
import xyz.dogboy.paperplane.gui.CaptureWindow;
import xyz.dogboy.paperplane.gui.components.HistoryElement;
import xyz.dogboy.paperplane.helper.CaptureHelper;
import xyz.dogboy.paperplane.helper.CollectingKeyListener;
import xyz.dogboy.paperplane.helper.JsonHelper;
import xyz.dogboy.paperplane.helper.SecureStore;
import xyz.dogboy.paperplane.helper.SoundPlayer;
import xyz.dogboy.paperplane.impl.uploading.GyazoUploadService;
import xyz.dogboy.paperplane.impl.uploading.YaniusUploadService;
import xyz.dogboy.paperplane.logging.Log;
import xyz.dogboy.paperplane.models.HistoricalFile;

import java.awt.Desktop;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.GeneralSecurityException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

public class PaperPlane extends CollectingKeyListener {

    private final File configDir;
    private final Set<UploadService> uploadServices = new HashSet<>();
    private JsonObject config;
    private SecureStore secureStore;

    private AppFrame appFrame;

    protected PaperPlane() {
        String osName = System.getProperty("os.name").toLowerCase();
        File homeDir = new File(System.getProperty("user.home"));
        if (osName.contains("win")) {
            this.configDir = new File(homeDir, "AppData/Roaming/PaperPlane");
        } else if (osName.contains("mac")) {
            this.configDir = new File(homeDir, "Library/Application Support/PaperPlane");
        } else {
            this.configDir = new File(homeDir, ".paperplane");
        }

        if (!this.configDir.isDirectory()) {
            this.configDir.mkdirs();
        }
    }

    protected void init(boolean silent) {
        Log.info("PaperPlane Config directory: " + this.configDir.getAbsolutePath());

        this.config = new JsonObject();
        File configFile = new File(this.configDir, "config.json");
        if (configFile.isFile()) {
            try {
                this.config = new Gson()
                        .fromJson(new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8),
                                JsonElement.class)
                        .getAsJsonObject();
            } catch (IOException e) {
                Log.error("Failed to load config file", e);
            }
        }

        if (this.config.has("secure_store_key")) {
            this.secureStore = new SecureStore(this.config.get("secure_store_key").getAsString());
        } else {
            this.secureStore = new SecureStore();
            this.config.addProperty("secure_store_key", this.secureStore.getKey());
            this.saveConfig();
        }

        this.registerUploadService(new YaniusUploadService());
        this.registerUploadService(new GyazoUploadService());

        this.appFrame = new AppFrame();
        if (!silent) {
            this.appFrame.setVisible(true);
        }
    }

    public void registerUploadService(UploadService uploadService) {
        this.uploadServices.add(uploadService);
    }

    public Set<UploadService> getUploadServices() {
        return this.uploadServices;
    }

    public AppFrame getAppFrame() {
        return this.appFrame;
    }

    /* ================================================== Upload wrapper ================================================== */

    public void uploadFile(File file) {
        HistoricalFile historicalFile = new HistoricalFile();
        historicalFile.fileName = file.getName();
        historicalFile.size = file.length();
        try {
            historicalFile.mime = Optional.ofNullable(Files.probeContentType(file.toPath())).orElse("");
        } catch (IOException e) {
            historicalFile.mime = "";
        }

        HistoryElement element = new HistoryElement(historicalFile);

        this.appFrame.getHistory().add(element, 0);
        this.appFrame.getHistory().revalidate();
        this.appFrame.getHistory().repaint();

        Optional<UploadService> uploadService = this.getCurrentUploadService();
        if (!uploadService.isPresent()) {
            SoundPlayer.play(SoundPlayer.SoundType.FAIL);
            return;
        }

        Optional<JsonObject> config = this.getConfigForUploadService(uploadService.get());
        if (!config.isPresent()) {
            SoundPlayer.play(SoundPlayer.SoundType.FAIL);
            return;
        }

        uploadService.get().upload(file, config.get(), this.uploadProgressHandler(element))
                .thenAccept(this.uploadSuccessHandler(element)).exceptionally(this::uploadErrorHandler);
    }

    public void uploadImage(BufferedImage image) {
        try {
            ContentType type = Settings.UPLOAD_AS_PNG.getValue() ? ContentType.IMAGE_PNG : ContentType.IMAGE_JPEG;

            SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

            HistoricalFile historicalFile = new HistoricalFile();
            historicalFile.fileName = dateTimeFormat.format(new Date()) + "." + type.getMimeType().substring(6);

            InputStream stream = CaptureHelper.getInputStream(image, type.getMimeType().substring(6));
            historicalFile.size = stream.available();
            historicalFile.mime = type.getMimeType();

            HistoryElement element = new HistoryElement(historicalFile);

            this.appFrame.getHistory().add(element, 0);
            this.appFrame.getHistory().revalidate();
            this.appFrame.getHistory().repaint();

            Optional<UploadService> uploadService = this.getCurrentUploadService();
            if (!uploadService.isPresent()) {
                Log.warn("Service " + this.getCurrentUploadServiceId() + " not found");
                SoundPlayer.play(SoundPlayer.SoundType.FAIL);
                return;
            }

            Optional<JsonObject> config = this.getConfigForUploadService(uploadService.get());
            if (!config.isPresent()) {
                Log.warn("Config for service " + uploadService.get().getServiceId() + " is incomplete");
                SoundPlayer.play(SoundPlayer.SoundType.FAIL);
                return;
            }

            uploadService.get()
                    .upload(historicalFile.fileName, stream, type, config.get(),
                            this.uploadProgressHandler(element))
                    .thenAccept(this.uploadSuccessHandler(element)).exceptionally(this::uploadErrorHandler);
        } catch (Exception e) {
            Log.warn("Failed to upload image", e);
            SoundPlayer.play(SoundPlayer.SoundType.FAIL);
        }
    }

    private Consumer<URL> uploadSuccessHandler(HistoryElement historyElement) {
        return url -> {
            historyElement.getFile().url = url.toString();
            historyElement.getFile().uploadProgress = 0;
            historyElement.repaint();
            this.addToHistory(historyElement.getFile());
            StringSelection clipboardContent = new StringSelection(url.toString());

            if (Settings.COPY_URL_AFTER_UPLOAD.getValue()) {
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(clipboardContent, clipboardContent);
            }
            if (Settings.OPEN_URL_AFTER_UPLOAD.getValue()) {
                try {
                    Desktop.getDesktop().browse(url.toURI());
                } catch (Exception e) {
                    Log.warn("Failed to open url", e);
                }
            }

            SoundPlayer.play(SoundPlayer.SoundType.SUCCESS);
        };
    }

    private UploadCallback uploadProgressHandler(HistoryElement historyElement) {
        return progress -> {
            historyElement.getFile().uploadProgress = progress;
            historyElement.repaint();
        };
    }

    private Void uploadErrorHandler(Throwable throwable) {
        Log.warn("Failed to upload image", throwable);
        SoundPlayer.play(SoundPlayer.SoundType.FAIL);
        return null;
    }

    /* ================================================== Config related stuff ================================================== */

    private void saveConfig() {
        try {
            File configFile = new File(this.configDir, "config.json");
            Files.write(configFile.toPath(), new GsonBuilder().setPrettyPrinting().create().toJson(this.config)
                    .getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            Log.warn("Failed to write config file", e);
        }
    }

    public String getCurrentUploadServiceId() {
        if (!this.config.has("upload_service")) {
            return null;
        }

        return this.config.get("upload_service").getAsString();
    }

    private Optional<UploadService> getCurrentUploadService() {
        if (!this.config.has("upload_service")) {
            return Optional.empty();
        }

        return this.uploadServices.stream()
                .filter(service -> service.getServiceId().equals(this.config.get("upload_service").getAsString()))
                .findFirst();
    }

    public void setUploadService(UploadService service) {
        this.config.addProperty("upload_service", service.getServiceId());
        this.saveConfig();
    }

    public List<HistoricalFile> getHistory() {
        if (!this.config.has("history")) {
            return Collections.emptyList();
        }

        return JsonHelper.getCollection(this.config.getAsJsonArray("history"), ArrayList::new,
                HistoricalFile.class);
    }

    public void setUploadConfig(UploadService service, String config, String value) {
        if (!service.getNeededSettings().containsKey(config)) {
            return;
        }
        UploadSettingType type = service.getNeededSettings().get(config);

        JsonObject services = this.config.has("services") ? this.config.getAsJsonObject("services")
                : new JsonObject();
        JsonObject serviceCfg = services.has(service.getServiceId())
                ? services.getAsJsonObject(service.getServiceId())
                : new JsonObject();

        try {
            serviceCfg.addProperty(config, type.getEncoder().encode(this.secureStore, value));
        } catch (GeneralSecurityException e) {
            Log.warn("Failed to encrypt secured value " + config, e);
        }

        services.add(service.getServiceId(), serviceCfg);
        this.config.add("services", services);
        this.saveConfig();
    }

    public String getUploadConfig(UploadService service, String config) {
        if (!service.getNeededSettings().containsKey(config)) {
            return "";
        }

        JsonObject services = this.config.has("services") ? this.config.getAsJsonObject("services")
                : new JsonObject();
        JsonObject serviceCfg = services.has(service.getServiceId())
                ? services.getAsJsonObject(service.getServiceId())
                : new JsonObject();
        if (!serviceCfg.has(config)) {
            return "";
        }

        String value = serviceCfg.get(config).getAsString();
        if (value.isEmpty()) {
            return "";
        }

        try {
            return service.getNeededSettings().get(config).getDecoder().encode(this.secureStore, value);
        } catch (GeneralSecurityException e) {
            Log.warn("Failed to decrypt secured value " + config, e);
            return "";
        }
    }

    private Optional<JsonObject> getConfigForUploadService(UploadService service) {
        if (!this.config.has("services")) {
            return Optional.empty();
        }
        JsonObject services = this.config.getAsJsonObject("services");
        if (!services.has(service.getServiceId())) {
            return Optional.empty();
        }

        JsonObject config = services.getAsJsonObject(service.getServiceId());
        JsonObject finalConfig = new JsonObject();

        for (Map.Entry<String, UploadSettingType> entry : service.getNeededSettings().entrySet()) {
            if (!config.has(entry.getKey()) || !config.get(entry.getKey()).isJsonPrimitive()) {
                return Optional.empty();
            }

            JsonPrimitive setting = config.getAsJsonPrimitive(entry.getKey());
            if (!setting.isString()) {
                return Optional.empty();
            }

            try {
                finalConfig.addProperty(entry.getKey(),
                        entry.getValue().getDecoder().encode(this.secureStore, setting.getAsString()));
            } catch (GeneralSecurityException e) {
                Log.warn("Failed to decrypt secured value " + entry.getKey(), e);
            }
        }

        return Optional.of(finalConfig);
    }

    private void addToHistory(HistoricalFile file) {
        Gson gson = new Gson();
        JsonElement element = gson.toJsonTree(file);

        JsonArray history = this.config.has("history") ? this.config.getAsJsonArray("history") : new JsonArray();
        history.add(element);
        this.config.add("history", history);
        this.saveConfig();
    }

    public Set<Integer> getHotkeyConfig(String keyConfig) {
        if (!this.config.has("hotkeys")) {
            return Collections.emptySet();
        }

        JsonObject hotkeys = this.config.getAsJsonObject("hotkeys");
        if (!hotkeys.has(keyConfig)) {
            return Collections.emptySet();
        }

        return JsonHelper.getCollection(hotkeys.getAsJsonArray(keyConfig), HashSet::new, Integer.class);
    }

    public void setHotkeyConfig(String keyConfig, Set<Integer> keys) {
        JsonObject hotkeys = this.config.has("hotkeys") ? this.config.getAsJsonObject("hotkeys") : new JsonObject();
        hotkeys.add(keyConfig, JsonHelper.getArray(keys));
        this.config.add("hotkeys", hotkeys);
        this.saveConfig();
    }

    private boolean matchesKeyConfig(String keyConfig, Set<Integer> keys) {
        Set<Integer> config = this.getHotkeyConfig(keyConfig);
        if (config.isEmpty()) {
            return false;
        }
        return keys.containsAll(config);
    }

    public <T> Optional<T> getConfigValue(String name, Class<T> type) {
        if (!this.config.has(name)) {
            return Optional.empty();
        }
        return Optional.of(new Gson().fromJson(this.config.get(name), type));
    }

    public void setConfigValue(String name, JsonElement value) {
        this.config.add(name, value);
        this.saveConfig();
    }

    @Override
    protected void onKeyPress(int keyCode) {
        if (this.matchesKeyConfig("capture_screens", this.pressedKeys)) {
            CaptureHelper.capture().ifPresent(this::uploadImage);
        } else if (this.matchesKeyConfig("capture_current_screen", this.pressedKeys)) {
            CaptureHelper.capture(CaptureHelper.getCursorScreen()).ifPresent(this::uploadImage);
        } else if (this.matchesKeyConfig("capture_area", this.pressedKeys)) {
            CaptureWindow.show(CaptureHelper.getScreenBounds());
        }
    }

}