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; SyncResult(int code, String message)120 SyncResult(int code, String message) { 121 mCode = code; 122 mMessage = message; 123 } 124 SyncResult(int code, Exception e)125 SyncResult(int code, Exception e) { 126 this(code, e.getMessage()); 127 } 128 SyncResult(int code)129 SyncResult(int code) { 130 this(code, errorCodeToString(code)); 131 } 132 getCode()133 public int getCode() { 134 return mCode; 135 } 136 getMessage()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 */ start(int totalWork)151 public void start(int totalWork); 152 /** 153 * Sent when the transfer is finished or interrupted. 154 */ stop()155 public void stop(); 156 /** 157 * Sent to query for possible cancellation. 158 * @return true if the transfer should be stopped. 159 */ isCanceled()160 public boolean isCanceled(); 161 /** 162 * Sent when a sub task is started. 163 * @param name the name of the sub task. 164 */ startSubTask(String name)165 public void startSubTask(String name); 166 /** 167 * Sent when some progress have been made. 168 * @param work the amount of work done. 169 */ advance(int work)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 { advance(int work)177 public void advance(int work) { 178 } isCanceled()179 public boolean isCanceled() { 180 return false; 181 } 182 start(int totalWork)183 public void start(int totalWork) { 184 } startSubTask(String name)185 public void startSubTask(String name) { 186 } stop()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 */ SyncService(InetSocketAddress address, Device device)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 */ openSync()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 */ close()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 */ getNullProgressMonitor()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 */ errorCodeToString(int code)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 */ pull(FileEntry[] entries, String localPath, ISyncProgressMonitor monitor)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 */ pullFile(FileEntry remote, String localFilename, ISyncProgressMonitor monitor)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 */ pullFile(String remoteFilepath, String localFilename, ISyncProgressMonitor monitor)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 */ push(String[] local, FileEntry remote, ISyncProgressMonitor monitor)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 */ pushFile(String local, String remote, ISyncProgressMonitor monitor)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 */ getTotalRemoteFileSize(FileEntry[] entries, FileListingService fls)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 */ getTotalLocalFileSize(File[] files)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 */ doPull(FileEntry[] entries, String localPath, FileListingService fileListingService, ISyncProgressMonitor monitor)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 */ doPullFile(String remotePath, String localPath, ISyncProgressMonitor monitor)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 */ doPush(File[] fileArray, String remotePath, ISyncProgressMonitor monitor)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 */ doPushFile(String localPath, String remotePath, ISyncProgressMonitor monitor)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 */ readMode(String path)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 */ createReq(byte[] command, int value)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 */ createFileReq(byte[] command, String path)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 */ createFileReq(byte[] command, byte[] path)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 createSendFileReq(byte[] command, byte[] path, int mode)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 */ checkResult(byte[] result, byte[] code)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 getFileType(int mode)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 } 981