diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d3e1a127..77c991721 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log All notable changes to this project will be documented in this file. +## [Unreleased] +### Added +- #1202 Check for modifications outside FFDec and ask user to reload + ## [13.0.3] - 2021-02-12 ### Added - #1594 Option to disable AS3 P-code indentation, label on separate line diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 1abc39ddd..d95dcf864 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -672,6 +672,10 @@ public final class Configuration { @ConfigurationCategory("script") public static ConfigurationItem useOldStyleLookupSwitchAs3PCode = null; + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("ui") + public static ConfigurationItem checkForModifications = null; + private enum OSId { WINDOWS, OSX, UNIX } diff --git a/src/com/jpexs/decompiler/flash/gui/FilesChangedDialog.java b/src/com/jpexs/decompiler/flash/gui/FilesChangedDialog.java new file mode 100644 index 000000000..b62196b6f --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/FilesChangedDialog.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import static com.jpexs.decompiler.flash.gui.AppDialog.CANCEL_OPTION; +import static com.jpexs.decompiler.flash.gui.AppDialog.ERROR_OPTION; +import static com.jpexs.decompiler.flash.gui.AppDialog.OK_OPTION; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.border.EmptyBorder; + +/** + * + * @author JPEXS + */ +public class FilesChangedDialog extends AppDialog { + private final JButton okButton = new JButton(translate("button.ok")); + + private final JButton cancelButton = new JButton(translate("button.cancel")); + + private JList filesList; + + private DefaultListModel listModel; + + + public FilesChangedDialog() { + setDefaultCloseOperation(HIDE_ON_CLOSE); + setTitle(translate("dialog.title")); + + + addComponentListener(new ComponentAdapter() { + @Override + public void componentHidden(ComponentEvent e) { + clearList(); + } + + }); + + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + + listModel = new DefaultListModel<>(); + JLabel label = new JLabel(translate("fileschanged")); + label.setBorder(new EmptyBorder(10, 10, 10, 10)); + cnt.add(label, BorderLayout.NORTH); + filesList = new JList(listModel); + filesList.setBackground(Color.white); + cnt.add(new JScrollPane(filesList), BorderLayout.CENTER); + + JPanel panButtons = new JPanel(new FlowLayout()); + okButton.addActionListener(this::okButtonActionPerformed); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + panButtons.add(okButton); + panButtons.add(cancelButton); + + add(panButtons, BorderLayout.SOUTH); + + setAlwaysOnTop(true); + View.setWindowIcon(this); + setResizable(true); + pack(); + View.centerScreen(this); + } + + public void addItem(String item) { + if (listModel.contains(item)) { + return; + } + listModel.addElement(item); + filesList.setModel(listModel); + } + + @Override + public void setVisible(boolean b) { + super.setVisible(b); + } + + private void okButtonActionPerformed(ActionEvent evt) { + setVisible(false); + View.execInEventDispatchLater(new Runnable() { + @Override + public void run() { + for (int i = 0; i < listModel.size(); i++) { + String fileName = listModel.elementAt(i); + Main.reloadFile(new File(fileName)); + } + } + }); + + } + + private void clearList() { + listModel.clear(); + filesList.setModel(listModel); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + clearList(); + setVisible(false); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index ddc1b31ed..fb1c81328 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -87,11 +87,18 @@ import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; import java.net.URLConnection; +import java.nio.file.FileSystems; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -100,6 +107,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.ConsoleHandler; import java.util.logging.FileHandler; import java.util.logging.Formatter; @@ -170,6 +178,16 @@ public class Main { private static List runTempFiles = new ArrayList<>(); + private static WatchService watcher; + + private static SwingWorker watcherWorker; + + private static Map watchedDirectories = new HashMap<>(); + + private static FilesChangedDialog filesChangedDialog; + + private static List savedFiles = Collections.synchronizedList(new ArrayList<>()); + public static void freeRun() { synchronized (Main.class) { if (runTempFile != null) { @@ -951,7 +969,9 @@ public class Main { } public static void saveFile(SWF swf, String outfile, SaveFileMode mode, ExeExportMode exeExportMode) throws IOException { - if (mode == SaveFileMode.SAVEAS && swf.swfList!= null /*SWF in binarydata has null*/ && !swf.swfList.isBundle()) { + File savedFile = new File(outfile); + savedFiles.add(savedFile); + if (mode == SaveFileMode.SAVEAS && swf.swfList != null /*SWF in binarydata has null*/ && !swf.swfList.isBundle()) { swf.setFile(outfile); swf.swfList.sourceInfo.setFile(outfile); } @@ -1028,6 +1048,18 @@ public class Main { } else { throw new IOException("Output not found"); } + View.execInEventDispatchLater(new Runnable() { + @Override + public void run() { + //TODO: handle this better + try { + Thread.sleep(2000); + } catch (InterruptedException ex) { + //ignore + } + savedFiles.remove(savedFile); + } + }); } private static class OpenFileWorker extends SwingWorker { @@ -1292,6 +1324,15 @@ public class Main { String fileName = si.getFile(); if (fileName != null) { Configuration.addRecentFile(fileName); + try { + File dir = new File(fileName).getParentFile(); + if (!watchedDirectories.containsValue(dir)) { + WatchKey key = dir.toPath().register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); + watchedDirectories.put(key, dir); + } + } catch (IOException ex) { + //ignore + } } } @@ -1320,6 +1361,18 @@ public class Main { openFile(swf.sourceInfo, null, sourceInfos.indexOf(swf.sourceInfo)); } + public static void reloadFile(File file) { + for (int i = 0; i < sourceInfos.size(); i++) { + SWFSourceInfo info = sourceInfos.get(i); + if (info.getFile() == null) { + continue; + } + if (file.equals(new File(info.getFile()))) { + openFile(info, null, i); + } + } + } + public static boolean closeAll() { View.checkAccess(); @@ -1328,6 +1381,8 @@ public class Main { sourceInfos.clear(); } + filesChangedDialog.setVisible(false); + return closeResult; } @@ -1636,6 +1691,76 @@ public class Main { offerAssociation(); loadingDialog = new LoadingDialog(); + if (Configuration.checkForModifications.get()) { + try { + watcher = FileSystems.getDefault().newWatchService(); + } catch (IOException ex) { + //ignore + } + } + + filesChangedDialog = new FilesChangedDialog(); + + if (watcher != null) { + watcherWorker = new SwingWorker() { + @Override + protected Object doInBackground() throws Exception { + while (true) { + WatchKey key; + try { + key = watcher.take(); + } catch (InterruptedException ex) { + return null; + } + + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + if (kind == StandardWatchEventKinds.OVERFLOW) { + System.err.println("overflow"); + continue; + } + + @SuppressWarnings("unchecked") + WatchEvent ev = (WatchEvent) event; + + java.nio.file.Path filename = ev.context(); + + if (watchedDirectories.containsKey(key)) { + File dir = watchedDirectories.get(key); + java.nio.file.Path child = dir.toPath().resolve(filename); + File fullPath = child.toFile(); + if (savedFiles.contains(fullPath)) { + continue; + } + + for (SWFSourceInfo info : sourceInfos) { + final String infoFile = info.getFile(); + if (infoFile != null && new File(infoFile).equals(fullPath)) { + View.execInEventDispatchLater(new Runnable() { + @Override + public void run() { + filesChangedDialog.addItem(infoFile); + if (!filesChangedDialog.isVisible()) { + filesChangedDialog.setVisible(true); + } + } + }); + } + } + } + + } + boolean valid = key.reset(); + if (!valid) { + break; + } + } + return null; + } + }; + watcherWorker.execute(); + } + DebuggerTools.initDebugger().addMessageListener(new DebugListener() { @Override public void onMessage(String clientId, String msg) { diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index cba2e046a..0f57fa51e 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -498,3 +498,5 @@ config.description.useOldStyleGetSetLocalsAs3PCode = Use oldstyle getlocal_x, se config.name.useOldStyleLookupSwitchAs3PCode = Use oldstyle lookupswitch without brackets in AS3 P-code config.description.useOldStyleLookupSwitchAs3PCode = Use oldstyle lookupswitch from FFDec 12.x or older +config.name.checkForModifications = Check for file modifications outside FFDec +config.description.checkForModifications = Check for modifications of files by other applications and ask to reload \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties index d2f5a7037..310df458f 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties @@ -489,3 +489,6 @@ config.description.useOldStyleGetSetLocalsAs3PCode = Pou\u017e\u00edvat postaru config.name.useOldStyleLookupSwitchAs3PCode = Pou\u017e\u00edvat star\u00fd styl lookupswitch bez hranat\u00fdch z\u00e1vorek v AS3 P-k\u00f3du config.description.useOldStyleLookupSwitchAs3PCode = Pou\u017e\u00edvat star\u00fd styl lookupswitch z FFDec 12.x a star\u0161\u00edho + +config.name.checkForModifications = Kontrolovat zm\u011bny soubor\u016f mimo FFDec +config.description.checkForModifications = Kontrolovat zm\u011bny soubor\u016f jin\u00fdmi aplikacemi a pt\u00e1t se na nov\u00e9 na\u010dten\u00ed \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/FilesChangedDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/FilesChangedDialog.properties new file mode 100644 index 000000000..864d6c227 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/FilesChangedDialog.properties @@ -0,0 +1,4 @@ +dialog.title = Files were modified +button.ok = OK +button.cancel = Cancel +fileschanged = Following files were modified outside the application. Do you want to reload them from disk? diff --git a/src/com/jpexs/decompiler/flash/gui/locales/FilesChangedDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/FilesChangedDialog_cs.properties new file mode 100644 index 000000000..d8a9b0724 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/FilesChangedDialog_cs.properties @@ -0,0 +1,4 @@ +dialog.title = Soubory byly zm\u011bn\u011bny +button.ok = OK +button.cancel = Storno +fileschanged = N\u00e1sleduj\u00edc\u00ed soubory byly zm\u011bn\u011bny mimo aplikaci. Chcete je znovu na\u010d\u00edst z disku?