1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ddmuilib.explorer; 18 19 import com.android.ddmlib.AdbCommandRejectedException; 20 import com.android.ddmlib.DdmConstants; 21 import com.android.ddmlib.FileListingService; 22 import com.android.ddmlib.FileListingService.FileEntry; 23 import com.android.ddmlib.IDevice; 24 import com.android.ddmlib.IShellOutputReceiver; 25 import com.android.ddmlib.ShellCommandUnresponsiveException; 26 import com.android.ddmlib.SyncException; 27 import com.android.ddmlib.SyncService; 28 import com.android.ddmlib.SyncService.ISyncProgressMonitor; 29 import com.android.ddmlib.TimeoutException; 30 import com.android.ddmuilib.DdmUiPreferences; 31 import com.android.ddmuilib.ImageLoader; 32 import com.android.ddmuilib.Panel; 33 import com.android.ddmuilib.SyncProgressHelper; 34 import com.android.ddmuilib.SyncProgressHelper.SyncRunnable; 35 import com.android.ddmuilib.TableHelper; 36 import com.android.ddmuilib.actions.ICommonAction; 37 import com.android.ddmuilib.console.DdmConsole; 38 39 import org.eclipse.core.runtime.IStatus; 40 import org.eclipse.core.runtime.Status; 41 import org.eclipse.jface.dialogs.ErrorDialog; 42 import org.eclipse.jface.dialogs.IInputValidator; 43 import org.eclipse.jface.dialogs.InputDialog; 44 import org.eclipse.jface.preference.IPreferenceStore; 45 import org.eclipse.jface.viewers.DoubleClickEvent; 46 import org.eclipse.jface.viewers.IDoubleClickListener; 47 import org.eclipse.jface.viewers.ISelection; 48 import org.eclipse.jface.viewers.ISelectionChangedListener; 49 import org.eclipse.jface.viewers.IStructuredSelection; 50 import org.eclipse.jface.viewers.SelectionChangedEvent; 51 import org.eclipse.jface.viewers.TreeViewer; 52 import org.eclipse.jface.viewers.ViewerDropAdapter; 53 import org.eclipse.swt.SWT; 54 import org.eclipse.swt.dnd.DND; 55 import org.eclipse.swt.dnd.FileTransfer; 56 import org.eclipse.swt.dnd.Transfer; 57 import org.eclipse.swt.dnd.TransferData; 58 import org.eclipse.swt.graphics.Image; 59 import org.eclipse.swt.layout.FillLayout; 60 import org.eclipse.swt.widgets.Composite; 61 import org.eclipse.swt.widgets.Control; 62 import org.eclipse.swt.widgets.DirectoryDialog; 63 import org.eclipse.swt.widgets.Display; 64 import org.eclipse.swt.widgets.FileDialog; 65 import org.eclipse.swt.widgets.Tree; 66 import org.eclipse.swt.widgets.TreeItem; 67 68 import java.io.BufferedReader; 69 import java.io.File; 70 import java.io.IOException; 71 import java.io.InputStreamReader; 72 import java.util.ArrayList; 73 import java.util.regex.Matcher; 74 import java.util.regex.Pattern; 75 76 /** 77 * Device filesystem explorer class. 78 */ 79 public class DeviceExplorer extends Panel { 80 81 private final static String TRACE_KEY_EXT = ".key"; // $NON-NLS-1S 82 private final static String TRACE_DATA_EXT = ".data"; // $NON-NLS-1S 83 84 private static Pattern mKeyFilePattern = Pattern.compile( 85 "(.+)\\" + TRACE_KEY_EXT); // $NON-NLS-1S 86 private static Pattern mDataFilePattern = Pattern.compile( 87 "(.+)\\" + TRACE_DATA_EXT); // $NON-NLS-1S 88 89 public static String COLUMN_NAME = "android.explorer.name"; //$NON-NLS-1S 90 public static String COLUMN_SIZE = "android.explorer.size"; //$NON-NLS-1S 91 public static String COLUMN_DATE = "android.explorer.data"; //$NON-NLS-1S 92 public static String COLUMN_TIME = "android.explorer.time"; //$NON-NLS-1S 93 public static String COLUMN_PERMISSIONS = "android.explorer.permissions"; // $NON-NLS-1S 94 public static String COLUMN_INFO = "android.explorer.info"; // $NON-NLS-1S 95 96 private Composite mParent; 97 private TreeViewer mTreeViewer; 98 private Tree mTree; 99 private DeviceContentProvider mContentProvider; 100 101 private ICommonAction mPushAction; 102 private ICommonAction mPullAction; 103 private ICommonAction mDeleteAction; 104 private ICommonAction mCreateNewFolderAction; 105 106 private Image mFileImage; 107 private Image mFolderImage; 108 private Image mPackageImage; 109 private Image mOtherImage; 110 111 private IDevice mCurrentDevice; 112 113 private String mDefaultSave; 114 115 public DeviceExplorer() { 116 } 117 118 /** 119 * Sets custom images for the device explorer. If none are set then defaults are used. 120 * This can be useful to set platform-specific explorer icons. 121 * 122 * This should be called before {@link #createControl(Composite)}. 123 * 124 * @param fileImage the icon to represent a file. 125 * @param folderImage the icon to represent a folder. 126 * @param packageImage the icon to represent an apk. 127 * @param otherImage the icon to represent other types of files. 128 */ 129 public void setCustomImages(Image fileImage, Image folderImage, Image packageImage, 130 Image otherImage) { 131 mFileImage = fileImage; 132 mFolderImage = folderImage; 133 mPackageImage = packageImage; 134 mOtherImage = otherImage; 135 } 136 137 /** 138 * Sets the actions so that the device explorer can enable/disable them based on the current 139 * selection 140 * @param pushAction 141 * @param pullAction 142 * @param deleteAction 143 * @param createNewFolderAction 144 */ 145 public void setActions(ICommonAction pushAction, ICommonAction pullAction, 146 ICommonAction deleteAction, ICommonAction createNewFolderAction) { 147 mPushAction = pushAction; 148 mPullAction = pullAction; 149 mDeleteAction = deleteAction; 150 mCreateNewFolderAction = createNewFolderAction; 151 } 152 153 /** 154 * Creates a control capable of displaying some information. This is 155 * called once, when the application is initializing, from the UI thread. 156 */ 157 @Override 158 protected Control createControl(Composite parent) { 159 mParent = parent; 160 parent.setLayout(new FillLayout()); 161 162 ImageLoader loader = ImageLoader.getDdmUiLibLoader(); 163 if (mFileImage == null) { 164 mFileImage = loader.loadImage("file.png", mParent.getDisplay()); 165 } 166 if (mFolderImage == null) { 167 mFolderImage = loader.loadImage("folder.png", mParent.getDisplay()); 168 } 169 if (mPackageImage == null) { 170 mPackageImage = loader.loadImage("android.png", mParent.getDisplay()); 171 } 172 if (mOtherImage == null) { 173 // TODO: find a default image for other. 174 } 175 176 mTree = new Tree(parent, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL); 177 mTree.setHeaderVisible(true); 178 179 IPreferenceStore store = DdmUiPreferences.getStore(); 180 181 // create columns 182 TableHelper.createTreeColumn(mTree, "Name", SWT.LEFT, 183 "0000drwxrwxrwx", COLUMN_NAME, store); //$NON-NLS-1$ 184 TableHelper.createTreeColumn(mTree, "Size", SWT.RIGHT, 185 "000000", COLUMN_SIZE, store); //$NON-NLS-1$ 186 TableHelper.createTreeColumn(mTree, "Date", SWT.LEFT, 187 "2007-08-14", COLUMN_DATE, store); //$NON-NLS-1$ 188 TableHelper.createTreeColumn(mTree, "Time", SWT.LEFT, 189 "20:54", COLUMN_TIME, store); //$NON-NLS-1$ 190 TableHelper.createTreeColumn(mTree, "Permissions", SWT.LEFT, 191 "drwxrwxrwx", COLUMN_PERMISSIONS, store); //$NON-NLS-1$ 192 TableHelper.createTreeColumn(mTree, "Info", SWT.LEFT, 193 "drwxrwxrwx", COLUMN_INFO, store); //$NON-NLS-1$ 194 195 // create the jface wrapper 196 mTreeViewer = new TreeViewer(mTree); 197 198 // setup data provider 199 mContentProvider = new DeviceContentProvider(); 200 mTreeViewer.setContentProvider(mContentProvider); 201 mTreeViewer.setLabelProvider(new FileLabelProvider(mFileImage, 202 mFolderImage, mPackageImage, mOtherImage)); 203 204 // setup a listener for selection 205 mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() { 206 public void selectionChanged(SelectionChangedEvent event) { 207 ISelection sel = event.getSelection(); 208 if (sel.isEmpty()) { 209 mPullAction.setEnabled(false); 210 mPushAction.setEnabled(false); 211 mDeleteAction.setEnabled(false); 212 mCreateNewFolderAction.setEnabled(false); 213 return; 214 } 215 if (sel instanceof IStructuredSelection) { 216 IStructuredSelection selection = (IStructuredSelection) sel; 217 Object element = selection.getFirstElement(); 218 if (element == null) 219 return; 220 if (element instanceof FileEntry) { 221 mPullAction.setEnabled(true); 222 mPushAction.setEnabled(selection.size() == 1); 223 if (selection.size() == 1) { 224 FileEntry entry = (FileEntry) element; 225 setDeleteEnabledState(entry); 226 mCreateNewFolderAction.setEnabled(entry.isDirectory()); 227 } else { 228 mDeleteAction.setEnabled(false); 229 } 230 } 231 } 232 } 233 }); 234 235 // add support for double click 236 mTreeViewer.addDoubleClickListener(new IDoubleClickListener() { 237 public void doubleClick(DoubleClickEvent event) { 238 ISelection sel = event.getSelection(); 239 240 if (sel instanceof IStructuredSelection) { 241 IStructuredSelection selection = (IStructuredSelection) sel; 242 243 if (selection.size() == 1) { 244 FileEntry entry = (FileEntry)selection.getFirstElement(); 245 String name = entry.getName(); 246 247 FileEntry parentEntry = entry.getParent(); 248 249 // can't really do anything with no parent 250 if (parentEntry == null) { 251 return; 252 } 253 254 // check this is a file like we want. 255 Matcher m = mKeyFilePattern.matcher(name); 256 if (m.matches()) { 257 // get the name w/o the extension 258 String baseName = m.group(1); 259 260 // add the data extension 261 String dataName = baseName + TRACE_DATA_EXT; 262 263 FileEntry dataEntry = parentEntry.findChild(dataName); 264 265 handleTraceDoubleClick(baseName, entry, dataEntry); 266 267 } else { 268 m = mDataFilePattern.matcher(name); 269 if (m.matches()) { 270 // get the name w/o the extension 271 String baseName = m.group(1); 272 273 // add the key extension 274 String keyName = baseName + TRACE_KEY_EXT; 275 276 FileEntry keyEntry = parentEntry.findChild(keyName); 277 278 handleTraceDoubleClick(baseName, keyEntry, entry); 279 } 280 } 281 } 282 } 283 } 284 }); 285 286 // setup drop listener 287 mTreeViewer.addDropSupport(DND.DROP_COPY | DND.DROP_MOVE, 288 new Transfer[] { FileTransfer.getInstance() }, 289 new ViewerDropAdapter(mTreeViewer) { 290 @Override 291 public boolean performDrop(Object data) { 292 // get the item on which we dropped the item(s) 293 FileEntry target = (FileEntry)getCurrentTarget(); 294 295 // in case we drop at the same level as root 296 if (target == null) { 297 return false; 298 } 299 300 // if the target is not a directory, we get the parent directory 301 if (target.isDirectory() == false) { 302 target = target.getParent(); 303 } 304 305 if (target == null) { 306 return false; 307 } 308 309 // get the list of files to drop 310 String[] files = (String[])data; 311 312 // do the drop 313 pushFiles(files, target); 314 315 // we need to finish with a refresh 316 refresh(target); 317 318 return true; 319 } 320 321 @Override 322 public boolean validateDrop(Object target, int operation, TransferData transferType) { 323 if (target == null) { 324 return false; 325 } 326 327 // convert to the real item 328 FileEntry targetEntry = (FileEntry)target; 329 330 // if the target is not a directory, we get the parent directory 331 if (targetEntry.isDirectory() == false) { 332 target = targetEntry.getParent(); 333 } 334 335 if (target == null) { 336 return false; 337 } 338 339 return true; 340 } 341 }); 342 343 // create and start the refresh thread 344 new Thread("Device Ls refresher") { 345 @Override 346 public void run() { 347 while (true) { 348 try { 349 sleep(FileListingService.REFRESH_RATE); 350 } catch (InterruptedException e) { 351 return; 352 } 353 354 if (mTree != null && mTree.isDisposed() == false) { 355 Display display = mTree.getDisplay(); 356 if (display.isDisposed() == false) { 357 display.asyncExec(new Runnable() { 358 public void run() { 359 if (mTree.isDisposed() == false) { 360 mTreeViewer.refresh(true); 361 } 362 } 363 }); 364 } else { 365 return; 366 } 367 } else { 368 return; 369 } 370 } 371 372 } 373 }.start(); 374 375 return mTree; 376 } 377 378 @Override 379 protected void postCreation() { 380 381 } 382 383 /** 384 * Sets the focus to the proper control inside the panel. 385 */ 386 @Override 387 public void setFocus() { 388 mTree.setFocus(); 389 } 390 391 /** 392 * Processes a double click on a trace file 393 * @param baseName the base name of the 2 files. 394 * @param keyEntry The FileEntry for the .key file. 395 * @param dataEntry The FileEntry for the .data file. 396 */ 397 private void handleTraceDoubleClick(String baseName, FileEntry keyEntry, 398 FileEntry dataEntry) { 399 // first we need to download the files. 400 File keyFile; 401 File dataFile; 402 String path; 403 try { 404 // create a temp file for keyFile 405 File f = File.createTempFile(baseName, DdmConstants.DOT_TRACE); 406 f.delete(); 407 f.mkdir(); 408 409 path = f.getAbsolutePath(); 410 411 keyFile = new File(path + File.separator + keyEntry.getName()); 412 dataFile = new File(path + File.separator + dataEntry.getName()); 413 } catch (IOException e) { 414 return; 415 } 416 417 // download the files 418 try { 419 SyncService sync = mCurrentDevice.getSyncService(); 420 if (sync != null) { 421 ISyncProgressMonitor monitor = SyncService.getNullProgressMonitor(); 422 sync.pullFile(keyEntry, keyFile.getAbsolutePath(), monitor); 423 sync.pullFile(dataEntry, dataFile.getAbsolutePath(), monitor); 424 425 // now that we have the file, we need to launch traceview 426 String[] command = new String[2]; 427 command[0] = DdmUiPreferences.getTraceview(); 428 command[1] = path + File.separator + baseName; 429 430 try { 431 final Process p = Runtime.getRuntime().exec(command); 432 433 // create a thread for the output 434 new Thread("Traceview output") { 435 @Override 436 public void run() { 437 // create a buffer to read the stderr output 438 InputStreamReader is = new InputStreamReader(p.getErrorStream()); 439 BufferedReader resultReader = new BufferedReader(is); 440 441 // read the lines as they come. if null is returned, it's 442 // because the process finished 443 try { 444 while (true) { 445 String line = resultReader.readLine(); 446 if (line != null) { 447 DdmConsole.printErrorToConsole("Traceview: " + line); 448 } else { 449 break; 450 } 451 } 452 // get the return code from the process 453 p.waitFor(); 454 } catch (IOException e) { 455 } catch (InterruptedException e) { 456 457 } 458 } 459 }.start(); 460 461 } catch (IOException e) { 462 } 463 } 464 } catch (IOException e) { 465 DdmConsole.printErrorToConsole(String.format( 466 "Failed to pull %1$s: %2$s", keyEntry.getName(), e.getMessage())); 467 return; 468 } catch (SyncException e) { 469 if (e.wasCanceled() == false) { 470 DdmConsole.printErrorToConsole(String.format( 471 "Failed to pull %1$s: %2$s", keyEntry.getName(), e.getMessage())); 472 return; 473 } 474 } catch (TimeoutException e) { 475 DdmConsole.printErrorToConsole(String.format( 476 "Failed to pull %1$s: timeout", keyEntry.getName())); 477 } catch (AdbCommandRejectedException e) { 478 DdmConsole.printErrorToConsole(String.format( 479 "Failed to pull %1$s: %2$s", keyEntry.getName(), e.getMessage())); 480 } 481 } 482 483 /** 484 * Pull the current selection on the local drive. This method displays 485 * a dialog box to let the user select where to store the file(s) and 486 * folder(s). 487 */ 488 public void pullSelection() { 489 // get the selection 490 TreeItem[] items = mTree.getSelection(); 491 492 // name of the single file pull, or null if we're pulling a directory 493 // or more than one object. 494 String filePullName = null; 495 FileEntry singleEntry = null; 496 497 // are we pulling a single file? 498 if (items.length == 1) { 499 singleEntry = (FileEntry)items[0].getData(); 500 if (singleEntry.getType() == FileListingService.TYPE_FILE) { 501 filePullName = singleEntry.getName(); 502 } 503 } 504 505 // where do we save by default? 506 String defaultPath = mDefaultSave; 507 if (defaultPath == null) { 508 defaultPath = System.getProperty("user.home"); //$NON-NLS-1$ 509 } 510 511 if (filePullName != null) { 512 FileDialog fileDialog = new FileDialog(mParent.getShell(), SWT.SAVE); 513 514 fileDialog.setText("Get Device File"); 515 fileDialog.setFileName(filePullName); 516 fileDialog.setFilterPath(defaultPath); 517 518 String fileName = fileDialog.open(); 519 if (fileName != null) { 520 mDefaultSave = fileDialog.getFilterPath(); 521 522 pullFile(singleEntry, fileName); 523 } 524 } else { 525 DirectoryDialog directoryDialog = new DirectoryDialog(mParent.getShell(), SWT.SAVE); 526 527 directoryDialog.setText("Get Device Files/Folders"); 528 directoryDialog.setFilterPath(defaultPath); 529 530 String directoryName = directoryDialog.open(); 531 if (directoryName != null) { 532 pullSelection(items, directoryName); 533 } 534 } 535 } 536 537 /** 538 * Push new file(s) and folder(s) into the current selection. Current 539 * selection must be single item. If the current selection is not a 540 * directory, the parent directory is used. 541 * This method displays a dialog to let the user choose file to push to 542 * the device. 543 */ 544 public void pushIntoSelection() { 545 // get the name of the object we're going to pull 546 TreeItem[] items = mTree.getSelection(); 547 548 if (items.length == 0) { 549 return; 550 } 551 552 FileDialog dlg = new FileDialog(mParent.getShell(), SWT.OPEN); 553 String fileName; 554 555 dlg.setText("Put File on Device"); 556 557 // There should be only one. 558 FileEntry entry = (FileEntry)items[0].getData(); 559 dlg.setFileName(entry.getName()); 560 561 String defaultPath = mDefaultSave; 562 if (defaultPath == null) { 563 defaultPath = System.getProperty("user.home"); //$NON-NLS-1$ 564 } 565 dlg.setFilterPath(defaultPath); 566 567 fileName = dlg.open(); 568 if (fileName != null) { 569 mDefaultSave = dlg.getFilterPath(); 570 571 // we need to figure out the remote path based on the current selection type. 572 String remotePath; 573 FileEntry toRefresh = entry; 574 if (entry.isDirectory()) { 575 remotePath = entry.getFullPath(); 576 } else { 577 toRefresh = entry.getParent(); 578 remotePath = toRefresh.getFullPath(); 579 } 580 581 pushFile(fileName, remotePath); 582 mTreeViewer.refresh(toRefresh); 583 } 584 } 585 586 public void deleteSelection() { 587 // get the name of the object we're going to pull 588 TreeItem[] items = mTree.getSelection(); 589 590 if (items.length != 1) { 591 return; 592 } 593 594 FileEntry entry = (FileEntry)items[0].getData(); 595 final FileEntry parentEntry = entry.getParent(); 596 597 // create the delete command 598 String command = "rm " + entry.getFullEscapedPath(); //$NON-NLS-1$ 599 600 try { 601 mCurrentDevice.executeShellCommand(command, new IShellOutputReceiver() { 602 public void addOutput(byte[] data, int offset, int length) { 603 // pass 604 // TODO get output to display errors if any. 605 } 606 607 public void flush() { 608 mTreeViewer.refresh(parentEntry); 609 } 610 611 public boolean isCancelled() { 612 return false; 613 } 614 }); 615 } catch (IOException e) { 616 // adb failed somehow, we do nothing. We should be displaying the error from the output 617 // of the shell command. 618 } catch (TimeoutException e) { 619 // adb failed somehow, we do nothing. We should be displaying the error from the output 620 // of the shell command. 621 } catch (AdbCommandRejectedException e) { 622 // adb failed somehow, we do nothing. We should be displaying the error from the output 623 // of the shell command. 624 } catch (ShellCommandUnresponsiveException e) { 625 // adb failed somehow, we do nothing. We should be displaying the error from the output 626 // of the shell command. 627 } 628 629 } 630 631 public void createNewFolderInSelection() { 632 TreeItem[] items = mTree.getSelection(); 633 634 if (items.length != 1) { 635 return; 636 } 637 638 final FileEntry entry = (FileEntry) items[0].getData(); 639 640 if (entry.isDirectory()) { 641 InputDialog inputDialog = new InputDialog(mTree.getShell(), "New Folder", 642 "Please enter the new folder name", "New Folder", new IInputValidator() { 643 public String isValid(String newText) { 644 if ((newText != null) && (newText.length() > 0) 645 && (newText.trim().length() > 0) 646 && (newText.indexOf('/') == -1) 647 && (newText.indexOf('\\') == -1)) { 648 return null; 649 } else { 650 return "Invalid name"; 651 } 652 } 653 }); 654 inputDialog.open(); 655 String value = inputDialog.getValue(); 656 657 if (value != null) { 658 // create the mkdir command 659 String command = "mkdir " + entry.getFullEscapedPath() //$NON-NLS-1$ 660 + FileListingService.FILE_SEPARATOR + FileEntry.escape(value); 661 662 try { 663 mCurrentDevice.executeShellCommand(command, new IShellOutputReceiver() { 664 665 public boolean isCancelled() { 666 return false; 667 } 668 669 public void flush() { 670 mTreeViewer.refresh(entry); 671 } 672 673 public void addOutput(byte[] data, int offset, int length) { 674 String errorMessage; 675 if (data != null) { 676 errorMessage = new String(data); 677 } else { 678 errorMessage = ""; 679 } 680 Status status = new Status(IStatus.ERROR, 681 "DeviceExplorer", 0, errorMessage, null); //$NON-NLS-1$ 682 ErrorDialog.openError(mTree.getShell(), "New Folder Error", 683 "New Folder Error", status); 684 } 685 }); 686 } catch (TimeoutException e) { 687 // adb failed somehow, we do nothing. We should be 688 // displaying the error from the output of the shell 689 // command. 690 } catch (AdbCommandRejectedException e) { 691 // adb failed somehow, we do nothing. We should be 692 // displaying the error from the output of the shell 693 // command. 694 } catch (ShellCommandUnresponsiveException e) { 695 // adb failed somehow, we do nothing. We should be 696 // displaying the error from the output of the shell 697 // command. 698 } catch (IOException e) { 699 // adb failed somehow, we do nothing. We should be 700 // displaying the error from the output of the shell 701 // command. 702 } 703 } 704 } 705 } 706 707 /** 708 * Force a full refresh of the explorer. 709 */ 710 public void refresh() { 711 mTreeViewer.refresh(true); 712 } 713 714 /** 715 * Sets the new device to explorer 716 */ 717 public void switchDevice(final IDevice device) { 718 if (device != mCurrentDevice) { 719 mCurrentDevice = device; 720 // now we change the input. but we need to do that in the 721 // ui thread. 722 if (mTree.isDisposed() == false) { 723 Display d = mTree.getDisplay(); 724 d.asyncExec(new Runnable() { 725 public void run() { 726 if (mTree.isDisposed() == false) { 727 // new service 728 if (mCurrentDevice != null) { 729 FileListingService fls = mCurrentDevice.getFileListingService(); 730 mContentProvider.setListingService(fls); 731 mTreeViewer.setInput(fls.getRoot()); 732 } 733 } 734 } 735 }); 736 } 737 } 738 } 739 740 /** 741 * Refresh an entry from a non ui thread. 742 * @param entry the entry to refresh. 743 */ 744 private void refresh(final FileEntry entry) { 745 Display d = mTreeViewer.getTree().getDisplay(); 746 d.asyncExec(new Runnable() { 747 public void run() { 748 mTreeViewer.refresh(entry); 749 } 750 }); 751 } 752 753 /** 754 * Pulls the selection from a device. 755 * @param items the tree selection the remote file on the device 756 * @param localDirector the local directory in which to save the files. 757 */ 758 private void pullSelection(TreeItem[] items, final String localDirectory) { 759 try { 760 final SyncService sync = mCurrentDevice.getSyncService(); 761 if (sync != null) { 762 // make a list of the FileEntry. 763 ArrayList<FileEntry> entries = new ArrayList<FileEntry>(); 764 for (TreeItem item : items) { 765 Object data = item.getData(); 766 if (data instanceof FileEntry) { 767 entries.add((FileEntry)data); 768 } 769 } 770 final FileEntry[] entryArray = entries.toArray( 771 new FileEntry[entries.size()]); 772 773 SyncProgressHelper.run(new SyncRunnable() { 774 public void run(ISyncProgressMonitor monitor) 775 throws SyncException, IOException, TimeoutException { 776 sync.pull(entryArray, localDirectory, monitor); 777 } 778 779 public void close() { 780 sync.close(); 781 } 782 }, "Pulling file(s) from the device", mParent.getShell()); 783 } 784 } catch (SyncException e) { 785 if (e.wasCanceled() == false) { 786 DdmConsole.printErrorToConsole(String.format( 787 "Failed to pull selection: %1$s", e.getMessage())); 788 } 789 } catch (Exception e) { 790 DdmConsole.printErrorToConsole( "Failed to pull selection"); 791 DdmConsole.printErrorToConsole(e.getMessage()); 792 } 793 } 794 795 /** 796 * Pulls a file from a device. 797 * @param remote the remote file on the device 798 * @param local the destination filepath 799 */ 800 private void pullFile(final FileEntry remote, final String local) { 801 try { 802 final SyncService sync = mCurrentDevice.getSyncService(); 803 if (sync != null) { 804 SyncProgressHelper.run(new SyncRunnable() { 805 public void run(ISyncProgressMonitor monitor) 806 throws SyncException, IOException, TimeoutException { 807 sync.pullFile(remote, local, monitor); 808 } 809 810 public void close() { 811 sync.close(); 812 } 813 }, String.format("Pulling %1$s from the device", remote.getName()), 814 mParent.getShell()); 815 } 816 } catch (SyncException e) { 817 if (e.wasCanceled() == false) { 818 DdmConsole.printErrorToConsole(String.format( 819 "Failed to pull selection: %1$s", e.getMessage())); 820 } 821 } catch (Exception e) { 822 DdmConsole.printErrorToConsole( "Failed to pull selection"); 823 DdmConsole.printErrorToConsole(e.getMessage()); 824 } 825 } 826 827 /** 828 * Pushes several files and directory into a remote directory. 829 * @param localFiles 830 * @param remoteDirectory 831 */ 832 private void pushFiles(final String[] localFiles, final FileEntry remoteDirectory) { 833 try { 834 final SyncService sync = mCurrentDevice.getSyncService(); 835 if (sync != null) { 836 SyncProgressHelper.run(new SyncRunnable() { 837 public void run(ISyncProgressMonitor monitor) 838 throws SyncException, IOException, TimeoutException { 839 sync.push(localFiles, remoteDirectory, monitor); 840 } 841 842 public void close() { 843 sync.close(); 844 } 845 }, "Pushing file(s) to the device", mParent.getShell()); 846 } 847 } catch (SyncException e) { 848 if (e.wasCanceled() == false) { 849 DdmConsole.printErrorToConsole(String.format( 850 "Failed to push selection: %1$s", e.getMessage())); 851 } 852 } catch (Exception e) { 853 DdmConsole.printErrorToConsole("Failed to push the items"); 854 DdmConsole.printErrorToConsole(e.getMessage()); 855 } 856 } 857 858 /** 859 * Pushes a file on a device. 860 * @param local the local filepath of the file to push 861 * @param remoteDirectory the remote destination directory on the device 862 */ 863 private void pushFile(final String local, final String remoteDirectory) { 864 try { 865 final SyncService sync = mCurrentDevice.getSyncService(); 866 if (sync != null) { 867 // get the file name 868 String[] segs = local.split(Pattern.quote(File.separator)); 869 String name = segs[segs.length-1]; 870 final String remoteFile = remoteDirectory + FileListingService.FILE_SEPARATOR 871 + name; 872 873 SyncProgressHelper.run(new SyncRunnable() { 874 public void run(ISyncProgressMonitor monitor) 875 throws SyncException, IOException, TimeoutException { 876 sync.pushFile(local, remoteFile, monitor); 877 } 878 879 public void close() { 880 sync.close(); 881 } 882 }, String.format("Pushing %1$s to the device.", name), mParent.getShell()); 883 } 884 } catch (SyncException e) { 885 if (e.wasCanceled() == false) { 886 DdmConsole.printErrorToConsole(String.format( 887 "Failed to push selection: %1$s", e.getMessage())); 888 } 889 } catch (Exception e) { 890 DdmConsole.printErrorToConsole("Failed to push the item(s)."); 891 DdmConsole.printErrorToConsole(e.getMessage()); 892 } 893 } 894 895 /** 896 * Sets the enabled state based on a FileEntry properties 897 * @param element The selected FileEntry 898 */ 899 protected void setDeleteEnabledState(FileEntry element) { 900 mDeleteAction.setEnabled(element.getType() == FileListingService.TYPE_FILE); 901 } 902 } 903