skypager 0.0.2

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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/Gemfile +9 -0
  4. data/LICENSE.txt +22 -0
  5. data/Rakefile +21 -0
  6. data/bin/skypager +17 -0
  7. data/examples/.gitignore +4 -0
  8. data/examples/blog-site/.gitignore +18 -0
  9. data/examples/blog-site/.pryrc +4 -0
  10. data/examples/blog-site/Gemfile +8 -0
  11. data/examples/blog-site/config.rb +17 -0
  12. data/examples/blog-site/data/dropbox.json +1 -0
  13. data/examples/blog-site/source/images/background.png +0 -0
  14. data/examples/blog-site/source/images/middleman.png +0 -0
  15. data/examples/blog-site/source/index.html.erb +10 -0
  16. data/examples/blog-site/source/javascripts/all.js +1 -0
  17. data/examples/blog-site/source/layouts/layout.erb +19 -0
  18. data/examples/blog-site/source/posts/introduction-to-skypager.html.md +23 -0
  19. data/examples/blog-site/source/posts/skypager-and-dnsimple-and-amazon-web-services-combo.html.md +9 -0
  20. data/examples/blog-site/source/stylesheets/all.css +55 -0
  21. data/examples/blog-site/source/stylesheets/normalize.css +375 -0
  22. data/examples/gallery-site/.gitignore +18 -0
  23. data/examples/gallery-site/.pryrc +4 -0
  24. data/examples/gallery-site/Gemfile +11 -0
  25. data/examples/gallery-site/config.rb +38 -0
  26. data/examples/gallery-site/data/dropbox.json +1 -0
  27. data/examples/gallery-site/data/galleries.json +1 -0
  28. data/examples/gallery-site/source/gallery.html.erb +7 -0
  29. data/examples/gallery-site/source/images/background.png +0 -0
  30. data/examples/gallery-site/source/images/galleries/cristian-gallery-1/001.jpg +0 -0
  31. data/examples/gallery-site/source/images/galleries/cristian-gallery-1/002.jpg +0 -0
  32. data/examples/gallery-site/source/images/galleries/cristian-gallery-1/003.jpg +0 -0
  33. data/examples/gallery-site/source/images/galleries/cristian-gallery-1/004.jpg +0 -0
  34. data/examples/gallery-site/source/images/galleries/luca-gallery-1/001.jpg +0 -0
  35. data/examples/gallery-site/source/images/galleries/luca-gallery-1/002.JPG +0 -0
  36. data/examples/gallery-site/source/images/galleries/luca-gallery-1/003.jpg +0 -0
  37. data/examples/gallery-site/source/images/galleries/luca-gallery-1/004.JPG +0 -0
  38. data/examples/gallery-site/source/images/middleman.png +0 -0
  39. data/examples/gallery-site/source/index.html.erb +10 -0
  40. data/examples/gallery-site/source/javascripts/all.js +1 -0
  41. data/examples/gallery-site/source/layouts/layout.erb +20 -0
  42. data/examples/gallery-site/source/stylesheets/all.css +0 -0
  43. data/examples/gallery-site/source/stylesheets/normalize.css +375 -0
  44. data/examples/gallery-site/source/tutorial.md +151 -0
  45. data/lib/skypager.rb +92 -0
  46. data/lib/skypager/build_server.rb +17 -0
  47. data/lib/skypager/cli/commands/config.rb +58 -0
  48. data/lib/skypager/cli/commands/create.rb +98 -0
  49. data/lib/skypager/cli/commands/deploy.rb +30 -0
  50. data/lib/skypager/cli/commands/edit.rb +32 -0
  51. data/lib/skypager/cli/commands/list.rb +12 -0
  52. data/lib/skypager/cli/commands/setup.rb +124 -0
  53. data/lib/skypager/cli/commands/sync.rb +18 -0
  54. data/lib/skypager/configuration.rb +173 -0
  55. data/lib/skypager/data.rb +8 -0
  56. data/lib/skypager/data/excel_spreadsheet.rb +8 -0
  57. data/lib/skypager/data/google_spreadsheet.rb +225 -0
  58. data/lib/skypager/data/request.rb +12 -0
  59. data/lib/skypager/data/source.rb +171 -0
  60. data/lib/skypager/data/source_routes_proxy.rb +30 -0
  61. data/lib/skypager/dns.rb +65 -0
  62. data/lib/skypager/extension.rb +203 -0
  63. data/lib/skypager/middleman/commands/data.rb +0 -0
  64. data/lib/skypager/middleman/commands/deploy.rb +0 -0
  65. data/lib/skypager/middleman/commands/sync.rb +0 -0
  66. data/lib/skypager/site.rb +208 -0
  67. data/lib/skypager/sync.rb +23 -0
  68. data/lib/skypager/sync/amazon.rb +171 -0
  69. data/lib/skypager/sync/dropbox.rb +173 -0
  70. data/lib/skypager/sync/dropbox/delta.rb +67 -0
  71. data/lib/skypager/sync/folder.rb +235 -0
  72. data/lib/skypager/sync/google.rb +143 -0
  73. data/lib/skypager/tar.rb +77 -0
  74. data/lib/skypager/version.rb +3 -0
  75. data/skypager.gemspec +40 -0
  76. data/spec/lib/skypager/configuration_spec.rb +5 -0
  77. data/spec/lib/skypager/data_spec.rb +5 -0
  78. data/spec/lib/skypager/site_spec.rb +5 -0
  79. data/spec/spec_helper.rb +14 -0
  80. data/spec/support/json_helper.rb +7 -0
  81. metadata +383 -0
