diff --git a/.classpath b/.classpath index 899de31..b3a1b28 100644 --- a/.classpath +++ b/.classpath @@ -6,16 +6,18 @@ + + + - diff --git a/pom.xml b/pom.xml index dea8da7..fdfd606 100644 --- a/pom.xml +++ b/pom.xml @@ -4,8 +4,36 @@ 4.0.0 BelegScanner BelegScanner - 0.0.2 + 0.0.3 BelegScanner + jar + BelegScanner + https://github.com/srsoftware-de/Belegscanner + + + SRSoftware + https://srsoftware.de + + + + UTF-8 + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Stephan Richter + s.richter@srsoftware.de + SRSoftware + http://www.srsoftware.de + + @@ -47,6 +75,41 @@ 11 + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + + + de.srsoftware.web4rail.Application + + + + + jar-with-dependencies + + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.4.0 + + de.srsoftware.web4rail.Application + + + + \ No newline at end of file diff --git a/resources/logback.xml b/resources/logback.xml new file mode 100644 index 0000000..29567a5 --- /dev/null +++ b/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/translations/Application.de.translation b/resources/translations/Application.de.translation new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/de/srsoftware/belegscanner/Configuration.java b/src/main/java/de/srsoftware/belegscanner/Configuration.java index c8a512b..ec02449 100644 --- a/src/main/java/de/srsoftware/belegscanner/Configuration.java +++ b/src/main/java/de/srsoftware/belegscanner/Configuration.java @@ -128,11 +128,13 @@ public class Configuration { String localKey = parts[i]; LOG.debug("localKey = {}",localKey); boolean lastPart = i == parts.length-1; - if (!localJson.has(localKey)) { - LOG.debug("{} not present in {}, creating…",localKey,localJson); - localJson.put(localKey, lastPart ? value : new JSONObject()); + if (!lastPart && !localJson.has(localKey)) { + localJson.put(localKey, new JSONObject()); + } + if (lastPart) { + localJson.put(localKey, value); + break; } - if (lastPart) break; localJson = localJson.getJSONObject(localKey); } LOG.debug("altered json: {}",json); diff --git a/src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java b/src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java index 8151f28..2ad5042 100644 --- a/src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java +++ b/src/main/java/de/srsoftware/belegscanner/gui/MainFrame.java @@ -2,9 +2,18 @@ package de.srsoftware.belegscanner.gui; import java.awt.BorderLayout; import java.awt.Dimension; +import java.awt.Point; import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map.Entry; import java.util.Vector; import java.util.regex.Matcher; @@ -12,6 +21,7 @@ import java.util.regex.Pattern; import javax.swing.JFrame; +import org.json.JSONArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +35,7 @@ public class MainFrame extends JFrame { private static final String HOME = System.getProperty("user.home"); private static final Pattern MARKEN = Pattern.compile("\\$([a-zA-Z]+)"); + private static FilenameFilter PAGES = (dir,name) -> name.toLowerCase().endsWith("pdf") && name.toLowerCase().startsWith("page"); private StatusBar statusBar; private Toolbar toolbar; @@ -41,7 +52,13 @@ public class MainFrame extends JFrame { private Configuration config; + private int xSize = 209; + private int ySize = 297; + private int resoultion = 150; + private String mode = "Color"; + public MainFrame(Configuration config) { + super("BelegScanner"); this.config = config; int width = config.getOrCreate("app.main.dimenstion.w",800); int height = config.getOrCreate("app.main.dimenstion.h",600); @@ -57,8 +74,14 @@ public class MainFrame extends JFrame { toolbar.addCategoryListener(this::setCategory) .addDateListener(this::setDate) .addFieldListener(this::setField) + .addFolderListener(this::openFolder) + .addJoinListener(this::join) .addPathListener(this::setPath) - .addScanListener(this::scan); + .addScanListener(this::scan); + + int x = config.getOrCreate("app.main.position.x", 20); + int y = config.getOrCreate("app.main.position.y", 20); + setLocation(new Point(x, y)); setPreferredSize(new Dimension(width,height)); pack(); @@ -72,9 +95,159 @@ public class MainFrame extends JFrame { toolbar.addFieldsFor(marks); }; + private void join(ActionEvent ev) { + new Thread(() -> joinFiles(patchedPath)).start(); + } + + private void joinFiles(String path) { + LOG.debug("joinFiles({})",path); + File folder = new File(path); + if (!folder.exists()) return; + + List pdfs = Arrays.asList(folder.list(PAGES)); + Collections.sort(pdfs); + + Vector cmd = new Vector<>(); + cmd.add("pdftk"); + cmd.addAll(pdfs); + cmd.add("cat"); + cmd.add("output"); + cmd.add(folder.getName()+".pdf"); + LOG.debug("executing {}",cmd); + ProcessBuilder builder = new ProcessBuilder(cmd); + builder.directory(folder); + try { + Process process = builder.start(); + statusBar.setAction("Verbinde PDFs…"); + int errorCode = process.waitFor(); + if (errorCode != 0) { + LOG.error("error code: {} for {}",errorCode,cmd); + } else LOG.debug("error code: {}",errorCode); + } catch (InterruptedException | IOException e) { + LOG.error("{} terminated: ",builder,e); + } + for (String page : pdfs) Path.of(path,page).toFile().delete(); + statusBar.setAction("Bereit."); + } + + private void openFolder(ActionEvent ev) { + new Thread(() -> openFolder(patchedPath)).start(); + } + + private void openFolder(String path) { + File folder = new File(path); + if (!folder.exists()) return; + + ProcessBuilder builder = new ProcessBuilder(List.of("killall","thunar")); + builder.directory(folder); + try { + builder.start().waitFor(); + } catch (IOException | InterruptedException e) { + LOG.error("{} terminated: ",builder,e); + } + + builder = new ProcessBuilder("thunar"); + builder.directory(folder); + try { + builder.start(); + } catch (IOException e) { + LOG.error("{} terminated: ",builder,e); + } + } + + private void performScan(String path) { + LOG.debug("performScan({})",path); + toolbar.readyToScan(false); + File folder = new File(path); + if (!folder.exists()) { + LOG.warn("Path '{}' does not exist!",path); + folder.mkdirs(); + } + + long timestamp = new Date().getTime(); + String raw = timestamp+".jpg"; + + Vector cmd = new Vector<>(); + cmd.add("scanimage"); + cmd.add("-x"); + cmd.add(xSize+""); + cmd.add("-y"); + cmd.add(ySize+""); + cmd.add("--mode"); + cmd.add(mode); + cmd.add("--resolution"); + cmd.add(resoultion+""); + cmd.add("-o"); + cmd.add(raw); + LOG.debug("executing {}",cmd); + + ProcessBuilder builder = new ProcessBuilder(cmd); + builder.directory(folder); + try { + Process process = builder.start(); + statusBar.setAction("Scanne nach "+path+"…"); + int errorCode = process.waitFor(); + if (errorCode != 0) { + LOG.error("error code: {} for {}",errorCode,cmd); + } else LOG.debug("error code: {}",errorCode); + + } catch (InterruptedException | IOException e) { + LOG.error("{} terminated: ",builder,e); + } + + String pdf = "page."+timestamp+".pdf"; + cmd = new Vector<>(); + cmd.add("convert"); + cmd.add(raw); + cmd.add(pdf); + builder = new ProcessBuilder(cmd); + builder.directory(folder); + try { + Process process = builder.start(); + statusBar.setAction("Kovertiere zu PDF…"); + toolbar.readyToScan(true); + int errorCode = process.waitFor(); + if (errorCode != 0) { + LOG.error("error code: {} for {}",errorCode,cmd); + } else LOG.debug("error code: {}",errorCode); + + } catch (InterruptedException | IOException e) { + LOG.error("{} terminated: ",builder,e); + } + + Path.of(path, raw).toFile().delete(); + String ocr = "page."+timestamp+".ocr.pdf"; + + cmd = new Vector<>(); + cmd.add("pdfsandwich"); + cmd.add("-lang"); + cmd.add("deu"); + cmd.add("-rgb"); + cmd.add("-o"); + + cmd.add(ocr); + cmd.add(pdf); + builder = new ProcessBuilder(cmd); + builder.directory(folder); + try { + Process process = builder.start(); + statusBar.setAction("Texterkennung…"); + toolbar.readyToScan(true); + int errorCode = process.waitFor(); + if (errorCode != 0) { + LOG.error("error code: {} for {}",errorCode,cmd); + } else LOG.debug("error code: {}",errorCode); + + } catch (InterruptedException | IOException e) { + LOG.error("{} terminated: ",builder,e); + } + Path.of(path, pdf).toFile().delete(); + statusBar.setAction("Bereit."); + } + private void scan(ActionEvent ev) { - LOG.debug("Scanning into '{}'",patchedPath); - config.set("app.categories."+category+".path",path); + updateConfig(); + new Thread(() -> performScan(patchedPath)).start(); } private void setCategory(String category) { @@ -89,7 +262,9 @@ public class MainFrame extends JFrame { } private void setField(String key, String val) { - fields.put(key, val); + if (val == null || val.isEmpty()) { + fields.remove(key); + } else fields.put(key, val); updatePath(); } @@ -99,6 +274,23 @@ public class MainFrame extends JFrame { updatePath(); } + private void updateConfig() { + + String prefix = "app.categories."+category+"."; + for (Entry entry : fields.entrySet()) { + String key = entry.getKey(); + String val = entry.getValue(); + JSONArray arr = config.getOrCreateArray(prefix+"fields."+key); + HashSet existing = new HashSet<>(); + arr.forEach(existing::add); + if (!existing.contains(val)) arr.put(val); + } + config.set(prefix+"path",path); + Point loc = getLocation(); + config.set("app.main.position.x", loc.x); + config.set("app.main.position.y", loc.y); + } + @SuppressWarnings("deprecation") private void updatePath() { LOG.debug("updatePath() [path = {}]",path); diff --git a/src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java b/src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java index abee5a0..2ca21f3 100644 --- a/src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java +++ b/src/main/java/de/srsoftware/belegscanner/gui/StatusBar.java @@ -14,9 +14,12 @@ public class StatusBar extends JPanel { private static final long serialVersionUID = 8102800846089594705L; private JLabel path; + private JLabel action; + public StatusBar() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - add(path = new JLabel()); + add(action = new JLabel("Bereit.")); + add(path = new JLabel("Kein Pfad gewält.")); } public StatusBar setPath(String path) { @@ -24,4 +27,9 @@ public class StatusBar extends JPanel { this.path.setText(path); return this; } + + public StatusBar setAction(String action) { + this.action.setText(action); + return this; + } } diff --git a/src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java b/src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java index 388a283..fb2244c 100644 --- a/src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java +++ b/src/main/java/de/srsoftware/belegscanner/gui/Toolbar.java @@ -16,6 +16,7 @@ import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; +import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,15 +48,19 @@ public class Toolbar extends JPanel { private static final long serialVersionUID = -5834326573752788233L; + private static final int OFFSET = 4; + private Vector categories = new Vector<>(); private Vector paths = new Vector<>(); + private HashMap additonalComponents = new HashMap<>(); private HashSet categoryListeners = new HashSet<>(); private HashSet dateListeners = new HashSet<>(); private HashSet fieldListeners = new HashSet<>(); private HashSet pathListeners = new HashSet<>(); + private HashSet folderListeners = new HashSet<>(); + private HashSet joinListeners = new HashSet<>(); private HashSet scanListeners = new HashSet<>(); - private HashMap additonalComponents = new HashMap<>(); private DateChooser date; @@ -63,8 +68,11 @@ public class Toolbar extends JPanel { private SelectComboBox pathPicker; + private JButton scanButton; + + private JButton joinButton; + private JButton folderButton; - public Toolbar(Configuration config) { this.config = config; setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); @@ -72,6 +80,8 @@ public class Toolbar extends JPanel { add(input("Kategorie",categoryPicker(config))); add(input("Pfad",pathPicker = pathPicker())); add(scanButton()); + add(joinButton()); + add(folderButton()); add(externButton()); } @@ -83,10 +93,10 @@ public class Toolbar extends JPanel { for (String name : marks) { if (additonalComponents.containsKey(name)) continue; - List knownValues = List.of(); + Vector knownValues = new Vector<>(); SelectComboBox valuePicker = new SelectComboBox(knownValues).onUpdateText(newText -> updateField(name,newText)); - Component input = input(name, valuePicker); - add(input,getComponentCount()-2); + JPanel input = input(name, valuePicker); + add(input,getComponentCount()-OFFSET); additonalComponents.put(name, input); } } @@ -105,6 +115,17 @@ public class Toolbar extends JPanel { fieldListeners.add(listener); return this; } + + public Toolbar addFolderListener(ActionListener listener) { + folderListeners.add(listener); + return this; + } + + public Toolbar addJoinListener(ActionListener listener) { + joinListeners.add(listener); + return this; + } + public Toolbar addPathListener(PathListener listener){ pathListeners.add(listener); @@ -136,7 +157,31 @@ public class Toolbar extends JPanel { return btn; } - private Component input(String caption, Component component) { + private Component folderButton() { + folderButton = new JButton("Ordner öffnen!"); + folderButton.addActionListener(this::folderPressed); + return folderButton; + } + + private void folderPressed(ActionEvent evt) { + folderListeners.forEach(l->l.actionPerformed(evt)); + } + + public Date getDate() { + return date.getSelectedDate(); + } + + + public SelectComboBox getSelector(JPanel panel) { + if (panel != null && panel.getComponentCount()>1) { + Component child = panel.getComponent(1); + if (child instanceof SelectComboBox) return (SelectComboBox) child; + } + return null; + } + + + private JPanel input(String caption, Component component) { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); panel.add(new JLabel(caption+" ")); @@ -144,11 +189,17 @@ public class Toolbar extends JPanel { return panel; } - - public Date getDate() { - return date.getSelectedDate(); + private Component joinButton() { + joinButton = new JButton("zusammenfügen!"); + joinButton.addActionListener(this::joinPressed); + return joinButton; } + private void joinPressed(ActionEvent evt) { + joinListeners.forEach(l->l.actionPerformed(evt)); + } + + private SelectComboBox pathPicker() { JSONObject cats = config.getOrCreate("app.categories",new JSONObject()); for (String catName : cats.keySet()) { @@ -160,7 +211,7 @@ public class Toolbar extends JPanel { } private Component scanButton() { - JButton scanButton = new JButton("scannen!"); + scanButton = new JButton("scannen!"); scanButton.addActionListener(this::scanPressed); return scanButton; } @@ -169,15 +220,34 @@ public class Toolbar extends JPanel { scanListeners.forEach(l->l.actionPerformed(evt)); } + public void readyToScan(boolean val) { + scanButton.setEnabled(val); + + } + + protected void updateCat(String newCat) { LOG.debug("updateCat({})",newCat); categoryListeners.forEach(l -> l.setCategory(newCat)); if (!newCat.isEmpty()) { - String path = config.get("app.categories."+newCat+".path"); - LOG.debug("path: {}",path); + String prefix = "app.categories."+newCat+"."; + String path = config.get(prefix+"path"); if (path != null) { pathPicker.setSelectedItem(path); updatePath(path); + } + + JSONObject fields = config.get(prefix+"fields"); + if (fields == null) return; + LOG.debug("fields: {}",fields); + for (String fieldName : fields.keySet()) { + path = prefix+"fields."+fieldName; + + JSONArray arr = config.getOrCreateArray(path); + HashSet values = new HashSet<>(); + arr.forEach(values::add); + SelectComboBox selector = getSelector(additonalComponents.get(fieldName)); + if (selector != null) selector.setElements(values); } } }