• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ddmlib;
18 
19 import com.android.ddmlib.AdbHelper.AdbResponse;
20 import com.android.ddmlib.FileListingService.FileEntry;
21 import com.android.ddmlib.utils.ArrayHelper;
22 
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.UnsupportedEncodingException;
29 import java.net.InetSocketAddress;
30 import java.nio.channels.SocketChannel;
31 import java.util.ArrayList;
32 
33 /**
34  * Sync service class to push/pull to/from devices/emulators, through the debug bridge.
35  * <p/>
36  * To get a {@link SyncService} object, use {@link Device#getSyncService()}.
37  */
38 public final class SyncService {
39 
40     private final static byte[] ID_OKAY = { 'O', 'K', 'A', 'Y' };
41     private final static byte[] ID_FAIL = { 'F', 'A', 'I', 'L' };
42     private final static byte[] ID_STAT = { 'S', 'T', 'A', 'T' };
43     private final static byte[] ID_RECV = { 'R', 'E', 'C', 'V' };
44     private final static byte[] ID_DATA = { 'D', 'A', 'T', 'A' };
45     private final static byte[] ID_DONE = { 'D', 'O', 'N', 'E' };
46     private final static byte[] ID_SEND = { 'S', 'E', 'N', 'D' };
47 //    private final static byte[] ID_LIST = { 'L', 'I', 'S', 'T' };
48 //    private final static byte[] ID_DENT = { 'D', 'E', 'N', 'T' };
49 
50     private final static NullSyncProgresMonitor sNullSyncProgressMonitor =
51             new NullSyncProgresMonitor();
52 
53     private final static int S_ISOCK = 0xC000; // type: symbolic link
54     private final static int S_IFLNK = 0xA000; // type: symbolic link
55     private final static int S_IFREG = 0x8000; // type: regular file
56     private final static int S_IFBLK = 0x6000; // type: block device
57     private final static int S_IFDIR = 0x4000; // type: directory
58     private final static int S_IFCHR = 0x2000; // type: character device
59     private final static int S_IFIFO = 0x1000; // type: fifo
60 /*
61     private final static int S_ISUID = 0x0800; // set-uid bit
62     private final static int S_ISGID = 0x0400; // set-gid bit
63     private final static int S_ISVTX = 0x0200; // sticky bit
64     private final static int S_IRWXU = 0x01C0; // user permissions
65     private final static int S_IRUSR = 0x0100; // user: read
66     private final static int S_IWUSR = 0x0080; // user: write
67     private final static int S_IXUSR = 0x0040; // user: execute
68     private final static int S_IRWXG = 0x0038; // group permissions
69     private final static int S_IRGRP = 0x0020; // group: read
70     private final static int S_IWGRP = 0x0010; // group: write
71     private final static int S_IXGRP = 0x0008; // group: execute
72     private final static int S_IRWXO = 0x0007; // other permissions
73     private final static int S_IROTH = 0x0004; // other: read
74     private final static int S_IWOTH = 0x0002; // other: write
75     private final static int S_IXOTH = 0x0001; // other: execute
76 */
77 
78     private final static int SYNC_DATA_MAX = 64*1024;
79     private final static int REMOTE_PATH_MAX_LENGTH = 1024;
80 
81     /** Result code for transfer success. */
82     public static final int RESULT_OK = 0;
83     /** Result code for canceled transfer */
84     public static final int RESULT_CANCELED = 1;
85     /** Result code for unknown error */
86     public static final int RESULT_UNKNOWN_ERROR = 2;
87     /** Result code for network connection error */
88     public static final int RESULT_CONNECTION_ERROR = 3;
89     /** Result code for unknown remote object during a pull */
90     public static final int RESULT_NO_REMOTE_OBJECT = 4;
91     /** Result code when attempting to pull multiple files into a file */
92     public static final int RESULT_TARGET_IS_FILE = 5;
93     /** Result code when attempting to pull multiple into a directory that does not exist. */
94     public static final int RESULT_NO_DIR_TARGET = 6;
95     /** Result code for wrong encoding on the remote path. */
96     public static final int RESULT_REMOTE_PATH_ENCODING = 7;
97     /** Result code for remote path that is too long. */
98     public static final int RESULT_REMOTE_PATH_LENGTH = 8;
99     /** Result code for error while writing local file. */
100     public static final int RESULT_FILE_WRITE_ERROR = 9;
101     /** Result code for error while reading local file. */
102     public static final int RESULT_FILE_READ_ERROR = 10;
103     /** Result code for attempting to push a file that does not exist. */
104     public static final int RESULT_NO_LOCAL_FILE = 11;
105     /** Result code for attempting to push a directory. */
106     public static final int RESULT_LOCAL_IS_DIRECTORY = 12;
107     /** Result code for when the target path of a multi file push is a file. */
108     public static final int RESULT_REMOTE_IS_FILE = 13;
109     /** Result code for receiving too much data from the remove device at once */
110     public static final int RESULT_BUFFER_OVERRUN = 14;
111     /** Result code for network connection timeout */
112     public static final int RESULT_CONNECTION_TIMEOUT = 15;
113 
114     /**
115      * A file transfer result.
116      * <p/>
117      * This contains a code, and an optional string
118      */
119     public static class SyncResult {
120         private int mCode;
121         private String mMessage;
SyncResult(int code, String message)122         SyncResult(int code, String message) {
123             mCode = code;
124             mMessage = message;
125         }
126 
SyncResult(int code, Exception e)127         SyncResult(int code, Exception e) {
128             this(code, e.getMessage());
129         }
130 
SyncResult(int code)131         SyncResult(int code) {
132             this(code, errorCodeToString(code));
133         }
134 
getCode()135         public int getCode() {
136             return mCode;
137         }
138 
getMessage()139         public String getMessage() {
140             return mMessage;
141         }
142     }
143 
144     /**
145      * Classes which implement this interface provide methods that deal
146      * with displaying transfer progress.
147      */
148     public interface ISyncProgressMonitor {
149         /**
150          * Sent when the transfer starts
151          * @param totalWork the total amount of work.
152          */
start(int totalWork)153         public void start(int totalWork);
154         /**
155          * Sent when the transfer is finished or interrupted.
156          */
stop()157         public void stop();
158         /**
159          * Sent to query for possible cancellation.
160          * @return true if the transfer should be stopped.
161          */
isCanceled()162         public boolean isCanceled();
163         /**
164          * Sent when a sub task is started.
165          * @param name the name of the sub task.
166          */
startSubTask(String name)167         public void startSubTask(String name);
168         /**
169          * Sent when some progress have been made.
170          * @param work the amount of work done.
171          */
advance(int work)172         public void advance(int work);
173     }
174 
175     /**
176      * A Sync progress monitor that does nothing
177      */
178     private static class NullSyncProgresMonitor implements ISyncProgressMonitor {
advance(int work)179         public void advance(int work) {
180         }
isCanceled()181         public boolean isCanceled() {
182             return false;
183         }
184 
start(int totalWork)185         public void start(int totalWork) {
186         }
startSubTask(String name)187         public void startSubTask(String name) {
188         }
stop()189         public void stop() {
190         }
191     }
192 
193     private InetSocketAddress mAddress;
194     private Device mDevice;
195     private SocketChannel mChannel;
196 
197     /**
198      * Buffer used to send data. Allocated when needed and reused afterward.
199      */
200     private byte[] mBuffer;
201 
202     /**
203      * Creates a Sync service object.
204      * @param address The address to connect to
205      * @param device the {@link Device} that the service connects to.
206      */
SyncService(InetSocketAddress address, Device device)207     SyncService(InetSocketAddress address, Device device) {
208         mAddress = address;
209         mDevice = device;
210     }
211 
212     /**
213      * Opens the sync connection. This must be called before any calls to push[File] / pull[File].
214      * @return true if the connection opened, false if adb refuse the connection. This can happen
215      * if the {@link Device} is invalid.
216      * @throws TimeoutException in case of timeout on the connection.
217      * @throws AdbCommandRejectedException if adb rejects the command
218      * @throws IOException If the connection to adb failed.
219      */
openSync()220     boolean openSync() throws TimeoutException, AdbCommandRejectedException, IOException {
221         try {
222             mChannel = SocketChannel.open(mAddress);
223             mChannel.configureBlocking(false);
224 
225             // target a specific device
226             AdbHelper.setDevice(mChannel, mDevice);
227 
228             byte[] request = AdbHelper.formAdbRequest("sync:"); // $NON-NLS-1$
229             AdbHelper.write(mChannel, request, -1, DdmPreferences.getTimeOut());
230 
231             AdbResponse resp = AdbHelper.readAdbResponse(mChannel, false /* readDiagString */);
232 
233             if (resp.okay == false) {
234                 Log.w("ddms", "Got unhappy response from ADB sync req: " + resp.message);
235                 mChannel.close();
236                 mChannel = null;
237                 return false;
238             }
239         } catch (TimeoutException e) {
240             if (mChannel != null) {
241                 try {
242                     mChannel.close();
243                 } catch (IOException e2) {
244                     // we want to throw the original exception, so we ignore this one.
245                 }
246                 mChannel = null;
247             }
248 
249             throw e;
250         } catch (IOException e) {
251             if (mChannel != null) {
252                 try {
253                     mChannel.close();
254                 } catch (IOException e2) {
255                     // we want to throw the original exception, so we ignore this one.
256                 }
257                 mChannel = null;
258             }
259 
260             throw e;
261         }
262 
263         return true;
264     }
265 
266     /**
267      * Closes the connection.
268      */
close()269     public void close() {
270         if (mChannel != null) {
271             try {
272                 mChannel.close();
273             } catch (IOException e) {
274                 // nothing to be done really...
275             }
276             mChannel = null;
277         }
278     }
279 
280     /**
281      * Returns a sync progress monitor that does nothing. This allows background tasks that don't
282      * want/need to display ui, to pass a valid {@link ISyncProgressMonitor}.
283      * <p/>This object can be reused multiple times and can be used by concurrent threads.
284      */
getNullProgressMonitor()285     public static ISyncProgressMonitor getNullProgressMonitor() {
286         return sNullSyncProgressMonitor;
287     }
288 
289     /**
290      * Converts an error code into a non-localized string
291      * @param code the error code;
292      */
errorCodeToString(int code)293     private static String errorCodeToString(int code) {
294         switch (code) {
295             case RESULT_OK:
296                 return "Success.";
297             case RESULT_CANCELED:
298                 return "Tranfert canceled by the user.";
299             case RESULT_UNKNOWN_ERROR:
300                 return "Unknown Error.";
301             case RESULT_CONNECTION_ERROR:
302                 return "Adb Connection Error.";
303             case RESULT_NO_REMOTE_OBJECT:
304                 return "Remote object doesn't exist!";
305             case RESULT_TARGET_IS_FILE:
306                 return "Target object is a file.";
307             case RESULT_NO_DIR_TARGET:
308                 return "Target directory doesn't exist.";
309             case RESULT_REMOTE_PATH_ENCODING:
310                 return "Remote Path encoding is not supported.";
311             case RESULT_REMOTE_PATH_LENGTH:
312                 return "Remove path is too long.";
313             case RESULT_FILE_WRITE_ERROR:
314                 return "Writing local file failed!";
315             case RESULT_FILE_READ_ERROR:
316                 return "Reading local file failed!";
317             case RESULT_NO_LOCAL_FILE:
318                 return "Local file doesn't exist.";
319             case RESULT_LOCAL_IS_DIRECTORY:
320                 return "Local path is a directory.";
321             case RESULT_REMOTE_IS_FILE:
322                 return "Remote path is a file.";
323             case RESULT_BUFFER_OVERRUN:
324                 return "Receiving too much data.";
325             case RESULT_CONNECTION_TIMEOUT:
326                 return "timeout";
327         }
328 
329         throw new RuntimeException();
330     }
331 
332     /**
333      * Pulls file(s) or folder(s).
334      * @param entries the remote item(s) to pull
335      * @param localPath The local destination. If the entries count is > 1 or
336      *      if the unique entry is a folder, this should be a folder.
337      * @param monitor The progress monitor. Cannot be null.
338      * @return a {@link SyncResult} object with a code and an optional message.
339      *
340      * @see FileListingService.FileEntry
341      * @see #getNullProgressMonitor()
342      */
pull(FileEntry[] entries, String localPath, ISyncProgressMonitor monitor)343     public SyncResult pull(FileEntry[] entries, String localPath, ISyncProgressMonitor monitor) {
344 
345         // first we check the destination is a directory and exists
346         File f = new File(localPath);
347         if (f.exists() == false) {
348             return new SyncResult(RESULT_NO_DIR_TARGET);
349         }
350         if (f.isDirectory() == false) {
351             return new SyncResult(RESULT_TARGET_IS_FILE);
352         }
353 
354         // get a FileListingService object
355         FileListingService fls = new FileListingService(mDevice);
356 
357         // compute the number of file to move
358         int total = getTotalRemoteFileSize(entries, fls);
359 
360         // start the monitor
361         monitor.start(total);
362 
363         SyncResult result = doPull(entries, localPath, fls, monitor);
364 
365         monitor.stop();
366 
367         return result;
368     }
369 
370     /**
371      * Pulls a single file.
372      * @param remote the remote file
373      * @param localFilename The local destination.
374      * @param monitor The progress monitor. Cannot be null.
375      * @return a {@link SyncResult} object with a code and an optional message.
376      *
377      * @see FileListingService.FileEntry
378      * @see #getNullProgressMonitor()
379      */
pullFile(FileEntry remote, String localFilename, ISyncProgressMonitor monitor)380     public SyncResult pullFile(FileEntry remote, String localFilename,
381             ISyncProgressMonitor monitor) {
382         int total = remote.getSizeValue();
383         monitor.start(total);
384 
385         SyncResult result = doPullFile(remote.getFullPath(), localFilename, monitor);
386 
387         monitor.stop();
388         return result;
389     }
390 
391     /**
392      * Pulls a single file.
393      * <p/>Because this method just deals with a String for the remote file instead of a
394      * {@link FileEntry}, the size of the file being pulled is unknown and the
395      * {@link ISyncProgressMonitor} will not properly show the progress
396      * @param remoteFilepath the full path to the remote file
397      * @param localFilename The local destination.
398      * @param monitor The progress monitor. Cannot be null.
399      * @return a {@link SyncResult} object with a code and an optional message.
400      *
401      * @see #getNullProgressMonitor()
402      */
pullFile(String remoteFilepath, String localFilename, ISyncProgressMonitor monitor)403     public SyncResult pullFile(String remoteFilepath, String localFilename,
404             ISyncProgressMonitor monitor) {
405         monitor.start(0);
406         //TODO: use the {@link FileListingService} to get the file size.
407 
408         SyncResult result = doPullFile(remoteFilepath, localFilename, monitor);
409 
410         monitor.stop();
411         return result;
412     }
413 
414     /**
415      * Push several files.
416      * @param local An array of loca files to push
417      * @param remote the remote {@link FileEntry} representing a directory.
418      * @param monitor The progress monitor. Cannot be null.
419      * @return a {@link SyncResult} object with a code and an optional message.
420      */
push(String[] local, FileEntry remote, ISyncProgressMonitor monitor)421     public SyncResult push(String[] local, FileEntry remote, ISyncProgressMonitor monitor) {
422         if (remote.isDirectory() == false) {
423             return new SyncResult(RESULT_REMOTE_IS_FILE);
424         }
425 
426         // make a list of File from the list of String
427         ArrayList<File> files = new ArrayList<File>();
428         for (String path : local) {
429             files.add(new File(path));
430         }
431 
432         // get the total count of the bytes to transfer
433         File[] fileArray = files.toArray(new File[files.size()]);
434         int total = getTotalLocalFileSize(fileArray);
435 
436         monitor.start(total);
437 
438         SyncResult result = doPush(fileArray, remote.getFullPath(), monitor);
439 
440         monitor.stop();
441 
442         return result;
443     }
444 
445     /**
446      * Push a single file.
447      * @param local the local filepath.
448      * @param remote The remote filepath.
449      * @param monitor The progress monitor. Cannot be null.
450      * @return a {@link SyncResult} object with a code and an optional message.
451      */
pushFile(String local, String remote, ISyncProgressMonitor monitor)452     public SyncResult pushFile(String local, String remote, ISyncProgressMonitor monitor) {
453         File f = new File(local);
454         if (f.exists() == false) {
455             return new SyncResult(RESULT_NO_LOCAL_FILE);
456         }
457 
458         if (f.isDirectory()) {
459             return new SyncResult(RESULT_LOCAL_IS_DIRECTORY);
460         }
461 
462         monitor.start((int)f.length());
463 
464         SyncResult result = doPushFile(local, remote, monitor);
465 
466         monitor.stop();
467 
468         return result;
469     }
470 
471     /**
472      * compute the recursive file size of all the files in the list. Folder
473      * have a weight of 1.
474      * @param entries
475      * @param fls
476      * @return
477      */
getTotalRemoteFileSize(FileEntry[] entries, FileListingService fls)478     private int getTotalRemoteFileSize(FileEntry[] entries, FileListingService fls) {
479         int count = 0;
480         for (FileEntry e : entries) {
481             int type = e.getType();
482             if (type == FileListingService.TYPE_DIRECTORY) {
483                 // get the children
484                 FileEntry[] children = fls.getChildren(e, false, null);
485                 count += getTotalRemoteFileSize(children, fls) + 1;
486             } else if (type == FileListingService.TYPE_FILE) {
487                 count += e.getSizeValue();
488             }
489         }
490 
491         return count;
492     }
493 
494     /**
495      * compute the recursive file size of all the files in the list. Folder
496      * have a weight of 1.
497      * This does not check for circular links.
498      * @param files
499      * @return
500      */
getTotalLocalFileSize(File[] files)501     private int getTotalLocalFileSize(File[] files) {
502         int count = 0;
503 
504         for (File f : files) {
505             if (f.exists()) {
506                 if (f.isDirectory()) {
507                     return getTotalLocalFileSize(f.listFiles()) + 1;
508                 } else if (f.isFile()) {
509                     count += f.length();
510                 }
511             }
512         }
513 
514         return count;
515     }
516 
517     /**
518      * Pulls multiple files/folders recursively.
519      * @param entries The list of entry to pull
520      * @param localPath the localpath to a directory
521      * @param fileListingService a FileListingService object to browse through remote directories.
522      * @param monitor the progress monitor. Must be started already.
523      * @return a {@link SyncResult} object with a code and an optional message.
524      */
doPull(FileEntry[] entries, String localPath, FileListingService fileListingService, ISyncProgressMonitor monitor)525     private SyncResult doPull(FileEntry[] entries, String localPath,
526             FileListingService fileListingService,
527             ISyncProgressMonitor monitor) {
528 
529         for (FileEntry e : entries) {
530             // check if we're cancelled
531             if (monitor.isCanceled() == true) {
532                 return new SyncResult(RESULT_CANCELED);
533             }
534 
535             // get type (we only pull directory and files for now)
536             int type = e.getType();
537             if (type == FileListingService.TYPE_DIRECTORY) {
538                 monitor.startSubTask(e.getFullPath());
539                 String dest = localPath + File.separator + e.getName();
540 
541                 // make the directory
542                 File d = new File(dest);
543                 d.mkdir();
544 
545                 // then recursively call the content. Since we did a ls command
546                 // to get the number of files, we can use the cache
547                 FileEntry[] children = fileListingService.getChildren(e, true, null);
548                 SyncResult result = doPull(children, dest, fileListingService, monitor);
549                 if (result.mCode != RESULT_OK) {
550                     return result;
551                 }
552                 monitor.advance(1);
553             } else if (type == FileListingService.TYPE_FILE) {
554                 monitor.startSubTask(e.getFullPath());
555                 String dest = localPath + File.separator + e.getName();
556                 SyncResult result = doPullFile(e.getFullPath(), dest, monitor);
557                 if (result.mCode != RESULT_OK) {
558                     return result;
559                 }
560             }
561         }
562 
563         return new SyncResult(RESULT_OK);
564     }
565 
566     /**
567      * Pulls a remote file
568      * @param remotePath the remote file (length max is 1024)
569      * @param localPath the local destination
570      * @param monitor the monitor. The monitor must be started already.
571      * @return a {@link SyncResult} object with a code and an optional message.
572      */
doPullFile(String remotePath, String localPath, ISyncProgressMonitor monitor)573     private SyncResult doPullFile(String remotePath, String localPath,
574             ISyncProgressMonitor monitor) {
575         byte[] msg = null;
576         byte[] pullResult = new byte[8];
577 
578         final int timeOut = DdmPreferences.getTimeOut();
579 
580         try {
581             byte[] remotePathContent = remotePath.getBytes(AdbHelper.DEFAULT_ENCODING);
582 
583             if (remotePathContent.length > REMOTE_PATH_MAX_LENGTH) {
584                 return new SyncResult(RESULT_REMOTE_PATH_LENGTH);
585             }
586 
587             // create the full request message
588             msg = createFileReq(ID_RECV, remotePathContent);
589 
590             // and send it.
591             AdbHelper.write(mChannel, msg, -1, timeOut);
592 
593             // read the result, in a byte array containing 2 ints
594             // (id, size)
595             AdbHelper.read(mChannel, pullResult, -1, timeOut);
596 
597             // check we have the proper data back
598             if (checkResult(pullResult, ID_DATA) == false &&
599                     checkResult(pullResult, ID_DONE) == false) {
600                 return new SyncResult(RESULT_CONNECTION_ERROR);
601             }
602         } catch (UnsupportedEncodingException e) {
603             return new SyncResult(RESULT_REMOTE_PATH_ENCODING, e);
604         } catch (TimeoutException e) {
605             return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
606         } catch (IOException e) {
607             return new SyncResult(RESULT_CONNECTION_ERROR, e);
608         }
609 
610         // access the destination file
611         File f = new File(localPath);
612 
613         // create the stream to write in the file. We use a new try/catch block to differentiate
614         // between file and network io exceptions.
615         FileOutputStream fos = null;
616         try {
617             fos = new FileOutputStream(f);
618         } catch (FileNotFoundException e) {
619             return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
620         }
621 
622         // the buffer to read the data
623         byte[] data = new byte[SYNC_DATA_MAX];
624 
625         // loop to get data until we're done.
626         while (true) {
627             // check if we're cancelled
628             if (monitor.isCanceled() == true) {
629                 return new SyncResult(RESULT_CANCELED);
630             }
631 
632             // if we're done, we stop the loop
633             if (checkResult(pullResult, ID_DONE)) {
634                 break;
635             }
636             if (checkResult(pullResult, ID_DATA) == false) {
637                 // hmm there's an error
638                 return new SyncResult(RESULT_CONNECTION_ERROR);
639             }
640             int length = ArrayHelper.swap32bitFromArray(pullResult, 4);
641             if (length > SYNC_DATA_MAX) {
642                 // buffer overrun!
643                 // error and exit
644                 return new SyncResult(RESULT_BUFFER_OVERRUN);
645             }
646 
647             try {
648                 // now read the length we received
649                 AdbHelper.read(mChannel, data, length, timeOut);
650 
651                 // get the header for the next packet.
652                 AdbHelper.read(mChannel, pullResult, -1, timeOut);
653             } catch (TimeoutException e) {
654                 return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
655             } catch (IOException e) {
656                 return new SyncResult(RESULT_CONNECTION_ERROR, e);
657             }
658 
659             // write the content in the file
660             try {
661                 fos.write(data, 0, length);
662             } catch (IOException e) {
663                 return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
664             }
665 
666             monitor.advance(length);
667         }
668 
669         try {
670             fos.flush();
671         } catch (IOException e) {
672             return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
673         }
674         return new SyncResult(RESULT_OK);
675     }
676 
677 
678     /**
679      * Push multiple files
680      * @param fileArray
681      * @param remotePath
682      * @param monitor
683      * @return a {@link SyncResult} object with a code and an optional message.
684      */
doPush(File[] fileArray, String remotePath, ISyncProgressMonitor monitor)685     private SyncResult doPush(File[] fileArray, String remotePath, ISyncProgressMonitor monitor) {
686         for (File f : fileArray) {
687             // check if we're canceled
688             if (monitor.isCanceled() == true) {
689                 return new SyncResult(RESULT_CANCELED);
690             }
691             if (f.exists()) {
692                 if (f.isDirectory()) {
693                     // append the name of the directory to the remote path
694                     String dest = remotePath + "/" + f.getName(); // $NON-NLS-1S
695                     monitor.startSubTask(dest);
696                     SyncResult result = doPush(f.listFiles(), dest, monitor);
697 
698                     if (result.mCode != RESULT_OK) {
699                         return result;
700                     }
701 
702                     monitor.advance(1);
703                 } else if (f.isFile()) {
704                     // append the name of the file to the remote path
705                     String remoteFile = remotePath + "/" + f.getName(); // $NON-NLS-1S
706                     monitor.startSubTask(remoteFile);
707                     SyncResult result = doPushFile(f.getAbsolutePath(), remoteFile, monitor);
708                     if (result.mCode != RESULT_OK) {
709                         return result;
710                     }
711                 }
712             }
713         }
714 
715         return new SyncResult(RESULT_OK);
716     }
717 
718     /**
719      * Push a single file
720      * @param localPath the local file to push
721      * @param remotePath the remote file (length max is 1024)
722      * @param monitor the monitor. The monitor must be started already.
723      * @return a {@link SyncResult} object with a code and an optional message.
724      */
doPushFile(String localPath, String remotePath, ISyncProgressMonitor monitor)725     private SyncResult doPushFile(String localPath, String remotePath,
726             ISyncProgressMonitor monitor) {
727         FileInputStream fis = null;
728         byte[] msg;
729 
730         final int timeOut = DdmPreferences.getTimeOut();
731 
732         try {
733             byte[] remotePathContent = remotePath.getBytes(AdbHelper.DEFAULT_ENCODING);
734 
735             if (remotePathContent.length > REMOTE_PATH_MAX_LENGTH) {
736                 return new SyncResult(RESULT_REMOTE_PATH_LENGTH);
737             }
738 
739             File f = new File(localPath);
740 
741             // this shouldn't happen but still...
742             if (f.exists() == false) {
743                 return new SyncResult(RESULT_NO_LOCAL_FILE);
744             }
745 
746             // create the stream to read the file
747             fis = new FileInputStream(f);
748 
749             // create the header for the action
750             msg = createSendFileReq(ID_SEND, remotePathContent, 0644);
751         } catch (UnsupportedEncodingException e) {
752             return new SyncResult(RESULT_REMOTE_PATH_ENCODING, e);
753         } catch (FileNotFoundException e) {
754             return new SyncResult(RESULT_FILE_READ_ERROR, e);
755         }
756 
757         // and send it. We use a custom try/catch block to make the difference between
758         // file and network IO exceptions.
759         try {
760             AdbHelper.write(mChannel, msg, -1, timeOut);
761         } catch (TimeoutException e) {
762             return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
763         } catch (IOException e) {
764             return new SyncResult(RESULT_CONNECTION_ERROR, e);
765         }
766 
767         // create the buffer used to read.
768         // we read max SYNC_DATA_MAX, but we need 2 4 bytes at the beginning.
769         if (mBuffer == null) {
770             mBuffer = new byte[SYNC_DATA_MAX + 8];
771         }
772         System.arraycopy(ID_DATA, 0, mBuffer, 0, ID_DATA.length);
773 
774         // look while there is something to read
775         while (true) {
776             // check if we're canceled
777             if (monitor.isCanceled() == true) {
778                 return new SyncResult(RESULT_CANCELED);
779             }
780 
781             // read up to SYNC_DATA_MAX
782             int readCount = 0;
783             try {
784                 readCount = fis.read(mBuffer, 8, SYNC_DATA_MAX);
785             } catch (IOException e) {
786                 return new SyncResult(RESULT_FILE_READ_ERROR, e);
787             }
788 
789             if (readCount == -1) {
790                 // we reached the end of the file
791                 break;
792             }
793 
794             // now send the data to the device
795             // first write the amount read
796             ArrayHelper.swap32bitsToArray(readCount, mBuffer, 4);
797 
798             // now write it
799             try {
800                 AdbHelper.write(mChannel, mBuffer, readCount+8, timeOut);
801             } catch (TimeoutException e) {
802                 return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
803             } catch (IOException e) {
804                 return new SyncResult(RESULT_CONNECTION_ERROR, e);
805             }
806 
807             // and advance the monitor
808             monitor.advance(readCount);
809         }
810         // close the local file
811         try {
812             fis.close();
813         } catch (IOException e) {
814             return new SyncResult(RESULT_FILE_READ_ERROR, e);
815         }
816 
817         try {
818             // create the DONE message
819             long time = System.currentTimeMillis() / 1000;
820             msg = createReq(ID_DONE, (int)time);
821 
822             // and send it.
823             AdbHelper.write(mChannel, msg, -1, timeOut);
824 
825             // read the result, in a byte array containing 2 ints
826             // (id, size)
827             byte[] result = new byte[8];
828             AdbHelper.read(mChannel, result, -1 /* full length */, timeOut);
829 
830             if (checkResult(result, ID_OKAY) == false) {
831                 if (checkResult(result, ID_FAIL)) {
832                     // read some error message...
833                     int len = ArrayHelper.swap32bitFromArray(result, 4);
834 
835                     AdbHelper.read(mChannel, mBuffer, len, timeOut);
836 
837                     // output the result?
838                     String message = new String(mBuffer, 0, len);
839                     Log.e("ddms", "transfer error: " + message);
840                     return new SyncResult(RESULT_UNKNOWN_ERROR, message);
841                 }
842 
843                 return new SyncResult(RESULT_UNKNOWN_ERROR);
844             }
845         } catch (TimeoutException e) {
846             return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
847         } catch (IOException e) {
848             return new SyncResult(RESULT_CONNECTION_ERROR, e);
849         }
850 
851         return new SyncResult(RESULT_OK);
852     }
853 
854     /**
855      * Returns the mode of the remote file.
856      * @param path the remote file
857      * @return and Integer containing the mode if all went well or null
858      *      otherwise
859      * @throws IOException
860      * @throws TimeoutException
861      */
readMode(String path)862     private Integer readMode(String path) throws TimeoutException, IOException {
863         // create the stat request message.
864         byte[] msg = createFileReq(ID_STAT, path);
865 
866         AdbHelper.write(mChannel, msg, -1 /* full length */, DdmPreferences.getTimeOut());
867 
868         // read the result, in a byte array containing 4 ints
869         // (id, mode, size, time)
870         byte[] statResult = new byte[16];
871         AdbHelper.read(mChannel, statResult, -1 /* full length */, DdmPreferences.getTimeOut());
872 
873         // check we have the proper data back
874         if (checkResult(statResult, ID_STAT) == false) {
875             return null;
876         }
877 
878         // we return the mode (2nd int in the array)
879         return ArrayHelper.swap32bitFromArray(statResult, 4);
880     }
881 
882     /**
883      * Create a command with a code and an int values
884      * @param command
885      * @param value
886      * @return
887      */
createReq(byte[] command, int value)888     private static byte[] createReq(byte[] command, int value) {
889         byte[] array = new byte[8];
890 
891         System.arraycopy(command, 0, array, 0, 4);
892         ArrayHelper.swap32bitsToArray(value, array, 4);
893 
894         return array;
895     }
896 
897     /**
898      * Creates the data array for a stat request.
899      * @param command the 4 byte command (ID_STAT, ID_RECV, ...)
900      * @param path The path of the remote file on which to execute the command
901      * @return the byte[] to send to the device through adb
902      */
createFileReq(byte[] command, String path)903     private static byte[] createFileReq(byte[] command, String path) {
904         byte[] pathContent = null;
905         try {
906             pathContent = path.getBytes(AdbHelper.DEFAULT_ENCODING);
907         } catch (UnsupportedEncodingException e) {
908             return null;
909         }
910 
911         return createFileReq(command, pathContent);
912     }
913 
914     /**
915      * Creates the data array for a file request. This creates an array with a 4 byte command + the
916      * remote file name.
917      * @param command the 4 byte command (ID_STAT, ID_RECV, ...).
918      * @param path The path, as a byte array, of the remote file on which to
919      *      execute the command.
920      * @return the byte[] to send to the device through adb
921      */
createFileReq(byte[] command, byte[] path)922     private static byte[] createFileReq(byte[] command, byte[] path) {
923         byte[] array = new byte[8 + path.length];
924 
925         System.arraycopy(command, 0, array, 0, 4);
926         ArrayHelper.swap32bitsToArray(path.length, array, 4);
927         System.arraycopy(path, 0, array, 8, path.length);
928 
929         return array;
930     }
931 
createSendFileReq(byte[] command, byte[] path, int mode)932     private static byte[] createSendFileReq(byte[] command, byte[] path, int mode) {
933         // make the mode into a string
934         String modeStr = "," + (mode & 0777); // $NON-NLS-1S
935         byte[] modeContent = null;
936         try {
937             modeContent = modeStr.getBytes(AdbHelper.DEFAULT_ENCODING);
938         } catch (UnsupportedEncodingException e) {
939             return null;
940         }
941 
942         byte[] array = new byte[8 + path.length + modeContent.length];
943 
944         System.arraycopy(command, 0, array, 0, 4);
945         ArrayHelper.swap32bitsToArray(path.length + modeContent.length, array, 4);
946         System.arraycopy(path, 0, array, 8, path.length);
947         System.arraycopy(modeContent, 0, array, 8 + path.length, modeContent.length);
948 
949         return array;
950 
951 
952     }
953 
954     /**
955      * Checks the result array starts with the provided code
956      * @param result The result array to check
957      * @param code The 4 byte code.
958      * @return true if the code matches.
959      */
checkResult(byte[] result, byte[] code)960     private static boolean checkResult(byte[] result, byte[] code) {
961         if (result[0] != code[0] ||
962                 result[1] != code[1] ||
963                 result[2] != code[2] ||
964                 result[3] != code[3]) {
965             return false;
966         }
967 
968         return true;
969 
970     }
971 
getFileType(int mode)972     private static int getFileType(int mode) {
973         if ((mode & S_ISOCK) == S_ISOCK) {
974             return FileListingService.TYPE_SOCKET;
975         }
976 
977         if ((mode & S_IFLNK) == S_IFLNK) {
978             return FileListingService.TYPE_LINK;
979         }
980 
981         if ((mode & S_IFREG) == S_IFREG) {
982             return FileListingService.TYPE_FILE;
983         }
984 
985         if ((mode & S_IFBLK) == S_IFBLK) {
986             return FileListingService.TYPE_BLOCK;
987         }
988 
989         if ((mode & S_IFDIR) == S_IFDIR) {
990             return FileListingService.TYPE_DIRECTORY;
991         }
992 
993         if ((mode & S_IFCHR) == S_IFCHR) {
994             return FileListingService.TYPE_CHARACTER;
995         }
996 
997         if ((mode & S_IFIFO) == S_IFIFO) {
998             return FileListingService.TYPE_FIFO;
999         }
1000 
1001         return FileListingService.TYPE_OTHER;
1002     }
1003 }
1004