Java tutorial
/* * 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()); } } }