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