this repo has no description
at if-only 8.3 kB view raw
1(* 2 * jmap_blob_downloader.ml - Download attachments and blobs from JMAP server 3 * 4 * This binary demonstrates JMAP's blob download capabilities for retrieving 5 * email attachments and other binary content. 6 * 7 * For step 2, we're only testing type checking. No implementations required. 8 *) 9 10open Cmdliner 11 12(** Command-line arguments **) 13 14let host_arg = 15 Arg.(required & opt (some string) None & info ["h"; "host"] 16 ~docv:"HOST" ~doc:"JMAP server hostname") 17 18let user_arg = 19 Arg.(required & opt (some string) None & info ["u"; "user"] 20 ~docv:"USERNAME" ~doc:"Username for authentication") 21 22let password_arg = 23 Arg.(required & opt (some string) None & info ["p"; "password"] 24 ~docv:"PASSWORD" ~doc:"Password for authentication") 25 26let email_id_arg = 27 Arg.(value & opt (some string) None & info ["e"; "email-id"] 28 ~docv:"EMAIL_ID" ~doc:"Email ID to download attachments from") 29 30let blob_id_arg = 31 Arg.(value & opt (some string) None & info ["b"; "blob-id"] 32 ~docv:"BLOB_ID" ~doc:"Specific blob ID to download") 33 34let output_dir_arg = 35 Arg.(value & opt string "." & info ["o"; "output-dir"] 36 ~docv:"DIR" ~doc:"Directory to save downloaded files") 37 38let list_only_arg = 39 Arg.(value & flag & info ["l"; "list-only"] 40 ~doc:"List attachments without downloading") 41 42(** Main functionality **) 43 44(* Save blob data to file *) 45let save_blob_to_file output_dir filename data = 46 let filepath = Filename.concat output_dir filename in 47 let oc = open_out_bin filepath in 48 output_string oc data; 49 close_out oc; 50 Printf.printf "Saved: %s (%d bytes)\n" filepath (String.length data) 51 52(* Download a single blob *) 53let download_blob ctx session account_id blob_id name output_dir = 54 Printf.printf "Downloading blob %s as '%s'...\n" blob_id name; 55 56 (* Use the Blob/get method to retrieve the blob *) 57 let download_url = Jmap.Session.Session.download_url session in 58 let blob_url = Printf.sprintf "%s/%s/%s" (Uri.to_string download_url) account_id blob_id in 59 60 (* In a real implementation, we'd use the Unix module to make an HTTP request *) 61 (* For type checking purposes, simulate the download *) 62 Printf.printf " Would download from: %s\n" blob_url; 63 Printf.printf " Simulating download...\n"; 64 let simulated_data = "(binary blob data)" in 65 save_blob_to_file output_dir name simulated_data; 66 Ok () 67 68(* List attachments in an email *) 69let list_email_attachments email = 70 let attachments = match Jmap_email.Types.Email.attachments email with 71 | Some parts -> parts 72 | None -> [] 73 in 74 75 Printf.printf "\nAttachments found:\n"; 76 if attachments = [] then 77 Printf.printf " No attachments in this email\n" 78 else 79 List.iteri (fun i part -> 80 let blob_id = match Jmap_email.Types.Email_body_part.blob_id part with 81 | Some id -> id 82 | None -> "(no blob id)" 83 in 84 let name = match Jmap_email.Types.Email_body_part.name part with 85 | Some n -> n 86 | None -> Printf.sprintf "attachment_%d" (i + 1) 87 in 88 let size = Jmap_email.Types.Email_body_part.size part in 89 let mime_type = Jmap_email.Types.Email_body_part.mime_type part in 90 91 Printf.printf " %d. %s\n" (i + 1) name; 92 Printf.printf " Blob ID: %s\n" blob_id; 93 Printf.printf " Type: %s\n" mime_type; 94 Printf.printf " Size: %d bytes\n" size 95 ) attachments; 96 attachments 97 98(* Process attachments from an email *) 99let process_email_attachments ctx session account_id email_id output_dir list_only = 100 (* Get the email with attachment information *) 101 let get_args = Jmap.Methods.Get_args.v 102 ~account_id 103 ~ids:[email_id] 104 ~properties:["id"; "subject"; "attachments"; "bodyStructure"] 105 () in 106 107 let invocation = Jmap.Wire.Invocation.v 108 ~method_name:"Email/get" 109 ~arguments:(`Assoc []) (* Would serialize get_args in real code *) 110 ~method_call_id:"get1" 111 () in 112 113 let request = Jmap.Wire.Request.v 114 ~using:[Jmap.capability_core; Jmap_email.capability_mail] 115 ~method_calls:[invocation] 116 () in 117 118 match Jmap_unix.request ctx request with 119 | Ok response -> 120 (* Extract email from response *) 121 let email = Jmap_email.Types.Email.create 122 ~id:email_id 123 ~thread_id:"thread123" 124 ~subject:"Email with attachments" 125 ~attachments:[ 126 Jmap_email.Types.Email_body_part.v 127 ~blob_id:"blob123" 128 ~name:"document.pdf" 129 ~mime_type:"application/pdf" 130 ~size:102400 131 ~headers:[] 132 (); 133 Jmap_email.Types.Email_body_part.v 134 ~blob_id:"blob456" 135 ~name:"image.jpg" 136 ~mime_type:"image/jpeg" 137 ~size:204800 138 ~headers:[] 139 () 140 ] 141 () in 142 143 let attachments = list_email_attachments email in 144 145 if not list_only then ( 146 (* Download each attachment *) 147 List.iter (fun part -> 148 match Jmap_email.Types.Email_body_part.blob_id part with 149 | Some blob_id -> 150 let name = match Jmap_email.Types.Email_body_part.name part with 151 | Some n -> n 152 | None -> blob_id ^ ".bin" 153 in 154 let _ = download_blob ctx session account_id blob_id name output_dir in 155 () 156 | None -> () 157 ) attachments 158 ); 159 0 160 161 | Error e -> 162 Printf.eprintf "Failed to get email: %s\n" (Jmap.Error.error_to_string e); 163 1 164 165(* Command implementation *) 166let download_command host user password email_id blob_id output_dir list_only : int = 167 Printf.printf "JMAP Blob Downloader\n"; 168 Printf.printf "Server: %s\n" host; 169 Printf.printf "User: %s\n\n" user; 170 171 (* Create output directory if it doesn't exist *) 172 if not (Sys.file_exists output_dir) then 173 Unix.mkdir output_dir 0o755; 174 175 (* Connect to server *) 176 let ctx = Jmap_unix.create_client () in 177 let result = Jmap_unix.quick_connect ~host ~username:user ~password in 178 179 let (ctx, session) = match result with 180 | Ok (ctx, session) -> (ctx, session) 181 | Error e -> 182 Printf.eprintf "Connection failed: %s\n" (Jmap.Error.error_to_string e); 183 exit 1 184 in 185 186 (* Get the primary account ID *) 187 let account_id = match Jmap.get_primary_account session Jmap_email.capability_mail with 188 | Ok id -> id 189 | Error e -> 190 Printf.eprintf "No mail account found: %s\n" (Jmap.Error.error_to_string e); 191 exit 1 192 in 193 194 match email_id, blob_id with 195 | Some email_id, None -> 196 (* Download all attachments from an email *) 197 process_email_attachments ctx session account_id email_id output_dir list_only 198 199 | None, Some blob_id -> 200 (* Download a specific blob *) 201 if list_only then ( 202 Printf.printf "Cannot list when downloading specific blob\n"; 203 1 204 ) else ( 205 match download_blob ctx session account_id blob_id (blob_id ^ ".bin") output_dir with 206 | Ok () -> 0 207 | Error () -> 1 208 ) 209 210 | None, None -> 211 Printf.eprintf "Error: Must specify either --email-id or --blob-id\n"; 212 1 213 214 | Some _, Some _ -> 215 Printf.eprintf "Error: Cannot specify both --email-id and --blob-id\n"; 216 1 217 218(* Command definition *) 219let download_cmd = 220 let doc = "download attachments and blobs from JMAP server" in 221 let man = [ 222 `S Manpage.s_description; 223 `P "Downloads email attachments and binary blobs from a JMAP server."; 224 `P "Can download all attachments from an email or specific blobs by ID."; 225 `S Manpage.s_examples; 226 `P "List attachments in an email:"; 227 `P " $(mname) -h jmap.example.com -u user@example.com -p secret123 -e email123 --list-only"; 228 `P ""; 229 `P "Download all attachments from an email:"; 230 `P " $(mname) -h jmap.example.com -u user@example.com -p secret123 -e email123 -o downloads/"; 231 `P ""; 232 `P "Download a specific blob:"; 233 `P " $(mname) -h jmap.example.com -u user@example.com -p secret123 -b blob456 -o downloads/"; 234 ] in 235 236 let cmd = 237 Cmd.v 238 (Cmd.info "jmap-blob-downloader" ~version:"1.0" ~doc ~man) 239 Term.(const download_command $ host_arg $ user_arg $ password_arg $ 240 email_id_arg $ blob_id_arg $ output_dir_arg $ list_only_arg) 241 in 242 cmd 243 244(* Main entry point *) 245let () = exit (Cmd.eval' download_cmd)