地圖 (Jido) is a lightweight Unix TUI file explorer designed for speed and simplicity.
at main 663 lines 30 kB view raw
1const std = @import("std"); 2 3const vaxis = @import("vaxis"); 4const zuid = @import("zuid"); 5 6const App = @import("./app.zig"); 7const Archive = @import("./archive.zig"); 8const environment = @import("./environment.zig"); 9const path_utils = @import("./path_utils.zig"); 10const Preview = @import("./preview.zig"); 11 12const config = &@import("./config.zig").config; 13 14pub fn delete(app: *App) error{OutOfMemory}!void { 15 var message: ?[]const u8 = null; 16 defer if (message) |msg| app.alloc.free(msg); 17 18 const entry = (app.directories.getSelected() catch { 19 app.notification.write("Can not to delete item - no item selected.", .warn) catch {}; 20 return; 21 }) orelse return; 22 const clean_name = path_utils.getCleanName(entry); 23 24 var prev_path_buf: [std.fs.max_path_bytes]u8 = undefined; 25 const prev_path = app.directories.dir.realpath(clean_name, &prev_path_buf) catch { 26 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path.", .{entry.name}); 27 app.notification.write(message.?, .err) catch {}; 28 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 29 return; 30 }; 31 const prev_path_alloc = try app.alloc.dupe(u8, prev_path); 32 33 var trash_dir = dir: { 34 notfound: { 35 break :dir (config.trashDir() catch break :notfound) orelse break :notfound; 36 } 37 app.alloc.free(prev_path_alloc); 38 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve trash directory.", .{entry.name}); 39 app.notification.write(message.?, .err) catch {}; 40 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 41 return; 42 }; 43 defer trash_dir.close(); 44 45 var trash_dir_path_buf: [std.fs.max_path_bytes]u8 = undefined; 46 const trash_dir_path = trash_dir.realpath(".", &trash_dir_path_buf) catch { 47 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - unable to retrieve absolute path for trash directory.", .{entry.name}); 48 app.notification.write(message.?, .err) catch {}; 49 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 50 return; 51 }; 52 53 if (std.mem.eql(u8, prev_path_alloc, trash_dir_path)) { 54 app.notification.write("Can not delete trash directory.", .warn) catch {}; 55 app.alloc.free(prev_path_alloc); 56 return; 57 } 58 59 const tmp_path = try std.fmt.allocPrint(app.alloc, "{s}/{s}-{f}", .{ trash_dir_path, clean_name, zuid.new.v4() }); 60 if (app.directories.dir.rename(clean_name, tmp_path)) { 61 if (app.actions.push(.{ 62 .delete = .{ .prev_path = prev_path_alloc, .new_path = tmp_path }, 63 })) |prev_elem| { 64 app.alloc.free(prev_elem.delete.prev_path); 65 app.alloc.free(prev_elem.delete.new_path); 66 } 67 message = try std.fmt.allocPrint(app.alloc, "Deleted '{s}'.", .{entry.name}); 68 app.notification.write(message.?, .info) catch {}; 69 70 app.directories.removeSelected(); 71 Preview.loadPreviewForCurrentEntry(app) catch {}; 72 } else |err| { 73 app.alloc.free(prev_path_alloc); 74 app.alloc.free(tmp_path); 75 76 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - {}.", .{ entry.name, err }); 77 app.notification.write(message.?, .err) catch {}; 78 } 79} 80 81pub fn rename(app: *App) error{OutOfMemory}!void { 82 var message: ?[]const u8 = null; 83 defer if (message) |msg| app.alloc.free(msg); 84 85 const entry = (app.directories.getSelected() catch { 86 app.notification.write("Can not to rename item - no item selected.", .warn) catch {}; 87 return; 88 }) orelse return; 89 90 var dir_prefix_buf: [std.fs.max_path_bytes]u8 = undefined; 91 const dir_prefix = app.directories.dir.realpath(".", &dir_prefix_buf) catch { 92 message = try std.fmt.allocPrint(app.alloc, "Failed to rename '{s}' - unable to retrieve absolute path.", .{entry.name}); 93 app.notification.write(message.?, .err) catch {}; 94 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 95 return; 96 }; 97 98 const new_path = try app.text_input.toOwnedSlice(); 99 defer app.alloc.free(new_path); 100 101 if (environment.fileExists(app.directories.dir, new_path)) { 102 message = try std.fmt.allocPrint(app.alloc, "Can not rename file - '{s}' already exists.", .{new_path}); 103 app.notification.write(message.?, .warn) catch {}; 104 } else { 105 app.directories.dir.rename(entry.name, new_path) catch |err| { 106 message = try std.fmt.allocPrint(app.alloc, "Failed to rename '{s}' - {}.", .{ new_path, err }); 107 app.notification.write(message.?, .err) catch {}; 108 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 109 return; 110 }; 111 112 if (app.actions.push(.{ 113 .rename = .{ 114 .prev_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, entry.name }), 115 .new_path = try std.fs.path.join(app.alloc, &.{ dir_prefix, new_path }), 116 }, 117 })) |prev_elem| { 118 app.alloc.free(prev_elem.rename.prev_path); 119 app.alloc.free(prev_elem.rename.new_path); 120 } 121 122 app.directories.clearEntries(); 123 app.directories.populateEntries("") catch |err| { 124 const m = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}); 125 defer app.alloc.free(m); 126 app.notification.write(m, .err) catch {}; 127 if (app.file_logger) |file_logger| file_logger.write(m, .err) catch {}; 128 }; 129 130 const target_name = if (entry.kind == .directory) 131 try std.fmt.allocPrint(app.alloc, "{s}/", .{new_path}) 132 else 133 new_path; 134 defer if (entry.kind == .directory) app.alloc.free(target_name); 135 136 for (app.directories.entries.all(), 0..) |e, i| { 137 if (std.mem.eql(u8, e.name, target_name)) { 138 app.directories.entries.selected = i; 139 break; 140 } 141 } 142 143 // No need to revalidate cache as we're viewing the same file 144 Preview.loadPreviewForCurrentEntry(app) catch |err| { 145 if (app.file_logger) |file_logger| { 146 const msg = std.fmt.allocPrint(app.alloc, "Failed to load preview after repopulate: {}", .{err}) catch return; 147 defer app.alloc.free(msg); 148 file_logger.write(msg, .err) catch {}; 149 } 150 }; 151 152 message = try std.fmt.allocPrint(app.alloc, "Renamed '{s}' to '{s}'.", .{ entry.name, new_path }); 153 app.notification.write(message.?, .info) catch {}; 154 } 155} 156 157pub fn forceDelete(app: *App) error{OutOfMemory}!void { 158 const entry = (app.directories.getSelected() catch { 159 app.notification.write("Can not force delete item - no item selected.", .warn) catch {}; 160 return; 161 }) orelse return; 162 163 app.directories.dir.deleteTree(entry.name) catch |err| { 164 const error_message = try std.fmt.allocPrint(app.alloc, "Failed to force delete '{s}' - {}.", .{ entry.name, err }); 165 app.notification.write(error_message, .err) catch {}; 166 return; 167 }; 168 169 app.directories.removeSelected(); 170} 171 172pub fn toggleHiddenFiles(app: *App) error{OutOfMemory}!void { 173 config.show_hidden = !config.show_hidden; 174 175 try app.repopulateDirectory(""); 176 app.text_input.clearAndFree(); 177} 178 179pub fn yank(app: *App) error{OutOfMemory}!void { 180 var message: ?[]const u8 = null; 181 defer if (message) |msg| app.alloc.free(msg); 182 183 if (app.yanked) |yanked| { 184 app.alloc.free(yanked.dir); 185 app.alloc.free(yanked.entry.name); 186 } 187 188 app.yanked = lbl: { 189 const entry = (app.directories.getSelected() catch { 190 app.notification.write("Can not yank item - no item selected.", .warn) catch {}; 191 break :lbl null; 192 }) orelse break :lbl null; 193 194 switch (entry.kind) { 195 .file, .directory, .sym_link => { 196 break :lbl .{ 197 .dir = try app.alloc.dupe(u8, app.directories.fullPath(".") catch { 198 message = try std.fmt.allocPrint( 199 app.alloc, 200 "Failed to yank '{s}' - unable to retrieve directory path.", 201 .{entry.name}, 202 ); 203 app.notification.write(message.?, .err) catch {}; 204 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 205 break :lbl null; 206 }), 207 .entry = .{ 208 .kind = entry.kind, 209 .name = try app.alloc.dupe(u8, entry.name), 210 }, 211 }; 212 }, 213 else => { 214 message = try std.fmt.allocPrint(app.alloc, "Can not yank '{s}' - unsupported file type '{s}'.", .{ entry.name, @tagName(entry.kind) }); 215 app.notification.write(message.?, .warn) catch {}; 216 break :lbl null; 217 }, 218 } 219 }; 220 221 if (app.yanked) |y| { 222 message = try std.fmt.allocPrint(app.alloc, "Yanked '{s}'.", .{y.entry.name}); 223 app.notification.write(message.?, .info) catch {}; 224 } 225} 226 227pub fn paste(app: *App) error{ OutOfMemory, NoSpaceLeft }!void { 228 var message: ?[]const u8 = null; 229 defer if (message) |msg| app.alloc.free(msg); 230 231 const yanked = if (app.yanked) |y| y else return; 232 233 var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 234 const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, yanked.entry.name) catch { 235 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - path too long.", .{yanked.entry.name}); 236 app.notification.write(message.?, .err) catch {}; 237 return; 238 }; 239 240 switch (yanked.entry.kind) { 241 .directory => { 242 var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch { 243 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir }); 244 app.notification.write(message.?, .err) catch {}; 245 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 246 return; 247 }; 248 defer source_dir.close(); 249 250 var selected_dir = source_dir.openDir(yanked.entry.name, .{ .iterate = true }) catch { 251 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.entry.name }); 252 app.notification.write(message.?, .err) catch {}; 253 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 254 return; 255 }; 256 defer selected_dir.close(); 257 258 var walker = selected_dir.walk(app.alloc) catch |err| { 259 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to walk directory tree due to {}.", .{ yanked.entry.name, err }); 260 app.notification.write(message.?, .err) catch {}; 261 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 262 return; 263 }; 264 defer walker.deinit(); 265 266 // Make initial dir. 267 app.directories.dir.makeDir(new_path_res.path) catch |err| { 268 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to create new directory due to {}.", .{ yanked.entry.name, err }); 269 app.notification.write(message.?, .err) catch {}; 270 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 271 return; 272 }; 273 274 var errored = false; 275 var inner_path_buf: [std.fs.max_path_bytes]u8 = undefined; 276 while (walker.next() catch |err| { 277 message = try std.fmt.allocPrint(app.alloc, "Failed to copy one or more files - {}. A partial copy may have taken place.", .{err}); 278 app.notification.write(message.?, .err) catch {}; 279 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 280 return; 281 }) |entry| { 282 const path = try std.fmt.bufPrint(&inner_path_buf, "{s}{s}{s}", .{ new_path_res.path, std.fs.path.sep_str, entry.path }); 283 switch (entry.kind) { 284 .directory => { 285 app.directories.dir.makeDir(path) catch { 286 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to create containing directory '{s}'.", .{ entry.basename, path }); 287 app.notification.write(message.?, .err) catch {}; 288 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 289 errored = true; 290 }; 291 }, 292 .file, .sym_link => { 293 entry.dir.copyFile(entry.basename, app.directories.dir, path, .{}) catch |err| switch (err) { 294 error.FileNotFound => { 295 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{entry.path}); 296 app.notification.write(message.?, .err) catch {}; 297 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 298 errored = true; 299 }, 300 else => { 301 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ entry.path, err }); 302 app.notification.write(message.?, .err) catch {}; 303 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 304 errored = true; 305 }, 306 }; 307 }, 308 else => { 309 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unsupported file type '{s}'.", .{ entry.path, @tagName(entry.kind) }); 310 app.notification.write(message.?, .err) catch {}; 311 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 312 errored = true; 313 }, 314 } 315 } 316 317 if (errored) { 318 app.notification.write("Failed to copy some items, check the log file for more details.", .err) catch {}; 319 } else { 320 message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name}); 321 app.notification.write(message.?, .info) catch {}; 322 } 323 }, 324 .file, .sym_link => { 325 var source_dir = std.fs.openDirAbsolute(yanked.dir, .{ .iterate = true }) catch { 326 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - unable to open directory '{s}'.", .{ yanked.entry.name, yanked.dir }); 327 app.notification.write(message.?, .err) catch {}; 328 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 329 return; 330 }; 331 defer source_dir.close(); 332 333 std.fs.Dir.copyFile( 334 source_dir, 335 yanked.entry.name, 336 app.directories.dir, 337 new_path_res.path, 338 .{}, 339 ) catch |err| switch (err) { 340 error.FileNotFound => { 341 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - the original file was deleted or moved.", .{yanked.entry.name}); 342 app.notification.write(message.?, .err) catch {}; 343 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 344 return; 345 }, 346 else => { 347 message = try std.fmt.allocPrint(app.alloc, "Failed to copy '{s}' - {}.", .{ yanked.entry.name, err }); 348 app.notification.write(message.?, .err) catch {}; 349 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 350 return; 351 }, 352 }; 353 354 message = try std.fmt.allocPrint(app.alloc, "Copied '{s}'.", .{yanked.entry.name}); 355 app.notification.write(message.?, .info) catch {}; 356 }, 357 else => { 358 message = try std.fmt.allocPrint(app.alloc, "Can not copy '{s}' - unsupported file type '{s}'.", .{ yanked.entry.name, @tagName(yanked.entry.kind) }); 359 app.notification.write(message.?, .warn) catch {}; 360 return; 361 }, 362 } 363 364 // Append action to undo history. 365 var new_path_abs_buf: [std.fs.max_path_bytes]u8 = undefined; 366 const new_path_abs = app.directories.dir.realpath(new_path_res.path, &new_path_abs_buf) catch { 367 message = try std.fmt.allocPrint( 368 app.alloc, 369 "Failed to push copy action for '{s}' to undo history - unable to retrieve absolute directory path for '{s}'. This action will not be able to be undone via the `undo` keybind.", 370 .{ new_path_res.path, yanked.entry.name }, 371 ); 372 app.notification.write(message.?, .err) catch {}; 373 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 374 return; 375 }; 376 377 if (app.actions.push(.{ 378 .paste = try app.alloc.dupe(u8, new_path_abs), 379 })) |prev_elem| { 380 app.alloc.free(prev_elem.delete.prev_path); 381 app.alloc.free(prev_elem.delete.new_path); 382 } 383 384 try app.repopulateDirectory(""); 385 app.text_input.clearAndFree(); 386} 387 388pub fn traverseLeft(app: *App) error{OutOfMemory}!void { 389 app.text_input.clearAndFree(); 390 391 const dir = app.directories.dir.openDir("../", .{ .iterate = true }) catch |err| { 392 const message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}); 393 defer app.alloc.free(message); 394 app.notification.write(message, .err) catch {}; 395 if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {}; 396 return; 397 }; 398 399 app.directories.dir.close(); 400 app.directories.dir = dir; 401 402 try app.repopulateDirectory(""); 403 app.text_input.clearAndFree(); 404 405 if (app.directories.history.pop()) |history| { 406 if (history < app.directories.entries.len()) { 407 app.directories.entries.selected = history; 408 } 409 } 410} 411 412pub fn traverseRight(app: *App) !void { 413 var message: ?[]const u8 = null; 414 defer if (message) |msg| app.alloc.free(msg); 415 416 const entry = (app.directories.getSelected() catch { 417 app.notification.write("Can not rename item - no item selected.", .warn) catch {}; 418 return; 419 }) orelse return; 420 421 switch (entry.kind) { 422 .directory => { 423 app.text_input.clearAndFree(); 424 425 const dir = app.directories.dir.openDir(entry.name, .{ .iterate = true }) catch |err| { 426 message = try std.fmt.allocPrint(app.alloc, "Failed to read directory entries - {}.", .{err}); 427 app.notification.write(message.?, .err) catch {}; 428 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 429 return; 430 }; 431 432 app.directories.dir.close(); 433 app.directories.dir = dir; 434 _ = app.directories.history.push(app.directories.entries.selected); 435 try app.repopulateDirectory(""); 436 app.text_input.clearAndFree(); 437 }, 438 .file => { 439 if (environment.getEditor()) |editor| { 440 try app.vx.exitAltScreen(app.tty.writer()); 441 try app.vx.resetState(app.tty.writer()); 442 app.loop.stop(); 443 444 environment.openFile(app.alloc, app.directories.dir, entry.name, editor) catch |err| { 445 message = try std.fmt.allocPrint(app.alloc, "Failed to open file '{s}' - {}.", .{ entry.name, err }); 446 app.notification.write(message.?, .err) catch {}; 447 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 448 }; 449 450 try app.loop.start(); 451 try app.vx.enterAltScreen(app.tty.writer()); 452 try app.vx.enableDetectedFeatures(app.tty.writer()); 453 app.vx.queueRefresh(); 454 } else { 455 app.notification.write("Can not open file - $EDITOR not set.", .warn) catch {}; 456 } 457 }, 458 else => {}, 459 } 460} 461 462pub fn createNewDir(app: *App) error{OutOfMemory}!void { 463 var message: ?[]const u8 = null; 464 defer if (message) |msg| app.alloc.free(msg); 465 466 const dir = try app.text_input.toOwnedSlice(); 467 defer app.alloc.free(dir); 468 469 app.directories.dir.makeDir(dir) catch |err| { 470 message = try std.fmt.allocPrint(app.alloc, "Failed to create directory '{s}' - {}", .{ dir, err }); 471 app.notification.write(message.?, .err) catch {}; 472 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 473 return; 474 }; 475 476 try app.repopulateDirectory(""); 477 478 message = try std.fmt.allocPrint(app.alloc, "Created new directory '{s}'.", .{dir}); 479 app.notification.write(message.?, .info) catch {}; 480} 481 482pub fn createNewFile(app: *App) error{OutOfMemory}!void { 483 var message: ?[]const u8 = null; 484 defer if (message) |msg| app.alloc.free(msg); 485 486 const file = try app.text_input.toOwnedSlice(); 487 defer app.alloc.free(file); 488 489 if (environment.fileExists(app.directories.dir, file)) { 490 message = try std.fmt.allocPrint(app.alloc, "Can not create file - '{s}' already exists.", .{file}); 491 app.notification.write(message.?, .warn) catch {}; 492 } else { 493 _ = app.directories.dir.createFile(file, .{}) catch |err| { 494 message = try std.fmt.allocPrint(app.alloc, "Failed to create file '{s}' - {}", .{ file, err }); 495 app.notification.write(message.?, .err) catch {}; 496 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 497 return; 498 }; 499 500 try app.repopulateDirectory(""); 501 502 message = try std.fmt.allocPrint(app.alloc, "Created new file '{s}'.", .{file}); 503 app.notification.write(message.?, .info) catch {}; 504 } 505} 506 507pub fn undo(app: *App) error{OutOfMemory}!void { 508 var message: ?[]const u8 = null; 509 defer if (message) |msg| app.alloc.free(msg); 510 511 const action = app.actions.pop() orelse { 512 app.notification.write("There is nothing to undo.", .info) catch {}; 513 return; 514 }; 515 516 switch (action) { 517 .delete => |a| { 518 defer app.alloc.free(a.new_path); 519 defer app.alloc.free(a.prev_path); 520 521 var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 522 const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path) catch { 523 message = try std.fmt.allocPrint(app.alloc, "Failed to undo delete '{s}' - path too long.", .{a.prev_path}); 524 app.notification.write(message.?, .err) catch {}; 525 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 526 return; 527 }; 528 529 app.directories.dir.rename(a.new_path, new_path_res.path) catch |err| { 530 message = try std.fmt.allocPrint(app.alloc, "Failed to undo delete for '{s}' - {}.", .{ a.prev_path, err }); 531 app.notification.write(message.?, .err) catch {}; 532 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 533 return; 534 }; 535 536 message = try std.fmt.allocPrint(app.alloc, "Restored '{s}' as '{s}'.", .{ a.prev_path, new_path_res.path }); 537 app.notification.write(message.?, .info) catch {}; 538 }, 539 .rename => |a| { 540 defer app.alloc.free(a.new_path); 541 defer app.alloc.free(a.prev_path); 542 543 var new_path_buf: [std.fs.max_path_bytes]u8 = undefined; 544 const new_path_res = environment.checkDuplicatePath(&new_path_buf, app.directories.dir, a.prev_path) catch { 545 message = try std.fmt.allocPrint(app.alloc, "Failed to undo rename '{s}' - path too long.", .{a.prev_path}); 546 app.notification.write(message.?, .err) catch {}; 547 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 548 return; 549 }; 550 551 app.directories.dir.rename(a.new_path, new_path_res.path) catch |err| { 552 message = try std.fmt.allocPrint(app.alloc, "Failed to undo rename for '{s}' - {}.", .{ a.new_path, err }); 553 app.notification.write(message.?, .err) catch {}; 554 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 555 return; 556 }; 557 558 message = try std.fmt.allocPrint(app.alloc, "Reverted renaming of '{s}', now '{s}'.", .{ a.new_path, new_path_res.path }); 559 app.notification.write(message.?, .info) catch {}; 560 }, 561 .paste => |path| { 562 defer app.alloc.free(path); 563 564 app.directories.dir.deleteTree(path) catch |err| { 565 message = try std.fmt.allocPrint(app.alloc, "Failed to delete '{s}' - {}.", .{ path, err }); 566 app.notification.write(message.?, .err) catch {}; 567 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 568 return; 569 }; 570 }, 571 } 572 573 try app.repopulateDirectory(""); 574 app.text_input.clearAndFree(); 575} 576 577pub fn extractArchive(app: *App) error{OutOfMemory}!void { 578 var message: ?[]const u8 = null; 579 defer if (message) |msg| app.alloc.free(msg); 580 581 const entry = (app.directories.getSelected() catch { 582 app.notification.write("Can not extract - no item selected.", .warn) catch {}; 583 return; 584 }) orelse return; 585 586 const archive_type = Archive.ArchiveType.fromPath(entry.name) orelse { 587 app.notification.write("Not an archive file.", .warn) catch {}; 588 return; 589 }; 590 591 const extract_dir_name = Archive.getExtractDirName(entry.name); 592 593 if (environment.fileExists(app.directories.dir, extract_dir_name)) { 594 message = try std.fmt.allocPrint(app.alloc, "Can not extract file(s) - '{s}' already exists.", .{extract_dir_name}); 595 app.notification.write(message.?, .warn) catch {}; 596 return; 597 } 598 599 var dest_dir = app.directories.dir.makeOpenPath(extract_dir_name, .{}) catch |err| { 600 message = try std.fmt.allocPrint(app.alloc, "Failed to extract archive '{s}' - {}.", .{ extract_dir_name, err }); 601 app.notification.write(message.?, .err) catch {}; 602 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 603 return; 604 }; 605 defer dest_dir.close(); 606 607 const archive_file = app.directories.dir.openFile(entry.name, .{}) catch |err| { 608 message = try std.fmt.allocPrint( 609 app.alloc, 610 "Failed to open archive '{s}' - {}.", 611 .{ entry.name, err }, 612 ); 613 app.notification.write(message.?, .err) catch {}; 614 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 615 616 if (!config.keep_partial_extraction) { 617 app.directories.dir.deleteTree(extract_dir_name) catch {}; 618 } 619 return; 620 }; 621 defer archive_file.close(); 622 623 const result = Archive.extractArchive( 624 app.alloc, 625 archive_file, 626 archive_type, 627 dest_dir, 628 app.file_logger, 629 ) catch |err| { 630 message = try std.fmt.allocPrint( 631 app.alloc, 632 "Failed to extract '{s}' - {s}.", 633 .{ entry.name, @errorName(err) }, 634 ); 635 app.notification.write(message.?, .err) catch {}; 636 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 637 638 if (!config.keep_partial_extraction) { 639 app.directories.dir.deleteTree(extract_dir_name) catch {}; 640 } 641 return; 642 }; 643 644 if (result.files_skipped > 0) { 645 message = try std.fmt.allocPrint( 646 app.alloc, 647 "Extracted {d} files, {d} directories to './{s}{s}'. Failed to extract {d} files, check the log file for more details.", 648 .{ result.files_extracted, result.dirs_created, std.fs.path.sep_str, extract_dir_name, result.files_skipped }, 649 ); 650 app.notification.write(message.?, .err) catch {}; 651 if (app.file_logger) |file_logger| file_logger.write(message.?, .err) catch {}; 652 } else { 653 message = try std.fmt.allocPrint( 654 app.alloc, 655 "Extracted {d} files, {d} directories to './{s}{s}'.", 656 .{ result.files_extracted, result.dirs_created, std.fs.path.sep_str, extract_dir_name }, 657 ); 658 app.notification.write(message.?, .info) catch {}; 659 if (app.file_logger) |file_logger| file_logger.write(message.?, .info) catch {}; 660 } 661 662 try app.repopulateDirectory(""); 663}