├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── de │ └── onyxbits │ └── filecast │ ├── App.java │ ├── gui │ ├── BookmarksMenuAction.java │ ├── CodeAction.java │ ├── ContentView.java │ ├── CopyAction.java │ ├── EditMenuAction.java │ ├── FileMenuAction.java │ ├── FileTreeCellRenderer.java │ ├── FileTreeNode.java │ ├── HandbookAction.java │ ├── HelpMenuAction.java │ ├── MainWindow.java │ ├── Messages.java │ ├── NodeEnumeration.java │ ├── PasteAction.java │ ├── QrView.java │ ├── QuitAction.java │ └── TreeView.java │ └── io │ ├── FileComparator.java │ ├── HiddenFileFilter.java │ ├── Messages.java │ ├── PickupHandler.java │ └── Store.java └── resources ├── appicon-128.png ├── appicon-16.png ├── appicon-24.png ├── appicon-32.png ├── appicon-48.ico ├── appicon-48.png ├── appicon-64.png ├── appicon-96.png ├── de └── onyxbits │ └── filecast │ ├── gui │ ├── messages.properties │ └── messages_de.properties │ └── io │ ├── messages.properties │ └── messages__de.properties └── rsrc ├── directory.png ├── favicon.ico ├── favicon.png ├── file.png └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ 17 | 18 | DEADJOE 19 | *~ 20 | *.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FileCast - Zero setup, cross platform desktop to mobile file transfer tool 2 | -------------------------------------------------------------------------- 3 | 4 | Everyday problem: a file needs to be copied from a desktop computer to 5 | a mobile device (or another desktop computer somewhere on the internet). 6 | 7 | FileCast provides a simple, hassle free solution for sharing files and 8 | directories through its build-in webserver. All that is needed on the mobile 9 | device is a webbrowser and optionally a QR code scanner app for easily 10 | copying the download url. 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | de.onyxbits 5 | filecast 6 | 1.1 7 | FileCast 8 | 9 | 10 | 11 | org.eclipse.jetty 12 | jetty-server 13 | ${jettyVersion} 14 | 15 | 16 | 17 | org.eclipse.jetty 18 | jetty-servlet 19 | ${jettyVersion} 20 | 21 | 22 | com.google.zxing 23 | core 24 | 3.2.0 25 | 26 | 27 | commons-io 28 | commons-io 29 | 2.4 30 | 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-compiler-plugin 37 | 2.5.1 38 | 39 | 1.6 40 | 1.6 41 | 42 | 43 | 44 | 45 | org.apache.maven.plugins 46 | maven-shade-plugin 47 | 1.7.1 48 | 49 | 50 | package 51 | 52 | shade 53 | 54 | 55 | 56 | 57 | 58 | 59 | *:* 60 | 61 | META-INF/*.SF 62 | META-INF/*.DSA 63 | META-INF/*.RSA 64 | 65 | 66 | 67 | true 68 | shaded 69 | 70 | 72 | de.onyxbits.filecast.App 73 | 74 | 75 | 76 | 77 | 78 | com.akathist.maven.plugins.launch4j 79 | launch4j-maven-plugin 80 | 1.5.1 81 | 82 | 83 | l4j-clui 84 | package 85 | 86 | launch4j 87 | 88 | 89 | gui 90 | ${project.build.directory}/${artifactId}-${project.version}-shaded.jar 91 | ${project.build.directory}/${project.name}-${project.version}.exe 92 | http://java.com/download 93 | 94 | de.onyxbits.filecast.App 95 | anything 96 | 97 | src/main/resources/appicon-48.ico 98 | 99 | 1.6.0 100 | preferJre 101 | 102 | 103 | 1.0.0.0 104 | ${project.version} 105 | ${project.name} 106 | 2015 onyxbits.de 107 | 1.0.0.0 108 | ${project.version} 109 | ${project.name} 110 | onyxbits.de 111 | Onyxbits 112 | ${project.name}-${project.version}.exe 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 8.1.16.v20140903 122 | ${project.name}-${project.version} 123 | 124 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/App.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast; 2 | 3 | import java.io.File; 4 | import java.net.DatagramSocket; 5 | import java.net.InetAddress; 6 | 7 | import javax.swing.SwingUtilities; 8 | 9 | import org.eclipse.jetty.server.Server; 10 | 11 | import de.onyxbits.filecast.gui.MainWindow; 12 | import de.onyxbits.filecast.io.PickupHandler; 13 | import de.onyxbits.filecast.io.Store; 14 | 15 | public class App implements Runnable { 16 | 17 | /** 18 | * The system property that contains the server address. 19 | */ 20 | public static final String SYSPROP_ADDR = "address"; 21 | 22 | /** 23 | * The system property that contains the server port. 24 | */ 25 | public static final String SYSPROP_PORT = "port"; 26 | 27 | private App(Store store, File initial) { 28 | this.store = store; 29 | this.initial = initial; 30 | } 31 | 32 | private MainWindow mainWindow; 33 | private File initial; 34 | private Store store; 35 | 36 | /** 37 | * @param args 38 | * File/Directory to serve initially 39 | * @throws Exception 40 | */ 41 | public static void main(String[] args) throws Exception { 42 | Store store = new Store(); 43 | File initial = new File(System.getProperty("user.dir")); 44 | try { 45 | File tmp = new File(args[0]); 46 | if (tmp.exists()) { 47 | initial = tmp.getAbsoluteFile(); 48 | } 49 | } 50 | catch (Exception e) { 51 | } 52 | 53 | App app = new App(store, initial); 54 | 55 | if (System.getProperty(SYSPROP_ADDR) == null) { 56 | try { 57 | // Autodiscover a public IP address by making a pseudo connection to an 58 | // outside host and checking which network interface the OS would like 59 | // to route it through. This is not guaranteed to work, but in setups 60 | // where it doesn't, the user probably knows why and can force an 61 | // address. 62 | 63 | if (System.getProperty("java.net.preferIPv4Stack") == null) { 64 | // Dirty hack! Prevent autodection of an IPv6 address unless the user 65 | // absolutely wants it. 66 | System.setProperty("java.net.preferIPv4Stack", "true"); 67 | } 68 | 69 | DatagramSocket s = new DatagramSocket(); 70 | s.connect(InetAddress.getByAddress(new byte[] { 1, 1, 1, 1 }), 0); 71 | InetAddress tmp = s.getLocalAddress(); 72 | s.close(); 73 | if (tmp.getAddress().length > 8) { 74 | System.setProperty(SYSPROP_ADDR, "[" + tmp.getHostAddress() + "]"); 75 | } 76 | else { 77 | System.setProperty(SYSPROP_ADDR, tmp.getHostAddress()); 78 | } 79 | } 80 | catch (Exception e) { 81 | e.printStackTrace(); 82 | System.setProperty(SYSPROP_ADDR, "unknown-address"); 83 | } 84 | } 85 | int port = 0; 86 | try { 87 | port = Integer.parseInt(System.getProperty(SYSPROP_PORT)); 88 | } 89 | catch (Exception e) { 90 | // Can't parse -> autodetect 91 | } 92 | 93 | // Starting a server takes time, building the directory tree takes time. 94 | // Both are independent of each other, so we go through this bit of extra 95 | // hassle for faster startup. 96 | Thread t = new Thread(app); 97 | t.start(); 98 | Server server = new Server(port); 99 | server.setHandler(new PickupHandler(store)); 100 | server.start(); 101 | t.join(); // Block till the server is operational and has assigned a port. 102 | System.setProperty("port", server.getConnectors()[0].getLocalPort() + ""); 103 | SwingUtilities.invokeLater(app.mainWindow); 104 | } 105 | 106 | public void run() { 107 | mainWindow = new MainWindow(initial, store); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/BookmarksMenuAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.event.ActionEvent; 4 | 5 | import javax.swing.AbstractAction; 6 | import javax.swing.KeyStroke; 7 | 8 | public class BookmarksMenuAction extends AbstractAction { 9 | 10 | /** 11 | * 12 | */ 13 | private static final long serialVersionUID = 1L; 14 | 15 | public BookmarksMenuAction() { 16 | putValue(NAME,"Bookmarks"); 17 | putValue(MNEMONIC_KEY,KeyStroke.getKeyStroke("B").getKeyCode()); 18 | } 19 | 20 | public void actionPerformed(ActionEvent arg0) { 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/CodeAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.event.ActionEvent; 4 | 5 | import javax.swing.AbstractAction; 6 | import javax.swing.KeyStroke; 7 | 8 | class CodeAction extends AbstractAction { 9 | 10 | /** 11 | * 12 | */ 13 | private static final long serialVersionUID = 1L; 14 | 15 | protected String value; 16 | 17 | public ContentView contentView; 18 | 19 | public CodeAction() { 20 | putValue(NAME, Messages.getString("CodeAction.0")); //$NON-NLS-1$ 21 | putValue(MNEMONIC_KEY, KeyStroke.getKeyStroke(Messages.getString("CodeAction.1")).getKeyCode()); //$NON-NLS-1$ 22 | } 23 | 24 | public void actionPerformed(ActionEvent e) { 25 | try { 26 | contentView.qrView.setContentString(contentView.textView.getText()); 27 | } 28 | catch (Exception exp) { 29 | //exp.printStackTrace(); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/ContentView.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.Dimension; 4 | import java.awt.Font; 5 | import java.awt.GridBagConstraints; 6 | import java.awt.GridBagLayout; 7 | 8 | import javax.swing.BorderFactory; 9 | import javax.swing.JPanel; 10 | import javax.swing.JTextPane; 11 | 12 | 13 | /** 14 | * A Panel for showing content. 15 | * 16 | * @author patrick 17 | * 18 | */ 19 | class ContentView extends JPanel { 20 | 21 | /** 22 | * 23 | */ 24 | private static final long serialVersionUID = 1L; 25 | 26 | protected QrView qrView; 27 | 28 | protected JTextPane textView; 29 | 30 | public ContentView() { 31 | qrView = new QrView(); 32 | textView = new JTextPane(); 33 | textView.setContentType("text/plain"); 34 | textView.setFont(new Font("monospaced", Font.PLAIN, 14)); 35 | qrView.setPreferredSize(new Dimension(350, 350)); 36 | 37 | setLayout(new GridBagLayout()); 38 | GridBagConstraints gbc = new GridBagConstraints(); 39 | gbc.weightx=1; 40 | gbc.weighty=0.5d; 41 | add(qrView,gbc); 42 | 43 | gbc.weightx=0; 44 | gbc.weighty=0.5d; 45 | gbc.gridy=1; 46 | add(textView,gbc); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/CopyAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.Toolkit; 4 | import java.awt.datatransfer.Clipboard; 5 | import java.awt.datatransfer.ClipboardOwner; 6 | import java.awt.datatransfer.StringSelection; 7 | import java.awt.datatransfer.Transferable; 8 | import java.awt.event.ActionEvent; 9 | import java.awt.event.KeyEvent; 10 | 11 | import javax.swing.AbstractAction; 12 | import javax.swing.KeyStroke; 13 | 14 | class CopyAction extends AbstractAction implements ClipboardOwner { 15 | 16 | /** 17 | * 18 | */ 19 | private static final long serialVersionUID = 1L; 20 | 21 | protected String value; 22 | 23 | public CopyAction() { 24 | putValue(NAME, Messages.getString("CopyAction.0")); //$NON-NLS-1$ 25 | putValue(MNEMONIC_KEY, KeyStroke.getKeyStroke(Messages.getString("CopyAction.1")).getKeyCode()); //$NON-NLS-1$ 26 | putValue(ACCELERATOR_KEY, 27 | KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 28 | } 29 | 30 | public void actionPerformed(ActionEvent e) { 31 | Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); 32 | cb.setContents(new StringSelection(value), this); 33 | } 34 | 35 | public void lostOwnership(Clipboard clipboard, Transferable contents) { 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/EditMenuAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.event.ActionEvent; 4 | 5 | import javax.swing.AbstractAction; 6 | import javax.swing.KeyStroke; 7 | 8 | public class EditMenuAction extends AbstractAction { 9 | 10 | /** 11 | * 12 | */ 13 | private static final long serialVersionUID = 1L; 14 | 15 | public EditMenuAction() { 16 | putValue(NAME, Messages.getString("EditMenuAction.0")); //$NON-NLS-1$ 17 | putValue(MNEMONIC_KEY, KeyStroke 18 | .getKeyStroke(Messages.getString("EditMenuAction.1")).getKeyCode()); //$NON-NLS-1$ 19 | } 20 | 21 | public void actionPerformed(ActionEvent arg0) { 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/FileMenuAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.event.ActionEvent; 4 | 5 | import javax.swing.AbstractAction; 6 | import javax.swing.KeyStroke; 7 | 8 | public class FileMenuAction extends AbstractAction { 9 | 10 | /** 11 | * 12 | */ 13 | private static final long serialVersionUID = 1L; 14 | 15 | public FileMenuAction() { 16 | putValue(NAME,Messages.getString("FileMenuAction.0")); //$NON-NLS-1$ 17 | putValue(MNEMONIC_KEY,KeyStroke.getKeyStroke(Messages.getString("FileMenuAction.1")).getKeyCode()); //$NON-NLS-1$ 18 | } 19 | 20 | public void actionPerformed(ActionEvent arg0) { 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/FileTreeCellRenderer.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.Component; 4 | import java.io.File; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import javax.swing.Icon; 9 | import javax.swing.JLabel; 10 | import javax.swing.JTree; 11 | import javax.swing.filechooser.FileSystemView; 12 | import javax.swing.tree.DefaultTreeCellRenderer; 13 | 14 | class FileTreeCellRenderer extends DefaultTreeCellRenderer { 15 | /** 16 | * 17 | */ 18 | private static final long serialVersionUID = 1L; 19 | 20 | /** 21 | * Icon cache to speed the rendering. 22 | */ 23 | private Map iconCache = new HashMap(); 24 | 25 | /** 26 | * Root name cache to speed the rendering. 27 | */ 28 | private Map rootNameCache = new HashMap(); 29 | 30 | protected static FileSystemView fsv = FileSystemView.getFileSystemView(); 31 | 32 | /* 33 | * (non-Javadoc) 34 | * 35 | * @see 36 | * javax.swing.tree.DefaultTreeCellRenderer#getTreeCellRendererComponent(javax 37 | * .swing.JTree, java.lang.Object, boolean, boolean, boolean, int, boolean) 38 | */ 39 | @Override 40 | public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, 41 | boolean expanded, boolean leaf, int row, boolean hasFocus) { 42 | FileTreeNode ftn = (FileTreeNode) value; 43 | File file = ftn.file; 44 | String filename = ""; 45 | if (file != null) { 46 | if (ftn.isFileSystemRoot) { 47 | // long start = System.currentTimeMillis(); 48 | filename = rootNameCache.get(file); 49 | if (filename == null) { 50 | filename = fsv.getSystemDisplayName(file); 51 | rootNameCache.put(file, filename); 52 | } 53 | // long end = System.currentTimeMillis(); 54 | // System.out.println(filename + ":" + (end - start)); 55 | } 56 | else { 57 | filename = file.getName(); 58 | } 59 | } 60 | JLabel result = (JLabel) super.getTreeCellRendererComponent(tree, filename, sel, expanded, 61 | leaf, row, hasFocus); 62 | if (file != null) { 63 | Icon icon = iconCache.get(filename); 64 | if (icon == null) { 65 | // System.out.println("Getting icon of " + filename); 66 | icon = fsv.getSystemIcon(file); 67 | iconCache.put(filename, icon); 68 | } 69 | result.setIcon(icon); 70 | } 71 | return result; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/FileTreeNode.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.io.File; 4 | import java.util.Arrays; 5 | import java.util.Enumeration; 6 | import java.util.Vector; 7 | 8 | import javax.swing.tree.TreeNode; 9 | import javax.swing.tree.TreePath; 10 | 11 | import de.onyxbits.filecast.io.FileComparator; 12 | import de.onyxbits.filecast.io.HiddenFileFilter; 13 | 14 | class FileTreeNode implements TreeNode { 15 | 16 | /** 17 | * Node file. 18 | */ 19 | protected File file; 20 | 21 | /** 22 | * Children of the node file. 23 | */ 24 | private File[] children; 25 | 26 | private FileTreeNode[] nodeChildren; 27 | 28 | /** 29 | * Parent node. 30 | */ 31 | private FileTreeNode parent; 32 | 33 | /** 34 | * Indication whether this node corresponds to a file system root. 35 | */ 36 | protected boolean isFileSystemRoot; 37 | 38 | /** 39 | * Creates a new file tree node. 40 | * 41 | * @param file 42 | * Node file 43 | * @param isFileSystemRoot 44 | * Indicates whether the file is a file system root. 45 | * @param parent 46 | * Parent node. 47 | */ 48 | public FileTreeNode(File file, boolean isFileSystemRoot, FileTreeNode parent) { 49 | this.file = file; 50 | this.isFileSystemRoot = isFileSystemRoot; 51 | this.parent = parent; 52 | this.children = file.listFiles(new HiddenFileFilter()); 53 | if (this.children == null) { 54 | this.children = new File[0]; 55 | } 56 | nodeChildren = new FileTreeNode[children.length]; 57 | Arrays.sort(children, new FileComparator()); 58 | } 59 | 60 | /** 61 | * Creates a new file tree node. 62 | * 63 | * @param children 64 | * Children files. 65 | */ 66 | public FileTreeNode(File[] children) { 67 | this.file = null; 68 | this.parent = null; 69 | this.children = children; 70 | nodeChildren = new FileTreeNode[children.length]; 71 | } 72 | 73 | /* 74 | * (non-Javadoc) 75 | * 76 | * @see javax.swing.tree.TreeNode#children() 77 | */ 78 | public Enumeration children() { 79 | return new NodeEnumeration(this); 80 | } 81 | 82 | /* 83 | * (non-Javadoc) 84 | * 85 | * @see javax.swing.tree.TreeNode#getAllowsChildren() 86 | */ 87 | public boolean getAllowsChildren() { 88 | return true; 89 | } 90 | 91 | /* 92 | * (non-Javadoc) 93 | * 94 | * @see javax.swing.tree.TreeNode#getChildAt(int) 95 | */ 96 | public TreeNode getChildAt(int childIndex) { 97 | if (nodeChildren[childIndex] == null) { 98 | nodeChildren[childIndex] = new FileTreeNode(this.children[childIndex], this.parent == null, 99 | this); 100 | } 101 | return nodeChildren[childIndex]; 102 | } 103 | 104 | /* 105 | * (non-Javadoc) 106 | * 107 | * @see javax.swing.tree.TreeNode#getChildCount() 108 | */ 109 | public int getChildCount() { 110 | return children.length; 111 | } 112 | 113 | /* 114 | * (non-Javadoc) 115 | * 116 | * @see javax.swing.tree.TreeNode#getIndex(javax.swing.tree.TreeNode) 117 | */ 118 | public int getIndex(TreeNode node) { 119 | FileTreeNode ftn = (FileTreeNode) node; 120 | for (int i = 0; i < this.children.length; i++) { 121 | if (ftn.file.equals(this.children[i])) 122 | return i; 123 | } 124 | return -1; 125 | } 126 | 127 | /* 128 | * (non-Javadoc) 129 | * 130 | * @see javax.swing.tree.TreeNode#getParent() 131 | */ 132 | public TreeNode getParent() { 133 | return this.parent; 134 | } 135 | 136 | /* 137 | * (non-Javadoc) 138 | * 139 | * @see javax.swing.tree.TreeNode#isLeaf() 140 | */ 141 | public boolean isLeaf() { 142 | return (children.length == 0); 143 | } 144 | 145 | public TreePath getPath() { 146 | return new TreePath(getPathToRoot(this, 0)); 147 | } 148 | 149 | private FileTreeNode[] getPathToRoot(FileTreeNode node, int depth) { 150 | if (node == null) { 151 | if (depth == 0) { 152 | return null; 153 | } 154 | return new FileTreeNode[depth]; 155 | } 156 | FileTreeNode[] path = getPathToRoot(node.parent, depth + 1); 157 | path[path.length - depth - 1] = node; 158 | return path; 159 | } 160 | 161 | /** 162 | * Find a file in the hierarchy 163 | * 164 | * @param file 165 | * the file to look for 166 | * @return the node containing the file or null if not found 167 | */ 168 | public FileTreeNode searchFor(File f) { 169 | Vector collect = new Vector(); 170 | File tmp = f; 171 | while (tmp != null) { 172 | collect.add(tmp); 173 | tmp = tmp.getParentFile(); 174 | } 175 | File[] path = collect.toArray(new File[0]); 176 | return searchFor(path, path.length - 1); 177 | } 178 | 179 | private FileTreeNode searchFor(File[] path, int depth) { 180 | if (file != null && file.equals(path[0])) { 181 | // This node contains the file in question. 182 | return this; 183 | } 184 | if (children.length > 0) { // Only descent into directories 185 | // Don't descent into branches that cannot contain the file 186 | if (file == null || file.equals(path[depth])) { 187 | for (int i = 0; i < children.length; i++) { 188 | FileTreeNode ftn = (FileTreeNode) getChildAt(i); 189 | if (file == null) { // A Filesystem node 190 | ftn = ftn.searchFor(path, depth); 191 | } 192 | else { // A tree node 193 | ftn = ftn.searchFor(path, depth - 1); 194 | } 195 | if (ftn != null) { 196 | // A subnode contained the file -> transport all the way up. 197 | return ftn; 198 | } 199 | } 200 | } 201 | } 202 | // We neither have the file nor a subtree that might have it. 203 | return null; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/HandbookAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.Desktop; 4 | import java.awt.event.ActionEvent; 5 | import java.awt.event.KeyEvent; 6 | import java.io.IOException; 7 | import java.net.URI; 8 | import java.net.URISyntaxException; 9 | 10 | import javax.swing.AbstractAction; 11 | import javax.swing.KeyStroke; 12 | 13 | public class HandbookAction extends AbstractAction { 14 | 15 | /** 16 | * 17 | */ 18 | private static final long serialVersionUID = 1L; 19 | 20 | public HandbookAction() { 21 | putValue(NAME, Messages.getString("HandbookAction.0")); //$NON-NLS-1$ 22 | putValue(MNEMONIC_KEY, KeyStroke 23 | .getKeyStroke(Messages.getString("HandbookAction.1")).getKeyCode()); //$NON-NLS-1$ 24 | putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_F1,0)); 25 | } 26 | 27 | public void actionPerformed(ActionEvent e) { 28 | try { 29 | Desktop.getDesktop().browse(new URI(Messages.getString("HandbookAction.2"))); 30 | } 31 | catch (IOException e1) { 32 | // TODO Auto-generated catch block 33 | e1.printStackTrace(); 34 | } 35 | catch (URISyntaxException e1) { 36 | // TODO Auto-generated catch block 37 | e1.printStackTrace(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/HelpMenuAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.event.ActionEvent; 4 | 5 | import javax.swing.AbstractAction; 6 | import javax.swing.KeyStroke; 7 | 8 | public class HelpMenuAction extends AbstractAction { 9 | 10 | 11 | /** 12 | * 13 | */ 14 | private static final long serialVersionUID = 1L; 15 | 16 | public HelpMenuAction() { 17 | putValue(NAME, Messages.getString("HelpMenuAction.0")); //$NON-NLS-1$ 18 | putValue(MNEMONIC_KEY, KeyStroke 19 | .getKeyStroke(Messages.getString("HelpMenuAction.1")).getKeyCode()); //$NON-NLS-1$ 20 | } 21 | 22 | public void actionPerformed(ActionEvent arg0) { 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/MainWindow.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Image; 5 | import java.awt.Insets; 6 | import java.awt.event.ActionEvent; 7 | import java.awt.event.ActionListener; 8 | import java.io.File; 9 | import java.util.List; 10 | import java.util.Vector; 11 | 12 | import javax.swing.Box; 13 | import javax.swing.BoxLayout; 14 | import javax.swing.ImageIcon; 15 | import javax.swing.JFrame; 16 | import javax.swing.JLabel; 17 | import javax.swing.JMenu; 18 | import javax.swing.JMenuBar; 19 | import javax.swing.JMenuItem; 20 | import javax.swing.JPanel; 21 | import javax.swing.JScrollPane; 22 | import javax.swing.JSeparator; 23 | import javax.swing.JSplitPane; 24 | import javax.swing.JTextField; 25 | import javax.swing.JTree; 26 | import javax.swing.border.EmptyBorder; 27 | import javax.swing.event.TreeSelectionEvent; 28 | import javax.swing.event.TreeSelectionListener; 29 | import javax.swing.tree.TreePath; 30 | import javax.swing.tree.TreeSelectionModel; 31 | 32 | import de.onyxbits.filecast.App; 33 | import de.onyxbits.filecast.io.PickupHandler; 34 | import de.onyxbits.filecast.io.Store; 35 | 36 | public class MainWindow extends JFrame implements Runnable, TreeSelectionListener, ActionListener { 37 | 38 | private static final String[] ICONRESOURCES = { "appicon-16.png", //$NON-NLS-1$ 39 | "appicon-24.png", //$NON-NLS-1$ 40 | "appicon-32.png", //$NON-NLS-1$ 41 | "appicon-48.png", //$NON-NLS-1$ 42 | "appicon-64.png", //$NON-NLS-1$ 43 | "appicon-96.png", //$NON-NLS-1$ 44 | "appicon-128.png" }; //$NON-NLS-1$ 45 | 46 | private static final long serialVersionUID = 1L; 47 | 48 | private JMenuBar menuBar; 49 | private ContentView contentView; 50 | private JTree directory; 51 | private JTextField location; 52 | private CopyAction copyAction; 53 | private PasteAction pasteAction; 54 | private Store store; 55 | 56 | public MainWindow(File initial, Store store) { 57 | this.store = store; 58 | copyAction = new CopyAction(); 59 | pasteAction = new PasteAction(); 60 | CodeAction codeAction = new CodeAction(); 61 | 62 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 63 | menuBar = new JMenuBar(); 64 | setJMenuBar(menuBar); 65 | JMenu fileMenu = new JMenu(new FileMenuAction()); 66 | JMenuItem quitItem = new JMenuItem(new QuitAction()); 67 | fileMenu.add(quitItem); 68 | menuBar.add(fileMenu); 69 | JMenu editMenu = new JMenu(new EditMenuAction()); 70 | editMenu.add(new JMenuItem(codeAction)); 71 | editMenu.add(new JSeparator()); 72 | editMenu.add(new JMenuItem(copyAction)); 73 | editMenu.add(new JMenuItem(pasteAction)); 74 | menuBar.add(editMenu); 75 | JMenu helpMenu = new JMenu(new HelpMenuAction()); 76 | helpMenu.add(new JMenuItem(new HandbookAction())); 77 | menuBar.add(helpMenu); 78 | 79 | FileTreeNode ftn = new FileTreeNode(File.listRoots()); 80 | directory = new JTree(ftn); 81 | directory.setCellRenderer(new FileTreeCellRenderer()); 82 | directory.setRootVisible(false); 83 | directory.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 84 | directory.setPreferredSize(null); 85 | contentView = new ContentView(); 86 | codeAction.contentView = contentView; 87 | directory.getActionMap().put("copy", copyAction); 88 | 89 | location = new JTextField(initial.getAbsolutePath()); 90 | location.setMargin(new Insets(3, 3, 3, 3)); 91 | JScrollPane treeScroll = new JScrollPane(directory); 92 | JScrollPane contentScroll = new JScrollPane(contentView); 93 | JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScroll, contentScroll); 94 | splitPane.setResizeWeight(0.45d); 95 | 96 | JPanel content = new JPanel(); 97 | JPanel locationBar = new JPanel(); 98 | locationBar.setLayout(new BoxLayout(locationBar, BoxLayout.X_AXIS)); 99 | locationBar.add(new JLabel(Messages.getString("MainWindow.7"))); //$NON-NLS-1$ 100 | locationBar.add(Box.createHorizontalStrut(5)); 101 | locationBar.add(location); 102 | locationBar.setBorder(new EmptyBorder(8, 5, 8, 5)); 103 | content.setLayout(new BorderLayout()); 104 | content.add(splitPane, BorderLayout.CENTER); 105 | content.add(locationBar, BorderLayout.NORTH); 106 | setContentPane(content); 107 | content.setBorder(new EmptyBorder(0, 3, 1, 3)); 108 | List icons = new Vector(); 109 | for (String ico : ICONRESOURCES) { 110 | try { 111 | icons.add(new ImageIcon(ClassLoader.getSystemResource(ico), "").getImage()); //$NON-NLS-1$ 112 | } 113 | catch (Exception e) { 114 | // No need to make a fuss if we don't have all possible resolutions. 115 | } 116 | } 117 | setIconImages(icons); 118 | selectFile(initial); 119 | } 120 | 121 | /** 122 | * Programmatically select a file 123 | * 124 | * @param file 125 | * the file to display 126 | */ 127 | public void selectFile(File file) { 128 | try { 129 | FileTreeNode ftn = (FileTreeNode) directory.getModel().getRoot(); 130 | ftn = ftn.searchFor(file); 131 | TreePath tp = ftn.getPath(); 132 | directory.setSelectionPath(tp); 133 | directory.scrollPathToVisible(tp); 134 | if (file.isDirectory()) { 135 | directory.expandPath(tp); 136 | } 137 | } 138 | catch (NullPointerException e) { 139 | } 140 | } 141 | 142 | public void run() { 143 | setSize(800, 600); 144 | setLocationRelativeTo(null); 145 | setVisible(true); 146 | directory.addTreeSelectionListener(this); 147 | location.addActionListener(this); 148 | pasteAction.mainWindow = this; 149 | location.getActionMap().put("paste", pasteAction); 150 | directory.getActionMap().put("paste", pasteAction); 151 | // The first call to selectPath() is IO heavy, so we do it in the 152 | // constructor to keep it off the EDT. However, this means we need to 153 | // re-select the path here in order to for the UI to catch up. 154 | TreePath sel = directory.getSelectionPath(); 155 | directory.setSelectionPath(null); 156 | directory.setSelectionPath(sel); 157 | } 158 | 159 | public void valueChanged(TreeSelectionEvent e) { 160 | try { 161 | FileTreeNode ftn = (FileTreeNode) e.getNewLeadSelectionPath().getLastPathComponent(); 162 | store.clear(); 163 | String url = "http://" + System.getProperty(App.SYSPROP_ADDR) + ":" 164 | + System.getProperty(App.SYSPROP_PORT) + PickupHandler.pathFor(store.register(ftn.file)); 165 | contentView.qrView.setContentString(url); 166 | contentView.textView.setText(url); 167 | copyAction.value = url; 168 | setTitle(ftn.file.getPath() + " - Filecast"); 169 | location.setText(ftn.file.getAbsolutePath()); 170 | location.setCaretPosition(location.getText().length()); 171 | } 172 | catch (Exception e1) { 173 | contentView.textView.setText(""); 174 | contentView.qrView.clear(); 175 | copyAction.value = ""; 176 | setTitle("Filecast"); 177 | } 178 | } 179 | 180 | public void actionPerformed(ActionEvent e) { 181 | if (e.getSource() == location) { 182 | File f = new File(location.getText()); 183 | if (f.exists()) { 184 | selectFile(f); 185 | } 186 | } 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/Messages.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.util.MissingResourceException; 4 | import java.util.ResourceBundle; 5 | 6 | public class Messages { 7 | private static final String BUNDLE_NAME = "de.onyxbits.filecast.gui.messages"; //$NON-NLS-1$ 8 | 9 | private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); 10 | 11 | private Messages() { 12 | } 13 | 14 | public static String getString(String key) { 15 | try { 16 | return RESOURCE_BUNDLE.getString(key); 17 | } 18 | catch (MissingResourceException e) { 19 | return '!' + key + '!'; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/NodeEnumeration.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.util.Enumeration; 4 | 5 | class NodeEnumeration implements Enumeration { 6 | 7 | private FileTreeNode node; 8 | private int index; 9 | 10 | public NodeEnumeration(FileTreeNode node) { 11 | this.node = node; 12 | } 13 | 14 | public boolean hasMoreElements() { 15 | return index < node.getChildCount(); 16 | } 17 | 18 | public FileTreeNode nextElement() { 19 | FileTreeNode ret = (FileTreeNode) node.getChildAt(index); 20 | index++; 21 | return ret; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/PasteAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.Toolkit; 4 | import java.awt.datatransfer.Clipboard; 5 | import java.awt.datatransfer.DataFlavor; 6 | import java.awt.event.ActionEvent; 7 | import java.awt.event.KeyEvent; 8 | import java.io.File; 9 | import java.net.URI; 10 | 11 | import javax.swing.AbstractAction; 12 | import javax.swing.KeyStroke; 13 | 14 | public class PasteAction extends AbstractAction { 15 | 16 | /** 17 | * 18 | */ 19 | private static final long serialVersionUID = 1L; 20 | 21 | protected MainWindow mainWindow; 22 | 23 | public PasteAction() { 24 | putValue(NAME, Messages.getString("PasteAction.0")); //$NON-NLS-1$ 25 | putValue(MNEMONIC_KEY, KeyStroke.getKeyStroke(Messages.getString("PasteAction.1")).getKeyCode()); //$NON-NLS-1$ 26 | putValue(ACCELERATOR_KEY, 27 | KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 28 | } 29 | 30 | public void actionPerformed(ActionEvent e) { 31 | try { 32 | Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); 33 | URI uri = new URI(cb.getData(DataFlavor.stringFlavor).toString().trim()); 34 | mainWindow.selectFile(new File(uri)); 35 | } 36 | catch (Exception exp) { 37 | } 38 | 39 | try { 40 | Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); 41 | File f = new File(cb.getData(DataFlavor.stringFlavor).toString().trim()); 42 | if (f.exists()) { 43 | mainWindow.selectFile(f); 44 | } 45 | } 46 | catch (Exception exp) { 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/QrView.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics; 5 | import java.awt.Graphics2D; 6 | import java.awt.image.BufferedImage; 7 | import java.util.Hashtable; 8 | 9 | import javax.swing.JPanel; 10 | 11 | import com.google.zxing.BarcodeFormat; 12 | import com.google.zxing.EncodeHintType; 13 | import com.google.zxing.WriterException; 14 | import com.google.zxing.common.BitMatrix; 15 | import com.google.zxing.qrcode.QRCodeWriter; 16 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; 17 | 18 | public class QrView extends JPanel { 19 | 20 | /** 21 | * 22 | */ 23 | private static final long serialVersionUID = 1L; 24 | 25 | public static final BufferedImage NOTHING = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); 26 | 27 | private BufferedImage image; 28 | 29 | public QrView() { 30 | clear(); 31 | } 32 | 33 | public void clear() { 34 | image = NOTHING; 35 | repaint(); 36 | } 37 | 38 | public void setContentString(String content) throws WriterException { 39 | Hashtable hintMap = new Hashtable(); 40 | hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); 41 | QRCodeWriter qrCodeWriter = new QRCodeWriter(); 42 | BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, getWidth(), 43 | getWidth(), hintMap); 44 | int size = byteMatrix.getWidth(); 45 | 46 | image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); 47 | Graphics2D graphics = (Graphics2D) image.getGraphics(); 48 | graphics.setColor(getBackground()); 49 | graphics.fillRect(0, 0, size, size); 50 | graphics.setColor(Color.BLACK); 51 | 52 | for (int i = 0; i < size; i++) { 53 | for (int j = 0; j < size; j++) { 54 | if (byteMatrix.get(i, j)) { 55 | graphics.fillRect(i, j, 1, 1); 56 | } 57 | } 58 | } 59 | repaint(); 60 | } 61 | 62 | @Override 63 | public void paint(Graphics gr) { 64 | gr.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/QuitAction.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.awt.Toolkit; 4 | import java.awt.event.ActionEvent; 5 | import java.awt.event.KeyEvent; 6 | 7 | import javax.swing.AbstractAction; 8 | import javax.swing.KeyStroke; 9 | 10 | public class QuitAction extends AbstractAction { 11 | 12 | /** 13 | * 14 | */ 15 | private static final long serialVersionUID = 1L; 16 | 17 | public QuitAction() { 18 | putValue(NAME, Messages.getString("QuitAction.0")); //$NON-NLS-1$ 19 | putValue(MNEMONIC_KEY, KeyStroke.getKeyStroke(Messages.getString("QuitAction.1")).getKeyCode()); //$NON-NLS-1$ 20 | putValue(ACCELERATOR_KEY, 21 | KeyStroke.getKeyStroke(KeyEvent.VK_Q, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 22 | } 23 | 24 | public void actionPerformed(ActionEvent arg0) { 25 | System.exit(0); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/gui/TreeView.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.gui; 2 | 3 | import java.io.File; 4 | 5 | import javax.swing.BoxLayout; 6 | import javax.swing.JPanel; 7 | import javax.swing.JScrollPane; 8 | import javax.swing.JTextField; 9 | import javax.swing.JTree; 10 | 11 | public class TreeView extends JPanel { 12 | 13 | /** 14 | * 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | 18 | private JTree tree; 19 | private JTextField filter; 20 | 21 | public TreeView(File initial) { 22 | tree = new JTree(); 23 | filter = new JTextField(); 24 | JPanel content = new JPanel(); 25 | content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); 26 | content.add(new JScrollPane(tree)); 27 | content.add(filter); 28 | add(content); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/io/FileComparator.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.io; 2 | 3 | import java.io.File; 4 | import java.util.Comparator; 5 | 6 | public class FileComparator implements Comparator { 7 | 8 | public int compare(File f1, File f2) { 9 | if (f1.isDirectory() && !f2.isDirectory()) { 10 | // Directory before non-directory 11 | return -1; 12 | } 13 | else if (!f1.isDirectory() && f2.isDirectory()) { 14 | // Non-directory after directory 15 | return 1; 16 | } 17 | else { 18 | // Alphabetic order otherwise 19 | return f1.compareTo(f2); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/io/HiddenFileFilter.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.io; 2 | 3 | import java.io.File; 4 | import java.io.FileFilter; 5 | 6 | public class HiddenFileFilter implements FileFilter { 7 | 8 | public static boolean showHidden = false; 9 | 10 | public boolean accept(File file) { 11 | return (!file.isHidden() || showHidden); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/io/Messages.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.io; 2 | 3 | import java.util.MissingResourceException; 4 | import java.util.ResourceBundle; 5 | 6 | public class Messages { 7 | private static final String BUNDLE_NAME = "de.onyxbits.filecast.io.messages"; //$NON-NLS-1$ 8 | 9 | private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); 10 | 11 | private Messages() { 12 | } 13 | 14 | public static String getString(String key) { 15 | try { 16 | return RESOURCE_BUNDLE.getString(key); 17 | } 18 | catch (MissingResourceException e) { 19 | return '!' + key + '!'; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/io/PickupHandler.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.io; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.PrintWriter; 8 | import java.text.DateFormat; 9 | import java.util.Arrays; 10 | 11 | import javax.servlet.ServletException; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | import org.apache.commons.io.IOUtils; 16 | import org.eclipse.jetty.http.MimeTypes; 17 | import org.eclipse.jetty.io.Buffer; 18 | import org.eclipse.jetty.server.Request; 19 | import org.eclipse.jetty.server.handler.AbstractHandler; 20 | import org.eclipse.jetty.util.log.Log; 21 | import org.eclipse.jetty.util.log.Logger; 22 | 23 | public class PickupHandler extends AbstractHandler { 24 | 25 | private MimeTypes mimeTypes = new MimeTypes(); 26 | private DateFormat timeFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, 27 | DateFormat.MEDIUM); 28 | private Store store; 29 | private boolean zebra; 30 | private Logger logger; 31 | 32 | public PickupHandler(Store store) { 33 | this.store = store; 34 | logger = Log.getLogger(getClass()); 35 | } 36 | 37 | public void handle(String target, Request baseRequest, HttpServletRequest request, 38 | HttpServletResponse response) throws IOException, ServletException { 39 | File requested = store.lookup(getKey(target)); 40 | response.setStatus(HttpServletResponse.SC_OK); 41 | 42 | // Handle classpath resources 43 | InputStream ins = getClass().getClassLoader().getResourceAsStream("rsrc" + target); 44 | if (ins != null) { 45 | byte[] b = IOUtils.toByteArray(ins); 46 | response.setHeader("Content-Length", new Long(b.length).toString()); 47 | ins.close(); 48 | ins = getClass().getClassLoader().getResourceAsStream("rsrc" + target); 49 | IOUtils.copy(ins, response.getOutputStream()); 50 | ins.close(); 51 | return; 52 | } 53 | 54 | // Can't handle -> bail 55 | if (requested == null || !requested.canRead()) { 56 | response.setStatus(HttpServletResponse.SC_FORBIDDEN); 57 | response.sendError(HttpServletResponse.SC_FORBIDDEN, "403 - FORBIDDEN"); 58 | logger.info("FORBIDDEN: " + baseRequest.getRemoteAddr() + "-> " + target); 59 | baseRequest.setHandled(true); 60 | penalize(); 61 | return; 62 | } 63 | 64 | // Handle File Download 65 | if (requested.isFile()) { 66 | response.setHeader("Content-Length", new Long(requested.length()).toString()); 67 | response.setHeader("Content-Disposition", "attachment; filename=\"" + requested.getName()+"\""); 68 | Buffer mime = mimeTypes.getMimeByExtension(requested.getName()); 69 | if (mime != null) { 70 | response.setContentType(mime.toString()); 71 | } 72 | baseRequest.setHandled(true); 73 | logger.info("BEGIN: " + baseRequest.getRemoteAddr() + " -> " + requested.getPath()); 74 | IOUtils.copy(new FileInputStream(requested), response.getOutputStream()); 75 | logger.info("FINISHED: " + baseRequest.getRemoteAddr() + " -> " + requested.getPath()); 76 | } 77 | 78 | // Handle Directory listing 79 | if (requested.isDirectory()) { 80 | logger.info("LIST: " + baseRequest.getRemoteAddr() + " -> " + requested.getPath()); 81 | response.setContentType("text/html; charset=UTF-8"); 82 | PrintWriter pw = response.getWriter(); 83 | pw.write(""); 84 | File[] lst = requested.listFiles(new HiddenFileFilter()); 85 | String parent = store.reverseLookup(requested.getParentFile()); 86 | Arrays.sort(lst, new FileComparator()); 87 | pw.write("

" + requested.getName() + "

"); 88 | pw.write(""); 89 | pw.write(makeHeader()); 90 | if (parent != null) { 91 | pw.write(makeRow(requested.getParentFile(), "..")); 92 | } 93 | for (File f : lst) { 94 | pw.write(makeRow(f, null)); 95 | } 96 | pw.write("
"); 97 | baseRequest.setHandled(true); 98 | } 99 | } 100 | 101 | private String makeHeader() { 102 | StringBuilder sb = new StringBuilder(); 103 | sb.append(""); 104 | sb.append(Messages.getString("PickupHandler.name")); 105 | sb.append(""); 106 | sb.append(""); 107 | sb.append(Messages.getString("PickupHandler.size")); 108 | sb.append(""); 109 | sb.append(""); 110 | sb.append(Messages.getString("PickupHandler.date")); 111 | sb.append(""); 112 | return sb.toString(); 113 | } 114 | 115 | public static String pathFor(String key) { 116 | return "/" + key; 117 | } 118 | 119 | private String makeRow(File content, String nameOverride) { 120 | StringBuilder sb = new StringBuilder(); 121 | if (content != null) { 122 | String key = store.register(content); 123 | sb.append(""); 126 | } 127 | else { 128 | sb.append("class=\"oddrow\">"); 129 | } 130 | sb.append(""); 131 | sb.append(""); 134 | if (content.isDirectory()) { 135 | sb.append(" "); 136 | } 137 | else { 138 | sb.append(" "); 139 | } 140 | if (nameOverride != null) { 141 | sb.append(nameOverride); 142 | } 143 | else { 144 | sb.append(content.getName()); 145 | } 146 | sb.append(""); 147 | sb.append(""); 148 | sb.append(""); 149 | if (content.isDirectory()) { 150 | sb.append("-"); 151 | } 152 | else { 153 | sb.append(humanReadableByteCount(content.length(), true)); 154 | } 155 | sb.append(""); 156 | sb.append(""); 157 | sb.append(timeFormat.format(content.lastModified())); 158 | sb.append(""); 159 | sb.append(""); 160 | } 161 | return sb.toString(); 162 | } 163 | 164 | private String getKey(String target) { 165 | try { 166 | return target.split("/")[1]; 167 | } 168 | catch (Exception e) { 169 | } 170 | return ""; 171 | } 172 | 173 | private void penalize() { 174 | try { 175 | Thread.sleep(1000); 176 | } 177 | catch (InterruptedException e) { 178 | e.printStackTrace(); 179 | } 180 | } 181 | 182 | /** 183 | * Turn a raw filesize into a human readable one 184 | * 185 | * @param bytes 186 | * raw size 187 | * @param si 188 | * use SI units 189 | * @return formated string. 190 | */ 191 | public static String humanReadableByteCount(long bytes, boolean si) { 192 | int unit = si ? 1000 : 1024; 193 | if (bytes < unit) 194 | return bytes + " B"; 195 | int exp = (int) (Math.log(bytes) / Math.log(unit)); 196 | String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); 197 | return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /src/main/java/de/onyxbits/filecast/io/Store.java: -------------------------------------------------------------------------------- 1 | package de.onyxbits.filecast.io; 2 | 3 | import java.io.File; 4 | import java.util.HashMap; 5 | import java.util.Random; 6 | 7 | import org.eclipse.jetty.util.log.Log; 8 | import org.eclipse.jetty.util.log.Logger; 9 | 10 | /** 11 | * Central registry for keeping track (and providing access control) for what 12 | * files are shared over the network. 13 | * 14 | * @author patrick 15 | * 16 | */ 17 | public class Store { 18 | 19 | private HashMap mappings; 20 | private HashMap reverse; 21 | private Random rng; 22 | private Logger logger; 23 | 24 | private static final char[] SYMBOLS; 25 | 26 | static { 27 | StringBuilder tmp = new StringBuilder(); 28 | for (char ch = '0'; ch <= '9'; ++ch) 29 | tmp.append(ch); 30 | for (char ch = 'a'; ch <= 'z'; ++ch) 31 | tmp.append(ch); 32 | SYMBOLS = tmp.toString().toCharArray(); 33 | } 34 | 35 | public Store() { 36 | mappings = new HashMap(); 37 | reverse = new HashMap(); 38 | rng = new Random(System.currentTimeMillis()); 39 | logger = Log.getLogger(getClass()); 40 | } 41 | 42 | /** 43 | * Register a file for sharing 44 | * 45 | * @param file 46 | * the file to share. May be a directory. 47 | * @return a unique, randomly generated key by which the file may be retrieved 48 | * until it gets unregistered. 49 | */ 50 | public synchronized String register(File file) { 51 | String ret = reverse.get(file); 52 | if (ret != null) { 53 | return ret; 54 | } 55 | do { 56 | char[] buf = new char[6]; 57 | for (int idx = 0; idx < buf.length; ++idx) { 58 | buf[idx] = SYMBOLS[rng.nextInt(SYMBOLS.length)]; 59 | } 60 | ret = new String(buf); 61 | } 62 | while (reverse.containsKey(ret)); 63 | mappings.put(ret, file); 64 | reverse.put(file, ret); 65 | logger.info("REGISTER "+ret+" -> "+file.getPath()); 66 | return ret; 67 | } 68 | 69 | /** 70 | * clear all registrations. 71 | */ 72 | public void clear() { 73 | mappings.clear(); 74 | reverse.clear(); 75 | } 76 | 77 | /** 78 | * Lookup a file by its key. 79 | * 80 | * @param key 81 | * the key 82 | * @return the requested file or null if not registered. 83 | */ 84 | public File lookup(String key) { 85 | return mappings.get(key); 86 | } 87 | 88 | /** 89 | * Lookup the key for a file 90 | * 91 | * @param key 92 | * the key 93 | * @return the file or null if not registered. 94 | */ 95 | public String reverseLookup(File key) { 96 | return reverse.get(key); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/resources/appicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/appicon-128.png -------------------------------------------------------------------------------- /src/main/resources/appicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/appicon-16.png -------------------------------------------------------------------------------- /src/main/resources/appicon-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/appicon-24.png -------------------------------------------------------------------------------- /src/main/resources/appicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/appicon-32.png -------------------------------------------------------------------------------- /src/main/resources/appicon-48.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/appicon-48.ico -------------------------------------------------------------------------------- /src/main/resources/appicon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/appicon-48.png -------------------------------------------------------------------------------- /src/main/resources/appicon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/appicon-64.png -------------------------------------------------------------------------------- /src/main/resources/appicon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/appicon-96.png -------------------------------------------------------------------------------- /src/main/resources/de/onyxbits/filecast/gui/messages.properties: -------------------------------------------------------------------------------- 1 | FileMenuAction.0=File 2 | FileMenuAction.1=F 3 | EditMenuAction.0=Edit 4 | EditMenuAction.1=E 5 | HelpMenuAction.0=Help 6 | HelpMenuAction.1=H 7 | QuitAction.0=Quit 8 | QuitAction.1=Q 9 | CodeAction.0=Update QR Code 10 | CodeAction.1=Q 11 | CopyAction.0=Copy 12 | CopyAction.1=C 13 | PasteAction.0=Paste 14 | PasteAction.1=P 15 | MainWindow.7=Location: 16 | HandbookAction.0=Contents 17 | HandbookAction.1=C 18 | HandbookAction.2=http://www.onyxbits.de/filecast/handbook 19 | -------------------------------------------------------------------------------- /src/main/resources/de/onyxbits/filecast/gui/messages_de.properties: -------------------------------------------------------------------------------- 1 | FileMenuAction.0=Datei 2 | FileMenuAction.1=D 3 | EditMenuAction.0=Bearbeiten 4 | EditMenuAction.1=B 5 | HelpMenuAction.0=Hilfe 6 | HelpMenuAction.1=H 7 | CodeAction.0=QR Code aktualisieren 8 | CodeAction.1=Q 9 | QuitAction.0=Beenden 10 | QuitAction.1=E 11 | CopyAction.0=Kopieren 12 | CopyAction.1=K 13 | PasteAction.0=Einf\u00fcgen 14 | PasteAction.1=E 15 | MainWindow.7=Ort: 16 | HandbookAction.0=Inhalt 17 | HandbookAction.1=I 18 | HandbookAction.2=http://www.onyxbits.de/filecast/handbook 19 | -------------------------------------------------------------------------------- /src/main/resources/de/onyxbits/filecast/io/messages.properties: -------------------------------------------------------------------------------- 1 | PickupHandler.name=Name 2 | PickupHandler.size=Größe 3 | PickupHandler.date=Zuletzt geändert -------------------------------------------------------------------------------- /src/main/resources/de/onyxbits/filecast/io/messages__de.properties: -------------------------------------------------------------------------------- 1 | PickupHandler.name=Name 2 | PickupHandler.size=Size 3 | PickupHandler.date=Last modified -------------------------------------------------------------------------------- /src/main/resources/rsrc/directory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/rsrc/directory.png -------------------------------------------------------------------------------- /src/main/resources/rsrc/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/rsrc/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/rsrc/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/rsrc/favicon.png -------------------------------------------------------------------------------- /src/main/resources/rsrc/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onyxbits/FileCast/a5fe317fc7eee560a94bc489a52ea3404f46698e/src/main/resources/rsrc/file.png -------------------------------------------------------------------------------- /src/main/resources/rsrc/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding:0; 3 | margin:0; 4 | } 5 | 6 | body { 7 | color: #333; 8 | font: 14px Sans-Serif; 9 | padding: 50px; 10 | background: #eee; 11 | } 12 | 13 | h1 { 14 | text-align: center; 15 | padding: 20px 0 12px 0; 16 | margin: 0; 17 | } 18 | h2 { 19 | font-size: 16px; 20 | text-align: center; 21 | padding: 0 0 12px 0; 22 | } 23 | 24 | #container { 25 | box-shadow: 0 5px 10px -5px rgba(0,0,0,0.5); 26 | position: relative; 27 | background: white; 28 | } 29 | 30 | table { 31 | background-color: #F3F3F3; 32 | border-collapse: collapse; 33 | width: 100%; 34 | margin: 15px 0; 35 | } 36 | 37 | th { 38 | background-color: #FE4902; 39 | color: #FFF; 40 | cursor: pointer; 41 | padding: 5px 10px; 42 | } 43 | 44 | th small { 45 | font-size: 9px; 46 | } 47 | 48 | td, th { 49 | text-align: left; 50 | } 51 | 52 | a { 53 | text-decoration: none; 54 | } 55 | 56 | td a { 57 | color: #663300; 58 | display: block; 59 | padding: 5px 10px; 60 | } 61 | th a { 62 | padding-left: 0 63 | } 64 | 65 | td:first-of-type a { 66 | padding-left: 35px; 67 | } 68 | th:first-of-type { 69 | padding-left: 35px; 70 | } 71 | 72 | td:not(:first-of-type) a { 73 | background-image: none !important; 74 | } 75 | 76 | tr:nth-of-type(odd) { 77 | background-color: #E6E6E6; 78 | } 79 | 80 | tr:hover td { 81 | background-color:#CACACA; 82 | } 83 | 84 | tr:hover td a { 85 | color: #000; 86 | } 87 | --------------------------------------------------------------------------------