summaryrefslogtreecommitdiff
path: root/lua/rclone.lua
blob: c584b6b525b893a15b95e84794e9f50779114d3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
local config_file = ''
local config_dir = ''

---
-- Find config file and set global variables
--
-- @return string
local function find_config_file()
    if config_file ~= '' then
    	return config_file
    end

    local scandir = require('plenary.scandir').scan_dir
    local Path = require('plenary.path')

    local current_dir = Path:new('.')
    while config_file == '' do
    	local files = scandir(current_dir.filename, { depth = 1 })
	for _, file in ipairs(files) do
	    if string.find(file, 'rclone.conf') then
		config_file = file
		config_dir = current_dir.filename

		return config_file
	    end
	end

	--if current_dir:is_root() then
	if current_dir.filename == '/' then
	    -- set to 'rclone config file' output
	    local handle = io.popen('rclone config file', 'r')
	    handle:read() -- advance one line because we want the second line only
	    local output = handle:read();
	    handle:close()

	    config_file = output
	    local config_file_path = Path:new(config_file)
	    config_dir = config_file_path:parent().filename

	    return config_file
	end

	current_dir = current_dir:parent()
    end
end

---
-- Get rclone config as JSON
-- and parse to table
--
-- @return string
local function get_config()
    local handle = io.popen('rclone config dump --config=' .. find_config_file(), 'r')
    local config = handle:read('*all');
    handle:close()

    return vim.fn.json_decode(config)
end

---
-- Get the desired remote config from the config file
--
-- @param remote string
--
-- @return string
-- @return table
-- @return nil
local function get_remote_config(remote)
    local config = get_config()
    local target_remote = nil
    local target_config = nil

    if remote then
	if config[remote] then
	    target_remote = remote
	    target_config = config[remote]
	else
	    print("Couldn't find remote: \"" .. remote .. '"')
	    return
	end
    else
        local config_length = 0
	for _, _ in pairs(config) do
	    config_length = config_length + 1
        end

	if config_length == 0 then
	    print('No remotes defined in config file at "' .. find_config_file() .. '"')
	    return
	end

	if config_length == 1 then
	    for remote_name, remote_config in pairs(config) do
		target_remote = remote_name
		target_config = remote_config
	    end
	else
	    for remote_name, remote_config in pairs(config) do
		if remote_config.vim_rclone_default then
		    target_remote = remote_name
		    target_config = remote_config
		end
	    end

	    if target_remote == nil then
		print('No default remote specified')
		return
	    end
	end
    end


    if target_config.vim_rclone_local_path == nil or target_config.vim_rclone_local_path == '' or
	target_config.vim_rclone_remote_path == nil or target_config.vim_rclone_remote_path == '' then
	print('No local and or remote path set for remote "' .. target_remote .. '"')
	return
    end

    return target_remote, target_config
end

---
-- Build the local path
--
-- @param config table
--
-- @return string
local function prepare_cmd_local_path(config)
    local Path = require('plenary.path')
    local local_path = Path:new(config.vim_rclone_local_path)

    local_path._cwd = config_dir -- set cwd to config dir to resolve path correctly
    local_path = local_path:absolute()
    local_path = local_path:gsub('%./', '') -- remove wrongly kept ./ for relative paths

    return local_path
end

---
-- Add remote path
--
-- @param config table
-- @param remote string
--
-- @return string
local function build_cmd_remote_path(config, remote)
    return remote .. ':' .. config.vim_rclone_remote_path
end

---
-- Add config location path
--
-- @return string
local function build_cmd_config()
    return '--config=' .. find_config_file()
end

