001 package ideExtensions.popupAction;
002
003 import com.bea.ide.Application;
004 import com.bea.ide.actions.DefaultAction;
005 import com.bea.ide.actions.IActionProxy;
006 import com.bea.ide.actions.IPopupAction;
007 import com.bea.ide.actions.IPopupContext;
008 import com.bea.ide.core.MessageSvc;
009 import com.bea.ide.core.ResourceSvc;
010 import com.bea.ide.core.asynctask.AsyncTaskSvc;
011 import com.bea.ide.core.asynctask.IAsyncTask;
012 import com.bea.ide.filesystem.FileSvc;
013 import com.bea.ide.filesystem.IFile;
014 import com.bea.ide.ui.output.OutputMessage;
015 import com.bea.ide.ui.output.OutputSvc;
016 import com.bea.ide.util.URIUtil;
017 import com.bea.ide.workspace.IProject;
018 import com.bea.ide.workspace.IWorkspace;
019 import com.bea.ide.workspace.IWorkspaceEventContext;
020 import com.bea.wlw.runtime.core.util.CryptUtil;
021
022 import java.awt.Color;
023 import java.awt.event.ActionEvent;
024 import java.io.File;
025 import java.io.FileInputStream;
026 import java.net.URI;
027 import java.net.UnknownHostException;
028 import java.util.Enumeration;
029 import java.util.Hashtable;
030 import java.util.Stack;
031 import java.util.prefs.Preferences;
032
033 import org.apache.commons.net.ftp.FTPClient;
034 import org.apache.commons.net.ftp.FTPReply;
035
036 /**
037 * An action behind a popup menu that uploads the currently selected
038 * file or folder via FTP.
039 *
040 * From DefaultAction, this class gets
041 */
042 public class UploadAction extends DefaultAction implements IPopupAction
043 {
044 static ResourceSvc.IResourcePkg s_pkg =
045 ResourceSvc.get().getResourcePackage(FTPPrefsPanel.class, "ftp");
046
047 // A flag to indicate whether it's valid to go ahead and upload.
048 private boolean m_isValidUploadContext;
049 // An IFile repressenting the project from which files will be uploaded.
050 private IFile m_projectDirectory;
051 /**
052 * A project within which there are selected files; used to test for
053 * selections across projects.
054 */
055 private IProject m_project;
056 /**
057 * A flag to indicate whether one of the items selected to upload
058 * is a project directory. A project will need special handling.
059 */
060 private boolean m_projectSelected;
061 /**
062 * A flag to indicate, whether the list items selected for upload
063 * spans multiple projects.
064 */
065 private boolean m_activeProjectChanged;
066
067 // A flag to indicate whether this action is currently trying to upload.
068 private boolean m_uploadInProgress;
069 /**
070 * A flag to indicate that the popup is figuring out whether (and
071 * what) to display.
072 */
073 private boolean m_prepareInProgress;
074
075 // An FTPClient instance to give this action FTP abilities.
076 private FTPClient m_ftp;
077
078 // The root directory as specified in project FTP properties.
079 private String m_rootDir;
080
081 private Hashtable m_LCAncestors;
082
083 // A window to display FTP status.
084 private OutputSvc.IOutputWindow m_win;
085 // The window's description.
086 private OutputSvc.OutputWindowDescription m_desc;
087
088 private static final Color BLACK = new Color(55, 47, 47);
089 private static final Color RED = new Color(223, 45, 45);
090
091 public UploadAction()
092 {
093 // initialize the output window.
094 m_desc = new OutputSvc.OutputWindowDescription();
095 m_desc.title = "FTP Output";
096 m_desc.destination = "FTP";
097 m_win = OutputSvc.get().getWindow(m_desc, false, false);
098 m_activeProjectChanged = false;
099 m_uploadInProgress = false;
100 m_prepareInProgress = false;
101 }
102
103 /**
104 * Called by the IDE to inform the popup that it is about to be requested
105 * for display. The <em>pc</em> parameter includes information
106 * about the popup's potential position in the IDE.
107 *
108 * This method retrieves information
109 * needs to handle the case where multiple items in the application
110 * tree are selected
111 *
112 * given the set of nodes that are selected in the application tree,
113 * determines whether the "upload" menu item should be visible.
114 */
115 public void prepare(IPopupContext pc)
116 {
117 // If there's already some FTPing going on, don't show this command.
118 if (m_uploadInProgress || m_prepareInProgress)
119 {
120 getProxy().setEnabled(false);
121 getProxy().putValue(IActionProxy.PROP_Visible, Boolean.valueOf(true));
122 getProxy().putValue(IActionProxy.PROP_Label, "Upload");
123 return;
124 }
125 // Set flags to initial values.
126 m_prepareInProgress = true;
127 m_isValidUploadContext = true;
128 m_project = null;
129 m_projectSelected = false;
130
131 /**
132 * Get a list of all the projects in this application, along with the
133 * files in each project. Add each file to an IFile array. An IFile
134 * provides many methods useful to handling files through an
135 * extension.
136 */
137 IWorkspace workspace =
138 (IWorkspace) Application.get().getProperty(Application.PROP_ActiveWorkspace);
139 IProject[] projects = workspace.getProjects();
140 IFile[] projectIFiles = new IFile[projects.length];
141 for (int i = 0; i < projectIFiles.length; i++)
142 {
143 projectIFiles[i] = projects[i].getIFile();
144 }
145
146 /**
147 * This section verifies that the files selected in the Application
148 * window are part of the same project; FTP actions are not
149 * permitted for multiple files crossing project boundaries.
150 */
151 IWorkspaceEventContext ctx = (IWorkspaceEventContext) pc;
152 // Get the URIs of the selected nodes in the Application window.
153 URI[] uris = ctx.getURIs();
154
155 /**
156 *
157 */
158 for (int i = 0; i < uris.length; i++)
159 {
160 URI uri = uris[i];
161 /**
162 * Find out if an entire project is selected. If it is, this action
163 * will later need to add all of the project's file to the list of those
164 * to upload.
165 */
166 if (uri.getScheme().equals("project"))
167 {
168 /**
169 * FileSvc.I.getIFile() expects URIs whose scheme component is "file",
170 * so adjust "project" URIs accordingly.
171 */
172 uri = URIUtil.changeScheme(uri, "file");
173 if (uri == null)
174 {
175 MessageSvc.get().displayError(s_pkg.getString("uriChangeSchemeError"), MessageSvc.LEVEL_ERROR);
176 m_prepareInProgress = false;
177 return;
178 }
179 m_projectSelected = true;
180 }
181
182 /**
183 * The item selected in the window must be a regular file, folder, or a
184 * project (as noted above). In other words, the scheme must be
185 * "file," rather than "library", "workspace", or "resource."
186 */
187 if (!uri.getScheme().equals("file"))
188 {
189 m_isValidUploadContext = false;
190 break;
191 }
192
193 // Get an IFile for the item at the URI.
194 IFile file = FileSvc.get().getIFile(uri);
195 /**
196 * A flag to indicate whether the current file's parent project
197 * has been found.
198 */
199 boolean foundProject = false;
200
201 while (file != null)
202 {
203 /**
204 * Loop through the list of projects in this application, checking
205 * (essentially) to see whether there are multiple projects
206 * represented in the list of files selected in the Application
207 * window.
208 */
209 for (int pIndex = 0; pIndex < projectIFiles.length; pIndex++)
210 {
211 IFile projectIFile = projectIFiles[pIndex];
212 /**
213 * If the current file in the list of selected files is the same
214 * as the current IFile representing a project in the application,
215 * then find out if the current project IFile is also the same as the
216 * the project IFile established for testing. If a different project
217 * IFile, then the selection list spans multiple projects, and
218 * that's bad because this action doesn't support that.
219 */
220 if (file.compareTo(projectIFile) == 0)
221 {
222 /**
223 * If, among the files selected for upload, a test project hasn't
224 * been determined yet, then set the test project to the
225 * project represented by the current projectIFile.
226 */
227 if (m_project == null)
228 {
229 /**
230 * If the current projectIFile is not the same as the IFile
231 * established for testing, then a change of projects
232 * has occurred.
233 */
234 if (projectIFile != m_projectDirectory)
235 {
236 m_activeProjectChanged = true;
237 } else
238 {
239 m_activeProjectChanged = false;
240 }
241 /**
242 * Set the test project directory and test project to the
243 * current directory and project. If these don't change
244 * during the course of this loop, then they'll be used
245 * for the FTP upload. If they do change, then this action
246 * is unsupported because it means the selection list
247 * spans projects.
248 */
249 m_projectDirectory = projectIFile;
250 m_project = projects[pIndex];
251 /**
252 * If the test project directory is not the same as the current
253 * project IFile, then this action is unsupported because
254 * multiple projects are represented in the list of files to
255 * upload.
256 */
257 } else if (m_projectDirectory.compareTo(projectIFile) != 0)
258 {
259 m_isValidUploadContext = false;
260 }
261 foundProject = true;
262 break;
263 }
264 }
265
266 /**
267 * If a project has been found, exit this loop. Otherwise,
268 * set the file to check to the current file's parent.
269 */
270 if (foundProject)
271 {
272 break;
273 }
274 file = file.getParentIFile();
275 }
276
277 /**
278 * For some reason, the upload action can't be done. Stop
279 * looping through the list of selected files.
280 */
281 if (!m_isValidUploadContext)
282 {
283 break;
284 }
285 }
286 /**
287 * If no project is represented by the list of selected files, then
288 * set the "valid" flag to false. There's nothing to upload.
289 */
290 if (m_project == null)
291 {
292 m_isValidUploadContext = false;
293 }
294
295 /**
296 * If the items selected are valid for uploading, then enable display
297 * of this popup and set display characteristics. Otherwise,
298 * don't allow display.
299 */
300 if (m_isValidUploadContext)
301 {
302 getProxy().setEnabled(true);
303 getProxy().putValue(IActionProxy.PROP_Visible, Boolean.valueOf(true));
304 getProxy().putValue(IActionProxy.PROP_Label, "Upload");
305 } else
306 {
307 getProxy().setEnabled(false);
308 getProxy().putValue(IActionProxy.PROP_Visible, Boolean.valueOf(false));
309 }
310 // All done now, so indicate that preparation is finished.
311 m_prepareInProgress = false;
312 }
313
314 /**
315 * A background task for performing the FTP operation.
316 */
317 private class FTPTask implements IAsyncTask
318 {
319 // Constructs an instance of this class.
320 FTPTask()
321 {}
322
323 // No task to run.
324 public void cleanup()
325 {}
326
327 // No task to run.
328 public void interrupt()
329 {}
330
331 // No task to run.
332 public void runForeground()
333 {}
334
335 /**
336 * Called by the IDE for code to be run in the background.
337 */
338 public void runBackground()
339 {
340 // If uploading is already happening, then don't do it now.
341 if (m_uploadInProgress || m_prepareInProgress)
342 {
343 return;
344 }
345 // If the upload action wasn't in progress, it is now.
346 m_uploadInProgress = true;
347
348 // Initialize the status window.
349 m_win = OutputSvc.get().getWindow(m_desc, true, true);
350 m_win.addMessage(new OutputMessage("", BLACK, null));
351 m_win.addMessage(new OutputMessage("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", BLACK, null));
352 m_win.addMessage(new OutputMessage("Current project: " +
353 m_projectDirectory.getName(), BLACK, null));
354
355 // Store all the nodes in a hashtable as IFiles.
356 m_LCAncestors = new Hashtable();
357 /**
358 * If the project itself is selected, put all its direct descendants
359 * on the list.
360 */
361 if (m_projectSelected)
362 {
363 IFile[] childIFiles = m_projectDirectory.listIFiles();
364 for (int i = 0; i < childIFiles.length; i++)
365 {
366 m_LCAncestors.put(childIFiles[i], Boolean.TRUE);
367 }
368 } else
369 {
370 /**
371 * Use an object representing the current application tree's
372 * selections to get URIs for the selected items.
373 */
374 IWorkspaceEventContext ctx =
375 (IWorkspaceEventContext) Application.get().getCookie(IWorkspaceEventContext.KEY);
376 URI[] uris = ctx.getURIs();
377 /**
378 * Get an IFile for each of the selected items, then put each into
379 * a HashTable to
380 */
381 for (int i = 0; i < uris.length; i++)
382 {
383 URI uri = uris[i];
384 IFile file = FileSvc.get().getIFile(uri);
385 m_LCAncestors.put(file, Boolean.TRUE);
386 }
387
388 /**
389 * For each child node, if one of the other nodes is an ancestor,
390 * remove the child node from the list of least common ancestors.
391 */
392 for (int i = 0; i < uris.length; i++)
393 {
394 URI childURI = uris[i];
395 IFile childIFile = FileSvc.get().getIFile(childURI);
396 boolean ancestorFound = false;
397
398 for (int j = 0; j < uris.length; j++)
399 {
400 URI ancestorURI = uris[j];
401 IFile ancestorIFile = FileSvc.get().getIFile(ancestorURI);
402
403 if (childIFile.compareTo(ancestorIFile) == 0)
404 {
405 continue;
406 }
407
408 IFile parentIFile = childIFile.getParentIFile();
409 while (parentIFile != null && parentIFile.compareTo(m_projectDirectory) != 0)
410 {
411 if (parentIFile.compareTo(ancestorIFile) == 0)
412 {
413 if (m_LCAncestors.containsKey(childIFile))
414 {
415 m_LCAncestors.remove(childIFile);
416 }
417 ancestorFound = true;
418 break;
419 }
420
421 parentIFile = parentIFile.getParentIFile();
422 }
423
424 if (ancestorFound)
425 {
426 break;
427 }
428 }
429 }
430 }
431
432 // Get the FTP preferences set by the user.
433 Preferences prefs = m_project.systemNodeForPackage(FTPPrefsPanel.class);
434
435 /**
436 * If there's no FTP connection yet, attempt to create one using
437 * FTP preferences.
438 */
439 if (m_ftp == null || !m_ftp.isConnected() || m_activeProjectChanged)
440 {
441 String hostname = "";
442 try
443 {
444 m_ftp = new FTPClient();
445 hostname = prefs.get(FTPSettings.HOSTNAME, "");
446 hostname = hostname.trim();
447 if (hostname.equals(""))
448 {
449 m_win.addMessage(new OutputMessage("You must specify a hostname for FTP connections", RED, null));
450 m_uploadInProgress = false;
451 return;
452 }
453 m_ftp.connect(hostname);
454 int reply = m_ftp.getReplyCode();
455 if (!(FTPReply.isPositiveCompletion(reply)))
456 {
457 m_win.addMessage(new OutputMessage(hostname + ": refused connection.", BLACK, null));
458 m_ftp.disconnect();
459 m_uploadInProgress = false;
460 return;
461 }
462 m_win.addMessage(new OutputMessage("Connected to " + hostname, BLACK, null));
463
464 // Get the FTP password and decrypt it.
465 String password = prefs.get(FTPSettings.PASSWORD, null);
466 if (password != null)
467 {
468 try
469 {
470 password = CryptUtil.get().deobfuscate(password);
471 } catch (Exception ex)
472 {
473 MessageSvc.get().debugLog("Exception while attempting to decrypt proxy password: " + ex.getMessage());
474 password = "";
475 }
476 }
477
478 // Get the preferred FTP port number and set it for file transfers.
479 String port = prefs.get(FTPSettings.PORT, null);
480 int portNumber;
481 try
482 {
483 portNumber = Integer.parseInt(port);
484 } catch (Exception ex)
485 {
486 portNumber = FTPSettings.DEFAULT_PORT;
487 }
488 if (m_ftp.getDefaultPort() != portNumber)
489 {
490 m_ftp.setDefaultPort(portNumber);
491 }
492
493 /**
494 * Get the preferred username and attempt to log in using
495 * it and the preferred password. Publish a message to
496 * the status window.
497 */
498 String username = prefs.get(FTPSettings.USERNAME, "");
499 username = username.trim();
500 if (!m_ftp.login(username, password))
501 {
502 m_win.addMessage(new OutputMessage("Login failed for user: \"" + username + "\"", RED, null));
503 m_ftp.disconnect();
504 m_uploadInProgress = false;
505 return;
506 }
507 m_win.addMessage(new OutputMessage("Login successful for user: \"" + username + "\"", BLACK, null));
508
509 /**
510 * Get the preferred root directory and change to that directory
511 * on the server.
512 */
513 m_rootDir = prefs.get(FTPSettings.REMOTE_DIRECTORY, "");
514 m_rootDir = m_rootDir.trim();
515 if (m_rootDir.equals(""))
516 {
517 m_win.addMessage(new OutputMessage("You must specify a directory where files should be uploaded.", RED, null));
518 m_uploadInProgress = false;
519 return;
520 }
521 if (!m_ftp.changeWorkingDirectory(m_rootDir))
522 {
523 if (!m_ftp.makeDirectory(m_rootDir))
524 {
525 m_win.addMessage(new OutputMessage("Failure changing to directory: " + m_rootDir, RED, null));
526 m_uploadInProgress = false;
527 return;
528 }
529 }
530 m_win.addMessage(new OutputMessage("Remote directory: " + m_rootDir, BLACK, null));
531 } catch (UnknownHostException ex)
532 {
533 m_win.addMessage(new OutputMessage("\"" + hostname + "\" is an unknown host.", RED, null));
534 m_uploadInProgress = false;
535 return;
536 } catch (Exception ex)
537 {
538 ex.printStackTrace();
539 try
540 {
541 m_ftp.disconnect();
542 } catch (Exception exc)
543 {
544 exc.printStackTrace();
545 }
546 m_uploadInProgress = false;
547 return;
548 }
549 }
550
551 // Ensure that the root directory exists.
552 try
553 {
554 if (!m_ftp.changeWorkingDirectory(m_rootDir))
555 {
556 if (!m_ftp.makeDirectory(m_rootDir))
557 {
558 m_win.addMessage(new OutputMessage("Failure changing to directory: " + m_rootDir, RED, null));
559 m_uploadInProgress = false;
560 return;
561 }
562 }
563 } catch (Exception ex)
564 {
565 ex.printStackTrace();
566 try
567 {
568 m_ftp.disconnect();
569 } catch (Exception exc)
570 {
571 exc.printStackTrace();
572 }
573 m_uploadInProgress = false;
574 return;
575 }
576
577 /**
578 * For each node to transfer, create its parents on the remote server
579 * if necessary, then upload the node and its children.
580 */
581 Enumeration enum = m_LCAncestors.keys();
582 while (enum.hasMoreElements())
583 {
584 IFile file = (IFile) (enum.nextElement());
585 String cwd = m_createParents(file, m_rootDir);
586 m_uploadFile(cwd, file);
587 }
588 // Publish a completion message to the FTP status window.
589 m_win.addMessage(new OutputMessage("DONE with FTP task.", BLACK, null));
590 // Signal FTPing is done now.
591 m_uploadInProgress = false;
592 }
593 }
594
595 /**
596 * Called by the IDE to execute the action associated with this popup.
597 * This method adds to the IDE a background task to perform the
598 * file upload.
599 *
600 * @param e The IDE even that occurred.
601 */
602 public void actionPerformed(ActionEvent e)
603 {
604 AsyncTaskSvc.get().addTask(new FTPTask());
605 }
606
607 /**
608 * Creates the subdirectory path from a file to the parent project on
609 * the remote server. Returns the string that maps to the file's parent
610 * directory. This method is called by the background task in FTPTask
611 * in preparation for uploading.
612 *
613 * @param file The file to upload.
614 * @param cwd The remote root directory specified in project properties.
615 */
616 private String m_createParents(IFile file, String cwd)
617 {
618 /**
619 * If the IFile is really a project root, then just return the
620 * root directory; this function is really only intended to be
621 * called with a file inside a project root.
622 */
623 if (file.compareTo(m_projectDirectory) == 0)
624 {
625 return cwd;
626 }
627
628 try
629 {
630 Stack subdirs = new Stack();
631
632 // Push the file and its parents onto a stack.
633 while ((file = file.getParentIFile()) != null && file.compareTo(m_projectDirectory) != 0)
634 {
635 subdirs.push(file);
636 }
637
638 /**
639 * For each of the items in the stack, make an IFile and
640 * append its name to a path. Then set the current FTP working
641 * directory to the one specified by the path.
642 */
643 while (!subdirs.empty())
644 {
645 IFile dir = (IFile) subdirs.pop();
646 cwd += "/" + dir.getName();
647 if (!m_ftp.changeWorkingDirectory(cwd))
648 {
649 if (!m_ftp.makeDirectory(cwd))
650 {
651 break;
652 }
653 }
654 }
655 } catch (Exception ex)
656 {
657 ex.printStackTrace();
658 }
659
660 return cwd;
661 }
662
663 /**
664 * Uploads the specified <em>file</em> to the root directory specified
665 * by <em>cwd</em>.
666 *
667 * @param cwd The root directory to upload to.
668 * @param file The file to upload.
669 */
670 private void m_uploadFile(String cwd, IFile file)
671 {
672 try
673 {
674 /**
675 * If this is a file, not a directory, then prepend the path to the
676 * root and upload it. Publish a status message to the status window.
677 */
678 if (file.isFile())
679 {
680 File f = new File(file.getURI());
681 FileInputStream stream = new FileInputStream(f);
682 String path = cwd + "/" + file.getName();
683 boolean ret = m_ftp.storeFile(path, stream);
684 if (ret)
685 {
686 m_win.addMessage(new OutputMessage(path + ": upload successful.", BLACK, null));
687 } else
688 {
689 m_win.addMessage(new OutputMessage(path + ": upload failed.", RED, null));
690 }
691 return;
692 }
693
694 /**
695 * If the file is a directory, make a corresponding directory on
696 * the receiving server.
697 */
698 cwd += "/" + file.getName();
699 if (!m_ftp.changeWorkingDirectory(cwd))
700 {
701 if (!m_ftp.makeDirectory(cwd))
702 {
703 return;
704 }
705 }
706
707 // Upload each of the files in the current directory.
708 IFile[] childIFiles = file.listIFiles();
709 for (int i = 0; i < childIFiles.length; i++)
710 {
711 m_uploadFile(cwd, childIFiles[i]);
712 }
713 } catch (Exception ex)
714 {
715 ex.printStackTrace();
716 }
717 }
718 }
|