dorkbox 0.9.0
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/executables/dorkbox +32 -0
- data/executables/test +7 -0
- data/lib/dorkbox.rb +243 -0
- metadata +50 -0
data/executables/dorkbox
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'dorkbox'
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
# This hash will hold all of the options
|
8
|
+
# parsed from the command-line by
|
9
|
+
# OptionParser.
|
10
|
+
options = {}
|
11
|
+
|
12
|
+
optparse = OptionParser.new do |opts|
|
13
|
+
# TODO: Put command-line options here
|
14
|
+
|
15
|
+
# This displays the help screen, all programs are
|
16
|
+
# assumed to have this option.
|
17
|
+
opts.on('-h', '--help', 'Display this screen') do
|
18
|
+
puts opts
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
end
|
22
|
+
optparse.parse!
|
23
|
+
|
24
|
+
command = ARGV[0]
|
25
|
+
ARGV.shift
|
26
|
+
|
27
|
+
VALID_COMMANDS = ['create', 'connect', 'sync', 'sync_tracked', 'cleanup_tracked']
|
28
|
+
|
29
|
+
VALID_COMMANDS.include?(command) or exit(1)
|
30
|
+
## TODO: something like dorkbox clone with remote
|
31
|
+
|
32
|
+
Dorkbox::send(command.to_sym, *ARGV)
|
data/executables/test
ADDED
data/lib/dorkbox.rb
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'open3'
|
3
|
+
require 'io/wait'
|
4
|
+
|
5
|
+
module BashLike
|
6
|
+
ALWAYS_PRINT_STDOUT=false
|
7
|
+
ALWAYS_ECHO_CMDLINE=true
|
8
|
+
EXCEPTION_ON_NONZERO_STATUS=true
|
9
|
+
LOGFILE=nil
|
10
|
+
LOGFILE_MAX_LINES=5000
|
11
|
+
|
12
|
+
def `(cmdline)
|
13
|
+
c(cmdline, true)
|
14
|
+
return $exit == 0 ? true : false
|
15
|
+
end
|
16
|
+
|
17
|
+
def c(cmdline, print_each_line=ALWAYS_PRINT_STDOUT)
|
18
|
+
$stdout.puts "+ #{cmdline}" if ALWAYS_ECHO_CMDLINE
|
19
|
+
Open3.popen3(cmdline) { |stdin, stdout, stderr, wait_thr|
|
20
|
+
all_out = ''
|
21
|
+
all_err = ''
|
22
|
+
while wait_thr.alive?
|
23
|
+
if !(stderr.ready? || stdout.ready?)
|
24
|
+
sleep(0.1)
|
25
|
+
next
|
26
|
+
end
|
27
|
+
current_out = stdout.read(stdout.nread)
|
28
|
+
current_err = stderr.read(stderr.nread)
|
29
|
+
all_out = all_out + current_out
|
30
|
+
all_err = all_err + current_err
|
31
|
+
$stdout.puts current_out if (print_each_line && !current_out.empty?)
|
32
|
+
$stderr.puts current_err if !current_err.empty?
|
33
|
+
end
|
34
|
+
status = wait_thr.value
|
35
|
+
# do it once more to catch everything that was buffered
|
36
|
+
current_out = stdout.read()
|
37
|
+
current_err = stderr.read()
|
38
|
+
all_out = all_out + current_out
|
39
|
+
all_err = all_err + current_err
|
40
|
+
$stdout.puts current_out if (print_each_line && !current_out.empty?)
|
41
|
+
$stderr.puts current_err if !current_err.empty?
|
42
|
+
$out = all_out
|
43
|
+
$err = all_err
|
44
|
+
$exit = status.exitstatus
|
45
|
+
raise StandardError.new("ERROR: #{cmdline.split()[0]} -> exit status #{status.exitstatus}") if (status.exitstatus != 0 && EXCEPTION_ON_NONZERO_STATUS)
|
46
|
+
all_out
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def log(message)
|
51
|
+
if $global_log.nil? and !LOGFILE.nil?
|
52
|
+
# remove the exceeding lines
|
53
|
+
if File.exists?(LOGFILE)
|
54
|
+
f = File.open(LOGFILE, "r")
|
55
|
+
lines = f.readlines()
|
56
|
+
retain_from = [lines.size, LOGFILE_MAX_LINES].min
|
57
|
+
retain_lines = lines[-retain_from..-1]
|
58
|
+
f.close()
|
59
|
+
else
|
60
|
+
retain_lines=[]
|
61
|
+
end
|
62
|
+
$global_log = File.open(LOGFILE, "w")
|
63
|
+
$global_log.write(retain_lines.join(''))
|
64
|
+
end
|
65
|
+
|
66
|
+
final_message = "[#{Time.now.to_s}] #{message}"
|
67
|
+
$global_log.puts(final_message) unless $global_log.nil?
|
68
|
+
$stdout.puts(final_message)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
include BashLike
|
74
|
+
# BASHINGTHEBASH END - write your things down there
|
75
|
+
|
76
|
+
USAGE = '
|
77
|
+
create: create new repository with passed remote, that should exist and be empty.
|
78
|
+
connect: connect to existing repository
|
79
|
+
sync: sync current repository
|
80
|
+
sync_tracked: sync all tracked repositories
|
81
|
+
cleanup_tracked: cleanup tracked repositories that don''t exist anymore
|
82
|
+
|
83
|
+
MISSING:
|
84
|
+
notification on conflict
|
85
|
+
re-track repository (if moved)
|
86
|
+
refactoring for better architecture
|
87
|
+
'
|
88
|
+
|
89
|
+
|
90
|
+
require 'socket'
|
91
|
+
require 'securerandom'
|
92
|
+
require 'yaml'
|
93
|
+
require 'tempfile'
|
94
|
+
|
95
|
+
module Dorkbox
|
96
|
+
|
97
|
+
CONFLICT_STRING='CONFLICT_MUST_MANUALLY_MERGE'
|
98
|
+
GITIGNORE='.gitignore'
|
99
|
+
DORKBOX_CONFIG_PATH = File.join(Dir.home, ".dorkbox.yml")
|
100
|
+
DORKBOX_CRONTAB_COMMENT = '# dorkbox sync cronjob'
|
101
|
+
|
102
|
+
def create_new_client_id
|
103
|
+
'dorkbox-' + Socket.gethostname() + "-" + SecureRandom.urlsafe_base64(5)
|
104
|
+
end
|
105
|
+
|
106
|
+
def align_client_ref_to_master(dorkbox_client_id)
|
107
|
+
`git update-ref refs/heads/#{dorkbox_client_id} master`
|
108
|
+
end
|
109
|
+
|
110
|
+
def configure_client_id(dorkbox_client_id)
|
111
|
+
`git config --local dorkbox.client-id #{dorkbox_client_id}`
|
112
|
+
end
|
113
|
+
|
114
|
+
def retrieve_client_id
|
115
|
+
dorkbox_client_id = c("git config --get dorkbox.client-id").strip()
|
116
|
+
end
|
117
|
+
|
118
|
+
def enable_dorkbox_cronjob(current_path=File.expand_path(__FILE__))
|
119
|
+
cron_start = "#{DORKBOX_CRONTAB_COMMENT} start\n"
|
120
|
+
cron_end = "#{DORKBOX_CRONTAB_COMMENT} end\n"
|
121
|
+
old_crontab = c('crontab -l 2>/dev/null || /bin/true')
|
122
|
+
old_crontab.sub!(/#{cron_start}.*?#{cron_end}/m, '')
|
123
|
+
|
124
|
+
tmp = Tempfile.new("dorkbox-temp")
|
125
|
+
if (old_crontab.size > 0) && (old_crontab[-1] != "\n")
|
126
|
+
old_crontab.concat("\n")
|
127
|
+
end
|
128
|
+
|
129
|
+
old_crontab.concat(cron_start).concat("*/5 * * * * #{current_path}\n").concat(cron_end)
|
130
|
+
tmp.puts(old_crontab)
|
131
|
+
tmp.flush()
|
132
|
+
`crontab #{tmp.path}`
|
133
|
+
tmp.close()
|
134
|
+
end
|
135
|
+
|
136
|
+
# we should lock the config file on this.
|
137
|
+
def track
|
138
|
+
begin
|
139
|
+
cfg = YAML.load_file(DORKBOX_CONFIG_PATH)
|
140
|
+
rescue Errno::ENOENT
|
141
|
+
cfg = {:track => []}
|
142
|
+
end
|
143
|
+
cfg[:track].push(File.expand_path(Dir.pwd))
|
144
|
+
cfg[:track].uniq!
|
145
|
+
File.open(DORKBOX_CONFIG_PATH, 'w') { |f| f.write(cfg.to_yaml) }
|
146
|
+
end
|
147
|
+
|
148
|
+
def cleanup_tracked
|
149
|
+
begin
|
150
|
+
cfg = YAML.load_file(DORKBOX_CONFIG_PATH)
|
151
|
+
rescue Errno::ENOENT
|
152
|
+
return
|
153
|
+
end
|
154
|
+
# TODO: check for dorkbox-enabled dir, e.g. try retrieving client id
|
155
|
+
cfg[:track].select! { |d| Dir.exists?(d) }
|
156
|
+
File.open(DORKBOX_CONFIG_PATH, 'w') { |f| f.write(cfg.to_yaml) }
|
157
|
+
end
|
158
|
+
|
159
|
+
def sync_tracked
|
160
|
+
begin
|
161
|
+
cfg = YAML.load_file(DORKBOX_CONFIG_PATH)
|
162
|
+
rescue Errno::ENOENT
|
163
|
+
return
|
164
|
+
end
|
165
|
+
cfg[:track].each { |d| Dir.chdir(d) { sync() } }
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def create(dorkbox_remote)
|
170
|
+
log "Will create new dorkbox-enabled repository in local directory. Remote #{dorkbox_remote} should exist and be empty."
|
171
|
+
if Dir.exists?('.git')
|
172
|
+
raise StandardError.new("git repository found.")
|
173
|
+
end
|
174
|
+
`git init`
|
175
|
+
File.open(GITIGNORE, 'a') { |f|
|
176
|
+
f.puts(CONFLICT_STRING)
|
177
|
+
}
|
178
|
+
`git remote add dorkbox #{dorkbox_remote}`
|
179
|
+
`git add #{GITIGNORE}`
|
180
|
+
`git commit -m 'enabling dorkbox'`
|
181
|
+
dorkbox_client_id = create_new_client_id
|
182
|
+
configure_client_id(dorkbox_client_id)
|
183
|
+
align_client_ref_to_master(dorkbox_client_id)
|
184
|
+
`git push -u dorkbox master #{dorkbox_client_id}`
|
185
|
+
track()
|
186
|
+
log "New dorkbox enabled with remote #{dorkbox_remote}"
|
187
|
+
dorkbox_client_id
|
188
|
+
end
|
189
|
+
|
190
|
+
def connect(dorkbox_remote)
|
191
|
+
log "Will create new git repo in local directory and connect to remote existing dorkbox repository #{dorkbox_remote}"
|
192
|
+
if Dir.exists?('.git')
|
193
|
+
raise StandardError.new("git repository found.")
|
194
|
+
end
|
195
|
+
`git init`
|
196
|
+
`git remote add dorkbox #{dorkbox_remote}`
|
197
|
+
`git fetch --all`
|
198
|
+
`git checkout master`
|
199
|
+
# TODO: check for hostname duplication and/or autogenerate client ids
|
200
|
+
dorkbox_client_id = create_new_client_id
|
201
|
+
configure_client_id(dorkbox_client_id)
|
202
|
+
align_client_ref_to_master(dorkbox_client_id)
|
203
|
+
`git push -u dorkbox master #{dorkbox_client_id}`
|
204
|
+
track()
|
205
|
+
dorkbox_client_id
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
def sync
|
210
|
+
if File.exists?(CONFLICT_STRING)
|
211
|
+
log "Conflict found, not syncing."
|
212
|
+
raise StandardError.new("Conflict found, not syncing.")
|
213
|
+
end
|
214
|
+
`git fetch --all`
|
215
|
+
`git add -A`
|
216
|
+
any_change = c("git diff --staged").strip()
|
217
|
+
if !any_change.empty?
|
218
|
+
`git commit -m "Automatic dorkbox commit"`
|
219
|
+
end
|
220
|
+
begin
|
221
|
+
dorkbox_client_id = retrieve_client_id
|
222
|
+
`git merge --ff-only dorkbox/master`
|
223
|
+
align_client_ref_to_master(dorkbox_client_id)
|
224
|
+
`git push dorkbox master #{dorkbox_client_id}`
|
225
|
+
rescue
|
226
|
+
# TODO: check which error actually happens and think about
|
227
|
+
# a solving strategy.
|
228
|
+
log "Error while syncing, stopping until solved."
|
229
|
+
FileUtils.touch(CONFLICT_STRING)
|
230
|
+
raise StandardError.new("Conflict found, syncing stopped.")
|
231
|
+
end
|
232
|
+
log "sync succeeded"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
|
242
|
+
|
243
|
+
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dorkbox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alan Franzoni
|
9
|
+
autorequire:
|
10
|
+
bindir: executables
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-05-25 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: dead simple personal file syncing
|
15
|
+
email: username@franzoni.eu
|
16
|
+
executables:
|
17
|
+
- dorkbox
|
18
|
+
- test
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- lib/dorkbox.rb
|
23
|
+
- executables/dorkbox
|
24
|
+
- executables/test
|
25
|
+
homepage: https://github.com/alanfranz/dorkbox
|
26
|
+
licenses:
|
27
|
+
- Apache-2.0
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 1.8.23
|
47
|
+
signing_key:
|
48
|
+
specification_version: 3
|
49
|
+
summary: ! 'dorkbox: dead simple personal file syncing'
|
50
|
+
test_files: []
|