---
-- Add exclude patterns
--
-- @param local_path string
--
-- @return string
local function build_cmd_exclude(local_path)
    local Path = require('plenary.path')
    local cmd = ''

    -- TODO: add configurable default exclude patterns
    local configurable_default_exclude_patterns = {'.git/', '.gitignore', '.rcloneignore'}
    for _, pattern in pairs(configurable_default_exclude_patterns) do
    	cmd = cmd .. ' --exclude=' .. pattern
    end

    -- add .gitignore patterns
    local gitignore = Path:new(local_path .. '/.gitignore')
    if gitignore:exists() then
        cmd = cmd .. ' --exclude-from=' .. gitignore.filename
    end

    -- add .rcloneignore patterns
    local rcloneignore = Path:new(local_path .. '/.rcloneignore')
    if rcloneignore:exists() then
    	cmd = cmd .. ' --exclude-from=' .. rcloneignore.filename
    end

    return cmd:gsub('^%s?', '') -- ltrim
end

---
-- Add logging
--
-- @return string
local function build_cmd_logging()
    local log_level = 'INFO'
    local cmd = ''

    cmd = '--log-level=' .. log_level
    cmd = cmd .. ' --log-file=' .. vim.fn.stdpath('data') .. '/rclone_nvim/rclone.log'

    return cmd
end

---
-- Call the 'rclone copy' command with config values
-- 'rclone copy <args>'
--
-- @return any
local function copy(options)
    local remote, config = get_remote_config(options.remote)
    if remote == nil then
	return
    end

    local local_path = prepare_cmd_local_path(config)

    local cmd =
	'rclone copy ' ..
	local_path .. ' ' ..
	build_cmd_remote_path(config, remote) .. ' ' ..
	build_cmd_config() .. ' ' ..
	build_cmd_exclude(local_path) .. ' ' ..
	build_cmd_logging()

    os.execute(cmd)
    print(cmd)
    print("Copied!")

    return config
end

---
-- Call the 'rclone copy' command with config values for the current file
-- 'rclone copy <args>'
--
-- @return any
local function copyFile(options)
    local remote, config = get_remote_config(options.remote)
    if remote == nil then
	return
    end

    local local_path = prepare_cmd_local_path(config)

    -- build relative path to file for local and remote
    local Path = require('plenary.path')
    local local_file_path = Path:new(vim.fn.expand('%')):absolute()
    local local_file_path_relative = local_file_path:gsub(local_path:gsub('([^%w])', '%%%1'), '')
    local local_file_path_relative_parent = Path:new(local_file_path_relative):parent().filename

    local cmd =
	'rclone copy ' ..
	local_path .. local_file_path_relative .. ' ' ..
	build_cmd_remote_path(config, remote) .. local_file_path_relative_parent .. ' ' ..
	build_cmd_config() .. ' ' ..
	build_cmd_exclude(local_path) .. ' ' ..
	build_cmd_logging()

    os.execute(cmd)
    print(cmd)
    print("Copied file!")

    return config
end

---
-- Call the 'rclone sync' command with config values
-- 'rclone sync <args>'
--
-- @return any
local function sync(options)
    local remote, config = get_remote_config(options.remote)
    if remote == nil then
	return
    end

    local local_path = prepare_cmd_local_path(config)

    local cmd =
	'rclone sync ' ..
	local_path .. ' ' ..
	build_cmd_remote_path(config, remote) .. ' ' ..
	build_cmd_config() .. ' ' ..
	build_cmd_exclude(local_path) .. ' ' ..
	build_cmd_logging()

    os.execute(cmd)
    print(cmd)
    print("Synced!")

    return config
end

-- Table for dynamic command access
local commands = {
  copy = copy,
  copyFile = copyFile,
  sync = sync,
}

---
-- Parse args and run specified command
local function run(cmd, ...)
  local args = { ... }
  if cmd == nil then
    print('No command supplied!')
    return
  end

  if commands[cmd] == nil then
    print('Command "' .. cmd .. '" does not exist.')
    return
  end

  local options = {}
  for _, arg in ipairs(args) do
    if arg:find('%-%-') == 1 then
      options[arg] = true
    elseif arg:find('=', 1) == nil then
      options['remote'] = arg
    else
      local param = vim.split(arg, '=')
      local key = table.remove(param, 1)
      param = table.concat(param, '=')

      options[key] = param
    end
  end

  commands[cmd](options)
end

return {
  run = run,
}