droxi 0.2.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 41024246faf2e570c6f96a0c2cfa77af7a871cbb
4
- data.tar.gz: 9a516974414a11c3427e81590aa77d92a43e68d5
3
+ metadata.gz: 1c51dbec9e627b8b8b2e07e4ac8bf4464bee33dd
4
+ data.tar.gz: 480c18b2b62a8ec434cd6c8a8a481a40976f884b
5
5
  SHA512:
6
- metadata.gz: 13d4714c04d56e274c6dc50a7612bb51714c956503faf91a2b83b68d054221ffbb576455d44ad7568ee2a44998bb4f724cbed135108740e303d113ae2490a334
7
- data.tar.gz: a91221075eb67995cb8a38578a4a030531e7028e86ed1346fae2570d28c56cb0b4e153e9cf51c8592c66408dd038bd2963213445bddfd4089d1fd824972a52de
6
+ metadata.gz: 5af1c7d1ab268567fa44508dad484090c6da945f1861e022c025e34dff7527525d81af89d03812b613c1d8aa008e9e33e806656d47832f95fa9ae37654b16655
7
+ data.tar.gz: bd890a7e5aa0c14d912181f7d80643a46c1d41dbf914b4556919f660fa1e67edec98d8e18f48c25d01b9b6d71c4555e3c4e34a18553a6a613838410fba431906
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Brandon Mulcahy
1
+ Copyright (c) 2014-2015 Brandon Mulcahy
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,41 +1,43 @@
1
- droxi
1
+ Droxi
2
2
  =====
