OSDN Git Service

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