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