diff options
| author | 2024-04-28 12:14:39 +0800 | |
|---|---|---|
| committer | 2024-05-18 21:43:39 +0200 | |
| commit | d4efb4ed1bd16cf11fbd0e8c1c41522474aced8d (patch) | |
| tree | 6c0cda7d480a63b3e8f8d94a9b5305b71cd13561 /tools | |
| parent | 469d21c6d506d107a5462c8b10e516f5790c35d3 (diff) | |
| download | libimobiledevice-d4efb4ed1bd16cf11fbd0e8c1c41522474aced8d.tar.gz libimobiledevice-d4efb4ed1bd16cf11fbd0e8c1c41522474aced8d.tar.bz2 | |
tools/afcclient: Allow get folder from device to local.
Signed-off-by: tomriddly <tomriddly@qq.com>
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/afcclient.c | 250 | 
1 files changed, 164 insertions, 86 deletions
| diff --git a/tools/afcclient.c b/tools/afcclient.c index 9bcd77b..826ad5b 100644 --- a/tools/afcclient.c +++ b/tools/afcclient.c | |||
| @@ -89,6 +89,16 @@ static idevice_subscription_context_t context = NULL; | |||
| 89 | static char* curdir = NULL; | 89 | static char* curdir = NULL; | 
| 90 | static size_t curdir_len = 0; | 90 | static size_t curdir_len = 0; | 
| 91 | 91 | ||
| 92 | static int is_directory(const char* path) | ||
| 93 | { | ||
| 94 | struct stat tst; | ||
| 95 | #ifdef WIN32 | ||
| 96 | return (stat(path, &tst) == 0) && S_ISDIR(tst.st_mode); | ||
| 97 | #else | ||
| 98 | return (lstat(path, &tst) == 0) && S_ISDIR(tst.st_mode); | ||
| 99 | #endif | ||
| 100 | } | ||
| 101 | |||
| 92 | static void print_usage(int argc, char **argv, int is_error) | 102 | static void print_usage(int argc, char **argv, int is_error) | 
| 93 | { | 103 | { | 
| 94 | char *name = strrchr(argv[0], '/'); | 104 | char *name = strrchr(argv[0], '/'); | 
| @@ -173,7 +183,7 @@ static void handle_help(afc_client_t afc, int argc, char** argv) | |||
| 173 | printf("rm PATH - remove item at PATH\n"); | 183 | printf("rm PATH - remove item at PATH\n"); | 
| 174 | printf("get PATH [LOCALPATH] - transfer file at PATH from device to LOCALPATH\n"); | 184 | printf("get PATH [LOCALPATH] - transfer file at PATH from device to LOCALPATH\n"); | 
| 175 | printf("put LOCALPATH [PATH] - transfer local file at LOCALPATH to device at PATH\n"); | 185 | printf("put LOCALPATH [PATH] - transfer local file at LOCALPATH to device at PATH\n"); | 
| 176 | printf("\n"); | 186 | printf("\n"); | 
| 177 | } | 187 | } | 
| 178 | 188 | ||
| 179 | static const char* path_get_basename(const char* path) | 189 | static const char* path_get_basename(const char* path) | 
| @@ -200,7 +210,7 @@ static int timeval_subtract(struct timeval *result, struct timeval *x, struct ti | |||
| 200 | result->tv_sec = x->tv_sec - y->tv_sec; | 210 | result->tv_sec = x->tv_sec - y->tv_sec; | 
| 201 | result->tv_usec = x->tv_usec - y->tv_usec; | 211 | result->tv_usec = x->tv_usec - y->tv_usec; | 
| 202 | /* Return 1 if result is negative. */ | 212 | /* Return 1 if result is negative. */ | 
| 203 | return x->tv_sec < y->tv_sec; | 213 | return x->tv_sec < y->tv_sec; | 
| 204 | } | 214 | } | 
| 205 | 215 | ||
| 206 | struct str_item { | 216 | struct str_item { | 
| @@ -674,110 +684,178 @@ static void handle_remove(afc_client_t afc, int argc, char** argv) | |||
| 674 | } | 684 | } | 
| 675 | } | 685 | } | 
| 676 | 686 | ||
| 677 | static void handle_get(afc_client_t afc, int argc, char** argv) | 687 | static uint8_t get_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint64_t file_size) | 
| 678 | { | 688 | { | 
| 679 | if (argc < 1 || argc > 2) { | 689 | uint64_t fh = 0; | 
| 680 | printf("Error: Invalid number of arguments\n"); | 690 | afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh); | 
| 681 | return; | 691 | if (err != AFC_E_SUCCESS) { | 
| 692 | printf("Error: Failed to open file '%s': %s (%d)\n", srcpath, afc_strerror(err), err); | ||
| 693 | return 0; | ||
| 682 | } | 694 | } | 
| 683 | char *srcpath = NULL; | 695 | FILE *f = fopen(dstpath, "wb"); | 
| 684 | char* dstpath = NULL; | 696 | if (!f) { | 
| 685 | if (argc == 1) { | 697 | printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno)); | 
| 686 | srcpath = get_absolute_path(argv[0]); | 698 | return 0; | 
| 687 | dstpath = strdup(path_get_basename(argv[0])); | 699 | } | 
| 688 | } else { | 700 | struct timeval t1; | 
| 689 | srcpath = get_absolute_path(argv[0]); | 701 | struct timeval t2; | 
| 690 | dstpath = strdup(argv[1]); | 702 | struct timeval tdiff; | 
| 703 | size_t bufsize = 0x100000; | ||
| 704 | char *buf = malloc(bufsize); | ||
| 705 | size_t total = 0; | ||
| 706 | int progress = 0; | ||
| 707 | int lastprog = 0; | ||
| 708 | if (file_size > 0x400000) { | ||
| 709 | progress = 1; | ||
| 710 | gettimeofday(&t1, NULL); | ||
| 711 | } | ||
| 712 | uint8_t succeed = 1; | ||
| 713 | while (err == AFC_E_SUCCESS) { | ||
| 714 | uint32_t bytes_read = 0; | ||
| 715 | size_t chunk = 0; | ||
| 716 | err = afc_file_read(afc, fh, buf, bufsize, &bytes_read); | ||
| 717 | if (bytes_read == 0) { | ||
| 718 | break; | ||
| 719 | } | ||
| 720 | while (chunk < bytes_read) { | ||
| 721 | size_t wr = fwrite(buf + chunk, 1, bytes_read - chunk, f); | ||
| 722 | if (wr == 0) { | ||
| 723 | if (progress) { | ||
| 724 | printf("\n"); | ||
| 725 | } | ||
| 726 | printf("Error: Failed to write to local file\n"); | ||
| 727 | succeed = 0; | ||
| 728 | break; | ||
| 729 | } | ||
| 730 | chunk += wr; | ||
| 731 | } | ||
| 732 | total += chunk; | ||
| 733 | if (progress) { | ||
| 734 | int prog = (int) ((double) total / (double) file_size * 100.0f); | ||
| 735 | if (prog > lastprog) { | ||
| 736 | gettimeofday(&t2, NULL); | ||
| 737 | timeval_subtract(&tdiff, &t2, &t1); | ||
| 738 | double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000; | ||
| 739 | printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec); | ||
| 740 | fflush(stdout); | ||
| 741 | lastprog = prog; | ||
| 742 | } | ||
| 743 | } | ||
| 691 | } | 744 | } | 
| 745 | if (progress) { | ||
| 746 | printf("\n"); | ||
| 747 | } | ||
| 748 | if (err != AFC_E_SUCCESS) { | ||
| 749 | printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err); | ||
| 750 | succeed = 0; | ||
| 751 | } | ||
| 752 | free(buf); | ||
| 753 | fclose(f); | ||
| 754 | afc_file_close(afc, fh); | ||
| 755 | return succeed; | ||
| 756 | } | ||
| 692 | 757 | ||
| 758 | static uint8_t get_file(afc_client_t afc, const char *srcpath, const char *dstpath) | ||
| 759 | { | ||
| 693 | char **info = NULL; | 760 | char **info = NULL; | 
| 694 | uint64_t file_size = 0; | 761 | uint64_t file_size = 0; | 
| 695 | afc_get_file_info(afc, srcpath, &info); | 762 | afc_get_file_info(afc, srcpath, &info); | 
| 763 | uint8_t is_dir = 0; | ||
| 696 | if (info) { | 764 | if (info) { | 
| 697 | char **p = info; | 765 | char **p = info; | 
| 698 | while (p && *p) { | 766 | while (p && *p) { | 
| 699 | if (!strcmp(*p, "st_size")) { | 767 | if (!strcmp(*p, "st_size")) { | 
| 700 | p++; | 768 | p++; | 
| 701 | file_size = (uint64_t)strtoull(*p, NULL, 10); | 769 | file_size = (uint64_t) strtoull(*p, NULL, 10); | 
| 770 | } | ||
| 771 | if (!strcmp(*p, "st_ifmt")) { | ||
| 772 | p++; | ||
| 773 | is_dir = !strcmp(*p, "S_IFDIR"); | ||
| 774 | } | ||
| 775 | p++; | ||
| 776 | } | ||
| 777 | afc_dictionary_free(info); | ||
| 778 | } | ||
| 779 | uint8_t succeed = 1; | ||
| 780 | if (is_dir) { | ||
| 781 | char **entries = NULL; | ||
| 782 | afc_error_t err = afc_read_directory(afc, srcpath, &entries); | ||
| 783 | if (err != AFC_E_SUCCESS) { | ||
| 784 | printf("Error: Failed to list '%s': %s (%d)\n", srcpath, afc_strerror(err), err); | ||
| 785 | return 0; | ||
| 786 | } | ||
| 787 | char **p = entries; | ||
| 788 | size_t srcpath_len = strlen(srcpath); | ||
| 789 | int srcpath_is_root = strcmp(srcpath, "/") == 0; | ||
| 790 | if (!is_directory(dstpath) && mkdir(dstpath, 0777) != 0) { | ||
| 791 | printf("Error: Failed to create folder '%s': %s\n", dstpath, strerror(errno)); | ||
| 792 | afc_dictionary_free(entries); | ||
| 793 | return 0; | ||
| 794 | } | ||
| 795 | while (p && *p) { | ||
| 796 | if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) { | ||
| 797 | p++; | ||
| 798 | continue; | ||
| 799 | } | ||
| 800 | size_t len = srcpath_len + 1 + strlen(*p) + 1; | ||
| 801 | char *testpath = (char *) malloc(len); | ||
| 802 | if (srcpath_is_root) { | ||
| 803 | snprintf(testpath, len, "/%s", *p); | ||
| 804 | } else { | ||
| 805 | snprintf(testpath, len, "%s/%s", srcpath, *p); | ||
| 806 | } | ||
| 807 | size_t dst_len = strlen(dstpath) + 1 + strlen(*p) + 1; | ||
| 808 | char *newdst = (char *) malloc(dst_len); | ||
| 809 | snprintf(newdst, dst_len, "%s/%s", dstpath, *p); | ||
| 810 | if (!get_file(afc, testpath, newdst)) { | ||
| 811 | succeed = 0; | ||
| 702 | break; | 812 | break; | 
| 703 | } | 813 | } | 
| 814 | free(testpath); | ||
| 815 | free(newdst); | ||
| 704 | p++; | 816 | p++; | 
| 705 | } | 817 | } | 
| 818 | afc_dictionary_free(entries); | ||
| 819 | } else { | ||
| 820 | succeed = get_single_file(afc, srcpath, dstpath, file_size); | ||
| 706 | } | 821 | } | 
| 707 | uint64_t fh = 0; | 822 | return succeed; | 
| 708 | afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh); | 823 | } | 
| 709 | if (err != AFC_E_SUCCESS) { | 824 | |
| 710 | free(srcpath); | 825 | static void handle_get(afc_client_t afc, int argc, char **argv) | 
| 711 | free(dstpath); | 826 | { | 
| 712 | printf("Error: Failed to open file '%s': %s (%d)\n", argv[0], afc_strerror(err), err); | 827 | if (argc < 1 || argc > 2) { | 
| 828 | printf("Error: Invalid number of arguments\n"); | ||
| 713 | return; | 829 | return; | 
| 714 | } | 830 | } | 
| 715 | FILE *f = fopen(dstpath, "wb"); | 831 | char *srcpath = NULL; | 
| 716 | if (!f && errno == EISDIR) { | 832 | char *dstpath = NULL; | 
| 717 | const char* basen = path_get_basename(argv[0]); | 833 | size_t src_len = strlen(argv[0]); | 
| 834 | if (strcmp(argv[0], "/") != 0 && argv[0][src_len - 1] == '/') { | ||
| 835 | argv[0][src_len - 1] = '\0'; | ||
| 836 | } | ||
| 837 | if (argc == 1) { | ||
| 838 | srcpath = get_absolute_path(argv[0]); | ||
| 839 | dstpath = strdup(path_get_basename(argv[0])); | ||
| 840 | } else { | ||
| 841 | srcpath = get_absolute_path(argv[0]); | ||
| 842 | dstpath = strdup(argv[1]); | ||
| 843 | } | ||
| 844 | |||
| 845 | if (is_directory(dstpath)) { | ||
| 846 | const char *basen = path_get_basename(argv[0]); | ||
| 718 | size_t len = strlen(dstpath) + 1 + strlen(basen) + 1; | 847 | size_t len = strlen(dstpath) + 1 + strlen(basen) + 1; | 
| 719 | char* newdst = (char*)malloc(len); | 848 | char *newdst = (char *) malloc(len); | 
| 720 | snprintf(newdst, len, "%s/%s", dstpath, basen); | 849 | snprintf(newdst, len, "%s/%s", dstpath, basen); | 
| 721 | f = fopen(newdst, "wb"); | 850 | get_file(afc, srcpath, newdst); | 
| 851 | free(srcpath); | ||
| 722 | free(newdst); | 852 | free(newdst); | 
| 723 | } | 853 | free(dstpath); | 
| 724 | if (f) { | ||
| 725 | struct timeval t1; | ||
| 726 | struct timeval t2; | ||
| 727 | struct timeval tdiff; | ||
| 728 | size_t bufsize = 0x100000; | ||
| 729 | char* buf = malloc(bufsize); | ||
| 730 | size_t total = 0; | ||
| 731 | int progress = 0; | ||
| 732 | int lastprog = 0; | ||
| 733 | if (file_size > 0x400000) { | ||
| 734 | progress = 1; | ||
| 735 | gettimeofday(&t1, NULL); | ||
| 736 | } | ||
| 737 | while (err == AFC_E_SUCCESS) { | ||
| 738 | uint32_t bytes_read = 0; | ||
| 739 | size_t chunk = 0; | ||
| 740 | err = afc_file_read(afc, fh, buf, bufsize, &bytes_read); | ||
| 741 | if (bytes_read == 0) { | ||
| 742 | break; | ||
| 743 | } | ||
| 744 | while (chunk < bytes_read) { | ||
| 745 | size_t wr = fwrite(buf+chunk, 1, bytes_read-chunk, f); | ||
| 746 | if (wr == 0) { | ||
| 747 | if (progress) { | ||
| 748 | printf("\n"); | ||
| 749 | } | ||
| 750 | printf("Error: Failed to write to local file\n"); | ||
| 751 | break; | ||
| 752 | } | ||
| 753 | chunk += wr; | ||
| 754 | } | ||
| 755 | total += chunk; | ||
| 756 | if (progress) { | ||
| 757 | int prog = (int)((double)total / (double)file_size * 100.0f); | ||
| 758 | if (prog > lastprog) { | ||
| 759 | gettimeofday(&t2, NULL); | ||
| 760 | timeval_subtract(&tdiff, &t2, &t1); | ||
| 761 | double time_in_sec = (double)tdiff.tv_sec + (double)tdiff.tv_usec/1000000; | ||
| 762 | printf("\r%d%% (%0.1f MB/s) ", prog, (double)total/1048576.0f / time_in_sec); | ||
| 763 | fflush(stdout); | ||
| 764 | lastprog = prog; | ||
| 765 | } | ||
| 766 | } | ||
| 767 | } | ||
| 768 | if (progress) { | ||
| 769 | printf("\n"); | ||
| 770 | } | ||
| 771 | if (err != AFC_E_SUCCESS) { | ||
| 772 | printf("Error: Failed to read from file '%s': %s (%d)\n", argv[0], afc_strerror(err), err); | ||
| 773 | } | ||
| 774 | free(buf); | ||
| 775 | fclose(f); | ||
| 776 | } else { | 854 | } else { | 
| 777 | printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno)); | 855 | get_file(afc, srcpath, dstpath); | 
| 856 | free(srcpath); | ||
| 857 | free(dstpath); | ||
| 778 | } | 858 | } | 
| 779 | afc_file_close(afc, fh); | ||
| 780 | free(srcpath); | ||
| 781 | } | 859 | } | 
| 782 | 860 | ||
| 783 | static void handle_put(afc_client_t afc, int argc, char** argv) | 861 | static void handle_put(afc_client_t afc, int argc, char** argv) | 
| @@ -975,8 +1053,8 @@ static void parse_cmdline(int* p_argc, char*** p_argv, const char* cmdline) | |||
| 975 | } else if (*pos == '"' || *pos == '\'') { | 1053 | } else if (*pos == '"' || *pos == '\'') { | 
| 976 | if (!qpos) { | 1054 | if (!qpos) { | 
| 977 | qpos = pos; | 1055 | qpos = pos; | 
| 978 | } else { | 1056 | } else { | 
| 979 | qpos = NULL; | 1057 | qpos = NULL; | 
| 980 | } | 1058 | } | 
| 981 | pos++; | 1059 | pos++; | 
| 982 | } else if (*pos == '\0' || (!qpos && isspace(*pos))) { | 1060 | } else if (*pos == '\0' || (!qpos && isspace(*pos))) { | 
| @@ -1043,7 +1121,7 @@ static int process_args(afc_client_t afc, int argc, char** argv) | |||
| 1043 | handle_file_info(afc, argc-1, argv+1); | 1121 | handle_file_info(afc, argc-1, argv+1); | 
| 1044 | } | 1122 | } | 
| 1045 | else if (!strcmp(argv[0], "ls") || !strcmp(argv[0], "list")) { | 1123 | else if (!strcmp(argv[0], "ls") || !strcmp(argv[0], "list")) { | 
| 1046 | handle_list(afc, argc-1, argv+1); | 1124 | handle_list(afc, argc-1, argv+1); | 
| 1047 | } | 1125 | } | 
| 1048 | else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) { | 1126 | else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) { | 
| 1049 | handle_rename(afc, argc-1, argv+1); | 1127 | handle_rename(afc, argc-1, argv+1); | 
| @@ -1113,7 +1191,7 @@ static void start_cmdline(afc_client_t afc) | |||
| 1113 | break; | 1191 | break; | 
| 1114 | } | 1192 | } | 
| 1115 | } | 1193 | } | 
| 1116 | } | 1194 | } | 
| 1117 | } | 1195 | } | 
| 1118 | 1196 | ||
| 1119 | static void device_event_cb(const idevice_event_t* event, void* userdata) | 1197 | static void device_event_cb(const idevice_event_t* event, void* userdata) | 
