README.md | 9 +++++---- src/nnn.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++------- src/nnn.h | 3 +++ diff --git a/README.md b/README.md index cf762f096ad1c3909aa167138fed45f001e5f1f4..11f8e46bcde26093fcbc63b1022b664187ebde8b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ - Regex and substring match - 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 @@ | --- | --- | --- | | 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 @@ m ^K Sel range, clear M List selection 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 b2342d4437cba67b2d4bbf379416387512de1039..7f2cd5d452229a4bf715e71ebdff6962a7aa3b8c 100644 --- a/src/nnn.c +++ b/src/nnn.c @@ -383,6 +383,7 @@ #define STR_DATE_ID 2 #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 @@ "%F %T %z", "/.nnnXXXXXX", "0 selected", "missing dep", + "mount failed", }; /* Supported configuration environment variables */ @@ -2793,6 +2795,50 @@ 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 @@ env = cmd; /* 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 @@ cmd[10] = '\0'; 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 @@ "9m ^K Sel range, clear M List selection\n" "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 @@ copycurname(); /* 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 @@ 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 241e16e98a8907f421bef1823d26da05e2cafa25..2933d26437bf46eeb27a16a5dc7a6934e85de644 100644 --- a/src/nnn.h +++ b/src/nnn.h @@ -88,6 +88,7 @@ SEL_OPENWITH, SEL_NEW, SEL_RENAME, SEL_RENAMEMUL, + SEL_ARCHIVEMNT, SEL_SSHFS, SEL_UMOUNT, SEL_HELP, @@ -229,6 +230,8 @@ { CONTROL('R'), SEL_RENAME }, { 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 */