@@ -0,0 +1,67 @@
1
+ module Skypager
2
+ module Sync
3
+ class DropboxDelta
4
+
5
+ attr_accessor :client,
6
+ :data,
7
+ :cursor,
8
+ :entries,
9
+ :path_prefix
10
+
11
+ def initialize(client, cursor, path_prefix=nil)
12
+ @client = client
13
+ @cursor = cursor
14
+ @path_prefix = path_prefix
15
+ end
16
+
17
+ def processed!
18
+ # TODO
19
+ # Should update cursor
20
+ end
21
+
22
+ def entries
23
+ return @entries if @entries
24
+ fetch
25
+ @entries
26
+ end
27
+
28
+ def _dropbox_delta at=nil
29
+ at ||= cursor
30
+ response = client.delta(at, path_prefix)
31
+ self.cursor = response["cursor"]
32
+ response
33
+ end
34
+
35
+ def data
36
+ @data ||= fetch
37
+ end
38
+
39
+ def on_reset path_prefix, cursor
40
+ end
41
+
42
+ def fetch
43
+ return @response if @response
44
+
45
+ response = _dropbox_delta
46
+
47
+ if response["reset"] == true
48
+ on_reset(path_prefix, cursor)
49
+ end
50
+
51
+ self.entries = Hashie::Mash.new({})
52
+
53
+ response["entries"].each do |entry|
54
+ path, meta = entry
55
+ self.entries[path] = meta
56
+ end
57
+
58
+ if response["has_more"] == true
59
+ # TODO Implement
60
+ end
61
+
62
+ @response = response
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,235 @@
1
+ module Skypager
2
+ module Sync
3
+ class Folder
4
+ attr_accessor :options,
5
+ :cursor,
6
+ :app
7
+
8
+ def initialize(options={})
9
+ @options = options
10
+ @cursor = options.fetch(:cursor, nil)
11
+ @app = options[:app]
12
+ end
13
+
14
+ # Used to generate the config line in the
15
+ # middleman config.rb
16
+ def config_line
17
+ "dropbox_sync('#{ relative_local_path }','#{ remote_path }')"
18
+ end
19
+
20
+ # Gets the reference to the dropbox folder settings for the app
21
+ # which allows us to persist info about the sync state of the folder
22
+ def folder_settings
23
+ data = app.data.dropbox
24
+ data[:folders][options[:folder]].tap {|h| h && h.symbolize_keys! }
25
+ end
26
+
27
+ def cursor= value
28
+ folder_settings[:cursor] = value
29
+ save_folder_settings
30
+ end
31
+
32
+ def save_folder_settings
33
+ data = app.data.dropbox.dup
34
+
35
+ Pathname(app.data_dir).join("dropbox.json").open('w+') do |fh|
36
+ fh.write(data.to_json)
37
+ end
38
+ end
39
+
40
+ def auto_prefix
41
+ options[:site_name]
42
+ end
43
+
44
+ def dropbox?
45
+ options[:type] == "dropbox"
46
+ end
47
+
48
+ def google?
49
+ options[:type] == "google"
50
+ end
51
+
52
+ def sync
53
+ return dropbox_pull if dropbox?
54
+ return google_sync if google?
55
+ end
56
+
57
+ def dropbox_pull
58
+ delta = dropbox_delta
59
+
60
+ delta.entries.each do |entry|
61
+ local = matching_local_path_for(entry.path)
62
+
63
+ if entry.is_deleted
64
+ puts "Removing #{ local }"
65
+ local.rmtree
66
+ elsif entry.is_dir == true
67
+ unless local.exist?
68
+ puts "Creating #{ local } directory"
69
+ local.mkdir
70
+ end
71
+ elsif entry.is_dir == false || entry.is_dir == nil
72
+ puts "Downloading #{ entry.size } bytes to #{ local }"
73
+ local.open('wb+') do |fh|
74
+ fh.write(entry.download)
75
+ end
76
+ end
77
+ end
78
+
79
+ self.cursor = delta.cursor
80
+ end
81
+
82
+ def google_sync
83
+ # TODO
84
+ # Implement google drive folder cloning
85
+ end
86
+
87
+ def newer_local_files
88
+ remotes = all_remote_files
89
+
90
+ if dropbox?
91
+ all_local_files.select do |e|
92
+ remote = remotes.find do |x|
93
+ "#{x[:path]}".match(/#{ e[:path] }$/)
94
+ end
95
+
96
+ remote.nil? || (remote[:mtime] < e[:mtime])
97
+ end
98
+ end
99
+ end
100
+
101
+ def all_dropbox_files
102
+ h = lambda do |node|
103
+ base = [{
104
+ path: node.path,
105
+ mtime: DateTime.parse(node.modified).to_i
106
+ }]
107
+
108
+ base += node.ls.map(&h) if node.is_dir
109
+
110
+ base
111
+ end
112
+
113
+ dropbox_folder_files.map(&h).flatten
114
+ end
115
+
116
+ def dropbox_folder_files
117
+ Array((dropbox_folder rescue nil).try(:ls))
118
+ end
119
+
120
+ def matching_local_path_for(remote)
121
+ if sandboxed?
122
+ parts = remote.to_s.split("/")
123
+ parts.shift
124
+ part = parts.join("/")
125
+ root.join(part)
126
+ else
127
+ parts = remote.to_s.split("/")
128
+ parts.shift
129
+ part = parts.join("/")
130
+ local_path.join(part)
131
+ end
132
+ end
133
+
134
+ def root
135
+ Pathname(options[:root] || Dir.pwd)
136
+ end
137
+
138
+ def all_local_files
139
+ h = lambda do |path|
140
+ base = [{
141
+ path: path.relative_path_from(local_path).to_s,
142
+ realpath: path,
143
+ mtime: path.mtime.to_i
144
+ }]
145
+
146
+ base += path.children.map(&h) if path.directory?
147
+
148
+ base
149
+ end
150
+
151
+ local_path.children.map(&h).flatten
152
+ end
153
+
154
+ def all_remote_files
155
+ if dropbox?
156
+ all_dropbox_files
157
+ else
158
+
159
+ end
160
+ end
161
+
162
+ def relative_local_path
163
+ local_path.relative_path_from(root).to_s
164
+ end
165
+
166
+ def local_path
167
+ root.join(options[:local_path]).tap do |folder|
168
+ folder.mkdir unless folder.exist?
169
+ end
170
+ end
171
+
172
+ def synced
173
+ push_sync
174
+ self
175
+ end
176
+
177
+ def push_sync
178
+ return dropbox_push_sync if dropbox?
179
+ return google_push_sync if google?
180
+ end
181
+
182
+ def dropbox_push_sync
183
+ api = Skypager.dropbox.api
184
+
185
+ newer_local_files.each do |entry|
186
+ target = "#{ path_prefix }/#{ entry[:path] }"
187
+ target = target.gsub(/^\//,'')
188
+ api.upload(target, entry[:realpath].read, overwrite: true)
189
+ end
190
+ end
191
+
192
+ def google_push_sync
193
+
194
+ end
195
+
196
+ def dropbox_folder
197
+ Skypager.dropbox.find(path_prefix)
198
+ end
199
+
200
+ def remote_path
201
+ options[:remote_path].to_s
202
+ end
203
+
204
+ def sandboxed?
205
+ Skypager.config.dropbox_app_type == "sandbox"
206
+ end
207
+
208
+ # Which path do we scope our delta calls to?
209
+ def path_prefix
210
+ if sandboxed?
211
+ if remote_path.match(/^\/#{ options[:site_name] }/)
212
+ remote_path
213
+ else
214
+ "/#{ options[:site_name] }/#{ remote_path.gsub(/^\//,'') }"
215
+ end
216
+ else
217
+ unless remote_path.match(/^\//)
218
+ return "/#{ remote_path }"
219
+ end
220
+
221
+ remote_path
222
+ end
223
+ end
224
+
225
+ def has_remote_changes?
226
+ dropbox_delta.entries.length > 0
227
+ end
228
+
229
+ def dropbox_delta
230
+ @dropbox_delta ||= Skypager.dropbox.delta(cursor, path_prefix: path_prefix)
231
+ end
232
+
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,143 @@
1
+ module Skypager
2
+ module Sync
3
+ class Google
4
+ include Singleton
5
+
6
+ def self.method_missing(meth, *args, &block)
7
+ if client.respond_to?(meth)
8
+ return client.send(meth, *args, &block)
9
+ end
10
+
11
+ super
12
+ end
13
+
14
+ def self.client(options={})
15
+ @client ||= begin
16
+ instance.with_options(options)
17
+ end
18
+ end
19
+
20
+ def refreshable?
21
+ has_application_keys? && has_refresh_token?
22
+ end
23
+
24
+ # Runs through an interactive session where we get the
25
+ # necessary tokens needed to integrate with google drive.
26
+ def setup(options={})
27
+ get_application_keys unless has_application_keys?
28
+
29
+ if options[:client_id]
30
+ Skypager.config.set "google_client_id", options[:client_id]
31
+ end
32
+
33
+ if options[:client_secret]
34
+ Skypager.config.set "google_client_secret", options[:client_secret]
35
+ end
36
+
37
+ if has_refresh_token?
38
+ refresh_access_token!
39
+ else
40
+ Launchy.open(auth_client.authorization_uri)
41
+ say("\n1. Open this page:\n%s\n\n" % auth_client.authorization_uri)
42
+ auth_client.code = ask("2. Enter the authorization code shown in the page: ", String)
43
+ auth_client.fetch_access_token!
44
+ Skypager.config.set "google_refresh_token", auth_client.refresh_token
45
+ Skypager.config.set "google_access_token", auth_client.access_token
46
+ end
47
+ end
48
+
49
+ def session
50
+ api
51
+ end
52
+
53
+ def api
54
+ @api ||= begin
55
+ refresh_access_token!
56
+ GoogleDrive.login_with_oauth(Skypager.config.google_access_token)
57
+ end
58
+ end
59
+
60
+ def method_missing meth, *args, &block
61
+ if api.respond_to?(meth)
62
+ return api.send(meth, *args, &block)
63
+ end
64
+
65
+ super
66
+ end
67
+
68
+ def authorize(token, secret)
69
+ @api = nil if @api
70
+ options[:token] = token
71
+ options[:secret] = secret
72
+ self
73
+ end
74
+
75
+ def options
76
+ @options ||= {}
77
+ end
78
+
79
+ def with_options(opts={})
80
+ options.merge!(opts)
81
+ self
82
+ end
83
+
84
+ def has_application_keys?
85
+ (Skypager.config.google_client_id.to_s.length > 0 && Skypager.config.google_client_secret.to_s.length > 0)
86
+ end
87
+
88
+ def get_application_keys
89
+ unless Skypager.config.google_client_id.to_s.length > 0
90
+ google_client_id = ask("What is the Google Client ID?", String)
91
+ Skypager.config.set "google_client_id", google_client_id
92
+ end
93
+
94
+ unless Skypager.config.google_client_secret.to_s.length > 0
95
+ google_client_secret = ask("What is the Google Client Secret?", String)
96
+ Skypager.config.set "google_client_secret", google_client_secret
97
+ end
98
+ end
99
+
100
+ def auth_client
101
+ return @auth_client if @auth_client
102
+
103
+ client = ::Google::APIClient.new(
104
+ :application_name => "google_drive Ruby library",
105
+ :application_version => "0.3.11"
106
+ )
107
+
108
+ client_id = "452925651630-egr1f18o96acjjvphpbbd1qlsevkho1d.apps.googleusercontent.com"
109
+ client_secret = "1U3-Krii5x1oLPrwD5zgn-ry"
110
+
111
+ @auth_client = auth = client.authorization
112
+ auth.client_id = client_id #Skypager.config.google_client_id
113
+ auth.client_secret = client_secret #Skypager.config.google_client_secret
114
+ auth.scope =
115
+ "https://www.googleapis.com/auth/drive " +
116
+ "https://spreadsheets.google.com/feeds/ " +
117
+ "https://docs.google.com/feeds/ " +
118
+ "https://docs.googleusercontent.com/"
119
+
120
+ auth.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
121
+
122
+ auth
123
+ end
124
+
125
+ def refresh_token
126
+ Skypager.config.google_refresh_token.to_s
127
+ end
128
+
129
+ def has_refresh_token?
130
+ refresh_token.length > 0
131
+ end
132
+
133
+ def refresh_access_token!
134
+ if has_refresh_token?
135
+ auth_client.refresh_token = refresh_token
136
+ auth_client.fetch_access_token!
137
+ Skypager.config.set "google_access_token", auth_client.access_token
138
+ end
139
+ end
140
+
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package'
3
+ require 'zlib'
4
+ require 'fileutils'
5
+
6
+ module Util
7
+ module Tar
8
+ # Creates a tar file in memory recursively
9
+ # from the given path.
10
+ #
11
+ # Returns a StringIO whose underlying String
12
+ # is the contents of the tar file.
13
+ def tar(path)
14
+ tarfile = StringIO.new("")
15
+ Gem::Package::TarWriter.new(tarfile) do |tar|
16
+ Dir[File.join(path, "**/*")].each do |file|
17
+ mode = File.stat(file).mode
18
+ relative_file = file.sub /^#{Regexp::escape path}\/?/, ''
19
+
20
+ if File.directory?(file)
21
+ tar.mkdir relative_file, mode
22
+ else
23
+ tar.add_file relative_file, mode do |tf|
24
+ File.open(file, "rb") { |f| tf.write f.read }
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ tarfile.rewind
31
+ tarfile
32
+ end
33
+
34
+ # gzips the underlying string in the given StringIO,
35
+ # returning a new StringIO representing the
36
+ # compressed file.
37
+ def gzip(tarfile)
38
+ gz = StringIO.new("")
39
+ z = Zlib::GzipWriter.new(gz)
40
+ z.write tarfile.string
41
+ z.close # this is necessary!
42
+
43
+ # z was closed to write the gzip footer, so
44
+ # now we need a new StringIO
45
+ StringIO.new gz.string
46
+ end
47
+
48
+ # un-gzips the given IO, returning the
49
+ # decompressed version as a StringIO
50
+ def ungzip(tarfile)
51
+ z = Zlib::GzipReader.new(tarfile)
52
+ unzipped = StringIO.new(z.read)
53
+ z.close
54
+ unzipped
55
+ end
56
+
57
+ # untars the given IO into the specified
58
+ # directory
59
+ def untar(io, destination)
60
+ Gem::Package::TarReader.new io do |tar|
61
+ tar.each do |tarfile|
62
+ destination_file = File.join destination, tarfile.full_name
63
+
64
+ if tarfile.directory?
65
+ FileUtils.mkdir_p destination_file
66
+ else
67
+ destination_directory = File.dirname(destination_file)
68
+ FileUtils.mkdir_p destination_directory unless File.directory?(destination_directory)
69
+ File.open destination_file, "wb" do |f|
70
+ f.print tarfile.read
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end