3
+ An `ftp`-like command-line [Dropbox](https://www.dropbox.com/) interface in
4
+ [Ruby](https://www.ruby-lang.org/en/).
3
5
 
4
- ftp-like command-line [Dropbox](https://www.dropbox.com/) interface in
5
- [Ruby](https://www.ruby-lang.org/en/)
6
-
7
- installation
6
+ Installation
8
7
  ------------
8
+ Installation as Ruby gem:
9
9
 
10
10
  gem install droxi
11
11
 
12
- or
12
+ Manual installation:
13
13
 
14
14
  git clone https://github.com/jangler/droxi.git
15
15
  cd droxi && rake && sudo rake install
16
16
 
17
- or
18
-
19
- wget https://aur.archlinux.org/packages/dr/droxi/droxi.tar.gz
20
- tar -xzf droxi.tar.gz
21
- cd droxi && makepkg -s && sudo pacman -U droxi-*.pkg.tar.xz
17
+ If you use Arch Linux or a derivative, you may also install via the
18
+ [AUR package](https://aur.archlinux.org/packages/droxi/).
22
19
 
23
- features
20
+ Features
24
21
  --------
25
-
26
- - interface inspired by
22
+ - Interface based on
27
23
  [GNU coreutils](http://www.gnu.org/software/coreutils/),
28
24
  [GNU ftp](http://www.gnu.org/software/inetutils/), and
29
25
  [lftp](http://lftp.yar.ru/)
30
- - context-sensitive tab completion and path globbing
31
- - upload, download, organize, search, and share files
32
- - file revision control
33
- - man page and interactive help
34
-
35
- developer features
36
- ------------------
37
-
38
- - extensive spec-style unit tests using
39
- [MiniTest](https://github.com/seattlerb/minitest)
40
- - [RuboCop](https://github.com/bbatsov/rubocop)-approved
41
- - fully [RDoc](http://rdoc.sourceforge.net/) documented
26
+ - Context-sensitive tab completion and path globbing
27
+ - Upload, download, organize, search, and share files
28
+ - File revision control
29
+ - Man page and interactive help
30
+
31
+ Examples
32
+ --------
33
+ Start interactive session:
34
+
35
+ droxi
36
+
37
+ Invoke single command and exit:
38
+
39
+ droxi share Photos/pic.jpg
40
+
41
+ Scripting:
42
+
43
+ echo -e "cd Photos \n put -f *jpg" | droxi
data/droxi.1.template CHANGED
@@ -18,7 +18,7 @@ Pass a string to be executed by the local shell.
18
18
  --debug
19
19
  Enable the 'debug' command for the session.
20
20
  .TP
21
- --help
21
+ -h, --help
22
22
  Print help information and exit.
23
23
  .TP
24
24
  --version
data/droxi.gemspec CHANGED
@@ -1,11 +1,11 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'droxi'
3
3
  s.version = IO.read('lib/droxi.rb')[/VERSION = '(.+)'/, 1]
4
- s.date = '2014-12-17'
5
- s.summary = 'ftp-like command-line interface to Dropbox'
6
- s.description = "A command-line Dropbox interface inspired by GNU \
7
- coreutils, GNU ftp, and lftp. Features include smart tab \
8
- completion, globbing, and interactive help.".squeeze(' ')
4
+ s.date = '2015-05-09'
5
+ s.summary = 'An ftp-like command-line interface to Dropbox'
6
+ s.description = "A command-line Dropbox interface based on GNU coreutils, \
7
+ GNU ftp, and lftp. Features include smart tab completion, \
8
+ globbing, and interactive help.".squeeze(' ')
9
9
  s.authors = ['Brandon Mulcahy']
10
10
  s.email = 'brandon@jangler.info'
11
11
  s.files = `git ls-files`.split
data/lib/droxi/cache.rb CHANGED
@@ -3,7 +3,7 @@ class Cache < Hash
3
3
  # Add a metadata +Hash+ and its contents to the +Cache+ and return the
4
4
  # +Cache+.
5
5
  def add(metadata)
6
- path = metadata['path']
6
+ path = metadata['path'].downcase
7
7
  store(path, metadata)
8
8
  dirname = File.dirname(path)
9
9
  if dirname != path
@@ -17,14 +17,16 @@ class Cache < Hash
17
17
 
18
18
  # Remove a path's metadata from the +Cache+ and return the +Cache+.
19
19
  def remove(path)
20
+ path = path.downcase
20
21
  recursive_remove(path)
21
22
  contents = fetch(File.dirname(path), {}).fetch('contents', nil)
22
- contents.delete_if { |item| item['path'] == path } if contents
23
+ contents.delete_if { |item| item['path'].downcase == path } if contents
23
24
  self
24
25
  end
25
26
 
26
27
  # Return +true+ if the path's information is cached, +false+ otherwise.
27
28
  def full_info?(path, require_contents = true)
29
+ path = path.downcase
28
30
  info = fetch(path, nil)
29
31
  info && (!require_contents || !info['is_dir'] || info.include?('contents'))
30
32
  end
@@ -33,6 +35,7 @@ class Cache < Hash
33
35
 
34
36
  # Recursively remove a path and its sub-files and directories.
35
37
  def recursive_remove(path)
38
+ path = path.downcase
36
39
  contents = fetch(path, {}).fetch('contents', nil)
37
40
  contents.each { |item| recursive_remove(item['path']) } if contents
38
41
  delete(path)
@@ -165,12 +165,13 @@ module Commands
165
165
 
166
166
  # Download remote files.
167
167
  GET = Command.new(
168
- 'get [-f] REMOTE_FILE...',
168
+ 'get [-E] [-f] REMOTE_FILE...',
169
169
  "Download each specified remote file to a file of the same name in the \
170
170
  local working directory. Will refuse to overwrite existing files unless \
171
- invoked with the -f option.",
171
+ invoked with the -f option. If the -E option is given, remove each \
172
+ remote file after successful download.",
172
173
  lambda do |client, state, args|
173
- flags = extract_flags(GET.usage, args, '-f' => 0)
174
+ flags = extract_flags(GET.usage, args, '-E' => 0, '-f' => 0)
174
175
 
175
176
  state.expand_patterns(args).each do |path|
176
177
  if path.is_a?(GlobError)
@@ -181,6 +182,10 @@ module Commands
181
182
  if flags.include?('-f') || !File.exist?(basename)
182
183
  contents = client.get_file(path)
183
184
  IO.write(basename, contents, mode: 'wb')
185
+ if flags.include?('-E')
186
+ client.file_delete(path)
187
+ state.cache.remove(path)
188
+ end
184
189
  puts "#{basename} <- #{path}"
185
190
  else
186
191
  warn "get: #{basename}: local file already exists"
@@ -199,6 +204,7 @@ module Commands
199
204
  lambda do |_client, _state, args|
200
205
  extract_flags(HELP.usage, args, {})
201
206
  if args.empty?
207
+ puts 'Type "help <command>" for more info about a command:'
202
208
  Text.table(NAMES).each { |line| puts line }
203
209
  else
204
210
  cmd_name = args.first
@@ -263,7 +269,7 @@ module Commands
263
269
  state.local_oldpwd = Dir.pwd
264
270
  Dir.chdir(path)
265
271
  else
266
- warn "lcd: #{args.first}: no such file or directory"
272
+ warn "lcd: #{args.first}: no such directory"
267
273
  end
268
274
  end
269
275
  )
@@ -357,17 +363,18 @@ module Commands
357
363
 
358
364
  # Upload a local file.
359
365
  PUT = Command.new(
360
- 'put [-f] [-q] [-O REMOTE_DIR] [-t COUNT] LOCAL_FILE...',
366
+ 'put [-E] [-f] [-q] [-O REMOTE_DIR] [-t COUNT] LOCAL_FILE...',
361
367
  "Upload local files to the remote working directory. If a remote file of \
362
368
  the same name already exists, Dropbox will rename the upload unless the \
363
369
  the -f option is given, in which case the remote file will be \
364
- overwritten. If the -O option is given, the files will be uploaded to \
365
- the given directory instead of the current directory. The -q option \
366
- prevents progress from being printed. The -t option specifies the \
367
- number of tries in case of error. The default is 5; -t 0 will retry \
368
- infinitely.",
370
+ overwritten. If the -E option is given, delete each local file after \
371
+ successful upload. If the -O option is given, the files will be uploaded \
372
+ to the given directory instead of the current directory. The -q option \
373
+ prevents progress from being printed. The -t option specifies the number \
374
+ of tries in case of error. The default is 5; -t 0 will retry infinitely.",
369
375
  lambda do |client, state, args|
370
376
  flags = extract_flags(PUT.usage, args,
377
+ '-E' => 0,
371
378
  '-f' => 0,
372
379
  '-q' => 0,
373
380
  '-O' => 1,
@@ -388,11 +395,27 @@ module Commands
388
395
  tries_index = flags.find_index('-t')
389
396
  tries = tries_index ? flags[tries_index + 1].to_i : 5
390
397
 
398
+ # Glob arguments.
399
+ args.map! do |arg|
400
+ array = Dir.glob(File.expand_path(arg))
401
+ warn "put: #{arg}: no such file or directory" if array.empty?
402
+ array.map { |path| path.sub(File.dirname(path), File.dirname(arg)) }
403
+ end
404
+ args = args.reduce(:+)
405
+
391
406
  args.each do |arg|
392
407
  to_path = state.resolve_path(File.basename(arg))
393
408
 
394
409
  try_and_handle(StandardError) do
395
- File.open(File.expand_path(arg), 'rb') do |file|
410
+ path = File.expand_path(arg)
411
+ if File.directory?(path)
412
+ warn "put: #{arg}: cannot put directory"
413
+ next
414
+ end
415
+
416
+ success = false
417
+
418
+ File.open(path, 'rb') do |file|
396
419
  if flags.include?('-f') && state.metadata(to_path)
397
420
  client.file_delete(to_path)
398
421
  state.cache.remove(to_path)
@@ -400,15 +423,18 @@ module Commands
400
423
 
401
424
  # Chunked upload if file is more than 1M.
402
425
  if file.size > 1024 * 1024
403
- data = chunked_upload(client, to_path, file,
404
- flags.include?('-q'), tries)
426
+ data, success = chunked_upload(client, to_path, file,
427
+ flags.include?('-q'), tries)
405
428
  else
406
429
  data = client.put_file(to_path, file)
430
+ success = true
407
431
  end
408
432
 
409
433
  state.cache.add(data)
410
434
  puts "#{arg} -> #{data['path']}"
411
435
  end
436
+
437
+ File.delete(path) if flags.include?('-E') && success
412
438
  end
413
439
  end
414
440
 
@@ -706,12 +732,13 @@ module Commands
706
732
  thread = quiet ? nil : Thread.new { monitor_upload(uploader, to_path) }
707
733
  tries = -1 if tries == 0
708
734
  loop_upload(uploader, thread, tries)
735
+ success = (uploader.offset == uploader.total_size)
709
736
  data = uploader.finish(to_path)
710
737
  if thread
711
738
  thread.join
712
739
  print "\r" + (' ' * (18 + to_path.rpartition('/')[2].size)) + "\r"
713
740
  end
714
- data
741
+ [data, success]
715
742
  end
716
743
 
717
744
  # Continuously try to upload until successful or interrupted.
data/lib/droxi/state.rb CHANGED
@@ -35,6 +35,7 @@ class State
35
35
  # Return a +Hash+ of the Dropbox metadata for a file, or +nil+ if the file
36
36
  # does not exist.
37
37
  def metadata(path, require_contents = true)
38
+ path = path.downcase
38
39
  tokens = path.split('/').drop(1)
39
40
 
40
41
  (0..tokens.size).each do |i|
@@ -48,16 +49,18 @@ class State
48
49
 
49
50
  # Return an +Array+ of paths of files in a Dropbox directory.
50
51
  def contents(path)
52
+ path = path.downcase
51
53
  path = resolve_path(path)
52
54
  metadata(path)
53
55
  path = "#{path}/".sub('//', '/')
54
56
  @cache.keys.select do |key|
55
57
  key.start_with?(path) && key != path && !key.sub(path, '').include?('/')
56
- end
58
+ end.map { |key| @cache[key]['path'] }
57
59
  end
58
60
 
59
61
  # Return +true+ if the Dropbox path is a directory, +false+ otherwise.
60
62
  def directory?(path)
63
+ path = path.downcase
61
64
  path = resolve_path(path)
62
65
  metadata(File.dirname(path))
63
66
  @cache.include?(path) && @cache[path]['is_dir']
@@ -99,7 +102,7 @@ class State
99
102
  # Recursively remove directory contents from metadata cache. Yield lines of
100
103
  # (error) output if a block is given.
101
104
  def forget_contents(partial_path)
102
- path = resolve_path(partial_path)
105
+ path = resolve_path(partial_path).downcase
103
106
  if @cache.fetch(path, {}).include?('contents')
104
107
  @cache[path]['contents'].dup.each { |m| @cache.remove(m['path']) }
105
108
  @cache[path].delete('contents')
@@ -113,7 +116,7 @@ class State
113
116
  # Cache metadata for the remote file for a given path. Return +true+ if
114
117
  # successful, +false+ otherwise.
115
118
  def fetch_metadata(path)
116
- data = @client.metadata(path)
119
+ data = @client.metadata(path.downcase)
117
120
  return true if data['is_deleted']
118
121
  @cache.add(data)
119
122
  true
@@ -124,8 +127,11 @@ class State
124
127
  # Return an +Array+ of file paths matching a glob pattern, or a GlobError if
125
128
  # no files were matched.
126
129
  def get_matches(pattern, path, preserve_root)
130
+ path = path.downcase
127
131
  dir = File.dirname(path)
128
- matches = contents(dir).select { |entry| File.fnmatch(path, entry) }
132
+ matches = contents(dir).select do |entry|
133
+ File.fnmatch(path, entry.downcase)
134
+ end
129
135
  return GlobError.new(pattern) if matches.empty?
130
136
  return matches unless preserve_root
131
137
  prefix = pattern.rpartition('/')[0, 2].join
data/lib/droxi.rb CHANGED
@@ -1,4 +1,10 @@
1
- require 'dropbox_sdk'
1
+ begin
2
+ require 'dropbox_sdk'
3
+ rescue LoadError
4
+ puts "droxi requires the dropbox-sdk gem."
5
+ puts "Run `gem install dropbox-sdk` to install it."
6
+ exit
7
+ end
2
8
  require 'readline'
3
9
 
4
10
  require_relative 'droxi/commands'
@@ -10,15 +16,14 @@ require_relative 'droxi/text'
10
16
  # Command-line Dropbox client module.
11
17
  module Droxi
12
18
  # Version number of the program.
13
- VERSION = '0.2.3'
19
+ VERSION = '0.3.1'
14
20
 
15
21
  # Message to display when invoked with the --help option.
16
22
  HELP_TEXT =
17
23
  "If you've installed this program using Rake or the AUR package, you " \
18
- 'should also have the man page installed on your system. `man droxi` ' \
19
- "should do the trick. Otherwise--meaning you've probably installed it " \
20
- "as a Ruby gem--you don't, which is a shame. In that case, you can " \
21
- 'access the man page at http://jangler.info/man/droxi in HTML form.'
24
+ 'should also have the man page installed on your system. If you do not ' \
25
+ 'have the man page, you can access it at http://jangler.info/man/droxi ' \
26
+ 'in HTML form.'
22
27
 
23
28
  # Run the client.
24
29
  def self.run(args)
@@ -41,10 +46,12 @@ module Droxi
41
46
  # Handles command-line options extracted from an +Array+ and returns an
42
47
  # +Array+ of the extracted options.
43
48
  def self.handle_options(args)
44
- options = args.take_while { |s| s.start_with?('--') }
49
+ options = args.take_while { |s| s.start_with?('-') }
45
50
  puts "droxi v#{VERSION}" if options.include?('--version')
46
- Text.wrap(HELP_TEXT).each { |s| puts s } if options.include?('--help')
47
- exit if %w(--help --version).any? { |s| options.include?(s) }
51
+ if options.include?('-h') || options.include?('--help')
52
+ Text.wrap(HELP_TEXT).each { |s| puts s }
53
+ end
54
+ exit if %w(-h --help --version).any? { |s| options.include?(s) }
48
55
  options
49
56
  end
50
57
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: droxi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Mulcahy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-17 00:00:00.000000000 Z
11
+ date: 2015-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dropbox-sdk
@@ -30,8 +30,8 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 1.6.4
33
- description: A command-line Dropbox interface inspired by GNU coreutils, GNU ftp,
34
- and lftp. Features include smart tab completion, globbing, and interactive help.
33
+ description: A command-line Dropbox interface based on GNU coreutils, GNU ftp, and
34
+ lftp. Features include smart tab completion, globbing, and interactive help.
35
35
  email: brandon@jangler.info
36
36
  executables:
37
37
  - droxi
@@ -79,9 +79,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
79
  version: '0'
80
80
  requirements: []
81
81
  rubyforge_project:
82
- rubygems_version: 2.2.2
82
+ rubygems_version: 2.4.5
83
83
  signing_key:
84
84
  specification_version: 4
85
- summary: ftp-like command-line interface to Dropbox
85
+ summary: An ftp-like command-line interface to Dropbox
86
86
  test_files: []
87
- has_rdoc: