diff --git a/README.md b/README.md index cf762f09..11f8e46b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Add to that an awesome [Wiki](https://github.com/jarun/nnn/wiki)! - Subtree search to open or edit files (using plugin) - Mimes - Open with desktop opener or specify a custom app - - Create, list, extract archives + - Create, list, extract, mount (FUSE based) archives - Option to open all text files in EDITOR - Information - Detailed file information @@ -90,6 +90,7 @@ A curses library with wide char support (e.g. ncursesw), libreadline (`make O_NO | xdg-open (Linux), open(1) (macOS), cygstart (Cygwin) | base | desktop opener | | file, coreutils (cp, mv, rm), findutils (xargs) | base | file type, copy, move and remove | | tar, (un)zip [atool/bsdtar for more formats] | base | create, list, extract tar, gzip, bzip2, zip | +| archivemount | optional | mount archives over FUSE | | sshfs, fusermount(3) | optional | mount, unmount over SSHFS | | trash-cli | optional | trash files (default action: delete) | | vlock (Linux), bashlock (macOS), lock(1) (BSD) | optional | terminal locker (fallback: [cmatrix](https://github.com/abishekvashok/cmatrix)) | @@ -211,14 +212,14 @@ The list below is from the **dev branch**. Press ? in `nnn` to see th a Select all K Edit selection P Copy selection X Delete selection V Move selection ^X Delete entry - f Create archive C Execute entry + f Create archive T Mount archive ^F Extract archive F List archive e Edit in EDITOR p Open in PAGER ORDER TOGGLES A Apparent du S du - s Size E Extn t Time + s Size E Extn t Time MISC - ! ^] Shell = Launcher + ! ^] Shell = Launch C Execute entry R ^V Pick plugin :K xK Execute plugin K c SSHFS mount u Unmount ^P Prompt/run cmd L Lock diff --git a/src/nnn.c b/src/nnn.c index b2342d44..7f2cd5d4 100644 --- a/src/nnn.c +++ b/src/nnn.c @@ -383,6 +383,7 @@ static char mv[] = "mvg -gi"; #define STR_TMPFILE 3 #define NONE_SELECTED 4 #define UTIL_MISSING 5 +#define MOUNT_FAILED 6 static const char * const messages[] = { "no traversal", @@ -391,6 +392,7 @@ static const char * const messages[] = { "/.nnnXXXXXX", "0 selected", "missing dep", + "mount failed", }; /* Supported configuration environment variables */ @@ -2793,6 +2795,50 @@ static bool create_dir(const char *path) return TRUE; } +static bool archive_mount(char *name, char *path, char *newpath, int *presel) +{ + char *dir, *cmd = "archivemount"; + size_t len; + + if (!getutil(cmd)) { + printwait(messages[UTIL_MISSING], presel); + return FALSE; + } + + dir = strdup(name); + if (!dir) + return FALSE; + + len = strlen(dir); + + while (len > 1) + if (dir[--len] == '.') { + dir[len] = '\0'; + break; + } + + DPRINTF_S(dir); + + /* Create the mount point */ + mkpath(cfgdir, dir, newpath); + free(dir); + + if (!create_dir(newpath)) { + printwait(strerror(errno), presel); + return FALSE; + } + + /* Mount archive */ + DPRINTF_S(name); + DPRINTF_S(newpath); + if (spawn(cmd, name, newpath, path, F_NORMAL)) { + printwait(messages[MOUNT_FAILED], presel); + return FALSE; + } + + return TRUE; +} + static bool sshfs_mount(char *newpath, int *presel) { uchar flag = F_NORMAL; @@ -2828,18 +2874,23 @@ static bool sshfs_mount(char *newpath, int *presel) /* Connect to remote */ if (spawn(env, tmp, newpath, NULL, flag)) { - printwait("mount failed", presel); + printwait(messages[MOUNT_FAILED], presel); return FALSE; } return TRUE; } -static bool sshfs_unmount(char *newpath, int *presel) +/* + * Unmounts if the directory represented by name is a mount point. + * Otherwise, asks for hostname + */ +static bool unmount(char *name, char *newpath, int *presel) { static char cmd[] = "fusermount3"; /* Arch Linux utility */ static bool found = FALSE; - char *tmp; + char *tmp = name; + struct stat sb, psb; /* On Ubuntu it's fusermount */ if (!found && !getutil(cmd)) { @@ -2847,9 +2898,19 @@ static bool sshfs_unmount(char *newpath, int *presel) found = TRUE; } - tmp = xreadline(NULL, "host: "); - if (!tmp[0]) - return FALSE; + if (tmp) { + mkpath(cfgdir, tmp, newpath); + if ((lstat(newpath, &sb) == -1) || (lstat(dirname(newpath), &psb) == -1)) { + *presel = MSGWAIT; + return FALSE; + } + } + + if (!tmp || (sb.st_dev == psb.st_dev)) { + tmp = xreadline(NULL, "host: "); + if (!tmp[0]) + return FALSE; + } /* Create the mount point */ mkpath(cfgdir, tmp, newpath); @@ -2919,14 +2980,14 @@ static void show_help(const char *path) "ca Select all K Edit selection\n" "cP Copy selection X Delete selection\n" "cV Move selection ^X Delete entry\n" - "cf Create archive C Execute entry\n" + "cf Create archive T Mount archive\n" "b^F Extract archive F List archive\n" "ce Edit in EDITOR p Open in PAGER\n" "1ORDER TOGGLES\n" "cA Apparent du S du\n" - "cs Size E Extn t Time\n" + "cs Size E Extn t Time\n" "1MISC\n" - "9! ^] Shell = Launcher\n" + "9! ^] Shell = Launch C Execute entry\n" "9R ^V Pick plugin :K xK Execute plugin K\n" "cc SSHFS mount u Unmount\n" "b^P Prompt/run cmd L Lock\n"}; @@ -4577,8 +4638,11 @@ nochange: /* Repopulate as directory content may have changed */ goto begin; + case SEL_ARCHIVEMNT: + if (!ndents || !archive_mount(dents[cur].name, path, newpath, &presel)) + goto nochange; // fallthrough case SEL_SSHFS: - if (!sshfs_mount(newpath, &presel)) + if (sel == SEL_SSHFS && !sshfs_mount(newpath, &presel)) goto nochange; lastname[0] = '\0'; @@ -4592,7 +4656,8 @@ nochange: setdirwatch(); goto begin; case SEL_UMOUNT: - sshfs_unmount(newpath, &presel); + tmp = ndents ? dents[cur].name : NULL; + unmount(tmp, newpath, &presel); goto nochange; case SEL_QUITCD: // fallthrough case SEL_QUIT: diff --git a/src/nnn.h b/src/nnn.h index 241e16e9..2933d264 100644 --- a/src/nnn.h +++ b/src/nnn.h @@ -88,6 +88,7 @@ enum action { SEL_NEW, SEL_RENAME, SEL_RENAMEMUL, + SEL_ARCHIVEMNT, SEL_SSHFS, SEL_UMOUNT, SEL_HELP, @@ -229,6 +230,8 @@ static struct key bindings[] = { { KEY_F(2), SEL_RENAME }, /* Rename contents of current dir */ { 'r', SEL_RENAMEMUL }, + /* Mount an archive */ + { 'T', SEL_ARCHIVEMNT }, /* Connect to server over SSHFS */ { 'c', SEL_SSHFS }, /* Disconnect a SSHFS mount point */