Home | History | Annotate | Download | only in explorer
      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             @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