matthewtodd-downloads 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +66 -0
- data/TODO.rdoc +0 -0
- data/bin/downloads +12 -0
- data/lib/downloads.rb +3 -0
- data/lib/downloads/commands.rb +81 -0
- data/lib/downloads/commands/add.rb +36 -0
- data/lib/downloads/commands/attachments.rb +23 -0
- data/lib/downloads/commands/config.rb +26 -0
- data/lib/downloads/commands/help.rb +23 -0
- data/lib/downloads/commands/ls.rb +9 -0
- data/lib/downloads/commands/mv.rb +25 -0
- data/lib/downloads/commands/quit.rb +10 -0
- data/lib/downloads/commands/rm.rb +26 -0
- data/lib/downloads/commands/shell.rb +36 -0
- data/lib/downloads/commands/status.rb +40 -0
- data/lib/downloads/commands/sync.rb +40 -0
- data/lib/downloads/configuration.rb +61 -0
- data/lib/downloads/servers.rb +28 -0
- data/lib/downloads/servers/local.rb +25 -0
- data/lib/downloads/servers/remote.rb +52 -0
- data/lib/downloads/tabtab_definitions.rb +55 -0
- data/resources/applescripts/Add Downloads.app +0 -0
- data/resources/applescripts/folderaction_download_webloc.scpt +0 -0
- data/resources/applescripts/netnewswire_download_enclosure.scpt +0 -0
- metadata +110 -0
data/README.rdoc
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
Downloads helps me get big files into Tanzania. It's been fun to write! Maybe you'll be able to make use of it, too.
|
2
|
+
|
3
|
+
== Overview
|
4
|
+
|
5
|
+
Since http downloads can be a little flaky over a slow connection, we take this basic approach:
|
6
|
+
|
7
|
+
1. Ssh to a server with a fast connection and download the file to a special directory there.
|
8
|
+
2. Later, rsync down the contents of that directory.
|
9
|
+
|
10
|
+
Beyond that, it's all polish. There's a nice interactive shell, smart (non-)use of the network, and good tab completion all around.
|
11
|
+
|
12
|
+
== Sample Workflow
|
13
|
+
|
14
|
+
$ downloads add http://media.railscasts.com/videos/143_paypal_security.mov
|
15
|
+
$ downloads sync
|
16
|
+
receiving incremental file list
|
17
|
+
143_paypal_security.mov
|
18
|
+
229376 0% 7.86kB/s 0:50:27
|
19
|
+
...
|
20
|
+
$ downloads rm 143_paypal_security.mov
|
21
|
+
$
|
22
|
+
|
23
|
+
== Installation
|
24
|
+
|
25
|
+
sudo gem install matthewtodd-downloads --source http://gems.github.com
|
26
|
+
|
27
|
+
== Configuration
|
28
|
+
|
29
|
+
You can see your current configuration via the "config" command:
|
30
|
+
|
31
|
+
downloads config
|
32
|
+
|
33
|
+
To update these parameters, pass the key and the new value like so:
|
34
|
+
|
35
|
+
downloads config remote_host downloads.example.com
|
36
|
+
|
37
|
+
== Commands
|
38
|
+
|
39
|
+
$ downloads help
|
40
|
+
add URL ...
|
41
|
+
attachments
|
42
|
+
config [KEY [VALUE]]
|
43
|
+
help [COMMAND]
|
44
|
+
ls
|
45
|
+
mv SOURCE TARGET
|
46
|
+
quit
|
47
|
+
rm FILE ...
|
48
|
+
shell
|
49
|
+
status
|
50
|
+
sync [kill]
|
51
|
+
|
52
|
+
== Tips
|
53
|
+
|
54
|
+
1. <tt>downloads shell</tt> re-uses the same ssh connection through your entire session, <b>saving the re-connection overhead</b> for every remote command.
|
55
|
+
|
56
|
+
2. <b>Tab completion</b> is available for free in <tt>downloads shell</tt>. You can also get it at the command line thanks to Dr. Nic's excellent TabTab gem, http://github.com/drnic/tabtab/tree/REL-0.9.1/PostInstall.txt.
|
57
|
+
|
58
|
+
3. I use the following <b>cron jobs</b> to <b>sync my downloads overnight</b>. (Restarting <tt>sync</tt> every half hour helps recover from dropped connections.)
|
59
|
+
*/30 0-5,22-23 * * * downloads sync
|
60
|
+
30 6 * * * downloads sync kill
|
61
|
+
|
62
|
+
4. I like to set up a <b>GeekTool window</b> showing the results of <tt>downloads status</tt>. If you like, you can bottom-justify this text by piping it through http://gist.github.com/43363.
|
63
|
+
|
64
|
+
5. Install Downloads on your mail server, and you can set up a special email address, say <tt>big.files@downloads.example.com</tt>, that pipes all its messages through <tt>downloads attachments</tt>. Currently, the messages will be dropped on the floor, but <b>any attachments will be dropped into your downloads folder</b>, ready to be sync'ed down.
|
65
|
+
|
66
|
+
6. I've been noodling with some <b>applescripts</b>. Check out the <tt>resources</tt> folder.
|
data/TODO.rdoc
ADDED
File without changes
|
data/bin/downloads
ADDED
data/lib/downloads.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module Downloads #:nodoc:
|
2
|
+
module Commands
|
3
|
+
def self.configuration
|
4
|
+
@@configuration ||= Configuration.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.registry
|
8
|
+
@@registry ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.names
|
12
|
+
registry.keys.sort
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.objects
|
16
|
+
registry.values.sort_by { |command| command.command_name }
|
17
|
+
end
|
18
|
+
|
19
|
+
# MAYBE we could use optparse at this top level as well, allowing for (1) overriding host & directory config and (2) faking remote interactions
|
20
|
+
def self.lookup(argv)
|
21
|
+
klass = registry[argv.shift] || Help
|
22
|
+
klass.new(configuration, argv)
|
23
|
+
end
|
24
|
+
|
25
|
+
class Base
|
26
|
+
def self.command_name
|
27
|
+
name.split('::').last.downcase
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.usage
|
31
|
+
command_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.inherited(command)
|
35
|
+
Commands.registry[command.command_name] = command
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :configuration, :local, :remote, :options
|
39
|
+
|
40
|
+
def initialize(configuration, argv)
|
41
|
+
@configuration = configuration
|
42
|
+
@local = configuration.local_server
|
43
|
+
@remote = configuration.remote_server
|
44
|
+
configure(argv)
|
45
|
+
end
|
46
|
+
|
47
|
+
def configure(argv)
|
48
|
+
end
|
49
|
+
|
50
|
+
def usage
|
51
|
+
self.class.usage
|
52
|
+
end
|
53
|
+
|
54
|
+
def run
|
55
|
+
raise NotImplementedError
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def shift_argument(argv)
|
65
|
+
argv.shift unless argv.first =~ /^-/
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'downloads/commands/add'
|
72
|
+
require 'downloads/commands/attachments'
|
73
|
+
require 'downloads/commands/config'
|
74
|
+
require 'downloads/commands/help'
|
75
|
+
require 'downloads/commands/ls'
|
76
|
+
require 'downloads/commands/mv'
|
77
|
+
require 'downloads/commands/quit'
|
78
|
+
require 'downloads/commands/rm'
|
79
|
+
require 'downloads/commands/shell'
|
80
|
+
require 'downloads/commands/status'
|
81
|
+
require 'downloads/commands/sync'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Downloads
|
4
|
+
module Commands
|
5
|
+
class Add < Base
|
6
|
+
attr_accessor :uris
|
7
|
+
|
8
|
+
def self.usage
|
9
|
+
"#{super} URL ..."
|
10
|
+
end
|
11
|
+
|
12
|
+
def configure(argv)
|
13
|
+
self.uris = []
|
14
|
+
while uri = shift_argument(argv)
|
15
|
+
self.uris << parse_uri(uri)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
remote.run("wget '#{uris.join("' '")}' --no-check-certificate")
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
uris.any?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_uri(uri)
|
30
|
+
URI.parse(uri)
|
31
|
+
rescue URI::InvalidURIError => error
|
32
|
+
puts error.message
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'tmail'
|
3
|
+
|
4
|
+
module Downloads
|
5
|
+
module Commands
|
6
|
+
class Attachments < Base
|
7
|
+
attr_accessor :stream
|
8
|
+
|
9
|
+
def configure(argv)
|
10
|
+
self.stream = ARGF
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO forward original messages from attachment emails
|
14
|
+
def run
|
15
|
+
TMail::Mail.parse(stream.read).attachments.each do |attachment|
|
16
|
+
filename = File.join(local.directory, attachment.original_filename)
|
17
|
+
File.open(filename, 'wb') { |file| file.write(attachment.read) }
|
18
|
+
File.chmod(0644, filename)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Downloads
|
2
|
+
module Commands
|
3
|
+
class Config < Base
|
4
|
+
attr_accessor :key, :value
|
5
|
+
|
6
|
+
def self.usage
|
7
|
+
"#{super} [KEY [VALUE]]"
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure(argv)
|
11
|
+
self.key = shift_argument(argv)
|
12
|
+
self.value = shift_argument(argv)
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
if value
|
17
|
+
configuration[key] = value
|
18
|
+
elsif key
|
19
|
+
puts configuration[key]
|
20
|
+
else
|
21
|
+
puts configuration.to_yaml
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Downloads
|
2
|
+
module Commands
|
3
|
+
class Help < Base
|
4
|
+
attr_accessor :command
|
5
|
+
|
6
|
+
def self.usage
|
7
|
+
"#{super} [COMMAND]"
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure(argv)
|
11
|
+
self.command = shift_argument(argv)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
if command
|
16
|
+
puts Commands.registry[command].usage
|
17
|
+
else
|
18
|
+
puts Commands.objects.map { |command| command.usage }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Downloads
|
2
|
+
module Commands
|
3
|
+
class Mv < Base
|
4
|
+
attr_accessor :source, :target
|
5
|
+
|
6
|
+
def self.usage
|
7
|
+
"#{super} SOURCE TARGET"
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure(argv)
|
11
|
+
self.source = shift_argument(argv)
|
12
|
+
self.target = shift_argument(argv)
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
remote.run("mv '#{source}' '#{target}'")
|
17
|
+
local.run("mv '#{source}' '#{target}'") if local.exists?(source)
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
source && target
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Downloads
|
2
|
+
module Commands
|
3
|
+
class Rm < Base
|
4
|
+
attr_accessor :filenames
|
5
|
+
|
6
|
+
def self.usage
|
7
|
+
"#{super} FILE ..."
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure(argv)
|
11
|
+
self.filenames = []
|
12
|
+
while filename = shift_argument(argv)
|
13
|
+
self.filenames << filename
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
remote.run("rm '#{filenames.join("' '")}'")
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
filenames.any?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'tabtab' unless Object.const_defined?(:TabTab)
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'tabtab_definitions')
|
3
|
+
require 'readline'
|
4
|
+
require 'shellwords'
|
5
|
+
|
6
|
+
module Downloads
|
7
|
+
module Commands
|
8
|
+
class Shell < Base
|
9
|
+
def run
|
10
|
+
Readline.completer_word_break_characters = ''
|
11
|
+
Readline.completion_append_character = ' '
|
12
|
+
Readline.completion_proc = lambda do |line|
|
13
|
+
words = Shellwords.shellwords("downloads #{line}")
|
14
|
+
words << '' if line.split('')[-1] == ' '
|
15
|
+
previous_token, current_token = words[-2..-1]
|
16
|
+
completions = TabTab::Definition['downloads'].extract_completions(previous_token, current_token, {})
|
17
|
+
completions.map { |completion| (words[1..-2] + [completion]).join(' ') }
|
18
|
+
end
|
19
|
+
|
20
|
+
puts 'Type ctrl-d or quit to exit.'
|
21
|
+
|
22
|
+
loop do
|
23
|
+
line = Readline::readline('> ') || 'quit'
|
24
|
+
next if line.strip == ''
|
25
|
+
Readline::HISTORY.push(line)
|
26
|
+
command = Downloads::Commands.lookup(line.split(' '))
|
27
|
+
if command.valid?
|
28
|
+
command.run
|
29
|
+
else
|
30
|
+
puts command.usage
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Downloads
|
2
|
+
module Commands
|
3
|
+
class Status < Base
|
4
|
+
def run
|
5
|
+
longest_filename = remote.filenames.max { |a, b| a.length <=> b.length }
|
6
|
+
remote.files.each do |file|
|
7
|
+
puts "%-#{longest_filename.length}s\t%3s%%\t%5s" % [file[:name], status(file), human_readable(file[:size])]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def status(remote_file)
|
14
|
+
local_file = local.exists?(remote_file[:name]) || { :size => 0 }
|
15
|
+
percent(local_file[:size], remote_file[:size])
|
16
|
+
end
|
17
|
+
|
18
|
+
def percent(numerator, denominator)
|
19
|
+
if denominator.zero? # oddly enough, this is happening for a .webloc file on my Desktop right now
|
20
|
+
'-'
|
21
|
+
else
|
22
|
+
(numerator.to_f * 100 / denominator).to_i
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def human_readable(bytes)
|
27
|
+
case bytes
|
28
|
+
when (0...1024)
|
29
|
+
"#{bytes}B"
|
30
|
+
when (1024...1024**2)
|
31
|
+
"#{bytes/1024}K"
|
32
|
+
when (1024**2...1024**3)
|
33
|
+
"#{bytes/1024**2}M"
|
34
|
+
else
|
35
|
+
"#{bytes/1024**3}G"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Downloads
|
2
|
+
module Commands
|
3
|
+
class Sync < Base
|
4
|
+
attr_accessor :kill
|
5
|
+
|
6
|
+
def self.usage
|
7
|
+
"#{super} [kill]"
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure(argv)
|
11
|
+
self.kill = (shift_argument(argv) == 'kill')
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
if File.exists?(pid_file)
|
16
|
+
`kill #{File.read(pid_file)}`
|
17
|
+
File.delete(pid_file)
|
18
|
+
end
|
19
|
+
|
20
|
+
unless kill
|
21
|
+
pid = fork { exec("rsync --recursive --progress --partial #{remote.rsync_path} #{local.rsync_path}") }
|
22
|
+
File.open(pid_file, 'w') { |file| file.write(pid) }
|
23
|
+
|
24
|
+
begin
|
25
|
+
Process.wait
|
26
|
+
rescue Interrupt
|
27
|
+
# we don't need to see the stacktrace
|
28
|
+
puts # but a blank line is nice
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def pid_file
|
36
|
+
configuration.pid_file
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Downloads
|
5
|
+
class Configuration
|
6
|
+
DIRECTORY = Pathname.new(File.join(ENV['HOME'], '.downloads'))
|
7
|
+
DEFAULTS = { 'remote_host' => 'downloads', 'remote_directory' => 'downloads', 'local_directory' => File.join(ENV['HOME'], 'Desktop') }.freeze
|
8
|
+
|
9
|
+
attr_reader :local_server, :remote_server
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
DIRECTORY.mkdir unless DIRECTORY.directory?
|
13
|
+
|
14
|
+
@values = DEFAULTS.dup
|
15
|
+
@values.merge!(YAML.load_file(path('config'))) if path('config').file?
|
16
|
+
|
17
|
+
@local_server = build_local_server
|
18
|
+
@remote_server = build_remote_server
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
@values[key.to_s]
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(key, value)
|
26
|
+
@values[key.to_s] = value
|
27
|
+
File.open(path('config'), 'w') { |file| file.puts(to_yaml) }
|
28
|
+
|
29
|
+
case key.to_s
|
30
|
+
when /^remote/
|
31
|
+
File.delete(path('remote_cache')) if path('remote_cache').file?
|
32
|
+
@remote_server = build_remote_server
|
33
|
+
when /^local/
|
34
|
+
@local_server = build_local_server
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def pid_file
|
39
|
+
path('pid')
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_yaml
|
43
|
+
# Marginally nicer than @values.to_yaml, as it avoids the leading '---'
|
44
|
+
@values.map { |key, value| "#{key}: #{value}" }.join("\n")
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def build_local_server
|
50
|
+
Servers::Local.new(@values['local_directory'])
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_remote_server
|
54
|
+
Servers::Remote.new(@values['remote_host'], @values['remote_directory'], path('remote_cache'))
|
55
|
+
end
|
56
|
+
|
57
|
+
def path(path)
|
58
|
+
DIRECTORY + path
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Downloads
|
2
|
+
module Servers #:nodoc:
|
3
|
+
class Base
|
4
|
+
def exists?(filename)
|
5
|
+
filenames.include?(filename)
|
6
|
+
end
|
7
|
+
|
8
|
+
def files
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def filenames
|
13
|
+
files.map { |file| file[:name] }
|
14
|
+
end
|
15
|
+
|
16
|
+
def rsync_path
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(command)
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'downloads/servers/local'
|
28
|
+
require 'downloads/servers/remote'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Downloads
|
2
|
+
module Servers
|
3
|
+
class Local < Base
|
4
|
+
attr_reader :directory
|
5
|
+
|
6
|
+
def initialize(directory)
|
7
|
+
@directory = directory
|
8
|
+
end
|
9
|
+
|
10
|
+
def files
|
11
|
+
Dir.chdir(directory) do
|
12
|
+
Dir.glob('*').sort.map { |name| { :name => name, :size => File.size(name) } }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def rsync_path
|
17
|
+
"#{@directory}/"
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(command)
|
21
|
+
`sh -c "cd #{@directory}; #{command}"`
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
module Downloads
|
4
|
+
module Servers
|
5
|
+
class Remote < Base
|
6
|
+
def initialize(host, directory, cache_path)
|
7
|
+
@host = host
|
8
|
+
@directory = directory
|
9
|
+
@cache_path = cache_path
|
10
|
+
@connection = nil
|
11
|
+
@files = YAML.load_file(@cache_path) if File.exists?(@cache_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def files
|
15
|
+
populate_file_cache unless @files
|
16
|
+
@files
|
17
|
+
end
|
18
|
+
|
19
|
+
def rsync_path
|
20
|
+
"#{@host}:#{@directory}/"
|
21
|
+
end
|
22
|
+
|
23
|
+
def run(command)
|
24
|
+
result = run_in_directory(command)
|
25
|
+
populate_file_cache
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def connection
|
32
|
+
initialize_connection unless @connection
|
33
|
+
@connection
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize_connection
|
37
|
+
@connection = Net::SSH.start(@host, ENV['USER']) # TODO does it make sense to use ENV['USER']?
|
38
|
+
at_exit { @connection.close }
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_in_directory(command)
|
42
|
+
connection.exec!("cd #{@directory}; #{command}")
|
43
|
+
end
|
44
|
+
|
45
|
+
def populate_file_cache
|
46
|
+
yaml = run_in_directory(%{ruby -ryaml -e "puts Dir.glob('*').sort.map { |name| { :name => name, :size => File.size(name) } }.to_yaml"})
|
47
|
+
File.open(@cache_path, 'w') { |file| file.write(yaml) }
|
48
|
+
@files = YAML.load(yaml)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
TabTab::Definition.register('downloads') do |c|
|
2
|
+
def clipboard_contents
|
3
|
+
[`pbpaste`.strip]
|
4
|
+
end
|
5
|
+
|
6
|
+
def commands
|
7
|
+
require File.join(File.dirname(__FILE__), 'commands') unless Object.const_defined?(:Downloads)
|
8
|
+
Downloads::Commands.names
|
9
|
+
end
|
10
|
+
|
11
|
+
def remote_files
|
12
|
+
require File.join(File.dirname(__FILE__), 'commands') unless Object.const_defined?(:Downloads)
|
13
|
+
Downloads::Commands.configuration.remote_server.filenames
|
14
|
+
end
|
15
|
+
|
16
|
+
# FIXME waiting for tabtab to support completion for multiple filenames
|
17
|
+
c.command(:add) do |add|
|
18
|
+
add.default { clipboard_contents.grep(/^http:/) }
|
19
|
+
end
|
20
|
+
|
21
|
+
c.command(:config) do |config|
|
22
|
+
config.command(:remote_host) { }
|
23
|
+
config.command(:remote_directory) { }
|
24
|
+
config.command(:local_directory) { }
|
25
|
+
end
|
26
|
+
|
27
|
+
c.command(:help) do |help|
|
28
|
+
help.default { commands }
|
29
|
+
end
|
30
|
+
|
31
|
+
c.command(:ls) do |ls|
|
32
|
+
end
|
33
|
+
|
34
|
+
c.command(:mv) do |mv|
|
35
|
+
mv.default { remote_files }
|
36
|
+
end
|
37
|
+
|
38
|
+
# FIXME waiting for tabtab to support completion for multiple filenames
|
39
|
+
c.command(:rm) do |rm|
|
40
|
+
rm.default { remote_files }
|
41
|
+
end
|
42
|
+
|
43
|
+
c.command(:quit) do |shell|
|
44
|
+
end
|
45
|
+
|
46
|
+
c.command(:shell) do |shell|
|
47
|
+
end
|
48
|
+
|
49
|
+
c.command(:status) do |status|
|
50
|
+
end
|
51
|
+
|
52
|
+
c.command(:sync) do |sync|
|
53
|
+
sync.command :kill
|
54
|
+
end
|
55
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: matthewtodd-downloads
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Todd
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-06 00:00:00 -08:00
|
13
|
+
default_executable: downloads
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: net-ssh
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.3
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: tabtab
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 0.9.1
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: tmail
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.2.2
|
41
|
+
version:
|
42
|
+
description:
|
43
|
+
email: matthew.todd@gmail.com
|
44
|
+
executables:
|
45
|
+
- downloads
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
extra_rdoc_files:
|
49
|
+
- README.rdoc
|
50
|
+
- TODO.rdoc
|
51
|
+
- bin/downloads
|
52
|
+
files:
|
53
|
+
- README.rdoc
|
54
|
+
- TODO.rdoc
|
55
|
+
- bin/downloads
|
56
|
+
- lib/downloads/commands/add.rb
|
57
|
+
- lib/downloads/commands/attachments.rb
|
58
|
+
- lib/downloads/commands/config.rb
|
59
|
+
- lib/downloads/commands/help.rb
|
60
|
+
- lib/downloads/commands/ls.rb
|
61
|
+
- lib/downloads/commands/mv.rb
|
62
|
+
- lib/downloads/commands/quit.rb
|
63
|
+
- lib/downloads/commands/rm.rb
|
64
|
+
- lib/downloads/commands/shell.rb
|
65
|
+
- lib/downloads/commands/status.rb
|
66
|
+
- lib/downloads/commands/sync.rb
|
67
|
+
- lib/downloads/commands.rb
|
68
|
+
- lib/downloads/configuration.rb
|
69
|
+
- lib/downloads/servers/local.rb
|
70
|
+
- lib/downloads/servers/remote.rb
|
71
|
+
- lib/downloads/servers.rb
|
72
|
+
- lib/downloads/tabtab_definitions.rb
|
73
|
+
- lib/downloads.rb
|
74
|
+
- resources/applescripts
|
75
|
+
- resources/applescripts/Add Downloads.app
|
76
|
+
- resources/applescripts/folderaction_download_webloc.scpt
|
77
|
+
- resources/applescripts/netnewswire_download_enclosure.scpt
|
78
|
+
has_rdoc: true
|
79
|
+
homepage:
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options:
|
82
|
+
- --main
|
83
|
+
- README.rdoc
|
84
|
+
- --title
|
85
|
+
- downloads-0.6.5
|
86
|
+
- --inline-source
|
87
|
+
- --line-numbers
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "0"
|
95
|
+
version:
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
version:
|
102
|
+
requirements:
|
103
|
+
- rsync
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 1.2.0
|
106
|
+
signing_key:
|
107
|
+
specification_version: 2
|
108
|
+
summary: Downloads uses net-ssh, rsync and tmail to reliably get big files into Tanzania.
|
109
|
+
test_files: []
|
110
|
+
|