diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..36df9d2 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +install: + @echo 'export PATH=$$PATH:$(shell pwd)/bin' >> $(HOME)/.bashrc + @echo "let &runtimepath='$(shell pwd),'.&runtimepath" >> $(HOME)/.vimrc diff --git a/README b/README index d93eba3..4d55db3 100644 --- a/README +++ b/README @@ -17,6 +17,7 @@ Netrw supports reading and writing files across networks. One may use urls for :e rsync://[user@]machine[:port]/path uses rsync :e scp://[user@]machine[[:#]port]/path uses scp :e sftp://[user@]machine/path uses sftp + :e dbx:///path uses dbx.py, included and must be put into the path. REMOTE READING :Nread ? give help @@ -47,6 +48,7 @@ Netrw supports reading and writing files across networks. One may use urls for REMOTE DIRECTORY BROWSING :e [protocol]://[user]@hostname/path/ + :e dbx:///path :Nread [protocol]://[user]@hostname/path/ LOCAL DIRECTORY BROWSING diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c1e9e9 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +## Vim-Dropbox + +This is a fork of the [NetRw](https://github.com/vim-scripts/netrw.vim) project +which allows vim to support URL's of the type `dbx:///Path/Inside/Dropbox`. + +At the moment, it supports only reading files, creating files, and browsing +directories. It cannot perform file deletions or renames. + +Here is a [demo video](https://dl.dropboxusercontent.com/u/25959267/1313%20-%20Vim-Dropbox.mp4) showing the expected behavior. + +## Installation + +Installation is currently manual, but should not be too difficult for users +versed in the ways of the shell. + +1. Install the [Dropbox Python + SDK](https://github.com/dropbox/dropbox-sdk-python) if you do not already + have it installed. + +2. Go to https://www.dropbox.com/developers/apps and create a new app with any + name. + +3. Click into the App's settings page and click on the `Generate` button under + the label `Generated Access Token`. + +4. Copy this token and replace the string `INSERT_TOKEN_HERE` in the file `bin/dbx.py` with the token. + +5. Run the `make install` from this directory, which will simply append a line + to your `.vimrc` and `.bashrc` files to enable this plugin. + +If you run into any trouble with these steps, please open an issue on GitHub or contact the author out-of-band. + +## Usage + + vim dbx:/// + vim dbx:///TestFolder/Example.txt diff --git a/autoload/netrw.vim b/autoload/netrw.vim index ab6370a..ad326d0 100644 --- a/autoload/netrw.vim +++ b/autoload/netrw.vim @@ -125,6 +125,7 @@ call s:NetrwInit("g:netrw_rsync_cmd", "rsync") call s:NetrwInit("g:netrw_scp_cmd" , "scp -q") call s:NetrwInit("g:netrw_sftp_cmd" , "sftp") call s:NetrwInit("g:netrw_ssh_cmd" , "ssh") +call s:NetrwInit("g:netrw_dbx_cmd" , "dbx.py") if (has("win32") || has("win95") || has("win64") || has("win16")) \ && exists("g:netrw_use_nt_rcp") @@ -954,6 +955,11 @@ fun! netrw#NetRead(mode,...) let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) let b:netrw_lastfile = choice + " NetRead: (Dropbox) NetRead Method #10 {{{3 + elseif b:netrw_method == 10 + exe s:netrw_silentxfer."!".g:netrw_dbx_cmd." down ".shellescape(b:netrw_fname,1)." ".tmpfile + let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method) + let b:netrw_lastfile = choice "......................................... " NetRead: Complain {{{3 else @@ -1320,6 +1326,10 @@ fun! netrw#NetWrite(...) range exe filtbuf."bw!" let b:netrw_lastfile = choice + " NetWrite: (Dropbox) NetWrite Method #10 {{{3 + elseif b:netrw_method == 10 + exe s:netrw_silentxfer."!".g:netrw_dbx_cmd." up ".shellescape(b:netrw_fname,1)." ".tmpfile + let b:netrw_lastfile = choice "......................................... " NetWrite: Complain {{{3 else @@ -1572,6 +1582,7 @@ fun! s:NetrwMethod(choice) let rsyncurm = '^rsync://\([^/]\{-}\)/\(.*\)\=$' let fetchurm = '^fetch://\(\([^/@]\{-}\)@\)\=\([^/#:]\{-}\)\(:http\)\=/\(.*\)$' let sftpurm = '^sftp://\([^/]\{-}\)/\(.*\)\=$' + let dburm = '^dbx:///\(.*\)\=$' " call Decho("determine method:") " Determine Method @@ -1665,6 +1676,11 @@ fun! s:NetrwMethod(choice) endif endif + " Method#10: Get it from Dropbox + elseif match(a:choice, dburm) == 0 + let b:netrw_method = 10 + let b:netrw_fname = substitute(a:choice,dburm,'\1',"") + " Method#8: fetch {{{3 elseif match(a:choice,fetchurm) == 0 " call Decho("fetch://...") @@ -1718,7 +1734,6 @@ fun! s:NetrwMethod(choice) if userid != "" let g:netrw_uid= userid endif - " Cannot Determine Method {{{3 else if !exists("g:netrw_quiet") @@ -2510,7 +2525,7 @@ fun! s:NetrwBrowse(islocal,dirname) " set b:netrw_curdir to the new directory name {{{3 " call Decho("set b:netrw_curdir to the new directory name: (buf#".bufnr("%").")") let b:netrw_curdir= dirname - if b:netrw_curdir =~ '[/\\]$' + if b:netrw_curdir =~ '[/\\]$' && b:netrw_curdir !~ '^dbx://' let b:netrw_curdir= substitute(b:netrw_curdir,'[/\\]$','','e') endif if b:netrw_curdir == '' @@ -2583,6 +2598,14 @@ fun! s:NetrwBrowse(islocal,dirname) let dirname = substitute(dirname,'\\','/','g') " call Decho("(normal) dirname<".dirname.">") endif + " Check if the pattern matches a Dropbox URL. + let dbxpat = '^dbx:///\(.*\)\=' + if dirname =~ dbxpat + keepj call s:NetrwMaps(a:islocal) + keepj call s:PerformListing(a:islocal) + let s:locbrowseshellcmd= 1 + return + endif let dirpat = '^\(\w\{-}\)://\(\w\+@\)\=\([^/]\+\)/\(.*\)$' if dirname !~ dirpat @@ -3177,7 +3200,6 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...) else let dirpat= '[\/]$' endif -" call Decho("dirname<".dirname."> dirpat<".dirpat.">") if dirname !~ dirpat " apparently vim is "recognizing" that it is in a directory and @@ -6837,7 +6859,13 @@ fun! s:NetrwRemoteListing() keepj call histdel("/",-1) endif endif - + " Dropbox directory listing + elseif s:method == "dbx" + if s:path == '' + exe "sil! keepalt r! ".g:netrw_dbx_cmd." list /" + else + exe "sil! keepalt r! ".g:netrw_dbx_cmd." list ".shellescape(s:path) + endif else " use ssh to get remote file listing {{{3 " call Decho("use ssh to get remote file listing: s:path<".s:path.">") @@ -8364,6 +8392,17 @@ endfun fun! s:RemotePathAnalysis(dirname) " call Dfunc("s:RemotePathAnalysis(a:dirname<".a:dirname.">)") + let dbxpat = '^dbx:///\(.*\)\=' + if a:dirname =~ dbxpat + let s:method = 'dbx' + let s:user = '' + let s:machine = '' + let s:port = '' + let s:path = substitute(a:dirname,dbxpat,'\1','') + let s:fname = '' + return + endif + let dirpat = '^\(\w\{-}\)://\(\w\+@\)\=\([^/:#]\+\)\%([:#]\(\d\+\)\)\=/\(.*\)$' let s:method = substitute(a:dirname,dirpat,'\1','') let s:user = substitute(a:dirname,dirpat,'\2','') diff --git a/autoload/netrwSettings.vim b/autoload/netrwSettings.vim index 38f7299..2f413e8 100644 --- a/autoload/netrwSettings.vim +++ b/autoload/netrwSettings.vim @@ -79,6 +79,7 @@ fun! netrwSettings#NetrwSettings() put = 'let g:netrw_scp_cmd = '.g:netrw_scp_cmd put = 'let g:netrw_sftp_cmd = '.g:netrw_sftp_cmd put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd + put = 'let g:netrw_dbx_cmd = '.g:netrw_dbx_cmd let s:netrw_protocol_stop= line(".") put = '' diff --git a/bin/dbx.py b/bin/dbx.py new file mode 100755 index 0000000..c68ebdf --- /dev/null +++ b/bin/dbx.py @@ -0,0 +1,135 @@ +#!/usr/bin/python + +"""Upload or download individual files from your Dropbox. """ + +from __future__ import print_function + +import argparse +import contextlib +import datetime +import os +import six +import sys +import time +import unicodedata + +if sys.version.startswith('2'): + input = raw_input + +import dropbox +from dropbox.files import FileMetadata, FolderMetadata + +# OAuth2 access token. Obtain this from the +# https://www.dropbox.com/developers/apps by creating an App and the clicking on +# Generate Access Token. +TOKEN = 'INSERT_TOKEN_HERE' + +def main(): + """Main program. + + Update or download an individual file. + """ + if len(sys.argv) < 3: + usage() + command = sys.argv[1] + remote_path = sys.argv[2] + + if command == "up" or command == "down": + if len(sys.argv) < 4: + usage() + local_path = sys.argv[3] + + dbx = dropbox.Dropbox(TOKEN) + try: + if command == "up": + upload(dbx, remote_path, local_path) + elif command == "down": + download(dbx, remote_path, local_path) + elif command == "list": + list_folder(dbx, remote_path) + else: + usage() + except: + pass + +def list_folder(dbx, remote_path): + """List a folder. + + Return a dict mapping unicode filenames to + FileMetadata|FolderMetadata entries. + """ + if not remote_path.startswith("/"): + remote_path = '/' + remote_path + remote_path = remote_path.rstrip('/') + try: + with stopwatch('list_folder'): + res = dbx.files_list_folder(remote_path) + except dropbox.exceptions.ApiError as err: +# print('Folder listing failed for', remote_path, '-- assumped empty:', err) + return + else: + for entry in res.entries: + if isinstance(entry, FolderMetadata): + print(entry.name + "/") + else: + print(entry.name) + + + +def usage(): + print("Usage: python dbx.py [local_path]") + sys.exit(1) + + +def download(dbx, remote_path, local_path): + """Download a file. + + Return the bytes of the file, or None if it doesn't exist. + """ + if not remote_path.startswith("/"): + remote_path = '/' + remote_path + with stopwatch('download'): + try: + dbx.files_download_to_file(local_path, remote_path) + except dropbox.exceptions.HttpError as err: + print('*** HTTP error', err) + return None + +def upload(dbx, remote_path, local_path, overwrite=True): + """Upload a file. + + Return the request response, or None in case of error. + """ + if not remote_path.startswith("/"): + remote_path = '/' + remote_path + mode = (dropbox.files.WriteMode.overwrite + if overwrite + else dropbox.files.WriteMode.add) + mtime = os.path.getmtime(local_path) + with open(local_path, 'rb') as f: + data = f.read() + with stopwatch('upload %d bytes' % len(data)): + try: + res = dbx.files_upload( + data, remote_path, mode, + client_modified=datetime.datetime(*time.gmtime(mtime)[:6]), + mute=True) + except dropbox.exceptions.ApiError as err: + print('*** API error', err) + return None + print('uploaded as', res.name.encode('utf8')) + return res + +@contextlib.contextmanager +def stopwatch(message): + """Context manager to print how long a block of code took.""" + t0 = time.time() + try: + yield + finally: + t1 = time.time() +# print('Total elapsed time for %s: %.3f' % (message, t1 - t0), file=sys.stderr) + +if __name__ == '__main__': + main() + diff --git a/plugin/netrwPlugin.vim b/plugin/netrwPlugin.vim index e6a3c84..9cbfb10 100644 --- a/plugin/netrwPlugin.vim +++ b/plugin/netrwPlugin.vim @@ -53,14 +53,14 @@ augroup Network au BufReadCmd file://* call netrw#FileUrlRead(expand("")) au BufReadCmd file://localhost/* call netrw#FileUrlRead(substitute(expand("")),'file://localhost/','file:///','') endif - au BufReadCmd ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://* exe "silent doau BufReadPre ".fnameescape(expand(""))|call netrw#Nread(2,expand(""))|exe "silent doau BufReadPost ".fnameescape(expand("")) - au FileReadCmd ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://* exe "silent doau FileReadPre ".fnameescape(expand(""))|call netrw#Nread(1,expand(""))|exe "silent doau FileReadPost ".fnameescape(expand("")) - au BufWriteCmd ftp://*,rcp://*,scp://*,dav://*,davs://*,rsync://*,sftp://* exe "silent doau BufWritePre ".fnameescape(expand(""))|exe 'Nwrite '.fnameescape(expand(""))|exe "silent doau BufWritePost ".fnameescape(expand("")) - au FileWriteCmd ftp://*,rcp://*,scp://*,dav://*,davs://*,rsync://*,sftp://* exe "silent doau FileWritePre ".fnameescape(expand(""))|exe "'[,']".'Nwrite '.fnameescape(expand(""))|exe "silent doau FileWritePost ".fnameescape(expand("")) + au BufReadCmd ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://*,dbx://* exe "silent doau BufReadPre ".fnameescape(expand(""))|call netrw#Nread(2,expand(""))|exe "silent doau BufReadPost ".fnameescape(expand("")) + au FileReadCmd ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://*,dbx://* exe "silent doau FileReadPre ".fnameescape(expand(""))|call netrw#Nread(1,expand(""))|exe "silent doau FileReadPost ".fnameescape(expand("")) + au BufWriteCmd ftp://*,rcp://*,scp://*,dav://*,davs://*,rsync://*,sftp://*,dbx://* exe "silent doau BufWritePre ".fnameescape(expand(""))|exe 'Nwrite '.fnameescape(expand(""))|exe "silent doau BufWritePost ".fnameescape(expand("")) + au FileWriteCmd ftp://*,rcp://*,scp://*,dav://*,davs://*,rsync://*,sftp://*,dbx://* exe "silent doau FileWritePre ".fnameescape(expand(""))|exe "'[,']".'Nwrite '.fnameescape(expand(""))|exe "silent doau FileWritePost ".fnameescape(expand("")) try - au SourceCmd ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) + au SourceCmd ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://*,dbx://* exe 'Nsource '.fnameescape(expand("")) catch /^Vim\%((\a\+)\)\=:E216/ - au SourcePre ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("")) + au SourcePre ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://*,dbx://* exe 'Nsource '.fnameescape(expand("")) endtry augroup END