0) A plaintext flag indicating file encryption, plus newline
15 | * 1) The key size
16 | *
2) The cypher parameters, which includes a salt, plus newline
17 | *
3) A series of lines: Each line is a buffered chunk of the file, no larger than
18 | * our specified buffer length. The encryption bytes are base64-encoded.
19 | * Thus each line should be decryptable by itself.
20 | *
21 | * Note that we only generate our salted key once, instead of once per buffer chunk. As
22 | * far as we know this is secure enough.
23 | */
24 | public class EncryptionStream implements LightweightWriter {
25 |
26 | /////////////////////
27 | // True constants: //
28 | /////////////////////
29 |
30 |
31 | final static String pbeAlgorithm="PBEWithHmacSHA256AndAES_";
32 | final static Charset utf8=Charset.forName("UTF-8");
33 | final static int paramsLength=104;
34 | final static Base64.Decoder base64Decoder=Base64.getDecoder();
35 |
36 | public final static String encryptionFlag=
37 | "<<[*******]>> // ENCRYPTED BY KLONK // <<[*******]>>";
38 | private final static int iterations=1024*1024;
39 | private final static int bufferSize=1024;
40 | private final static Base64.Encoder base64Encoder=Base64.getEncoder();
41 |
42 | /////////////////////////
43 | // Instance variables: //
44 | /////////////////////////
45 |
46 | private final Writer writer;
47 | private final Cipher cipher;
48 | private final StringBuilder buffer=new StringBuilder();
49 |
50 | public static SecretKey getSecretKey(char[] pass, int keySize, String algorithm) throws Exception {
51 | byte[] salt=new byte[8];
52 | new SecureRandom().nextBytes(salt);
53 | return SecretKeyFactory.getInstance(algorithm)
54 | .generateSecret(
55 | new PBEKeySpec(pass, salt, iterations, keySize)
56 | );
57 | }
58 |
59 | public static boolean matches(String firstLine) {
60 | return firstLine.trim().equals(encryptionFlag);
61 | }
62 |
63 | public EncryptionStream(Writer writer, int keySize, char[] pass) throws Exception {
64 | this.writer=writer;
65 | String algorithm=pbeAlgorithm+keySize;
66 | this.cipher = Cipher.getInstance(algorithm);
67 | cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(pass, keySize, algorithm));
68 | byte[] params = cipher.getParameters().getEncoded();
69 | // Note: After JDK 10 this length changed, because the algorithm changed, and it's only
70 | // half-backwards compatible:
71 | // - JDK 13 can read JDK 10-encrypted files
72 | // - JDK 10 cannot read JDK 13-encrypted files :(
73 | //if (params.length != paramsLength)
74 | // throw new Exception("Cannot encrypt because params are wrong "+params.length);
75 | writer
76 | .append(encryptionFlag).append("\n")
77 | .append(String.valueOf(keySize)).append("\n")
78 | .append(base64Encoder.encodeToString(params)).append("\n")
79 | .flush();
80 | }
81 |
82 | public void append(CharSequence data) throws Exception {
83 | buffer.append(data);
84 | flush(false);
85 | }
86 | public void close() {
87 | try {
88 | flush(true);
89 | } catch (Exception e) {
90 | throw new RuntimeException(e);
91 | }
92 | }
93 | public void flush() throws java.io.IOException {
94 | flush(false);
95 | }
96 |
97 | private void flush(boolean force) {
98 | try {
99 | if (force || buffer.length()>bufferSize){
100 | writer.append(
101 | base64Encoder.encodeToString(
102 | cipher.doFinal(
103 | buffer.toString().getBytes(utf8)
104 | )
105 | )
106 | ).append("\n");
107 | buffer.setLength(0);
108 | }
109 | } catch (Exception e) {
110 | throw new RuntimeException(e);
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/io/FileMetaData.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.io;
2 |
3 | public class FileMetaData {
4 | public static String UTF8="UTF-8",
5 | UTF16BE="UTF-16BE",
6 | UTF16LE="UTF-16LE";
7 | public String delimiter;
8 | public boolean hasTabs=false;
9 | public String encoding=UTF8;
10 | public boolean encodingNeedsBOM=false;
11 | public int readOffset=0;
12 | public EncryptionParams encryption=null;
13 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/io/KLog.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.io;
2 | import java.io.*;
3 | import org.tmotte.klonk.config.KHome;
4 |
5 | public class KLog {
6 |
7 | private final long start=System.nanoTime();
8 | private KHome home;
9 | private PrintWriter commandLineWriter;
10 | private java.text.SimpleDateFormat sdformat;
11 |
12 | //////////////////
13 | // CONSTRUCTOR: //
14 | //////////////////
15 |
16 | public KLog(OutputStream os){
17 | this.commandLineWriter=new PrintWriter(new OutputStreamWriter(os));
18 | sdformat=new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ");
19 | }
20 | public KLog(KHome home, String pid){
21 | this.home=home;
22 | sdformat=new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS '"+pid+"\t'");
23 | }
24 |
25 | ///////////////////////
26 | // PUBLIC FUNCTIONS: //
27 | ///////////////////////
28 |
29 | public void log(String s) {
30 | logPlain(
31 | sdformat.format(new java.util.Date())+s
32 | );
33 | }
34 | public void error(String s) {
35 | log("ERROR: "+s);
36 | }
37 | public void log(Throwable e) {
38 | logError(e);
39 | }
40 | public void log(Throwable e, String s) {
41 | error(s);
42 | logError(e);
43 | }
44 | public File getLogFile(){
45 | return home.nameFile("log.txt");
46 | }
47 |
48 | //////////////////////
49 | // PRIVATE METHODS: //
50 | //////////////////////
51 |
52 | private void logError(Throwable e) {
53 | PrintWriter pw=getWriter();
54 | if (pw!=null)
55 | try {
56 | e.printStackTrace(pw);
57 | pw.flush();
58 | } finally {
59 | close(pw);
60 | }
61 | }
62 | private void logPlain(String s) {
63 | PrintWriter pw=getWriter();
64 | if (pw!=null)
65 | try {
66 | pw.println(s);
67 | pw.flush();
68 | } finally {
69 | close(pw);
70 | }
71 | }
72 | private void close(PrintWriter p){
73 | if (commandLineWriter!=null)
74 | return;
75 | p.close();
76 | }
77 | private PrintWriter getWriter() {
78 | if (commandLineWriter!=null)
79 | return commandLineWriter;
80 | File logFile=getLogFile();
81 | try {
82 | return new PrintWriter(new OutputStreamWriter(new FileOutputStream(logFile, true)));
83 | } catch (Exception e) {
84 | System.err.println("Could not create log file "+logFile.getAbsolutePath());
85 | e.printStackTrace();
86 | return null;
87 | }
88 | }
89 |
90 | ///////////
91 | // TEST: //
92 | ///////////
93 |
94 | public static void main(String[] args) {
95 | KLog log=new KLog(System.out);
96 | log.log("BARF");
97 | System.out.flush();
98 |
99 | }
100 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/io/LightweightWriter.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.io;
2 |
3 | public interface LightweightWriter extends java.io.Closeable, java.io.Flushable {
4 | public void append(CharSequence cs) throws Exception;
5 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/io/LockInterface.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.io;
2 | import org.tmotte.klonk.config.msg.Setter;
3 | import java.util.List;
4 |
5 | public interface LockInterface {
6 | public boolean lockOrSignal(String[] fileNames);
7 | public void startListener(Setter> fileReceiver);
8 | public Runnable getLockRemover();
9 | }
10 |
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/io/LockTest.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.io;
2 | import java.util.Random;
3 | import java.security.SecureRandom;
4 |
5 | public class LockTest {
6 | public static void main(String[] args) throws Exception {
7 | Random random=new SecureRandom();
8 | String fileName=args[0];
9 | String index=args[1];
10 | Locker locker=new Locker(new java.io.File(fileName), new KLog(System.out));
11 | int sleep1=random.nextInt(2000), sleep2=random.nextInt(2000);
12 | Thread.sleep(sleep1);
13 | if (locker.lock()) {
14 | System.out.println("\n"+index+": Got lock after "+sleep1);
15 | Thread.sleep(sleep2);
16 | locker.unlock();
17 | System.out.println(index+": Released lock after "+sleep2);
18 | }
19 | else
20 | System.out.println("\n"+index+": Did not get lock after "+sleep1);
21 | System.out.flush();
22 | }
23 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/io/Locker.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.io;
2 | import java.io.BufferedReader;
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.FileOutputStream;
6 | import java.io.InputStreamReader;
7 | import java.io.OutputStream;
8 | import java.io.PrintWriter;
9 | import java.io.RandomAccessFile;
10 | import java.nio.channels.FileChannel;
11 | import java.nio.channels.FileLock;
12 | import java.nio.file.FileSystem;
13 | import java.nio.file.FileSystems;
14 | import java.util.LinkedList;
15 | import java.util.List;
16 |
17 | class Locker {
18 | private File lockFile;
19 | private FileLock lock;
20 | private FileOutputStream fos;
21 | private KLog log;
22 |
23 | public Locker(File lockFile, KLog log) {
24 | this.lockFile=lockFile;
25 | this.log=log;
26 | }
27 |
28 | public boolean lock() {
29 | try {
30 | fos=new FileOutputStream(lockFile);
31 | FileChannel fc=fos.getChannel();
32 | lock=fc.tryLock();
33 | if (lock==null){
34 | log.log("Locker: Failed, got a null value on lockFile: "+lockFile);
35 | return false;
36 | }
37 | fos.write(1);
38 | return true;
39 | } catch (Exception e) {
40 | log.log(e);
41 | return false;
42 | }
43 | }
44 | public void unlock() {
45 | try {
46 | lock.release();
47 | fos.close();
48 | lockFile.delete();
49 | } catch (Exception e) {
50 | log.log(e);
51 | }
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/io/Printing.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.io;
2 | import java.awt.print.PrinterException;
3 | import java.awt.print.PrinterJob;
4 | import java.awt.print.Printable;
5 | import java.awt.print.PageFormat;
6 | import java.awt.print.Paper;
7 | import java.awt.Color;
8 | import javax.swing.JTextArea;
9 |
10 | public class Printing {
11 |
12 | private static Paper lastPaper;
13 |
14 | public static boolean print(JTextArea jta) {
15 | PrinterJob job=PrinterJob.getPrinterJob();
16 |
17 | //Switch colors so that we get pure black & white,
18 | //not greyscale or whatever. We'll switch them back
19 | //in a moment:
20 | Color fore=jta.getForeground(),
21 | back=jta.getBackground();
22 | jta.setForeground(Color.BLACK);
23 | jta.setBackground(Color.WHITE);
24 |
25 | try {
26 | job.setPrintable(jta.getPrintable(null, null));
27 | PageFormat pf=job.defaultPage();
28 |
29 | //Mainly we want some decent margins, and preserve
30 | //them between prints. Not going to bother persisting
31 | //them to disk though:
32 | if (lastPaper==null) {
33 | lastPaper=pf.getPaper();
34 | int margin=72/4;
35 | int margin2=2*margin;
36 | lastPaper.setImageableArea(
37 | margin, margin,
38 | lastPaper.getWidth()-margin2,
39 | lastPaper.getHeight()-margin2
40 | );
41 | }
42 | pf.setPaper(lastPaper);
43 |
44 | //Now show dialog and print. Note where we
45 | //save the selected paper format:
46 | PageFormat pf2=job.pageDialog(pf);
47 | if (pf2!=pf) {
48 | lastPaper=pf2.getPaper();
49 | job.print();
50 | return true;
51 | }
52 | return false;
53 |
54 | //This also seems like a nice option, but it doesn't
55 | //allow for setting page format.
56 | //if (job.printDialog())
57 | // job.print();
58 |
59 | } catch (Exception e) {
60 | throw new RuntimeException(e);
61 | } finally {
62 | jta.setForeground(fore);
63 | jta.setBackground(back);
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/ConnectionParse.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | import java.util.HashMap;
3 | import java.util.Map;
4 | import org.tmotte.common.text.StringChunker;
5 | import java.util.regex.Pattern;
6 | import java.util.Set;
7 |
8 | /**
9 | * This is really just an extension of SSHConnections, should maybe be combined
10 | */
11 | class ConnectionParse {
12 |
13 | private final Pattern sshPattern=Pattern.compile("ssh:/*", Pattern.CASE_INSENSITIVE);
14 | private final StringChunker chunker=new StringChunker();
15 |
16 | protected SSHFile parse(SSHConnections connMgr, String uri) {
17 | String user=null, host=null, dirFile=null;
18 | chunker.reset(uri);
19 |
20 | //Walk past the ssh:(////), we don't need it:
21 | if (!chunker.find(sshPattern))
22 | throw new RuntimeException(uri+" does not contain "+sshPattern);
23 |
24 | //Optionally get user@:
25 | if (chunker.find("@"))
26 | user=chunker.getUpTo();
27 |
28 | //Get host:
29 | if (chunker.find(":"))
30 | host=chunker.getUpTo();
31 | else
32 | if (chunker.find("/")){
33 | host=chunker.getUpTo();
34 | chunker.reset("/"+chunker.getRest());
35 | }
36 | else
37 | host=chunker.getRest();
38 |
39 | //Go back and try again on user if we need to:
40 | if (user==null)
41 | user=connMgr.inferUserForHost(host);
42 | if (user==null)
43 | return null;
44 |
45 | //Now make the SSH object & get file name:
46 | SSH ssh=connMgr.getOrCreate(user, host);
47 | if (ssh==null || !ssh.verifyConnection())
48 | return null;
49 |
50 | return startParse(ssh, chunker.getRest(), chunker);
51 | }
52 |
53 | private SSHFile startParse(SSH ssh, String toParse, StringChunker reuse) {
54 | if (toParse.contains("~"))
55 | toParse=toParse.replace("~", ssh.getTildeFix());
56 | SSHFile result=parse(ssh, null, reuse.reset(toParse));
57 | if (result==null)
58 | //No name given, default to "~"
59 | result=new SSHFile(ssh, null, ssh.getTildeFix());
60 | return result;
61 | }
62 | private SSHFile parse(SSH ssh, SSHFile parent, StringChunker left) {
63 | if (!left.find("/")){
64 | String remains=left.getRest();
65 | if (remains==null)
66 | return parent;
67 | else {
68 | remains=remains.trim();
69 | if (remains.equals(""))
70 | return parent;
71 | return new SSHFile(ssh, parent, remains);
72 | }
73 | }
74 | else {
75 | String name=left.getUpTo();
76 | if (name.equals("") && parent==null)
77 | name="/";
78 | return parse(
79 | ssh,
80 | new SSHFile(ssh, parent, name),
81 | left
82 | );
83 | }
84 | }
85 |
86 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/IFileGet.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | public interface IFileGet {
3 | public java.io.File get(String name);
4 | }
5 |
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/IUserPass.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | public interface IUserPass {
3 | public boolean get(String user, String host, boolean authFail, boolean needsPassword);
4 | public String getUser();
5 | public String getPass();
6 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/MeatCounter.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 |
3 | /**
4 | * This implements the take-a-number system at the grocery store, thus preventing
5 | * "thread starvation". It's very clever, but:
6 | *
7 | * Everyone must play nice and answer when their number is called.
8 | * If a thread fails to call unlock(), the whole system seizes up thereafter, so lock()
9 | * and unlock() should always be used with try-finally. When sleeping between lock
10 | * attempts, if the thread is sent an interrupt() we ignore that and keep going.
11 | */
12 | public class MeatCounter {
13 |
14 | //Yes, these ints will eventually wrap around to Integer.MIN_VALUE, which is ok:
15 | private volatile int nextUp=1;
16 | private int lastTicket=0;
17 | private final long spintime;
18 |
19 | /**
20 | * @param spintime This is how long we tell a Thread to sleep when it's
21 | * waiting for a lock. If the value is <= 0, we don't sleep at all.
22 | */
23 | public MeatCounter(long spintime){
24 | this.spintime=spintime;
25 | }
26 |
27 | /** Unsynchronized because it should only be called when you hold the lock. */
28 | public void unlock() {
29 | nextUp++;
30 | //mylog(nextUp, "UNLOCK ");
31 | }
32 |
33 | /**
34 | * @return true if we received an Exception when sleeping between
35 | * lock attempts, most likely an InterruptedException. We will still
36 | * keep sleeping until we get the lock, however.
37 | */
38 | public boolean lock(String name) {
39 | final int myTurn=takeANumber();
40 | //mylog(myTurn, "PICKED: "+name);
41 | boolean interrupted=false;
42 | while (nextUp!=myTurn) {
43 | //mylog(myTurn, "WAIT: "+name);
44 | if (spintime>0)
45 | try {Thread.sleep(spintime);}
46 | catch (Exception e) {
47 | //We have no choice but to keep trying, since otherwise
48 | //we end up with a lock that will never be unlocked.
49 | interrupted=true;
50 | }
51 | }
52 | //mylog(myTurn, "LOCKED: "+name);
53 | return interrupted;
54 | }
55 |
56 | /**
57 | * This must be synchronized because the ++ operator is not atomic,
58 | * nor is it "more atomic" just because you toss in a volatile keyword.
59 | */
60 | private synchronized int takeANumber() {
61 | return ++lastTicket;
62 | }
63 | private static void mylog(int number, String msg) {
64 | System.out.println("MeatCounter: "+number+" "+msg);
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/SFTP.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | import com.jcraft.jsch.Session;
3 | import com.jcraft.jsch.Channel;
4 | import com.jcraft.jsch.ChannelSftp;
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.InputStream;
7 | import java.io.InputStreamReader;
8 | import java.io.OutputStream;
9 | import java.io.OutputStreamWriter;
10 | import com.jcraft.jsch.SftpATTRS;
11 | import java.util.List;
12 | import java.util.ArrayList;
13 |
14 |
15 | /**
16 | * SFTP in jsch is not thread-safe. Worse yet, you can try to use synchronized blocks to ameliorate, but it will go right around them.
17 | * So I have a locking system in place to work around this, and it's pretty good. The only catch is that getInputStream() & getOutputStream()
18 | * get called from far away and it's hard to lock them down.
19 | */
20 | class SFTP {
21 |
22 | private SSH ssh;
23 | private ChannelSftp channel;
24 | private MeatCounter mc;
25 |
26 | SFTP(SSH ssh, MeatCounter mc) {
27 | this.ssh=ssh;
28 | this.mc=mc;
29 | }
30 |
31 | ////////////////////////////////
32 | // PACKAGE-PRIVATE FUNCTIONS: //
33 | ////////////////////////////////
34 |
35 |
36 | InputStream getInputStream(String file) throws Exception {
37 | try {
38 | return getChannel(file).get(file);
39 | } finally {
40 | unlock();
41 | }
42 | }
43 | OutputStream getOutputStream(String file) throws Exception {
44 | try {
45 | return getChannel(file).put(file);
46 | } finally {
47 | unlock();
48 | }
49 | }
50 | SSHFileAttr getAttributes(String file) {
51 | ssh.userNotify.log("SFTP.getAttributes "+file);
52 | try {
53 | ChannelSftp ch=getChannel(file);
54 | if (ch==null)
55 | return null;
56 | SSHFileAttr sfa=new SSHFileAttr(ch.stat(file));
57 | ssh.userNotify.log(sfa.toString());
58 | return sfa;
59 | } catch (Exception e) {
60 | try {close();} catch (Exception e2) {ssh.userNotify.log(e2);}
61 | String msg=e.getMessage();
62 | if (msg!=null && msg.equals("No such file")){
63 | ssh.userNotify.log(msg+file);
64 | return null;
65 | //This never worked out, just made a mess with ->new folder ->rename in file dialog. Unfortunately
66 | //it also means that we call this function again & again because we have null and want a value.
67 | //return new SSHFileAttr();
68 | }
69 | else
70 | if (canIgnore(e, file, "getAttributes"))
71 | return null;
72 | else
73 | throw new RuntimeException("Failed to get stat on: "+file, e);
74 | } finally {
75 | //ssh.userNotify.log("SFTP.getAttributes "+Thread.currentThread().hashCode()+" "+file+" COMPLETE ");
76 | unlock();
77 | }
78 | }
79 |
80 | private static String[] noFiles={};
81 | String[] listFiles(String file) {
82 | ssh.userNotify.log("SFTP.listFiles "+Thread.currentThread().hashCode()+" "+file);
83 | ChannelSftp ch=getChannel(file);
84 | try {
85 | List> vv=ch.ls(file);
86 | String[] values=new String[vv.size()];
87 | int count=0;
88 | if (vv!=null)
89 | for (int ii=0; ii-1; i--)
101 | if (values[i]!=null){
102 | realVals[count-1]=values[i];
103 | count--;
104 | }
105 | return realVals;
106 | } catch (Exception e) {
107 | try {close();} catch (Exception e2) {ssh.userNotify.log(e2);}
108 | if (!canIgnore(e, file, "listFiles"))
109 | ssh.userNotify.alert(e, "Failed to list files for: "+file);
110 | return noFiles;
111 | } finally {
112 | //ssh.userNotify.log("SFTP.listFiles "+Thread.currentThread().hashCode()+" "+file+" COMPLETE ");
113 | unlock();
114 | }
115 | }
116 |
117 | void close() throws Exception {
118 | if (channel!=null) {
119 | if (channel.isConnected())
120 | try {channel.disconnect();} catch (Exception e) {}
121 | channel=null;
122 | }
123 | }
124 |
125 | ////////////////////////
126 | // PRIVATE FUNCTIONS: //
127 | ////////////////////////
128 |
129 | /**
130 | * As we overload the connection, random stuff blows up but we can ignore it because
131 | * this is just the file chooser whacking us over the head and it will be just fine.
132 | */
133 | private boolean canIgnore(Throwable e, String file, String function) {
134 | e=getCause(e);
135 | String msg=e.getMessage();
136 | if (
137 | (e instanceof java.lang.InterruptedException) ||
138 | (e instanceof java.io.InterruptedIOException) ||
139 | (e instanceof java.lang.IndexOutOfBoundsException) ||
140 | (msg!=null && (
141 | msg.contains("Permission denied") ||
142 | msg.contains("No such file") ||
143 | msg.contains("inputstream is closed")
144 | ))
145 | ){
146 | ssh.userNotify.log("SFTP Hidden fail: "+function+" "+e+" ..."+file);
147 | ssh.userNotify.log(e);
148 | return true;
149 | }
150 | else if ((e instanceof java.io.IOException) && e.getMessage().equals("Pipe closed")){
151 | ssh.userNotify.log("SFTP Apparently closed: "+e+" ..."+file);
152 | try {ssh.close();} catch (Exception e2) {ssh.userNotify.log(e2);}
153 | return false;
154 | }
155 | else
156 | return false;
157 | }
158 | private Throwable getCause(Throwable e) {
159 | Throwable e1=e.getCause();
160 | return e1==null ?e :getCause(e1);
161 | }
162 |
163 |
164 | private ChannelSftp getChannel(String file) {
165 | lock(file);
166 | try {
167 | if (channel==null || !channel.isConnected())
168 | channel=makeChannel();
169 | } catch (Exception e) {
170 | unlock();
171 | throw new RuntimeException(e);
172 | }
173 | return channel;
174 | }
175 |
176 | private ChannelSftp makeChannel() throws Exception {
177 | Session session=ssh.getSession();
178 | if (session==null)
179 | return null;
180 | session.setConfig("compression.s2c", "zlib@openssh.com,zlib,none");//Fails if we don't do this - not that I understand it
181 | session.setConfig("compression.c2s", "zlib@openssh.com,zlib,none");
182 | ChannelSftp c=(ChannelSftp)session.openChannel("sftp");
183 | //setBulkRequests() seems to help... not sure. Might be making things worse:
184 | c.setBulkRequests(100);
185 | c.connect();
186 | return c;
187 | }
188 |
189 | private void lock(String file) {
190 | mc.lock(file);
191 | }
192 | private void unlock() {
193 | mc.unlock();
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/SSHCommandLine.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | import java.io.BufferedReader;
3 | import java.io.InputStreamReader;
4 | import java.io.File;
5 | import java.io.FilenameFilter;
6 | import java.io.FileFilter;
7 | import java.nio.file.Path;
8 | import java.util.List;
9 | import java.util.LinkedList;
10 | import org.tmotte.klonk.config.option.SSHOptions;
11 | import org.tmotte.klonk.config.msg.UserNotify;
12 |
13 | public class SSHCommandLine {
14 |
15 | public interface ArgHandler {
16 | /** Return a new index, or -1 to indicate nothing found */
17 | public int handle(String[] args, int currIndex);
18 | public String document();
19 | }
20 | public SSH ssh;
21 | public SSHConnections connections;
22 | public SSHFile sshFile;
23 |
24 | private ArgHandler argHandler;
25 |
26 | public SSHCommandLine(String[] args) throws Exception {
27 | this(args, null);
28 | }
29 | public SSHCommandLine(String[] args, ArgHandler argHandler) throws Exception {
30 | this.argHandler=argHandler;
31 | int max=0;
32 | String user=null, host=null, knownHosts=null, pass=null, privateKeys=null, fileName=null;
33 | for (int i=0; i [-k knownhostsfile] [-p pass] [-r privatekeyfile] "+
87 | (argHandler==null ?"" :argHandler.document())
88 | );
89 | System.exit(1);
90 | }
91 |
92 | ////////////////////////
93 | // INTERACTIVE LOGIN: //
94 | ////////////////////////
95 |
96 | private static class Login implements IUserPass {
97 | String user, pass;
98 | public Login(String user, String pass) {
99 | this.user=user;
100 | this.pass=pass;
101 | }
102 | final BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
103 | public boolean get(String u, String host, boolean authFail, boolean needsPassword) {
104 | user=u;
105 | if (user!=null && pass !=null)
106 | return true;
107 | try {
108 | System.out.println("Host: "+host);
109 |
110 | System.out.print("User: ");
111 | if (user!=null && !user.trim().equals(""))
112 | System.out.print("<"+user+"> press enter to keep or: ");
113 | u=br.readLine().trim();
114 | if (!u.trim().equals(""))
115 | this.user=u;
116 |
117 | System.out.print("Pass: ");
118 | String p=br.readLine().trim();
119 | if (p!=null && !p.trim().equals(""))
120 | this.pass=p;
121 |
122 | return user!=null && !user.trim().equals("") &&
123 | pass!=null && !pass.trim().equals("");
124 | } catch (Exception e) {
125 | throw new RuntimeException(e);
126 | }
127 | }
128 | public String getUser(){return user;}
129 | public String getPass(){return pass;}
130 | }
131 |
132 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/SSHConnections.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | import java.io.File;
3 | import java.util.HashMap;
4 | import java.util.HashSet;
5 | import java.util.List;
6 | import java.util.LinkedList;
7 | import java.util.Map;
8 | import java.util.Set;
9 | import java.util.regex.Pattern;
10 | import org.tmotte.klonk.config.option.SSHOptions;
11 | import org.tmotte.common.text.StringChunker;
12 | import org.tmotte.klonk.config.msg.UserNotify;
13 |
14 | /** This does not establish a connection. It only collects potential connections. Not thread-safe. */
15 | public class SSHConnections {
16 |
17 | private final Map> conns=new HashMap<>();
18 | private final ConnectionParse parser=new ConnectionParse();
19 | private Set users=new HashSet<>();
20 |
21 | private String knownHosts, privateKeys, openSSHConfig;
22 | private String defaultFilePerms, defaultDirPerms;
23 | private IUserPass iUserPass;
24 | private UserNotify userNotify;
25 |
26 | private IFileGet iFileGet=new IFileGet(){
27 | public File get(String uri) {
28 | if (is(uri)) return getSSHFile(uri);
29 | else return new File(uri);
30 | }
31 | };
32 |
33 | /////////////
34 | // CREATE: //
35 | /////////////
36 |
37 | public SSHConnections (UserNotify userNotify) {
38 | this.userNotify=userNotify;
39 | }
40 | public SSHConnections withOptions(SSHOptions opts){
41 | this.knownHosts=opts.getKnownHostsFilename();
42 | this.privateKeys=opts.getPrivateKeysFilename();
43 | this.defaultFilePerms=opts.getDefaultFilePermissions();
44 | this.defaultDirPerms=opts.getDefaultDirPermissions();
45 | this.openSSHConfig=opts.getOpenSSHConfigFilename();
46 | for (Map map: conns.values())
47 | for (SSH ssh: map.values())
48 | configure(ssh);
49 | return this;
50 | }
51 | public SSHConnections withLogin(IUserPass iUserPass) {
52 | this.iUserPass=iUserPass;
53 | return this;
54 | }
55 |
56 | /////////////
57 | // PUBLIC: //
58 | /////////////
59 |
60 | public void close(String host) {
61 | Map perHost=conns.get(host);
62 | for (SSH ssh: perHost.values())
63 | ssh.close();
64 | }
65 | public void close() throws Exception {
66 | for (String host: conns.keySet()){
67 | Map forHost=conns.get(host);
68 | for (String user: forHost.keySet())
69 | forHost.get(user).close();
70 | }
71 | try {
72 | } catch (Exception e) {
73 | e.printStackTrace();
74 | }
75 | }
76 | public boolean is(String uri) {
77 | return uri!=null && (
78 | uri.startsWith("ssh:") ||
79 | uri.startsWith("SSH:")
80 | );
81 | }
82 | public SSHFile getSSHFile(String uri) {
83 | return parser.parse(this, uri);
84 | }
85 | public SSH getOrCreate(String user, String host) {
86 | Map perHost=getForHost(host);
87 | SSH ssh=perHost.get(user);
88 | if (ssh==null) {
89 | ssh=
90 | configure(new SSH(user, host, userNotify))
91 | .withIUserPass(iUserPass);
92 | if (!ssh.verifyConnection())
93 | return null;
94 | perHost.put(user, ssh);
95 | users.add(user);
96 | }
97 | return ssh;
98 | }
99 | public String toString() {
100 | return conns.toString();
101 | }
102 | public IFileGet getFileResolver() {
103 | return iFileGet;
104 | }
105 | public List getConnectedHosts() {
106 | List h=new LinkedList<>();
107 | for (String s: conns.keySet()){
108 | Map zzz=conns.get(s);
109 | boolean is=false;
110 | for (String u: zzz.keySet())
111 | if (!is && zzz.get(u).isConnected())
112 | is=true;
113 | if (is) h.add(s);
114 | }
115 | return h;
116 | }
117 |
118 | //////////////////////
119 | // PACKAGE PRIVATE: //
120 | //////////////////////
121 |
122 | /** This is here only for the connection parser: */
123 | String inferUserForHost(String host) {
124 |
125 | //If there is only one user so far, assume it's them:
126 | if (users.size()==1)
127 | return users.iterator().next();
128 |
129 | //If there is an existing connection for that host,
130 | //use the user for it; else tell them to just log in.
131 | //Note that when they login, they may use a new password.
132 | Map perHost=getForHost(host);
133 | if (perHost.size()==1){
134 | SSH ssh=perHost.values().iterator().next();
135 | String s=ssh.getUser();
136 | if (s!=null)
137 | return s;
138 | }
139 |
140 | if (iUserPass!=null && iUserPass.get(null, host, false, privateKeys==null)){
141 | String user=iUserPass.getUser();
142 | SSH ssh=getOrCreate(user, host);
143 | String pass=iUserPass.getPass();
144 | if (pass!=null && !pass.trim().equals(""))
145 | ssh.withPassword(pass);
146 | return user;
147 | }
148 | else
149 | return null;
150 | }
151 |
152 | ////////////////////////
153 | // PRIVATE FUNCTIONS: //
154 | ////////////////////////
155 |
156 | private SSH configure(SSH ssh) {
157 | ssh
158 | .withKnown(knownHosts)
159 | .withPrivateKeys(privateKeys)
160 | .withOpenSSHConfig(openSSHConfig)
161 | .withDefaultPerms(defaultFilePerms, defaultDirPerms);
162 | return ssh;
163 | }
164 | private Map getForHost(String host) {
165 | Map perHost=conns.get(host);
166 | if (perHost==null){
167 | perHost=new HashMap();
168 | conns.put(host, perHost);
169 | }
170 | return perHost;
171 | }
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/SSHExec.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | import com.jcraft.jsch.*;
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.io.PrintStream;
7 | import org.tmotte.klonk.config.msg.UserNotify;
8 |
9 | public class SSHExec {
10 |
11 | private SSH ssh;
12 | private MeatCounter mc;
13 | protected UserNotify userNotify;
14 |
15 | SSHExec(SSH ssh, MeatCounter mc, UserNotify userNotify) {
16 | this.ssh=ssh;
17 | this.mc=mc;
18 | this.userNotify=userNotify;
19 | }
20 |
21 | SSHExecResult exec(String command, boolean alertFail) {
22 | StringBuilder out=new StringBuilder();
23 | ByteArrayOutputStream err=new ByteArrayOutputStream(512);
24 | int result=exec(command, out, err);
25 | if (err.size()>0 || result!=0)
26 | try {
27 | out.append(err.toString("utf-8"));
28 | result=1;
29 | String bad="SSH Failure: "+out.toString();
30 | if (alertFail)
31 | userNotify.alert(bad);
32 | mylog(bad);
33 | } catch (Exception e) {
34 | throw new RuntimeException(e);
35 | }
36 | return new SSHExecResult(result==0, out.toString());
37 | }
38 | private int exec(String command, Appendable out, Appendable err) throws Exception {
39 | ByteArrayOutputStream baos=new ByteArrayOutputStream(512);
40 | int result=exec(command, out, baos);
41 | try {
42 | err.append(baos.toString("utf-8"));
43 | } catch (Exception e) {
44 | throw new RuntimeException(e);
45 | }
46 | return result;
47 | }
48 |
49 | /**
50 | * @param sshErr DO NOT PASS SYSTEM.ERR/OUT TO THIS, IT WILL GET CHEWED TO PIECES (i.e. it will be closed by
51 | * the jsch library).
52 | * @return The output (typically 0,1,2) of the unix command, or -1 if we could not get a connection.
53 | */
54 | private int exec(String command, Appendable out, OutputStream sshErr) {
55 | mylog(Thread.currentThread().hashCode()+" "+command);
56 | ChannelExec channel=getChannel(command);
57 | if (channel==null)
58 | return -1;
59 | try {
60 | channel.setCommand(command);
61 | channel.setErrStream(sshErr);
62 | channel.setOutputStream(null);
63 | InputStream in=channel.getInputStream();
64 | channel.connect();
65 | byte[] tmp=new byte[1024];
66 | try {
67 | //This loop will spin rather heavily.
68 | while (!channel.isClosed()){
69 | while (in.available()>0){
70 | int i=in.read(tmp, 0, 1024);
71 | if (i<0) break;
72 | out.append(new String(tmp, 0, i));
73 | }
74 | //Doing this will cause output to be lost. I do not know why.
75 | //try {Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}
76 | }
77 | int result=channel.getExitStatus();
78 | sshErr.flush();
79 | //mylog(ssh.hashCode()+" "+command+" complete "+result);
80 | return result;
81 | } catch (Exception e) {
82 | closeOnFail();
83 | throw new RuntimeException(e);
84 | } finally {
85 | releaseChannel(channel);
86 | //Doing this will cause output to be lost which doesn't make sense. Let's blame the compiler:
87 | //in.close();
88 | }
89 | } catch (Exception e) {
90 | throw new RuntimeException("Failed to execute: "+command, e);
91 | }
92 | }
93 |
94 |
95 | private ChannelExec channel;
96 | private ChannelExec getChannel(String cmd) {
97 | mc.lock(cmd);
98 | try {
99 | //if (channel==null) channel=makeChannel();
100 | return makeChannel();
101 | } catch (Exception e) {
102 | mc.unlock();
103 | throw new RuntimeException(e);
104 | }
105 | }
106 | private void releaseChannel(ChannelExec channel) {
107 | try {
108 | channel.disconnect();
109 | //this.channel=null;
110 | } catch (Exception e) {
111 | throw new RuntimeException(e);
112 | } finally {
113 | mc.unlock();
114 | }
115 | }
116 | private void closeOnFail() {
117 | if (channel!=null)
118 | channel.disconnect();
119 | channel=null;
120 | }
121 | private ChannelExec makeChannel() throws Exception{
122 | Session session=ssh.getSession();
123 | if (session==null)
124 | return null;
125 | return (ChannelExec)session.openChannel("exec");
126 | }
127 |
128 |
129 | private void mylog(String s) {
130 | userNotify.log("SSHExec: "+s);
131 | }
132 |
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/SSHExecResult.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | class SSHExecResult {
3 | final boolean success;
4 | final String output;
5 | SSHExecResult(boolean success, String output){
6 | this.success=success;
7 | this.output=output;
8 | logFail();
9 | }
10 | public String toString() {
11 | return success+" "+output;
12 | }
13 | private void logFail() {
14 | }
15 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/SSHFileAttr.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | import com.jcraft.jsch.SftpATTRS;
3 | import java.io.File;
4 | import java.io.FileFilter;
5 | import java.io.FilenameFilter;
6 | import java.nio.file.Path;
7 | import java.util.LinkedList;
8 | import java.util.List;
9 | import java.util.regex.Pattern;
10 | import org.tmotte.common.text.StringChunker;
11 |
12 | class SSHFileAttr {
13 |
14 | public final boolean isDirectory;
15 | public final long size;
16 | public final boolean exists;
17 | private final long lastMod;//This is the time without milliseconds
18 |
19 | SSHFileAttr(SftpATTRS orig){
20 | isDirectory=orig.isDir();
21 | lastMod=orig.getMTime();
22 | size=orig.getSize();
23 | exists=true;
24 | }
25 | SSHFileAttr(){
26 | isDirectory=false;
27 | this.exists=false;
28 | this.size=0;
29 | this.lastMod=0;
30 | }
31 |
32 | long getLastModified() {
33 | return lastMod*1000;
34 | }
35 |
36 | public String toString() {
37 | return exists+" "+isDirectory+" "+size+" "+lastMod;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/WrapMap.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh;
2 | import java.util.HashMap;
3 |
4 | /**
5 | * This implements a basic Map cache where items are expired by age. When overcrowding occurs,
6 | * the oldest go overboard first. This would use generic types for both key & value, but we have
7 | * an internal array and those don't allow generics, thanks java :/
8 | */
9 | public class WrapMap {
10 | private final int max;
11 | private final long ageLimit;
12 |
13 | private final HashMap data;
14 | private final String[] names;
15 | private int limIndex=0;
16 |
17 | private final class IndexVal {
18 | B value;
19 | int index;
20 | long time;
21 | }
22 |
23 | public WrapMap(int max, long ageLimit) {
24 | this.max=max;
25 | this.ageLimit=ageLimit;
26 | data=new HashMap<>(this.max);
27 | names=new String[this.max];
28 | }
29 |
30 |
31 | public synchronized B get(String a) {
32 | IndexVal vi=data.get(a);
33 | if (vi==null)
34 | return null;
35 | else
36 | if (System.currentTimeMillis()-vi.time > ageLimit){
37 | data.remove(a);
38 | names[vi.index]=null;
39 | return null;
40 | }
41 | else
42 | return vi.value;
43 | }
44 | public synchronized void remove(String a) {
45 | IndexVal vi=data.get(a);
46 | if (vi!=null) {
47 | data.remove(a);
48 | names[vi.index]=null;
49 | }
50 | }
51 |
52 | public synchronized void put(String name, B b) {
53 | String other=names[limIndex];
54 | if (other!=null)
55 | data.remove(other);
56 |
57 | IndexVal vi=data.get(name);
58 | if (vi!=null)
59 | names[vi.index]=null;
60 | else {
61 | vi=new IndexVal();
62 | data.put(name, vi);
63 | }
64 |
65 | vi.value=b;
66 | vi.time=System.currentTimeMillis();
67 | vi.index=limIndex;
68 |
69 | names[limIndex]=name;
70 | limIndex++; if (limIndex==max) limIndex=0;
71 | }
72 | public synchronized int size(){
73 | return data.size();
74 | }
75 |
76 | public synchronized void reset() {
77 | for (int i=0; i=perLine && count%perLine==0)
105 | ps.println();
106 | }
107 | ps.println();
108 | }
109 |
110 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/test/FileListing.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh.test;
2 | import org.tmotte.klonk.ssh.SSHCommandLine;
3 | import org.tmotte.klonk.ssh.SSHConnections;
4 | import java.io.File;
5 |
6 | public class FileListing {
7 | public static void main(String[] args) throws Exception {
8 | SSHCommandLine cmd=new SSHCommandLine(args);
9 | try {
10 | File file=cmd.sshFile;
11 | mylog(file);
12 | System.out.println("Children...");
13 | for (File f: file.listFiles())
14 | mylog(f);
15 | /*
16 | while ((file=file.getParentFile())!=null) {
17 | System.out.println("Parent:");
18 | mylog(file);
19 | for (File f: file.listFiles()){
20 | System.out.println("Child:");
21 | mylog(f);
22 | }
23 | }
24 | */
25 | } finally {
26 | cmd.connections.close();
27 | }
28 | }
29 | private static void mylog(File f) {
30 | long last=f.lastModified();
31 | System.out.println(
32 | String.format("test.FilesListing: Dir: %-6s %s %s", ""+f.isDirectory(), new java.util.Date(f.lastModified()), f.getAbsolutePath())
33 | );
34 | }
35 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/test/FileSave.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh.test;
2 | import org.tmotte.klonk.ssh.SSHCommandLine;
3 | import org.tmotte.klonk.ssh.SSHFile;
4 | import org.tmotte.klonk.ssh.SSHConnections;
5 | import java.io.File;
6 | import java.io.OutputStream;
7 | import java.io.OutputStreamWriter;
8 | import java.io.InputStream;
9 | import java.io.InputStreamReader;
10 |
11 |
12 | public class FileSave {
13 | public static void main(String[] args) throws Exception {
14 | SSHCommandLine cmd=new SSHCommandLine(args);
15 | SSHFile file=cmd.sshFile;
16 | try {
17 | char[] readBuffer=new char[1024 * 20];
18 | for (int limit=1; limit<10; limit++){
19 | try (
20 | OutputStream output=file.getOutputStream();
21 | OutputStreamWriter outw=new OutputStreamWriter(output, "utf-8");
22 | ){
23 | if (!file.exists())
24 | throw new RuntimeException("What do you mean it doesn't exist");
25 | for (int i=0; i0){
41 | String s=new String(readBuffer, 0, charsRead);
42 | System.out.print(s);
43 | System.out.flush();
44 | }
45 | }
46 | System.out.println("File read");
47 | }
48 | file.delete();
49 | } finally {
50 | cmd.connections.close();
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/ssh/test/TestCaching.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.ssh.test;
2 | import org.tmotte.klonk.ssh.WrapMap;
3 |
4 | public class TestCaching {
5 | public static void main(String[] args) throws Exception {
6 | java.util.Random random=new java.util.Random(System.currentTimeMillis());
7 |
8 | int maxSize=100;
9 | int maxTime=200;
10 | int maxSleep=100;
11 | int tries=200;
12 | int maxval=199;
13 | String formatStr="%5s-%-6s ";
14 | int perLine=10;
15 |
16 | WrapMap wc=new WrapMap<>(maxSize, maxTime);
17 | {
18 | for (int count=0; countrightEdge,
41 | badY=w.ybottomEdge;
42 | if (badX) w.x=screen.x+120;
43 | if (badY) w.y=screen.y;
44 |
45 | boolean tooWide=w.x+w.width > rightEdge,
46 | tooTall=w.y+w.height > bottomEdge;
47 | if (tooWide) w.width =rightEdge - w.x;
48 | if (tooTall) w.height=bottomEdge - w.y;
49 | return w;
50 | }
51 |
52 | private static Rectangle getScreenBounds() {
53 | Rectangle bds = new Rectangle();
54 | for (GraphicsDevice gd: GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
55 | for (GraphicsConfiguration gc: gd.getConfigurations())
56 | bds=bds.union(gc.getBounds());
57 | return bds;
58 | }
59 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/About-Version.txt:
--------------------------------------------------------------------------------
1 | Klonk $version ©2013-2024 Troy Motte
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/About.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup;
2 | import java.awt.Component;
3 | import java.awt.Container;
4 | import java.awt.Dimension;
5 | import java.awt.Font;
6 | import java.awt.GraphicsEnvironment;
7 | import java.awt.Point;
8 | import java.awt.Rectangle;
9 | import java.awt.Window;
10 | import java.awt.event.ActionEvent;
11 | import java.awt.event.KeyAdapter;
12 | import java.awt.event.KeyEvent;
13 | import java.awt.event.KeyListener;
14 | import java.util.List;
15 | import java.util.Properties;
16 | import javax.swing.AbstractAction;
17 | import javax.swing.Action;
18 | import javax.swing.JButton;
19 | import javax.swing.JDialog;
20 | import javax.swing.JFrame;
21 | import javax.swing.JLabel;
22 | import javax.swing.JPanel;
23 | import javax.swing.JScrollBar;
24 | import javax.swing.JScrollPane;
25 | import javax.swing.JTextPane;
26 | import javax.swing.ScrollPaneConstants;
27 | import org.tmotte.common.io.Loader;
28 | import org.tmotte.common.swang.GridBug;
29 | import org.tmotte.common.swang.KeyMapper;
30 | import org.tmotte.common.text.StackTracer;
31 | import org.tmotte.klonk.config.PopupInfo;
32 | import org.tmotte.klonk.config.option.FontOptions;
33 | import org.tmotte.klonk.windows.Positioner;
34 |
35 | public class About {
36 |
37 | // DI:
38 | private PopupInfo pInfo;
39 | private FontOptions fontOptions;
40 |
41 | // Controls:
42 | private JDialog win;
43 | private JTextPane jtpLicense, jtpVersion, jtpJavaVersion;
44 | private JScrollPane jspLicense;
45 | private JButton btnOK;
46 |
47 | // State:
48 | private boolean initialized=false;
49 |
50 | /////////////////////
51 | // PUBLIC METHODS: //
52 | /////////////////////
53 |
54 | public About(PopupInfo pInfo, FontOptions fontOptions) {
55 | this.pInfo=pInfo;
56 | this.fontOptions=fontOptions;
57 | pInfo.addFontListener(fo -> setFont(fo));
58 | }
59 |
60 | public void show() {
61 | init();
62 | Positioner.set(pInfo.parentFrame, win);
63 | btnOK.requestFocusInWindow();
64 | win.pack();
65 | win.setVisible(true);
66 | win.toFront();
67 | }
68 | private void setFont(FontOptions fo) {
69 | this.fontOptions=fo;
70 | if (win!=null){
71 | fontOptions.getControlsFont().set(win);
72 | win.pack();
73 | }
74 | }
75 |
76 | //////////////////////
77 | // PRIVATE METHODS: //
78 | //////////////////////
79 |
80 | private void init() {
81 | if (!initialized){
82 | create();
83 | layout();
84 | listen();
85 | initialized=true;
86 | }
87 | }
88 |
89 | private void create(){
90 | win=new JDialog(pInfo.parentFrame, true);
91 | win.setTitle("About Klonk");
92 | win.setPreferredSize(new Dimension(400,400));
93 |
94 | jtpLicense=new JTextPane();
95 | jtpVersion=new JTextPane();
96 | jtpJavaVersion=new JTextPane();
97 | btnOK=new JButton();
98 |
99 | JTextPane[] jtps={jtpLicense, jtpVersion, jtpJavaVersion};
100 | for (JTextPane jtp: jtps) {
101 | jtp.setEditable(false);
102 | jtp.setBorder(null);
103 | jtp.setOpaque(false);
104 | }
105 | Font font=jtpVersion.getFont().deriveFont(Font.BOLD);
106 | jtpVersion.setFont(font);
107 | jtpJavaVersion.setFont(font);
108 |
109 | jtpLicense.setContentType("text/html");
110 | Properties props=new Properties();
111 | try (java.io.InputStream is=getClass().getResourceAsStream("About-Version-Number.txt");) {
112 | props.load(is);
113 | } catch (Exception e) {
114 | throw new RuntimeException(e);
115 | }
116 | String number=props.getProperty("VERSION.KLONK");
117 | String
118 | license=Loader.loadUTF8String(getClass(), "About-License.html"),
119 | version=Loader.loadUTF8String(getClass(), "About-Version.txt"),
120 | javaVersion="Running under Java version: "+System.getProperty("java.version");
121 | license=license.replaceAll("", "");
122 | version=version.replaceAll("\\$version", number);
123 | jtpLicense.setText(license);
124 | jtpVersion.setText(version);
125 | jtpJavaVersion.setText(javaVersion);
126 | jspLicense=new JScrollPane(jtpLicense);
127 | jspLicense.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
128 | //Force the stupid thing to scroll to top:
129 | jtpLicense.setCaretPosition(0);
130 |
131 | btnOK.setText("OK");
132 | btnOK.setMnemonic(KeyEvent.VK_K);
133 | }
134 | private void layout(){
135 | GridBug gb=new GridBug(win.getContentPane());
136 |
137 | gb.insets.left=3;gb.insets.right=3;
138 | gb.gridXY(0).weightXY(0);
139 |
140 | gb.insets.top=10;
141 | gb.insets.bottom=10;
142 | gb.weightx=1;
143 | gb.fill=GridBug.HORIZONTAL;
144 | gb.add(jtpVersion);
145 |
146 | gb.insets.top=0;
147 | gb.addY(jtpJavaVersion);
148 |
149 | gb.insets.top=0;
150 | gb.weightXY(1);
151 | gb.fill=GridBug.BOTH;
152 | gb.addY(jspLicense);
153 |
154 | gb.weightXY(0);
155 | gb.fill=GridBug.NONE;
156 | gb.insets.top=5;
157 | gb.insets.bottom=10;
158 | gb.addY(btnOK);
159 |
160 | setFont(fontOptions);
161 | }
162 |
163 | private void listen(){
164 | Action actions=new AbstractAction() {
165 | public void actionPerformed(ActionEvent event) {
166 | win.setVisible(false);
167 | }
168 | };
169 | btnOK.addActionListener(actions);
170 | pInfo.currentOS.fixEnterKey(btnOK, actions);
171 | KeyMapper.easyCancel(btnOK, actions);
172 | }
173 |
174 |
175 | ///////////
176 | // TEST: //
177 | ///////////
178 |
179 | public static void main(final String[] args) throws Exception {
180 | javax.swing.SwingUtilities.invokeLater(new Runnable() {
181 | public void run() {
182 | PopupTestContext ptc=new PopupTestContext();
183 | new About(ptc.getPopupInfo(), ptc.getFontOptions()).show();
184 | }
185 | });
186 | }
187 |
188 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/Finder.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup;
2 | import java.util.regex.Matcher;
3 | import java.util.regex.Pattern;
4 | import java.util.regex.PatternSyntaxException;
5 | import javax.swing.AbstractAction;
6 | import javax.swing.Action;
7 | import javax.swing.JButton;
8 | import javax.swing.JCheckBox;
9 | import javax.swing.JComponent;
10 | import javax.swing.JDialog;
11 | import javax.swing.JFrame;
12 | import javax.swing.JLabel;
13 | import javax.swing.JPanel;
14 | import javax.swing.JSeparator;
15 | import javax.swing.KeyStroke;
16 | import javax.swing.SwingConstants;
17 | import javax.swing.text.Caret;
18 | import javax.swing.text.Document;
19 | import javax.swing.text.Position;
20 | import javax.swing.text.Segment;
21 |
22 | class Finder {
23 | //Results:
24 | public String replaceResult;
25 | public String lastError;
26 |
27 | private int location=-1, locationEnd=-1;
28 |
29 | //Inputs held as instance variables for convenience:
30 | private Document doc;
31 | private int offset;
32 | private boolean replaceOn;
33 | private String replaceWith;
34 | //Pattern & matcher preserved for multiple invocations:
35 | private Pattern pattern;
36 | private Matcher matcher;
37 |
38 | public int getEnd(){return locationEnd;}
39 | public int getStart() {return location;}
40 |
41 | public void reset() {
42 | pattern=null;
43 | matcher=null;
44 | }
45 | public Finder setDocument(Document doc, int offset) {
46 | this.doc=doc;
47 | this.offset=offset;
48 | return this;
49 | }
50 | public Finder setReplace(boolean replaceOn, String replaceWith) {
51 | this.replaceOn=replaceOn;
52 | this.replaceWith=replaceWith;
53 | return this;
54 | }
55 |
56 | public boolean find(
57 | String searchFor,
58 | boolean forwards,
59 | boolean caseSensitive,
60 | boolean regex,
61 | boolean multiline
62 | ) {
63 | replaceResult=null;
64 | location=-1;
65 | locationEnd=-1;
66 |
67 | String searchIn;
68 | try {
69 | searchIn=forwards
70 | ?doc.getText(offset, doc.getLength()-offset)
71 | :doc.getText(0, offset);
72 | } catch (Exception e) {
73 | throw new RuntimeException(e);
74 | }
75 | if (regex)
76 | findRegex( searchFor, searchIn, forwards, caseSensitive, multiline);
77 | else
78 | findRegular(searchFor, searchIn, forwards, caseSensitive);
79 | return location!=-1;
80 | }
81 | private void findRegular(String searchFor, String searchIn, boolean forwards, boolean caseSensitive) {
82 | if (caseSensitive)
83 | location=forwards
84 | ?searchIn.indexOf(searchFor)
85 | :searchIn.lastIndexOf(searchFor);
86 | else {
87 | //We search both low & up case because apparently
88 | //there are character sets where this is the only thing
89 | //that works. Turkish or something. Whatever.
90 | searchFor=searchFor.toLowerCase();
91 | String searchInLow=searchIn.toLowerCase();
92 | location=forwards ?searchInLow.indexOf(searchFor)
93 | :searchInLow.lastIndexOf(searchFor);
94 | searchFor=searchFor.toUpperCase();
95 | String searchInHi=searchIn.toUpperCase();
96 | int loc =forwards ?searchInHi.indexOf(searchFor)
97 | :searchInHi.lastIndexOf(searchFor);
98 | if (location==-1 || (loc!=-1 && loc alerter;
38 |
39 | // Controls:
40 | private JDialog win;
41 | private JTextField jtfRow;
42 | private JButton btnOK, btnCancel;
43 | private boolean initialized;
44 |
45 | // State:
46 | private boolean badEntry=false, cancelled=false;
47 | private int result=-1;
48 |
49 | /////////////////////
50 | // PUBLIC METHODS: //
51 | /////////////////////
52 |
53 | public GoToLine(PopupInfo pInfo, FontOptions fontOptions, Setter alerter) {
54 | this.pInfo=pInfo;
55 | this.fontOptions=fontOptions;
56 | this.alerter=alerter;
57 | pInfo.addFontListener(fo -> setFont(fo));
58 | }
59 | public int show() {
60 | init();
61 | String s=jtfRow.getText();
62 | if (s!=null && !s.equals("")){
63 | jtfRow.setCaretPosition(0);
64 | jtfRow.moveCaretPosition(s.length());
65 | }
66 | win.pack();
67 | Positioner.set(pInfo.parentFrame, win, false);
68 |
69 | result=-1;
70 | badEntry=true;
71 | cancelled=false;
72 | while (badEntry && !cancelled)
73 | doShow();
74 | return result;
75 | }
76 |
77 | ////////////////////////
78 | // //
79 | // PRIVATE METHODS: //
80 | // //
81 | ////////////////////////
82 |
83 | private void doShow() {
84 | win.setVisible(true);
85 | win.toFront();
86 | }
87 |
88 | /** action=true means OK, false means Cancel */
89 | private void click(boolean action) {
90 | badEntry=false;
91 | cancelled=!action;
92 | win.setVisible(false);
93 | if (action){
94 | try {
95 | result=Integer.parseInt(jtfRow.getText());
96 | } catch (NumberFormatException e) {
97 | alerter.set("Value entered is not a valid number ");
98 | badEntry=true;
99 | return;
100 | }
101 | if (result<=0) {
102 | alerter.set("Value must be greater than 0");
103 | badEntry=true;
104 | return;
105 | }
106 | }
107 | }
108 |
109 | private void setFont(FontOptions fo) {
110 | this.fontOptions=fo;
111 | if (win!=null){
112 | fontOptions.getControlsFont().set(win);
113 | win.pack();
114 | }
115 | }
116 |
117 | ///////////////////////////
118 | // CREATE/LAYOUT/LISTEN: //
119 | ///////////////////////////
120 |
121 | private void init() {
122 | if (!initialized) {
123 | create();
124 | layout();
125 | listen();
126 | initialized=true;
127 | }
128 | }
129 |
130 | private void create(){
131 | win=new JDialog(pInfo.parentFrame, true);
132 | win.setResizable(false);
133 | win.setTitle("Go to line");
134 | jtfRow=new JTextField();
135 | jtfRow.setColumns(8);
136 | btnOK =new JButton("OK");
137 | btnOK.setMnemonic(KeyEvent.VK_K);
138 | btnCancel=new JButton("Cancel");
139 | btnCancel.setMnemonic(KeyEvent.VK_C);
140 | }
141 |
142 | /////////////
143 |
144 | private void layout() {
145 | GridBug gb=new GridBug(win);
146 | gb.gridy=0;
147 | gb.weightXY(0);
148 | gb.fill=GridBug.HORIZONTAL;
149 | gb.anchor=GridBug.NORTHWEST;
150 | gb.add(getInputPanel());
151 | gb.fill=GridBug.HORIZONTAL;
152 | gb.weightXY(1);
153 | gb.addY(getButtons());
154 | setFont(fontOptions);
155 | }
156 | private JPanel getInputPanel() {
157 | JPanel jp=new JPanel();
158 | GridBug gb=new GridBug(jp);
159 | gb.weightXY(0).gridXY(0);
160 | gb.anchor=GridBug.WEST;
161 |
162 | gb.insets.top=2;
163 | gb.insets.bottom=2;
164 | gb.insets.left=5;
165 |
166 | JLabel label=new JLabel("Line # ");
167 | gb.add(label);
168 | gb.insets.left=0;
169 | gb.insets.right=5;
170 | gb.weightXY(1, 0).setFill(GridBug.HORIZONTAL).addX(jtfRow);
171 |
172 | return jp;
173 | }
174 | private JPanel getButtons() {
175 | JPanel panel=new JPanel();
176 | GridBug gb=new GridBug(panel);
177 | Insets insets=gb.insets;
178 | insets.top=5;
179 | insets.bottom=5;
180 | insets.left=5;
181 | insets.right=5;
182 |
183 | gb.gridx=0;
184 | gb.add(btnOK);
185 | gb.addX(btnCancel);
186 | return panel;
187 | }
188 | private void listen() {
189 | Action okAction=new AbstractAction() {
190 | public void actionPerformed(ActionEvent event) {click(true);}
191 | };
192 | btnOK.addActionListener(okAction);
193 |
194 | // Pressing enter anywhere means ok:
195 | KeyMapper.accel(btnOK, okAction, KeyMapper.key(KeyEvent.VK_ENTER));
196 |
197 | Action cancelAction=new AbstractAction() {
198 | public void actionPerformed(ActionEvent event) {click(false);}
199 | };
200 | btnCancel.addActionListener(cancelAction);
201 | KeyMapper.easyCancel(btnCancel, cancelAction);
202 | win.addWindowListener(new WindowAdapter() {
203 | public void windowClosing(WindowEvent e){
204 | click(false);
205 | }
206 | });
207 | }
208 |
209 | /////////////
210 | /// TEST: ///
211 | /////////////
212 |
213 | public static void main(final String[] args) throws Exception {
214 | javax.swing.SwingUtilities.invokeLater(()->{
215 | try {
216 | PopupTestContext ptc=new PopupTestContext();
217 | KAlert alerter=new KAlert(ptc.getPopupInfo(), ptc.getFontOptions());
218 | GoToLine gtl=new GoToLine(ptc.getPopupInfo(), ptc.getFontOptions(), alerter);
219 | System.out.println(gtl.show());
220 | } catch (Exception e) {
221 | e.printStackTrace();
222 | }
223 | });
224 | }
225 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/Help.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup;
2 | import java.awt.Component;
3 | import java.awt.Container;
4 | import java.awt.Dimension;
5 | import java.awt.Font;
6 | import java.awt.GridBagConstraints;
7 | import java.awt.GridBagLayout;
8 | import java.awt.Insets;
9 | import java.awt.Point;
10 | import java.awt.Rectangle;
11 | import java.awt.Window;
12 | import java.awt.event.ActionEvent;
13 | import java.awt.event.ActionListener;
14 | import java.awt.event.KeyAdapter;
15 | import java.awt.event.KeyEvent;
16 | import java.awt.event.KeyListener;
17 | import java.io.InputStream;
18 | import javax.swing.AbstractAction;
19 | import javax.swing.Action;
20 | import javax.swing.BorderFactory;
21 | import javax.swing.BoxLayout;
22 | import javax.swing.JButton;
23 | import javax.swing.JDialog;
24 | import javax.swing.JFrame;
25 | import javax.swing.JLabel;
26 | import javax.swing.JPanel;
27 | import javax.swing.JScrollPane;
28 | import javax.swing.JTextPane;
29 | import javax.swing.event.HyperlinkEvent;
30 | import javax.swing.event.HyperlinkListener;
31 | import javax.swing.ScrollPaneConstants;
32 | import org.tmotte.common.io.Loader;
33 | import org.tmotte.common.swang.CurrentOS;
34 | import org.tmotte.common.swang.GridBug;
35 | import org.tmotte.common.swang.KeyMapper;
36 | import org.tmotte.klonk.config.option.FontOptions;
37 | import org.tmotte.klonk.config.PopupInfo;
38 |
39 | public class Help {
40 |
41 | // DI:
42 | private PopupInfo pInfo;
43 | private FontOptions fontOptions;
44 | private String homeDir;
45 |
46 | // Controls:
47 | private JButton btnOK;
48 | private JDialog win;
49 | private JTextPane jtp;
50 | private JScrollPane jsp;
51 | private Container mtaContainer;
52 |
53 | // State:
54 | private boolean initialized;
55 |
56 | public Help(PopupInfo pInfo, FontOptions fontOptions, String homeDir) {
57 | this.pInfo=pInfo;
58 | this.fontOptions=fontOptions;
59 | this.homeDir=homeDir;
60 | pInfo.addFontListener(fo -> setFont(fo));
61 | }
62 | public void show() {
63 | show(null);
64 | }
65 | public void show(Rectangle bounds) {
66 | init();
67 | Point pt=pInfo.parentFrame.getLocation();
68 | if (bounds!=null)
69 | win.setBounds(bounds);
70 | win.setLocation(pt.x+20, pt.y+20);
71 | win.setVisible(true);
72 | win.paintAll(win.getGraphics());
73 | win.toFront();
74 | }
75 | private void setFont(FontOptions fo) {
76 | this.fontOptions=fo;
77 | if (win!=null){
78 | fontOptions.getControlsFont().set(win);
79 | win.pack();
80 | }
81 | }
82 |
83 |
84 | ////////////////////////
85 | // PRIVATE METHODS: //
86 | ////////////////////////
87 |
88 | private void click() {
89 | win.setVisible(false);
90 | }
91 |
92 | private void init() {
93 | if (!initialized) {
94 | create();
95 | layout();
96 | listen();
97 | initialized=true;
98 | }
99 | }
100 |
101 | // CREATE/LAYOUT/LISTEN: //
102 | private void create() {
103 | win=new JDialog(pInfo.parentFrame, true);
104 |
105 | jtp=new JTextPane();
106 | jtp.setEditable(false);
107 | jtp.setBorder(null);
108 | jtp.setOpaque(false);
109 | jtp.setContentType("text/html");
110 |
111 | jtp.addHyperlinkListener(new HyperlinkListener() {
112 | @Override public void hyperlinkUpdate(final HyperlinkEvent evt) {
113 | if (HyperlinkEvent.EventType.ACTIVATED == evt.getEventType()){
114 | String desc = evt.getDescription();
115 | if (desc == null || !desc.startsWith("#")) return;
116 | desc = desc.substring(1);
117 | jtp.scrollToReference(desc);
118 | }
119 | }
120 | });
121 |
122 | String helpText=Loader.loadUTF8String(getClass(), "Help.html");
123 | helpText=helpText.replace("$[Home]", homeDir);
124 | jtp.setText(helpText);
125 | jsp=new JScrollPane(jtp);
126 | jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
127 | jsp.getVerticalScrollBar().setUnitIncrement(16);
128 | if (pInfo.currentOS.isOSX)
129 | jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
130 |
131 | //Force the stupid thing to scroll to top:
132 | jtp.setCaretPosition(0);
133 |
134 | btnOK=new JButton("OK");
135 | }
136 | private void layout(){
137 | GridBug gb=new GridBug(win);
138 | gb.gridy=0;
139 | gb.weightXY(1, 1);
140 | gb.fill=GridBug.BOTH;
141 | gb.add(jsp);
142 |
143 | gb.weighty=0.0;
144 | gb.insets.top=5;
145 | gb.insets.bottom=5;
146 | gb.fill=GridBug.NONE;
147 | gb.addY(btnOK);
148 |
149 | setFont(fontOptions);
150 |
151 | Rectangle rect=pInfo.parentFrame.getBounds();
152 | rect.x+=20; rect.y+=20;
153 | rect.width=Math.max(rect.width-40, 100);
154 | rect.height=Math.max(rect.height-40, 100);
155 | win.setBounds(rect);
156 | }
157 | private void listen() {
158 | Action okAction=new AbstractAction() {
159 | public void actionPerformed(ActionEvent event) {
160 | click();
161 | }
162 | };
163 | btnOK.addActionListener(okAction);
164 | KeyMapper.easyCancel(btnOK, okAction);
165 | pInfo.currentOS.fixEnterKey(btnOK, okAction);
166 | jtp.addKeyListener(new KeyAdapter() {
167 | public void keyPressed(KeyEvent ke) {
168 | if (ke.getKeyCode()==KeyEvent.VK_TAB)
169 | btnOK.requestFocusInWindow();
170 | }
171 | });
172 | }
173 |
174 | /////////////
175 | /// TEST: ///
176 | /////////////
177 |
178 | public static void main(final String[] args) throws Exception{
179 | javax.swing.SwingUtilities.invokeLater(new Runnable() {
180 | public void run() {
181 | PopupTestContext ptc=new PopupTestContext();
182 | Help h=new Help(
183 | ptc.getPopupInfo(), ptc.getFontOptions(), "."
184 | );
185 | h.show(new Rectangle(800,400));
186 | }
187 | });
188 | }
189 |
190 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/LineDelimiterListener.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup;
2 | public interface LineDelimiterListener {
3 | public void setDefault(String delimiter);
4 | public void setThis(String delimiter);
5 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/PopupTestContext.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup;
2 | import java.awt.Image;
3 | import java.awt.event.KeyAdapter;
4 | import java.awt.event.KeyEvent;
5 | import java.awt.event.KeyListener;
6 | import java.awt.event.WindowAdapter;
7 | import java.awt.event.WindowEvent;
8 | import java.awt.event.WindowListener;
9 | import java.util.List;
10 | import javax.swing.JFrame;
11 | import org.tmotte.common.swang.CurrentOS;
12 | import org.tmotte.klonk.config.msg.Setter;
13 | import org.tmotte.klonk.config.msg.StatusUpdate;
14 | import org.tmotte.klonk.config.option.FontOptions;
15 | import org.tmotte.klonk.config.KPersist;
16 | import org.tmotte.klonk.config.KHome;
17 | import org.tmotte.klonk.config.BootContext;
18 | import org.tmotte.klonk.config.PopupInfo;
19 | import org.tmotte.klonk.controller.CtrlMain;
20 | import org.tmotte.klonk.io.FileListen;
21 | import org.tmotte.klonk.io.KLog;
22 | import org.tmotte.klonk.Menus;
23 |
24 | /**
25 | * For testing popups without the overhead of the main application running.
26 | */
27 | public class PopupTestContext {
28 |
29 | //DI Components:
30 | KHome home;
31 | KLog log;
32 | KPersist persist;
33 | JFrame mainFrame;
34 | Setter fail;
35 | PopupInfo popupInfo;
36 | FontOptions fontOptions;
37 | CurrentOS currentOS=new CurrentOS();
38 |
39 |
40 |
41 | public KPersist getPersist() {
42 | if (persist==null)
43 | persist=new KPersist(getHome(), getFail(), currentOS);
44 | return persist;
45 | }
46 | protected KHome getHome() {
47 | if (home==null)
48 | //This could be improved by using a command line argument
49 | home=new KHome("./test/home");
50 | return home;
51 | }
52 | public JFrame getMainFrame() {
53 | if (mainFrame==null)
54 | mainFrame=makeMainFrame();
55 | return mainFrame;
56 | }
57 | public PopupInfo getPopupInfo() {
58 | if (popupInfo==null)
59 | popupInfo=new PopupInfo(getMainFrame(), getCurrentOS());
60 | return popupInfo;
61 | }
62 | public FontOptions getFontOptions() {
63 | if (fontOptions==null)
64 | fontOptions=getPersist().getFontAndColors();
65 | return fontOptions;
66 | }
67 | public Setter getFail() {
68 | if (fail==null)
69 | fail=(Throwable t)->t.printStackTrace(System.err);
70 | return fail;
71 | }
72 | public Image getPopupIcon() {
73 | return BootContext.getPopupIcon(this);
74 | }
75 | public CurrentOS getCurrentOS() {
76 | return currentOS;
77 | }
78 |
79 | ////////////////
80 | // UTILITIES: //
81 | ////////////////
82 |
83 | public static JFrame makeMainFrame() {
84 | BootContext.initLookFeel();
85 | JFrame mainFrame=new JFrame("Klonk - Test Main Frame");
86 | KeyAdapter ka=new KeyAdapter() {
87 | public void keyPressed(KeyEvent e){
88 | if (e.getKeyCode()==KeyEvent.VK_ESCAPE)
89 | System.exit(0);
90 | }
91 | };
92 | mainFrame.addKeyListener(ka);
93 | mainFrame.addWindowListener(new WindowAdapter() {
94 | public void windowClosing(WindowEvent e){
95 | System.exit(0);
96 | }
97 | });
98 | mainFrame.setVisible(true);
99 | mainFrame.setBounds(new java.awt.Rectangle(400,400,300,300));
100 | mainFrame.toFront();
101 | return mainFrame;
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/ShellCommandParser.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup;
2 | import java.util.List;
3 | import java.io.File;
4 | import java.util.ArrayList;
5 | import java.util.regex.Pattern;
6 | import org.tmotte.common.text.StringChunker;
7 | import org.tmotte.common.swang.CurrentOS;
8 |
9 | class ShellCommandParser {
10 | static Pattern delimiterPattern=Pattern.compile("(\"|'|\\p{Blank})");
11 | static String currFileMarker="[$1]";
12 |
13 | public static class Shex extends RuntimeException {
14 | private static final long serialVersionUID = 1L;
15 | public Shex(String msg) {super(msg);}
16 | }
17 | public static List parse(String cmd) {
18 | return parse(cmd, null);
19 | }
20 | public static List parse(String cmd, String currFileName) {
21 | List results=new ArrayList<>();
22 | if (referencesCurrFile(cmd) & currFileName!=null)
23 | cmd=cmd.replace(currFileMarker, currFileName);
24 |
25 | //Maybe it's just one big command that references a real file, spaces or no spaces -
26 | //like C:\program files\booger hooger\foo.exe:
27 | if (new File(cmd).exists()){
28 | results.add(cmd);
29 | return results;
30 | }
31 |
32 | //See if we can parse out an actual command that has spaces in it, and parameters
33 | //after that - because stupid people put programs in C:\Program Files:
34 | StringChunker sc=new StringChunker(cmd);
35 | boolean foundProgram=false;
36 | String execute="";
37 | while (sc.find(" ")) {
38 | execute+=sc.getUpTo();
39 | if (new File(execute).exists()){
40 | results.add(execute);
41 | return getProgramArguments(results, sc);
42 | }
43 | execute+=sc.getFound();
44 | }
45 |
46 | //If we never found a blank, this was definitely a straight command with no arguments
47 | //even though it doesn't point to an actual file, like say "ls" or "ps"
48 | if (execute.equals("")){
49 | results.add(cmd);
50 | return results;
51 | }
52 |
53 | //OK then let's just assume the first blank ends the program, even the program doesn't seem
54 | //to exist, like "ps -aux". Then everything else is an argument, isn't it:
55 | sc.reset(cmd);
56 | results.add(sc.getUpTo(" "));
57 | return getProgramArguments(results, sc);
58 | }
59 |
60 | private static boolean referencesCurrFile(String cmd) {
61 | return cmd.indexOf(currFileMarker)!=-1;
62 | }
63 |
64 | private static List getProgramArguments(List results, StringChunker sc){
65 |
66 | while (sc.find(delimiterPattern)){
67 | String found=sc.getFound();
68 | if (found.equals("\"")){
69 | if (!sc.find("\""))
70 | throw new Shex("You appear to be missing a trailing \" character");
71 | results.add(sc.getUpTo());
72 | }
73 | else
74 | if (found.equals("'")){
75 | if (!sc.find("'"))
76 | throw new Shex("You appear to be missing a trailing ' character");
77 | results.add(sc.getUpTo());
78 | }
79 | else {
80 | String r=sc.getUpTo().trim();
81 | if (!r.equals(""))
82 | results.add(r);
83 | }
84 | }
85 | if (!sc.finished())
86 | results.add(sc.getRest());
87 | return results;
88 | }
89 |
90 | ///////////
91 | // TEST: //
92 | ///////////
93 |
94 | public static void main(String[] args) {
95 | String
96 | command=args[0],
97 | currFileName=args.length>1
98 | ?args[1] :null;
99 | List result=parse(args[0], currFileName);
100 | for (String s: result)
101 | System.out.println("-->"+s);
102 | }
103 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/YesNoCancelAnswer.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup;
2 | public class YesNoCancelAnswer {
3 | public final static int YES=1, NO=2, CANCEL=3;
4 | private int answer;
5 | public YesNoCancelAnswer(int answer) {this.answer=answer;}
6 | public boolean isYes(){return answer==YES;}
7 | public boolean isNo() {return answer==NO;}
8 | public boolean isCancel() {return answer==CANCEL;}
9 | public String toString() {
10 | if (isYes())
11 | return "Yes";
12 | else
13 | if (isNo())
14 | return "No";
15 | else
16 | if (isCancel())
17 | return "Cancel";
18 | else
19 | throw new RuntimeException("Unexpected result "+answer);
20 | }
21 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/ssh/SSHFileDialogNoFileException.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup.ssh;
2 | /**
3 | * This is so we can throw an exception to force the FileDialog to
4 | * back off and bail, then catch it and accept it as ok and be quiet
5 | * instead of barking.
6 | */
7 | public class SSHFileDialogNoFileException extends RuntimeException {
8 | private static final long serialVersionUID = 1L;
9 | public SSHFileDialogNoFileException() {
10 | super();
11 | }
12 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/ssh/SSHFileView.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup.ssh;
2 | import org.tmotte.klonk.ssh.SSHFile;
3 | import javax.swing.filechooser.FileView;
4 | import java.io.File;
5 |
6 | public class SSHFileView extends FileView{
7 |
8 | public SSHFileView() {
9 | super();
10 | }
11 | /**
12 | * For some reason this gets called even though we already have a FileSystemView class that
13 | * does the same thing.
14 | */
15 | public Boolean isTraversable(File fdir){
16 | if (SSHFile.cast(fdir)==null)
17 | return super.isTraversable(fdir);
18 | return fdir.isDirectory();
19 | }
20 |
21 |
22 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/ssh/SSHOpenFromResult.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup.ssh;
2 | public class SSHOpenFromResult {
3 | public final String sshFilename;
4 | public boolean sudo=false;
5 | public SSHOpenFromResult(String f, boolean s) {
6 | this.sshFilename=f;
7 | this.sudo=s;
8 | }
9 | public String toString() {
10 | return sshFilename+ (sudo ?" SUDO" :"");
11 | }
12 | }
--------------------------------------------------------------------------------
/java/org/tmotte/klonk/windows/popup/ssh/Test.java:
--------------------------------------------------------------------------------
1 | package org.tmotte.klonk.windows.popup.ssh;
2 | import java.io.File;
3 | import javax.swing.JFrame;
4 | import org.tmotte.common.swang.CurrentOS;
5 | import org.tmotte.klonk.config.msg.UserNotify;
6 | import org.tmotte.klonk.config.option.SSHOptions;
7 | import org.tmotte.klonk.ssh.SSH;
8 | import org.tmotte.klonk.ssh.SSHConnections;
9 | import org.tmotte.klonk.ssh.SSHExec;
10 | import org.tmotte.klonk.ssh.SSHFile;
11 | import org.tmotte.klonk.windows.popup.KAlert;
12 | import org.tmotte.klonk.windows.popup.PopupTestContext;
13 |
14 | class Test {
15 |
16 | public static void main(String[] args) throws Exception {
17 | String path=null;
18 | boolean forSave=false;
19 | for (int i=0; i -s");
42 | }
43 | private static void test(final String path, final boolean forSave) throws Exception {
44 |
45 | //This is obnoxious but we have to do it when we get a null file:
46 | Thread.setDefaultUncaughtExceptionHandler(
47 | new Thread.UncaughtExceptionHandler() {
48 | public void uncaughtException(Thread t, Throwable e){
49 | if (e instanceof SSHFileDialogNoFileException)
50 | System.err.println("OK no biggie "+e);
51 | else
52 | e.printStackTrace();
53 | }
54 | }
55 | );
56 |
57 | final PopupTestContext ptc=new PopupTestContext();
58 | final SSHOptions options=ptc.getPersist().getSSHOptions();
59 | javax.swing.SwingUtilities.invokeLater(new Runnable() {
60 | public void run() {
61 | try {
62 | UserNotify notifier=new UserNotify(System.out);
63 | KAlert alerter=new KAlert(ptc.getPopupInfo(), ptc.getFontOptions());
64 | SSHConnections conns=new SSHConnections(notifier)
65 | .withOptions(options)
66 | .withLogin(
67 | new SSHLogin(ptc.getPopupInfo(), ptc.getFontOptions(), alerter)
68 | );
69 | org.tmotte.klonk.windows.popup.FileDialogWrapper fdw=
70 | new org.tmotte.klonk.windows.popup.FileDialogWrapper(
71 | ptc.getPopupInfo(),
72 | new SSHFileSystemView(conns, notifier),
73 | new SSHFileView()
74 | );
75 | {
76 | File dir=null, file=null;
77 | if (path!=null) {
78 | SSHFile temp=conns.getSSHFile(path);
79 | if (temp.isDirectory())
80 | dir=temp;
81 | else
82 | file=temp;
83 | }
84 | File picked=fdw.show(forSave, file, dir);
85 | System.out.println("RESULT: "+
86 | picked+" "+(picked==null ?"" :picked.getClass())
87 | );
88 | }
89 | } catch (Exception e) {
90 | e.printStackTrace();
91 | }
92 | }//run()
93 | });
94 | }
95 | }
--------------------------------------------------------------------------------
/lib/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/Thumbs.db
--------------------------------------------------------------------------------
/lib/app-find-replace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/app-find-replace.png
--------------------------------------------------------------------------------
/lib/app.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/app.icns
--------------------------------------------------------------------------------
/lib/app.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/app.ico
--------------------------------------------------------------------------------
/lib/app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/app.png
--------------------------------------------------------------------------------
/lib/apptest-find-replace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/apptest-find-replace.png
--------------------------------------------------------------------------------
/lib/apptest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/apptest.png
--------------------------------------------------------------------------------
/lib/classpath.sh:
--------------------------------------------------------------------------------
1 | export CLASSPATH="build"$(find lib -name '*.jar' | gawk '{printf ";"$1}')
2 |
--------------------------------------------------------------------------------
/lib/config.jsmooth:
--------------------------------------------------------------------------------
1 |
2 |
3 | registry
4 | javahome
5 | jrepath
6 | jdkpath
7 | exepath
8 | jview
9 | true
10 | ..\dist\bin\klonk.exe
11 |
12 | app.ico
13 |
14 | -1
15 | ..\dist\klonk.jar
16 |
17 |
18 |
19 | Xshare
20 | off
21 |
22 |
23 | org.tmotte.klonk.config.BootContext
24 | -1
25 |
26 |
27 | Windowed Wrapper
28 |
29 | Message
30 | Java has not been found on your computer. Do you want to download it?
31 |
32 |
33 | URL
34 | http://www.java.com
35 |
36 |
37 |
38 |
39 | SingleProcess
40 | 1
41 |
42 |
43 | SingleInstance
44 | 0
45 |
46 |
47 | JniSmooth
48 | 0
49 |
50 |
56 |
57 |
--------------------------------------------------------------------------------
/lib/jar/jsch-0.1.51.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/jar/jsch-0.1.51.jar
--------------------------------------------------------------------------------
/lib/jar/jsch-0.1.53.jar.broken:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/jar/jsch-0.1.53.jar.broken
--------------------------------------------------------------------------------
/lib/jar/vngx-jsch-0.10.jar.unused:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/lib/jar/vngx-jsch-0.10.jar.unused
--------------------------------------------------------------------------------
/lib/makedmg:
--------------------------------------------------------------------------------
1 | # We are limiting to 98 megabytes heap space below because otherwise it grows continuously
2 | # without garbage collection. Loading large files is so slow that it probably won't make sense to
3 | # go much larger. We are meaning to add back in serial garbage collection as well, because we
4 | # used to crash every 2.5 days otherwise (see javapackager script further below).
5 | #
6 | ant clean config.prod jar
7 |
8 | jpackage --name Klonk --input dist --main-jar klonk.jar --icon lib/app.icns \
9 | --main-class org.tmotte.klonk.config.Klonk --java-options "-Xmx128m -Xss1024k"
10 |
11 |
12 |
13 | ######################################################################################################
14 | # This is the old javapackager that came with Java 8, and tends to break on modern JDK - dunno why
15 | # it's even included if it can't do anything but die. Keeping the script for reference point, for now:
16 | #
17 | #javapackager -deploy -native dmg -srcfiles dist/klonk.jar -outdir dist/dmg -outfile klonk \
18 | # -appclass org.tmotte.klonk.config.Klonk -title "Klonk" -name Klonk -Bicon=lib/app.icns \
19 | # -BjvmOptions=-Xmx128m -BjvmOptions=-XX:+UseSerialGC -BjvmOptions=-Xss1024k -Bruntime=
20 |
21 |
--------------------------------------------------------------------------------
/lib/makeexe:
--------------------------------------------------------------------------------
1 | # This is not working right now. Our historic method still does.
2 | ant clean config.prod jar
3 | jpackage --name Klonk --input dist --main-jar klonk.jar --icon lib/app.icns \
4 | --type exe \
5 | --main-class org.tmotte.klonk.config.Klonk
--------------------------------------------------------------------------------
/lib/makewin.bat:
--------------------------------------------------------------------------------
1 | rem Note: javapackager comes with java 8 distributions only
2 | rem This will advise you to install software from http://www.jrsoftware.org/, which
3 | rem is kind of lame.
4 |
5 | ant clean config.prod jar
6 | javapackager -deploy -native exe -srcfiles dist/klonk.jar -outdir dist/ -outfile klonk -appclass org.tmotte.klonk.config.Klonk -title "Klonk" -name Klonk
7 |
--------------------------------------------------------------------------------
/license.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Klonk License
5 |
6 |
13 |
14 |
15 |
16 |
18 |
19 |
20 | Klonk License
21 | Copyright ©2013-2024 Troy Motte
22 |
23 |
Redistribution and use in source and binary forms, with or without
24 | modification, are permitted provided that the following conditions
25 | are met:
26 |
27 |
28 |
29 | 1. Redistributions of source and/or binary code must retain the above copyright
30 | notice, this list of conditions, and the following disclaimer.
31 |
32 |
33 |
34 | 2. The name "Klonk" must not be used to endorse or promote products
35 | derived from this software without prior written permission from
36 | the copyright holder or their authorized agent(s).
37 |
38 |
39 |
40 | 3. Products derived from this software may not be called "Klonk", nor
41 | may "Klonk" appear in their name, without prior written permission
42 | from the copyright holder or their authorized agent(s).
43 |
44 |
45 |
46 | The Standard Disclaimer
47 |
48 | THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
49 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
50 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OF KLONK
52 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
53 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
54 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
55 | USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
56 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
57 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
58 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59 | SUCH DAMAGE.
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/script/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd $(dirname $0)/..
3 | #java -version
4 | ant "$@" || exit 1
5 |
6 |
--------------------------------------------------------------------------------
/script/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 | repo="klonk"
3 | ant_command='clean config.prod exe dist'
4 |
5 | cd $(dirname $0)/..
6 | ant $ant_command
7 |
8 | cd ../zaboople.github.generate/lib/external-$repo
9 | echo
10 | echo "Current directory: "$(pwd)
11 | echo -n "WARNING: I am about to do an rm -rf. Is that okay? "
12 | read answer
13 | if [[ $answer == y* ]]; then
14 | rm -rf *
15 | fi
16 |
17 | echo
18 | cd ../../../$repo
19 | echo "Current directory: "$(pwd)
20 | echo -n "I am about to cp -r from here to zaboople.github.generate... Is that okay? "
21 | read answer
22 | if [[ $answer == y* ]]; then
23 | cp -r dist/site/* ../zaboople.github.generate/lib/external-$repo
24 | ant clean
25 | fi
26 |
27 | echo
28 | cd ../zaboople.github.generate
29 | echo "Current directory: "$(pwd)
30 | git status
--------------------------------------------------------------------------------
/script/testAny.sh:
--------------------------------------------------------------------------------
1 | cd $(dirname $0)/.. || exit 1
2 | source lib/classpath.sh || exit 1
3 | #java -version || exit 1
4 | ant config.test compile && java -Xshare:off -Xms32m "$@"
5 |
--------------------------------------------------------------------------------
/script/testApp.sh:
--------------------------------------------------------------------------------
1 | #Xshare:off is I think very important. Oh yeah let's share memory. No, thanks.
2 | cd $(dirname $0)/..
3 | #source lib/classpath.sh
4 | ant config.test compile || exit 1
5 | java \
6 | -Xshare:off \
7 | -Xms6m \
8 | -classpath 'build:lib\vngx-jsch-0.10.jar' org.tmotte.klonk.config.BootContext \
9 | -home test/home "$@"
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/script/testAppCygwin.sh:
--------------------------------------------------------------------------------
1 | #Xshare:off is I think very important. Oh yeah let's share memory. No, thanks.
2 | cd $(dirname $0)/..
3 | #source lib/classpath.sh
4 | ant config.test compile || exit 1
5 | java \
6 | -Xshare:off \
7 | -Xms6m \
8 | -classpath 'build;lib\vngx-jsch-0.10.jar' org.tmotte.klonk.config.BootContext \
9 | -home test/home "$@"
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/script/testLock.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 | cd $(dirname $0)/..
3 | ant config.test compile
4 | source lib/classpath.sh
5 | java -classpath build org.tmotte.klonk.io.LockTest test\dink 1 &
6 | java -classpath build org.tmotte.klonk.io.LockTest test\dink 2 &
7 | java -classpath build org.tmotte.klonk.io.LockTest test\dink 3 &
8 | java -classpath build org.tmotte.klonk.io.LockTest test\dink 4 &
9 | java -classpath build org.tmotte.klonk.io.LockTest test\dink 5 &
10 | java -classpath build org.tmotte.klonk.io.LockTest test\dink 6 &
11 | java -classpath build org.tmotte.klonk.io.LockTest test\dink 7 &
12 | java -classpath build org.tmotte.klonk.io.LockTest test\dink 8 &
13 |
14 |
--------------------------------------------------------------------------------
/test/CR.txt:
--------------------------------------------------------------------------------
1 | This is CR, baby.
Oh yeah it's CR.
CR CR.
--------------------------------------------------------------------------------
/test/CRLF.txt:
--------------------------------------------------------------------------------
1 | This is CRLF.
2 |
3 | Yes it is.
4 |
5 | OH yes.
--------------------------------------------------------------------------------
/test/LF.txt:
--------------------------------------------------------------------------------
1 | This is LF.
2 |
3 | Yes it is really.
4 |
5 | It is.
--------------------------------------------------------------------------------
/test/MS-UTF16-BE.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/test/MS-UTF16-BE.txt
--------------------------------------------------------------------------------
/test/MS-UTF16-LE.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zaboople/klonk/de682f1c5615c6219c9ff845f2f034e98906e9a6/test/MS-UTF16-LE.txt
--------------------------------------------------------------------------------
/test/MS-UTF8.txt:
--------------------------------------------------------------------------------
1 | This is
2 | a file.
3 | ☃☃☃☃
--------------------------------------------------------------------------------
/test/autoopen/11.aaa:
--------------------------------------------------------------------------------
1 |
2 | awefawef
3 | awefawef
4 |
5 | awef awefawef
--------------------------------------------------------------------------------
/test/autoopen/12.aaa:
--------------------------------------------------------------------------------
1 |
2 |
3 | THIS is file 12!!!
4 |
--------------------------------------------------------------------------------
/test/autoopen/13.aaa:
--------------------------------------------------------------------------------
1 | 13
2 |
--------------------------------------------------------------------------------
/test/autoopen/14.aaa:
--------------------------------------------------------------------------------
1 |
2 |
3 | 14
4 |
--------------------------------------------------------------------------------
/test/autoopen/15.aaa:
--------------------------------------------------------------------------------
1 |
2 |
3 | HEY MAN THIS IS 15
--------------------------------------------------------------------------------
/test/autoopen/16.aaa:
--------------------------------------------------------------------------------
1 | 16
--------------------------------------------------------------------------------
/test/autoopen/17.aaa:
--------------------------------------------------------------------------------
1 | start java -classpath build org.tmotte.klonk.lock.LockTest test\dink
2 | start java -classpath build org.tmotte.klonk.lock.LockTest test\dink
3 | start java -classpath build org.tmotte.klonk.lock.LockTest test\dink
4 | start java -classpath build org.tmotte.klonk.lock.LockTest test\dink
5 | start java -classpath build org.tmotte.klonk.lock.LockTest test\dink
6 |
--------------------------------------------------------------------------------
/test/autoopen/18.aaa:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | This is a.txt
5 |
--------------------------------------------------------------------------------
/test/autoopen/3.aaa:
--------------------------------------------------------------------------------
1 | This is file number 3
--------------------------------------------------------------------------------
/test/autoopen/4.aaa:
--------------------------------------------------------------------------------
1 | wwwwassdsf4
2 |
--------------------------------------------------------------------------------
/test/autoopen/5.aaa:
--------------------------------------------------------------------------------
1 | 5
2 |
--------------------------------------------------------------------------------
/test/autoopen/6.aaa:
--------------------------------------------------------------------------------
1 | This is number 6
2 |
--------------------------------------------------------------------------------
/test/autoopen/7.aaa:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 77777
5 |
--------------------------------------------------------------------------------
/test/autoopen/9.aaa:
--------------------------------------------------------------------------------
1 | 9
2 |
--------------------------------------------------------------------------------