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.
@@ -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)
@@ -0,0 +1,7 @@
1
+ require 'minitest/unit'
2
+ require 'minitest/spec'
3
+ require 'minitest/mock'
4
+
5
+ require 'dorkbox_test'
6
+
7
+ MiniTest::Unit.autorun
@@ -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: []