diff --git a/README.md b/README.md index e9d1e774..8fc27b12 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Add to that an awesome [Wiki](https://github.com/jarun/nnn/wiki)! - Convenience - Run plugins and commands with custom keybinds - FreeDesktop compliant trash (needs trash-cli) - - SSHFS mounts (needs sshfs) + - Remote mounts (needs sshfs, rclone) - Cross-dir file/all/range selection - Batch rename selection or dir entries - Copy (as), move (as), delete, archive, link selection @@ -92,7 +92,7 @@ A curses library with wide char support (e.g. ncursesw), libreadline (`make O_NO | file, coreutils (cp, mv, rm), 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, fusermount(3) | optional | mount, unmount archives | -| sshfs, fusermount(3) | optional | mount, unmount remotes | +| sshfs, [rclone](https://rclone.org/), fusermount(3) | optional | mount, unmount remotes | | trash-cli | optional | trash files (default action: rm) | | vlock (Linux), bashlock (macOS), lock(1) (BSD) | optional | terminal locker (fallback: [cmatrix](https://github.com/abishekvashok/cmatrix)) | | advcpmv (Linux) ([integration](https://github.com/jarun/nnn/wiki/Advanced-use-cases#show-cp-mv-progress)) | optional | copy, move progress | @@ -153,6 +153,7 @@ There is no config file. Associated files are stored under `${XDG_CONFIG_HOME:-$ | `NNN_USE_EDITOR=1` | open text files in `$VISUAL` (else `$EDITOR`, fallback vi) | | `NNN_CONTEXT_COLORS='1234'` | specify per context color [default: '4444' (all blue)] | | `NNN_SSHFS_OPTS='sshfs -o reconnect,idmap=user'` | specify SSHFS options | +| `NNN_RCLONE_OPTS='rclone mount --read-only'` | specify rclone options | | `NNN_IDLE_TIMEOUT=300` | idle seconds to lock terminal [default: disabled] | | `NNN_COPIER=copier` | clipboard copier script [default: none] | | `NNN_TRASH=1` | trash files to the desktop Trash [default: delete] | @@ -225,7 +226,7 @@ The list below is from the **dev branch**. Press ? in `nnn` to see th ! ^] Shell ;K :K xK Execute plugin K C Execute entry R ^V Pick plugin U Manage session = Launch - c SSHFS mount u Unmount + c Remote mount u Unmount ] ^P Prompt/run cmd L Lock ``` diff --git a/nnn.1 b/nnn.1 index 393e2396..870ae5bc 100644 --- a/nnn.1 +++ b/nnn.1 @@ -223,6 +223,13 @@ when dealing with the !, e and p commands respectively. A single combination to NOTE: The options must be preceded by `sshfs` and comma-separated without any space between them. .Ed .Pp +\fBNNN_RCLONE_OPTS:\fR pass additional options to rclone command: +.Bd -literal + export NNN_RCLONE_OPTS='rclone mount --read-only --no-checksum' + + NOTE: The options must be preceded by `rclone` and max 5 flags are supported. +.Ed +.Pp \fBNNN_OPENER:\fR specify a custom file opener. .Bd -literal export NNN_OPENER=mimeopen diff --git a/src/nnn.c b/src/nnn.c index 0b35ae76..72b15cc0 100644 --- a/src/nnn.c +++ b/src/nnn.c @@ -112,7 +112,8 @@ #endif #define _ABSSUB(N, M) (((N) <= (M)) ? ((M) - (N)) : ((N) - (M))) -#define DOUBLECLICK_INTERVAL_NS 400000000 +#define DOUBLECLICK_INTERVAL_NS (400000000) +#define XDELAY_INTERVAL_US (350000) /* 350 ms delay */ #define LEN(x) (sizeof(x) / sizeof(*(x))) #undef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -370,6 +371,7 @@ static bool g_plinit = FALSE; #define UTIL_SH_EXEC 8 #define UTIL_ARCHIVEMOUNT 9 #define UTIL_SSHFS 10 +#define UTIL_RCLONE 11 /* Utilities to open files, run actions */ static char * const utils[] = { @@ -396,6 +398,7 @@ static char * const utils[] = { "sh -c", "archivemount", "sshfs", + "rclone", }; /* Common strings */ @@ -438,6 +441,8 @@ static char * const utils[] = { #define MSG_0_FILES 36 #define MSG_EXISTS 37 #define MSG_FEW_COLOUMNS 38 +#define MSG_REMOTE_OPTS 39 +#define MSG_RCLONE_DELAY 40 static const char * const messages[] = { "no traversal", @@ -453,12 +458,12 @@ static const char * const messages[] = { "forcibly remove %s file%s (unrecoverable)?", "Create context %d?", "archive sel?", - "'f'(ile) / 'd'(ir) / 's'(ym) / 'h'(ard)?", + "'f'ile / 'd'ir / 's'ym / 'h'ard?", "cli mode?", "overwrite?", - "'s'(ave) / 'l'(oad) / 'r'(estore)?", + "'s'ave / 'l'oad / 'r'estore?", "Quit all contexts?", - "host: ", + "remote name: ", "archive name: ", "open with: ", "relative path: ", @@ -479,6 +484,8 @@ static const char * const messages[] = { "0 files", "entry exists", "too few columns!", + "'s'shfs / 'r'clone?", + "rclone mount may take a while" }; /* Supported configuration environment variables */ @@ -677,10 +684,10 @@ static int get_input(const char *prompt) return r; } -static void xdelay(void) +static void xdelay(useconds_t delay) { refresh(); - usleep(350000); /* 350 ms delay */ + usleep(delay); } static char confirm_force(bool selection) @@ -2865,7 +2872,7 @@ static bool load_session(const char *sname, char **path, char **lastdir, char ** fsession = fopen(spath, "rb"); if (!fsession) { printmsg(messages[MSG_SSN_MISSING]); - xdelay(); + xdelay(XDELAY_INTERVAL_US); return FALSE; } @@ -2896,7 +2903,7 @@ END: if (!status) { printmsg(messages[MSG_FAILED]); - xdelay(); + xdelay(XDELAY_INTERVAL_US); } return status; @@ -3139,7 +3146,7 @@ static void find_accessible_parent(char *path, char *newpath, char *lastname, in xstrlcpy(path, dir, PATH_MAX); printmsg(messages[MSG_DIR_ACCESS]); - xdelay(); + xdelay(XDELAY_INTERVAL_US); } static bool execute_file(int cur, char *path, char *newpath, int *presel) @@ -3263,11 +3270,23 @@ static bool archive_mount(char *name, char *path, char *newpath, int *presel) return TRUE; } -static bool sshfs_mount(char *newpath, int *presel) +static bool remote_mount(char *newpath, int *presel) { - uchar flag = F_NORMAL; - int r; - char *tmp, *env, *cmd = utils[UTIL_SSHFS]; + uchar flag = F_CLI; + int r, opt = get_input(messages[MSG_REMOTE_OPTS]); + char *tmp, *env, *cmd; + + if (opt == 's') { + cmd = utils[UTIL_SSHFS]; + env = xgetenv("NNN_SSHFS_OPTS", cmd); + } else if (opt == 'r') { + flag |= F_NOWAIT; + cmd = utils[UTIL_RCLONE]; + env = xgetenv("NNN_RCLONE_OPTS", "rclone mount"); + } else { + printwait(messages[MSG_FAILED], presel); + return FALSE; + } if (!getutil(cmd)) { printwait(messages[MSG_UTIL_MISSING], presel); @@ -3287,19 +3306,21 @@ static bool sshfs_mount(char *newpath, int *presel) /* Convert "Host" to "Host:" */ r = strlen(tmp); - tmp[r] = ':'; - tmp[r + 1] = '\0'; - - env = getenv("NNN_SSHFS_OPTS"); - if (env) - flag |= F_MULTI; - else - env = cmd; + if (tmp[r - 1] != ':') { /* Append ':' if missing */ + tmp[r] = ':'; + tmp[r + 1] = '\0'; + } /* Connect to remote */ - if (spawn(env, tmp, newpath, NULL, flag)) { - printwait(messages[MSG_FAILED], presel); - return FALSE; + if (opt == 's') { + if (spawn(env, tmp, newpath, NULL, flag)) { + printwait(messages[MSG_FAILED], presel); + return FALSE; + } + } else { + spawn(env, tmp, newpath, NULL, flag); + printmsg(messages[MSG_RCLONE_DELAY]); + xdelay(XDELAY_INTERVAL_US * 10); } return TRUE; @@ -3311,12 +3332,12 @@ static bool sshfs_mount(char *newpath, int *presel) */ static bool unmount(char *name, char *newpath, int *presel, char *currentpath) { - char cmd[] = "fusermount3"; /* Arch Linux utility */ + static char cmd[] = "fusermount3"; /* Arch Linux utility */ static bool found = FALSE; char *tmp = name; struct stat sb, psb; - bool child = false; - bool parent = false; + bool child = FALSE; + bool parent = FALSE; /* On Ubuntu it's fusermount */ if (!found && !getutil(cmd)) { @@ -4770,7 +4791,7 @@ nochange: inode = sb.st_ino; selstartid = cur; printmsg(messages[MSG_RANGE_SEL_ON]); - xdelay(); + xdelay(XDELAY_INTERVAL_US); continue; } @@ -4815,7 +4836,7 @@ nochange: /* Show the range count */ //r = selendid - selstartid + 1; //mvprintw(xlines - 1, 0, "+%d\n", r); - //xdelay(); + //xdelay(XDELAY_INTERVAL_US); //writesel(pselbuf, selbufpos - 1); /* Truncate NULL from end */ //spawn(copier, NULL, NULL, NULL, F_NOTRACE); @@ -5167,8 +5188,8 @@ nochange: case SEL_ARCHIVEMNT: if (!ndents || !archive_mount(dents[cur].name, path, newpath, &presel)) goto nochange; // fallthrough - case SEL_SSHFS: - if (sel == SEL_SSHFS && !sshfs_mount(newpath, &presel)) + case SEL_REMOTE: + if (sel == SEL_REMOTE && !remote_mount(newpath, &presel)) goto nochange; lastname[0] = '\0'; diff --git a/src/nnn.h b/src/nnn.h index ff6f8d97..40798a0a 100644 --- a/src/nnn.h +++ b/src/nnn.h @@ -90,7 +90,7 @@ enum action { SEL_RENAME, SEL_RENAMEMUL, SEL_ARCHIVEMNT, - SEL_SSHFS, + SEL_REMOTE, SEL_UMOUNT, SEL_HELP, SEL_EXEC, @@ -237,7 +237,7 @@ static struct key bindings[] = { /* Mount an archive */ { 'T', SEL_ARCHIVEMNT }, /* Connect to server over SSHFS */ - { 'c', SEL_SSHFS }, + { 'c', SEL_REMOTE }, /* Disconnect a SSHFS mount point */ { 'u', SEL_UMOUNT }, /* Show help */