filedepot 0.1.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/lib/filedepot/cli.rb +119 -31
- data/lib/filedepot/config.rb +4 -1
- data/lib/filedepot/storage/base.rb +95 -0
- data/lib/filedepot/storage_ssh.rb +189 -0
- data/lib/filedepot/version.rb +1 -1
- data/lib/filedepot.rb +2 -0
- metadata +61 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3323f568d221fde7de4cd30f7450b46444f48bcfe71b2e0dd16acfe84d97fd14
|
|
4
|
+
data.tar.gz: 39318899de695422ef75292f4e5c5a8cf3271b91ef0d474fdc1305ebfd78dead
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37f23733b8942db6547ab12960cef9308717f1836ff4c1ae6959c3a1196c00195508f4a5d996d8b9efd10a7ac93552799d633fa11e475f6575ddccee690985ee
|
|
7
|
+
data.tar.gz: 70f21d4037d84be1cc71c12032eb7e31253b304511af80e8d6fb7e397823e87a9c4718db35466bfe746ca3cfc249a089bc65e534d1e3330240196a5dc703c8a0
|
data/lib/filedepot/cli.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
3
4
|
require "thor"
|
|
4
5
|
|
|
5
6
|
module Filedepot
|
|
@@ -15,30 +16,28 @@ module Filedepot
|
|
|
15
16
|
HELP
|
|
16
17
|
push: <<~HELP,
|
|
17
18
|
Usage:
|
|
18
|
-
filedepot push HANDLE
|
|
19
|
+
filedepot push HANDLE FILE
|
|
19
20
|
|
|
20
|
-
Send a file with a specific handle
|
|
21
|
+
Send a file to the current storage with a specific handle.
|
|
22
|
+
Example: filedepot push test test.txt
|
|
21
23
|
HELP
|
|
22
24
|
pull: <<~HELP,
|
|
23
25
|
Usage:
|
|
24
|
-
filedepot pull HANDLE [VERSION]
|
|
25
|
-
|
|
26
|
-
Get
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
filedepot
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
delete: <<~HELP
|
|
36
|
-
Usage:
|
|
37
|
-
filedepot delete HANDLE [VERSION]
|
|
38
|
-
|
|
39
|
-
After confirmation, deletes all versions of a file.
|
|
40
|
-
If VERSION is specified, deletes only that specific version.
|
|
26
|
+
filedepot pull HANDLE [--path PATH] [--version VERSION]
|
|
27
|
+
|
|
28
|
+
Get a file from storage. By default gets the latest version and saves to current directory.
|
|
29
|
+
Options:
|
|
30
|
+
--path PATH Save to this local path (e.g. ./test/file.txt)
|
|
31
|
+
--version N Get specific version (default: latest)
|
|
32
|
+
Examples:
|
|
33
|
+
filedepot pull test
|
|
34
|
+
filedepot pull test --path ./test/file.txt
|
|
35
|
+
filedepot pull test --version 2
|
|
36
|
+
filedepot pull test --version 2 --path ./test/file.txt
|
|
41
37
|
HELP
|
|
38
|
+
versions: "Usage: filedepot versions HANDLE\n\nList all versions of a handle. Each version has an integer ID from 1 to n.",
|
|
39
|
+
delete: "Usage: filedepot delete HANDLE [VERSION]\n\nAfter confirmation, deletes all versions of a file.\nIf VERSION is specified, deletes only that specific version.",
|
|
40
|
+
info: "Usage: filedepot info HANDLE\n\nShow info for a handle: remote base path and current version."
|
|
42
41
|
}.freeze
|
|
43
42
|
|
|
44
43
|
desc "config", "Open the config file using $EDITOR"
|
|
@@ -49,23 +48,58 @@ module Filedepot
|
|
|
49
48
|
exec(editor, config_path)
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
desc "push HANDLE", "Send a file with a specific handle
|
|
53
|
-
def push(handle
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
desc "push HANDLE FILE", "Send a file to the current storage with a specific handle"
|
|
52
|
+
def push(handle, file_path)
|
|
53
|
+
source = Config.current_source
|
|
54
|
+
if source.nil?
|
|
55
|
+
puts "Error: No storage source configured. Run 'filedepot config' to set up."
|
|
56
56
|
return
|
|
57
57
|
end
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
storage = Storage::Base.for(source)
|
|
60
|
+
storage.push(handle, file_path)
|
|
61
|
+
version = storage.current_version(handle)
|
|
62
|
+
puts "Pushed #{file_path} as #{handle} (version #{version})"
|
|
63
|
+
uploaded_url = storage.url(handle, version, File.basename(file_path))
|
|
64
|
+
puts uploaded_url if uploaded_url
|
|
59
65
|
end
|
|
60
66
|
|
|
61
|
-
desc "pull HANDLE
|
|
62
|
-
|
|
67
|
+
desc "pull HANDLE", "Get file from storage"
|
|
68
|
+
method_option :path, type: :string, desc: "Local path to save the file"
|
|
69
|
+
method_option :version, type: :string, desc: "Version number to pull (default: latest)"
|
|
70
|
+
def pull(handle = nil)
|
|
63
71
|
if handle.nil?
|
|
64
72
|
puts COMMAND_HELP[:pull]
|
|
65
73
|
return
|
|
66
74
|
end
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
|
|
76
|
+
source = Config.current_source
|
|
77
|
+
if source.nil?
|
|
78
|
+
puts "Error: No storage source configured. Run 'filedepot config' to set up."
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
local_path = options[:path]
|
|
83
|
+
version = (options[:version].nil? || options[:version].empty?) ? nil : options[:version].to_i
|
|
84
|
+
|
|
85
|
+
storage = Storage::Base.for(source)
|
|
86
|
+
info = storage.pull_info(handle, version, local_path)
|
|
87
|
+
target_path = info[:target_path]
|
|
88
|
+
|
|
89
|
+
puts "Pulling #{handle} (version #{info[:version_num]})"
|
|
90
|
+
|
|
91
|
+
parent_dir = File.dirname(target_path)
|
|
92
|
+
unless parent_dir == "." || File.directory?(parent_dir)
|
|
93
|
+
return unless confirm?("create local directory #{parent_dir}, y/n?")
|
|
94
|
+
FileUtils.mkdir_p(parent_dir)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if File.exist?(target_path)
|
|
98
|
+
return unless confirm?("overwrite local #{target_path}, y/n?")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
storage.pull(handle, version, target_path)
|
|
102
|
+
puts "pulled to #{target_path}"
|
|
69
103
|
end
|
|
70
104
|
|
|
71
105
|
desc "versions HANDLE", "List all versions of a handle (each version has an integer from 1 to n)"
|
|
@@ -74,7 +108,49 @@ module Filedepot
|
|
|
74
108
|
puts COMMAND_HELP[:versions]
|
|
75
109
|
return
|
|
76
110
|
end
|
|
77
|
-
|
|
111
|
+
|
|
112
|
+
source = Config.current_source
|
|
113
|
+
if source.nil?
|
|
114
|
+
puts "Error: No storage source configured. Run 'filedepot config' to set up."
|
|
115
|
+
return
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
storage = Storage::Base.for(source)
|
|
119
|
+
versions_list = storage.versions(handle)
|
|
120
|
+
if versions_list.empty?
|
|
121
|
+
puts "No versions found for handle: #{handle}"
|
|
122
|
+
else
|
|
123
|
+
max_display = 10
|
|
124
|
+
to_show = versions_list.first(max_display)
|
|
125
|
+
to_show.each { |v, date_str| puts date_str.empty? ? v.to_s : "#{v} - #{date_str}" }
|
|
126
|
+
if versions_list.size > max_display
|
|
127
|
+
remaining = versions_list.size - max_display
|
|
128
|
+
puts "... and #{remaining} other ones for a total of #{versions_list.size} versions"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
desc "info HANDLE", "Show info for a handle"
|
|
134
|
+
def info(handle = nil)
|
|
135
|
+
if handle.nil?
|
|
136
|
+
puts COMMAND_HELP[:info]
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
source = Config.current_source
|
|
141
|
+
if source.nil?
|
|
142
|
+
puts "Error: No storage source configured. Run 'filedepot config' to set up."
|
|
143
|
+
return
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
storage = Storage::Base.for(source)
|
|
147
|
+
data = storage.info(handle)
|
|
148
|
+
|
|
149
|
+
puts "handle: #{data[:handle]}"
|
|
150
|
+
puts "remote_base_path: #{data[:remote_base_path]}"
|
|
151
|
+
puts "current version: #{data[:current_version]}"
|
|
152
|
+
puts "updated at: #{data[:updated_at]}" if data[:updated_at]
|
|
153
|
+
puts "latest version url: #{data[:latest_version_url]}" if data[:latest_version_url]
|
|
78
154
|
end
|
|
79
155
|
|
|
80
156
|
desc "delete HANDLE [VERSION]", "After confirmation, delete all versions of a file; or only a specific version if specified"
|
|
@@ -105,9 +181,10 @@ module Filedepot
|
|
|
105
181
|
puts ""
|
|
106
182
|
puts "Available commands:"
|
|
107
183
|
puts " filedepot config Open config file using $EDITOR"
|
|
108
|
-
puts " filedepot push HANDLE
|
|
109
|
-
puts " filedepot pull HANDLE [
|
|
184
|
+
puts " filedepot push HANDLE FILE Send file to current storage"
|
|
185
|
+
puts " filedepot pull HANDLE [--path PATH] [--version N] Get file from storage"
|
|
110
186
|
puts " filedepot versions HANDLE List all versions of a handle"
|
|
187
|
+
puts " filedepot info HANDLE Show info for a handle"
|
|
111
188
|
puts " filedepot delete HANDLE [VER] Delete file(s) after confirmation"
|
|
112
189
|
puts ""
|
|
113
190
|
puts "Use 'filedepot help COMMAND' for more information on a command."
|
|
@@ -116,5 +193,16 @@ module Filedepot
|
|
|
116
193
|
def self.exit_on_failure?
|
|
117
194
|
true
|
|
118
195
|
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def confirm?(prompt)
|
|
200
|
+
print "#{prompt} "
|
|
201
|
+
input = $stdin.gets&.strip&.downcase
|
|
202
|
+
input == "y" || input == "yes"
|
|
203
|
+
rescue Interrupt
|
|
204
|
+
puts
|
|
205
|
+
false
|
|
206
|
+
end
|
|
119
207
|
end
|
|
120
208
|
end
|
data/lib/filedepot/config.rb
CHANGED
|
@@ -36,7 +36,10 @@ module Filedepot
|
|
|
36
36
|
config = load
|
|
37
37
|
default = config["default_source"]
|
|
38
38
|
sources = config["sources"] || []
|
|
39
|
-
|
|
39
|
+
return nil if sources.empty?
|
|
40
|
+
|
|
41
|
+
source = sources.find { |s| (s["name"] || s[:name]) == default }
|
|
42
|
+
source || sources.first
|
|
40
43
|
end
|
|
41
44
|
end
|
|
42
45
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Filedepot
|
|
4
|
+
module Storage
|
|
5
|
+
class Base
|
|
6
|
+
def self.for(source)
|
|
7
|
+
if source["ssh"]
|
|
8
|
+
SshStorage.new(source)
|
|
9
|
+
else
|
|
10
|
+
raise ArgumentError, "Unknown storage type for source: #{source["name"]}"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(source)
|
|
15
|
+
@source = source
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def current_version(handle)
|
|
19
|
+
path = current_version_path(handle)
|
|
20
|
+
path.nil? || path.to_s.empty? ? 0 : File.basename(path).to_i
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def next_version(handle)
|
|
24
|
+
current_version(handle) + 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def next_version_path(handle)
|
|
28
|
+
File.join(remote_handle_path(handle), next_version(handle).to_s)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def current_version_path(handle)
|
|
32
|
+
raise "not implemented"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test
|
|
36
|
+
raise "not implemented"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ls
|
|
40
|
+
raise "not implemented"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def push(handle, local_path)
|
|
44
|
+
raise "not implemented"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pull(handle, version = nil, local_path = nil)
|
|
48
|
+
raise "not implemented"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def versions(handle)
|
|
52
|
+
raise "not implemented"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def versions_data(handle)
|
|
56
|
+
raise "not implemented"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def delete(handle, version = nil)
|
|
60
|
+
raise "not implemented"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def info(handle)
|
|
64
|
+
data = versions_data(handle)
|
|
65
|
+
latest = data.first
|
|
66
|
+
result = {
|
|
67
|
+
handle: handle,
|
|
68
|
+
remote_base_path: remote_base_path,
|
|
69
|
+
current_version: current_version(handle)
|
|
70
|
+
}
|
|
71
|
+
result[:updated_at] = latest[:datetime] if latest
|
|
72
|
+
result[:latest_version_url] = latest[:url] if latest && latest[:url]
|
|
73
|
+
result
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def url(handle, version, filename)
|
|
77
|
+
base = @source["public_base_path"].to_s.sub(%r{/$}, "")
|
|
78
|
+
return nil if base.empty? || filename.nil? || filename.empty?
|
|
79
|
+
|
|
80
|
+
[base, handle, version, filename].join("/").gsub(%r{/+}, "/")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
protected
|
|
84
|
+
|
|
85
|
+
def remote_base_path
|
|
86
|
+
@source["base_path"] || "/tmp/filedepot"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def remote_handle_path(handle)
|
|
90
|
+
safe_handle = handle.to_s.gsub(%r{[^\w\-./]}, "_")
|
|
91
|
+
File.join(remote_base_path, safe_handle)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require "net/ssh"
|
|
5
|
+
require "net/scp"
|
|
6
|
+
|
|
7
|
+
module Filedepot
|
|
8
|
+
module Storage
|
|
9
|
+
class SshStorage < Base
|
|
10
|
+
def test
|
|
11
|
+
ssh_session do |ssh|
|
|
12
|
+
result = ssh.exec!("echo ok")
|
|
13
|
+
raise "Connection failed" unless result&.include?("ok")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ls
|
|
18
|
+
handles = []
|
|
19
|
+
ssh_session do |ssh|
|
|
20
|
+
result = ssh.exec!("ls -1 #{shell_escape(remote_base_path)} 2>/dev/null || true")
|
|
21
|
+
return [] if result.nil? || result.strip.empty?
|
|
22
|
+
|
|
23
|
+
handles = result.strip.split("\n").select { |line| !line.empty? }
|
|
24
|
+
end
|
|
25
|
+
handles
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def push(handle, local_path)
|
|
29
|
+
path = File.expand_path(local_path)
|
|
30
|
+
raise "File not found: #{local_path}" unless File.file?(path)
|
|
31
|
+
|
|
32
|
+
push_path = next_version_path(handle)
|
|
33
|
+
|
|
34
|
+
ssh_session do |ssh|
|
|
35
|
+
ssh.exec!("mkdir -p #{shell_escape(push_path)}")
|
|
36
|
+
# Upload to directory - SCP places file with original name inside
|
|
37
|
+
ssh.scp.upload!(path, push_path)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def current_version_path(handle)
|
|
42
|
+
ssh_session do |ssh|
|
|
43
|
+
handle_dir = remote_handle_path(handle)
|
|
44
|
+
ssh.exec!("mkdir -p #{shell_escape(handle_dir)}")
|
|
45
|
+
|
|
46
|
+
result = ssh.exec!("ls -1 #{shell_escape(handle_dir)} 2>/dev/null || true")
|
|
47
|
+
versions = parse_versions(result)
|
|
48
|
+
|
|
49
|
+
if versions.empty?
|
|
50
|
+
nil
|
|
51
|
+
else
|
|
52
|
+
File.join(handle_dir, versions.max.to_s)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def pull(handle, version = nil, local_path = nil)
|
|
58
|
+
ssh_session do |ssh|
|
|
59
|
+
versions_list = versions_for(ssh, handle)
|
|
60
|
+
raise "No versions found for handle: #{handle}" if versions_list.empty?
|
|
61
|
+
|
|
62
|
+
version_num = version ? version.to_i : versions_list.max
|
|
63
|
+
raise "Version #{version} not found" unless versions_list.include?(version_num)
|
|
64
|
+
|
|
65
|
+
version_dir = File.join(remote_handle_path(handle), version_num.to_s)
|
|
66
|
+
remote_file = first_file_in_dir(ssh, version_dir)
|
|
67
|
+
raise "No file found in version #{version_num}" if remote_file.nil?
|
|
68
|
+
|
|
69
|
+
remote_filename = File.basename(remote_file)
|
|
70
|
+
target_path = resolve_local_path(local_path, remote_filename)
|
|
71
|
+
|
|
72
|
+
ssh.scp.download!(remote_file, target_path)
|
|
73
|
+
target_path
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def pull_info(handle, version = nil, local_path = nil)
|
|
78
|
+
ssh_session do |ssh|
|
|
79
|
+
versions_list = versions_for(ssh, handle)
|
|
80
|
+
raise "No versions found for handle: #{handle}" if versions_list.empty?
|
|
81
|
+
|
|
82
|
+
version_num = version ? version.to_i : versions_list.max
|
|
83
|
+
raise "Version #{version} not found" unless versions_list.include?(version_num)
|
|
84
|
+
|
|
85
|
+
version_dir = File.join(remote_handle_path(handle), version_num.to_s)
|
|
86
|
+
remote_file = first_file_in_dir(ssh, version_dir)
|
|
87
|
+
raise "No file found in version #{version_num}" if remote_file.nil?
|
|
88
|
+
|
|
89
|
+
remote_filename = File.basename(remote_file)
|
|
90
|
+
target_path = resolve_local_path(local_path, remote_filename)
|
|
91
|
+
|
|
92
|
+
{ remote_filename: remote_filename, version_num: version_num, target_path: target_path }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def versions_data(handle)
|
|
97
|
+
ssh_session do |ssh|
|
|
98
|
+
versions_list = versions_for(ssh, handle).sort.reverse
|
|
99
|
+
versions_list.map do |v|
|
|
100
|
+
version_dir = File.join(remote_handle_path(handle), v.to_s)
|
|
101
|
+
epoch = stat_mtime(ssh, version_dir)
|
|
102
|
+
remote_file = first_file_in_dir(ssh, version_dir)
|
|
103
|
+
path = remote_file || version_dir
|
|
104
|
+
filename = remote_file ? File.basename(remote_file) : nil
|
|
105
|
+
{
|
|
106
|
+
version: v,
|
|
107
|
+
datetime: epoch ? Time.at(epoch) : nil,
|
|
108
|
+
path: path,
|
|
109
|
+
handle: handle,
|
|
110
|
+
filename: filename,
|
|
111
|
+
url: url(handle, v, filename)
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def versions(handle)
|
|
118
|
+
versions_data(handle).map { |d| [d[:version], d[:datetime] ? d[:datetime].to_s : ""] }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def delete(handle, version = nil)
|
|
122
|
+
ssh_session do |ssh|
|
|
123
|
+
handle_dir = remote_handle_path(handle)
|
|
124
|
+
|
|
125
|
+
if version
|
|
126
|
+
version_dir = File.join(handle_dir, version.to_s)
|
|
127
|
+
ssh.exec!("rm -rf #{shell_escape(version_dir)}")
|
|
128
|
+
versions_remaining = versions_for(ssh, handle)
|
|
129
|
+
ssh.exec!("rmdir #{shell_escape(handle_dir)} 2>/dev/null || true") if versions_remaining.empty?
|
|
130
|
+
else
|
|
131
|
+
ssh.exec!("rm -rf #{shell_escape(handle_dir)}")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def shell_escape(path)
|
|
139
|
+
Shellwords.shellescape(path.to_s)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def ssh_session
|
|
143
|
+
host = @source["host"] || "localhost"
|
|
144
|
+
user = @source["username"].to_s.strip
|
|
145
|
+
user = ENV["USER"] if user.empty?
|
|
146
|
+
|
|
147
|
+
Net::SSH.start(host, user) do |ssh|
|
|
148
|
+
yield ssh
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def versions_for(ssh, handle)
|
|
153
|
+
remote_dir = remote_handle_path(handle)
|
|
154
|
+
result = ssh.exec!("ls -1 #{shell_escape(remote_dir)} 2>/dev/null || true")
|
|
155
|
+
parse_versions(result)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def parse_versions(ls_result)
|
|
159
|
+
return [] if ls_result.nil? || ls_result.strip.empty?
|
|
160
|
+
|
|
161
|
+
ls_result.strip.split("\n").map(&:to_i).select { |v| v.positive? }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def first_file_in_dir(ssh, dir)
|
|
165
|
+
result = ssh.exec!("ls -1 #{shell_escape(dir)} 2>/dev/null || true")
|
|
166
|
+
return nil if result.nil? || result.strip.empty?
|
|
167
|
+
|
|
168
|
+
first = result.strip.split("\n").first
|
|
169
|
+
first ? File.join(dir, first) : nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def stat_mtime(ssh, path)
|
|
173
|
+
# Linux: stat -c %Y; macOS: stat -f %m
|
|
174
|
+
result = ssh.exec!("stat -c %Y #{shell_escape(path)} 2>/dev/null || stat -f %m #{shell_escape(path)} 2>/dev/null")
|
|
175
|
+
result&.strip&.to_i
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def resolve_local_path(local_path_param, remote_filename)
|
|
179
|
+
if local_path_param.nil? || local_path_param.empty?
|
|
180
|
+
File.join(Dir.pwd, remote_filename)
|
|
181
|
+
elsif local_path_param.end_with?("/") || (File.exist?(local_path_param) && File.directory?(local_path_param))
|
|
182
|
+
File.join(File.expand_path(local_path_param), remote_filename)
|
|
183
|
+
else
|
|
184
|
+
File.expand_path(local_path_param)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
data/lib/filedepot/version.rb
CHANGED
data/lib/filedepot.rb
CHANGED
metadata
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: filedepot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Filedepot
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: net-scp
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '4.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '4.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: net-ssh
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.0'
|
|
13
40
|
- !ruby/object:Gem::Dependency
|
|
14
41
|
name: thor
|
|
15
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -24,6 +51,34 @@ dependencies:
|
|
|
24
51
|
- - "~>"
|
|
25
52
|
- !ruby/object:Gem::Version
|
|
26
53
|
version: '1.3'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
27
82
|
description: Command-line tool to sync files on remote storage via SSH
|
|
28
83
|
email:
|
|
29
84
|
- ''
|
|
@@ -37,6 +92,8 @@ files:
|
|
|
37
92
|
- lib/filedepot.rb
|
|
38
93
|
- lib/filedepot/cli.rb
|
|
39
94
|
- lib/filedepot/config.rb
|
|
95
|
+
- lib/filedepot/storage/base.rb
|
|
96
|
+
- lib/filedepot/storage_ssh.rb
|
|
40
97
|
- lib/filedepot/version.rb
|
|
41
98
|
homepage: https://github.com/filedepot/filedepot
|
|
42
99
|
licenses:
|
|
@@ -44,7 +101,6 @@ licenses:
|
|
|
44
101
|
metadata:
|
|
45
102
|
homepage_uri: https://github.com/filedepot/filedepot
|
|
46
103
|
source_code_uri: https://github.com/filedepot/filedepot
|
|
47
|
-
post_install_message:
|
|
48
104
|
rdoc_options: []
|
|
49
105
|
require_paths:
|
|
50
106
|
- lib
|
|
@@ -59,8 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
59
115
|
- !ruby/object:Gem::Version
|
|
60
116
|
version: '0'
|
|
61
117
|
requirements: []
|
|
62
|
-
rubygems_version:
|
|
63
|
-
signing_key:
|
|
118
|
+
rubygems_version: 4.0.6
|
|
64
119
|
specification_version: 4
|
|
65
120
|
summary: Sync files on remote storage
|
|
66
121
|
test_files: []
|