001 package org.hd.d.pg2k.clApp.uploader;
002
003 /**
004 * Created by IntelliJ IDEA.
005 * User: DHD
006 * Date: 05-Oct-2006
007 * Time: 19:59:06
008 */
009
010 import java.awt.datatransfer.DataFlavor;
011 import java.awt.datatransfer.Transferable;
012 import java.awt.datatransfer.UnsupportedFlavorException;
013 import java.io.ByteArrayInputStream;
014 import java.io.ByteArrayOutputStream;
015 import java.io.File;
016 import java.io.IOException;
017 import java.io.InputStream;
018 import java.io.OutputStream;
019 import java.net.URL;
020
021 import javax.jnlp.FileContents;
022 import javax.jnlp.JNLPRandomAccessFile;
023 import javax.swing.JCheckBox;
024 import javax.swing.JComponent;
025 import javax.swing.TransferHandler;
026 import javax.swing.table.AbstractTableModel;
027
028 import org.hd.d.pg2k.svrCore.uploader.UploadInfoBean;
029
030 /**Class to help DnD (drag-and-drop) files into the JWS uploader.
031 * Package-visible for access by the GUI classes.
032 * <p>
033 * TODO: Extend this to accept URLs, eg dragged from a browser.
034 */
035 class FileTransferHandler extends TransferHandler
036 {
037 private static final long serialVersionUID = 1151591254166507360L;
038
039 /**The business-logic component; never null. */
040 private final UploaderLogic logic;
041
042 /**The model for the table so we can force a redraw; never null. */
043 final AbstractTableModel uploadTableModel;
044
045 /**The "auto" file-guess mode check box; never null. */
046 final JCheckBox autoSuffixCheckBox;
047
048 /**The exhibit description bean; never null. */
049 final UploadInfoBean uib;
050
051 /**Create a new instance ready to receive files and drop them into the selected-files database. */
052 FileTransferHandler(final JCheckBox autoSuffixCheckBox,
053 final AbstractTableModel uploadTableModel,
054 final UploaderLogic logic,
055 final UploadInfoBean uib)
056 {
057 if((autoSuffixCheckBox == null) ||
058 (uploadTableModel == null) ||
059 (logic == null) ||
060 (uib == null))
061 { throw new IllegalArgumentException(); }
062 this.autoSuffixCheckBox = autoSuffixCheckBox;
063 this.uploadTableModel = uploadTableModel;
064 this.logic = logic;
065 this.uib = uib;
066 }
067
068 /**Attempt to import file lists, or http and ftp URLs.
069 */
070 @Override
071 @SuppressWarnings("unchecked")
072 public boolean importData(final JComponent c, final Transferable t)
073 {
074 // If we can't handle this data then return false.
075 if(!canImport(c, t.getTransferDataFlavors()))
076 { return(false); }
077
078 // FIXME: Load the files in another thread so as not to block the UI.
079 try
080 {
081 if(hasFileFlavor(t.getTransferDataFlavors()))
082 {
083 final java.util.List<File> files =
084 (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
085
086 // Wrap the files up as FileContents values...
087 final FileContents filesFC[];
088 // If we appear to be running in JNLP,
089 // then do this through the ExtendedService,
090 if(logic.exs != null)
091 {
092 final File f[] = new File[files.size()];
093 files.toArray(f);
094 filesFC = logic.exs.openFiles(f);
095 }
096 // else do it directly...
097 else
098 {
099 filesFC = new FileContents[files.size()];
100 for(int i = files.size(); --i >= 0; )
101 { filesFC[i] = new NormalFileContent(files.get(i)); }
102 }
103
104 // Add to the database...
105 logic.addSelectedFiles(autoSuffixCheckBox.isSelected(),
106 filesFC,
107 uib);
108
109 // Get the table re-drawn with the new data.
110 uploadTableModel.fireTableDataChanged();
111
112 return(true); // We got the data!
113 }
114 else if(hasStringFlavor(t.getTransferDataFlavors()))
115 {
116 // System.out.println("importData: got string data flavor");
117 final String s = (String) t.getTransferData(DataFlavor.stringFlavor);
118 // Assume that the URL is first component;
119 // other text may follow after whitespace.
120 final URL u = new URL(s.split("[ \\r\\n\\t]")[0]);
121 final String protocol = u.getProtocol();
122 if("http".equals(protocol) || "ftp".equals(protocol))
123 {
124 System.out.println("importData: got URL: " + u);
125 logic.logger.log("Selected URL: "+u);
126
127 // Maximum site of object that we can support upload of
128 // if we have to buffer it up first.
129 // Function of how much memory we have and other factors.
130 final int maxLengthToBuffer = (int) Math.min(16*1024*1024, Runtime.getRuntime().totalMemory()/4);
131
132 // Create single-"file" list from the URL passed to us.
133 final FileContents fileFC[] = new FileContents[]{
134 new FileContents(){
135 /**If we have read and buffered the content, this is non-null. */
136 private byte content[];
137
138 public boolean canRead() throws IOException
139 { return(true); }
140
141 public boolean canWrite() throws IOException
142 { return(false); }
143
144 /**Get stream for the remote URL each time. */
145 public InputStream getInputStream() throws IOException
146 {
147 // If we haven't buffered the data, return a stream.
148 if(content == null) { return(u.openStream()); }
149 // Return the buffered data.
150 return(new ByteArrayInputStream(content));
151 }
152
153 /**Get length of remote object if possible. */
154 public long getLength() throws IOException
155 {
156 final int declLength = u.openConnection().getContentLength();
157 if(declLength > -1) { return(declLength); }
158 // If we cannot get the Content-Length
159 // then we may actually have to fetch the data!
160 System.out.println("Trying to download item to find its length: "+u);
161 logic.logger.log("Could not get Content-Length, so will try to download item; max length: ~"+maxLengthToBuffer);
162
163 // Buffer to collect the input.
164 final byte buf[] = new byte[1<<13];
165 final ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
166 // Get the input.
167 final InputStream is = u.openStream();
168 while(baos.size() < maxLengthToBuffer)
169 {
170 final int n = is.read(buf);
171 if(n < 1)
172 {
173 content = baos.toByteArray();
174 return(content.length);
175 }
176 baos.write(buf, 0, n);
177 }
178 // Could not compute the input length (too large).
179 logic.logger.log("Could not compute item length (too large)...");
180 return(-1);
181 }
182
183 public long getMaxLength() throws IOException
184 { throw new UnsupportedOperationException(); }
185
186 /**Return full URL as name. */
187 public String getName() throws IOException
188 { return(s); }
189
190 public OutputStream getOutputStream(final boolean arg0) throws IOException
191 { throw new UnsupportedOperationException("no output possible"); }
192
193 public JNLPRandomAccessFile getRandomAccessFile(final String arg0) throws IOException
194 { throw new UnsupportedOperationException(); }
195
196 public long setMaxLength(final long arg0) throws IOException
197 { throw new UnsupportedOperationException(); }
198 }
199 };
200
201 // Add name/contents of URL to the database...
202 logic.addSelectedFiles(autoSuffixCheckBox.isSelected(),
203 fileFC,
204 uib);
205
206 // Get the table re-drawn with the new data.
207 uploadTableModel.fireTableDataChanged();
208
209 System.out.println("importData: done. ");
210 return(true); // Succeeded.
211 }
212 return(false); // Cannot import the data...
213 }
214 }
215 catch(final UnsupportedFlavorException ufe)
216 {
217 System.err.println("importData: unsupported data flavor");
218 }
219 catch(final IOException ieo)
220 {
221 System.err.println("importData: I/O exception");
222 }
223 return(false); // Cannot import the data...
224 }
225
226 /**Returns true iff we can import at least one of the specified data flavors.
227 * This will be false unless:
228 * <ul>
229 * <li>The data is presented as a file list or a String (potential URL).
230 * <li>Our uib is in a state to make a valid, unique exhibit name.
231 * </ul>
232 */
233 @Override
234 public boolean canImport(final JComponent c, final DataFlavor[] flavors)
235 {
236 // If the uib is not yet capable of generating us a good exhibit name
237 // then return false.
238 if(!uib.enoughValidUniqueInfo())
239 { return(false); }
240
241 if(!hasFileFlavor(flavors) && !hasStringFlavor(flavors))
242 { return(false); }
243
244 return(true); // Seems OK!
245 }
246
247 private boolean hasFileFlavor(final DataFlavor[] flavors)
248 {
249 for(int i = flavors.length; --i >= 0; )
250 {
251 if(DataFlavor.javaFileListFlavor.equals(flavors[i]))
252 { return(true); }
253 }
254 return(false);
255 }
256
257 /**To accept URLs as text strings? */
258 private boolean hasStringFlavor(final DataFlavor[] flavors)
259 {
260 for(int i = flavors.length; --i >= 0; )
261 {
262 if(flavors[i].isFlavorTextType())
263 // if(DataFlavor.stringFlavor.equals(flavors[i]))
264 { return(true); }
265 }
266 return(false);
267 }
268 }
269