gallery-remote 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ [License](http://mwalker.info/license.html)
data/README ADDED
@@ -0,0 +1,3 @@
1
+ I'm expanding the gallery-remote code for use in another project. I don't
2
+ intend to make it feature-complete or package it well, just make it serviceable
3
+ for my other work.
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/gempackagetask'
4
+
5
+ $:.unshift File.join(File.dirname(__FILE__), '/lib')
6
+ require 'gallery'
7
+
8
+ PKG_NAME = 'gallery-remote'
9
+ PKG_VERSION = Gallery::VERSION
10
+
11
+ LIB_FILES = Dir.glob('lib/**/*')
12
+ RELEASE_FILES = [ 'Rakefile', 'README', 'LICENSE.md' ] + LIB_FILES
13
+
14
+ task :default => [ :package ]
15
+
16
+ spec = Gem::Specification.new do |spec|
17
+ spec.name = PKG_NAME
18
+ spec.version = PKG_VERSION
19
+ spec.summary = 'A Ruby client for the Gallery2 photo gallery system'
20
+ spec.description = 'gallery-remote is an implementation of the Gallery Remote protocol in Ruby.'
21
+ spec.authors = ['Carl Leiby', 'Matt Walker']
22
+ spec.email = 'matt.r.walker@gmail.com'
23
+ spec.homepage = 'http://github.com/mrwalker/gallery-remote'
24
+ spec.files = RELEASE_FILES
25
+ spec.rubyforge_project = PKG_NAME
26
+ end
27
+
28
+ Rake::GemPackageTask.new(spec) do |package|
29
+ package.need_tar = true
30
+ end
@@ -0,0 +1,59 @@
1
+ #
2
+ # cookie_jar.rb
3
+ #
4
+ # Created on Sep 19, 2007, 7:39:33 PM
5
+ require 'date'
6
+
7
+ class CookieJar
8
+ def initialize
9
+ @cookies = []
10
+ end
11
+
12
+ def add(cookie_list)
13
+ return if ! cookie_list
14
+ cookie_list.each do |cookie_string|
15
+ cookie = Cookie.new(cookie_string)
16
+ if ! cookie.expired
17
+ @cookies = @cookies.reject {|c| c.name==cookie.name}
18
+ @cookies << cookie
19
+ end
20
+ end
21
+ end
22
+
23
+ def cookies
24
+ return if @cookies.length == 0
25
+ @cookies.map { |cookie| cookie.to_s }.join("; ")
26
+ end
27
+ end
28
+
29
+ class Cookie
30
+ attr_accessor :name, :value, :expiration
31
+
32
+ def initialize(cookie_str)
33
+ cookie_str.split(/;\s?/).each_with_index do |c,i|
34
+ if i==0
35
+ @name, *values = c.split("=")
36
+ @value = values.join("=")
37
+ else
38
+ attr_name, *attr_values = c.split("=")
39
+ attr_value = attr_values.join("=")
40
+ if attr_name =~ /expires/i
41
+ @expiration = attr_value
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def to_s
48
+ "#{@name}=#{@value}"
49
+ end
50
+
51
+ def expired
52
+ if @expiration
53
+ @expiration.sub!(/^([a-zA-Z]+,)(\d)/) { |s| "#{$1} #{$2}" }
54
+ expiration_date = DateTime.parse( @expiration, true )
55
+ return expiration_date < DateTime.now
56
+ end
57
+ false
58
+ end
59
+ end
@@ -0,0 +1,8 @@
1
+ require 'gallery/remote'
2
+ require 'gallery/gallery'
3
+ require 'gallery/album'
4
+ require 'gallery/image'
5
+
6
+ module Gallery
7
+ VERSION = '0.0.1'
8
+ end
@@ -0,0 +1,64 @@
1
+ module Gallery
2
+ class Album
3
+ attr_accessor :remote, :params
4
+
5
+ def initialize(remote, params)
6
+ @remote, @params = remote, params
7
+ end
8
+
9
+ def properties(params = {})
10
+ @remote.album_properties(name, params)
11
+ end
12
+
13
+ def images(params = {})
14
+ response = @remote.fetch_album_images(name, params)
15
+ image_params = response.keys.inject([]) do |image_params, key|
16
+ next image_params unless key =~ /image\.(.*)\.(\d+)/
17
+ _, param, index = key.match(/image\.(.*)\.(\d+)/).to_a
18
+ index = index.to_i
19
+ image_params[index] ||= {}
20
+ image_params[index][param] = response[key]
21
+ image_params
22
+ end.compact # Keys are 1-based; remove first element
23
+ image_params.map do |params|
24
+ image = Image.new(@remote, params)
25
+ yield image if block_given?
26
+ image
27
+ end
28
+ end
29
+
30
+ def add_item(file_name, params = {})
31
+ @remote.add_item(name, file_name, params)
32
+ end
33
+
34
+ def add_album(title, params = {})
35
+ @remote.new_album(name, { :newAlbumName => title_to_name(title), :newAlbumTitle => title }.merge(params))
36
+ end
37
+
38
+ def name
39
+ @params['name']
40
+ end
41
+
42
+ def title
43
+ @params['title']
44
+ end
45
+
46
+ def parent
47
+ @params['parent']
48
+ end
49
+
50
+ def to_s
51
+ "Album #{name}: #{title}"
52
+ end
53
+
54
+ private
55
+
56
+ def title_to_name(title)
57
+ name = title.dup
58
+ name.downcase!
59
+ name.gsub!(/[,'\-:]/, '') # Remove illegal characters
60
+ name.gsub!(/\s+/, '_') # Join words with underscores
61
+ name
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ module Gallery
2
+ class Gallery
3
+ attr_accessor :remote
4
+
5
+ def initialize(url, &block)
6
+ @remote = Remote.new(url)
7
+ instance_eval(&block) if block_given?
8
+ end
9
+
10
+ def login(user, pass)
11
+ @remote.login(user, pass)
12
+ end
13
+
14
+ def albums(params = {})
15
+ response = @remote.fetch_albums_prune
16
+ album_params = response.keys.inject([]) do |album_params, key|
17
+ next album_params unless key =~ /album\.(.*)\.(\d+)/
18
+ _, param, index = key.match(/album\.(.*)\.(\d+)/).to_a
19
+ index = index.to_i
20
+ album_params[index] ||= {}
21
+ album_params[index][param] = response[key]
22
+ album_params
23
+ end.compact # Keys are 1-based; remove first element
24
+ album_params.map do |params|
25
+ album = Album.new(@remote, params)
26
+ yield album if block_given?
27
+ album
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ module Gallery
2
+ class Image
3
+ attr_accessor :remote, :params
4
+
5
+ def initialize(remote, params = {})
6
+ @remote, @params = remote, params
7
+ end
8
+
9
+ def properties(params = {})
10
+ @remote.image_properites(name, params)
11
+ end
12
+
13
+ def name
14
+ @params['name']
15
+ end
16
+
17
+ def title
18
+ @params['title']
19
+ end
20
+
21
+ def caption
22
+ @params['caption']
23
+ end
24
+
25
+ def to_s
26
+ "Image #{name}: #{title}"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,233 @@
1
+ require 'cgi'
2
+ require 'net/http'
3
+ require 'cookie_jar'
4
+
5
+ module Gallery
6
+ class Remote
7
+ attr_accessor :last_response, :status, :status_text
8
+
9
+ GR_STAT_SUCCESS = 0
10
+ PROTO_MAJ_VER_INVAL = 101
11
+ PROTO_MIN_VER_INVAL = 102
12
+ PROTO_VER_FMT_INVAL = 103
13
+ PROTO_VER_MISSING = 104
14
+ PASSWD_WRONG = 201
15
+ LOGIN_MISSING = 202
16
+ UNKNOWN_CMD = 301
17
+ NO_ADD_PERMISSION = 401
18
+ NO_FILENAME = 402
19
+ UPLOAD_PHOTO_FAIL = 403
20
+ NO_WRITE_PERMISSION = 404
21
+ NO_VIEW_PERMISSIO = 405
22
+ NO_CREATE_ALBUM_PERMISSION = 501
23
+ CREATE_ALBUM_FAILED = 502
24
+ MOVE_ALBUM_FAILED = 503
25
+ ROTATE_IMAGE_FAILED = 504
26
+ STATUS_DESCRIPTIONS = {
27
+ GR_STAT_SUCCESS => 'The command the client sent in the request completed successfully. The data (if any) in the response should be considered valid.',
28
+ PROTO_MAJ_VER_INVAL => 'The protocol major version the client is using is not supported.',
29
+ PROTO_MIN_VER_INVAL => 'The protocol minor version the client is using is not supported.',
30
+ PROTO_VER_FMT_INVAL => 'The format of the protocol version string the client sent in the request is invalid.',
31
+ PROTO_VER_MISSING => 'The request did not contain the required protocol_version key.',
32
+ PASSWD_WRONG => 'The password and/or username the client send in the request is invalid.',
33
+ LOGIN_MISSING => 'The client used the login command in the request but failed to include either the username or password (or both) in the request.',
34
+ UNKNOWN_CMD => 'The value of the cmd key is not valid.',
35
+ NO_ADD_PERMISSION => 'The user does not have permission to add an item to the gallery.',
36
+ NO_FILENAME => 'No filename was specified.',
37
+ UPLOAD_PHOTO_FAIL => 'The file was received, but could not be processed or added to the album.',
38
+ NO_WRITE_PERMISSION => 'No write permission to destination album.',
39
+ NO_VIEW_PERMISSIO => 'No view permission for this image.',
40
+ NO_CREATE_ALBUM_PERMISSION => 'A new album could not be created because the user does not have permission to do so.',
41
+ CREATE_ALBUM_FAILED => 'A new album could not be created, for a different reason (name conflict).',
42
+ MOVE_ALBUM_FAILED => 'The album could not be moved.',
43
+ ROTATE_IMAGE_FAILED => 'The image could not be rotated'
44
+ }
45
+
46
+ @@supported_types = {
47
+ '.avi' => 'video/x-msvideo',
48
+ '.bmp' => 'image/bmp',
49
+ '.gif' => 'image/gif',
50
+ '.jpe' => 'image/jpeg',
51
+ '.jpg' => 'image/jpeg',
52
+ '.jpeg' => 'image/jpeg',
53
+ '.mov' => 'video/quicktime',
54
+ '.qt' => 'video/quicktime',
55
+ '.mp4' => 'video/mp4',
56
+ '.tif' => 'image/tiff',
57
+ '.tiff' => 'image/tiff'
58
+ }
59
+
60
+ def self.supported_type?(extension)
61
+ @@supported_types.has_key?(extension)
62
+ end
63
+
64
+ def initialize(url)
65
+ @uri = URI.parse(url)
66
+ @base_params = {
67
+ 'g2_controller' => 'remote:GalleryRemote',
68
+ 'g2_form[protocol_version]' => '2.9'
69
+ }
70
+ @cookie_jar = CookieJar.new
71
+ @boundary = '7d21f123d00c4'
72
+ end
73
+
74
+ # cmd=login
75
+ # protocol_version=2.0
76
+ # uname=gallery-user-name
77
+ # password=cleartext-password
78
+ def login(uname, password, params = {})
79
+ params = { :cmd => 'login', :uname => uname, :password => password }.merge(params)
80
+ send_request(params)
81
+ end
82
+
83
+ # cmd=fetch-albums-prune
84
+ # protocol_version=2.2
85
+ # no_perms=yes/no [optional, G2 since 2.9]
86
+ def fetch_albums_prune(params = {})
87
+ params = { :cmd => 'fetch-albums-prune', :no_perms => 'y' }.merge(params)
88
+ send_request(params)
89
+ end
90
+
91
+ # cmd=add-item
92
+ # protocol_version=2.0
93
+ # set_albumName=album name
94
+ # userfile=user-file
95
+ # userfile_name=file-name
96
+ # caption=caption [optional]
97
+ # force_filename=force-filename [optional]
98
+ # auto_rotate=yes/no [optional, since 2.5]
99
+ # extrafield.fieldname=fieldvalue [optional, since 2.3]
100
+ def add_item(set_albumName, userfile_name, params = {})
101
+ params = { :cmd => 'add-item', :set_albumName => set_albumName, :userfile_name => userfile_name }.merge(params)
102
+ send_request(params)
103
+ end
104
+
105
+ # cmd=album-properties
106
+ # protocol_version=2.0
107
+ # set_albumName=album-name
108
+ def album_properties(set_albumName, params = {})
109
+ params = { :cmd => 'album-properties', :set_albumName => set_albumName }.merge(params)
110
+ send_request(params)
111
+ end
112
+
113
+ # cmd=new-album
114
+ # protocol_version=2.1
115
+ # set_albumName=parent-album-name
116
+ # newAlbumName=album-name [optional]
117
+ # newAlbumTitle=album-title [optional]
118
+ # newAlbumDesc=album-description [optional]
119
+ def new_album(set_albumName, params = {})
120
+ params = { :cmd => 'new-album', :set_albumName => set_albumName }.merge(params)
121
+ send_request(params)
122
+ end
123
+
124
+ # cmd=fetch-album-images
125
+ # protocol_version=2.4
126
+ # set_albumName=album-name
127
+ # albums_too=yes/no [optional, since 2.13]
128
+ # random=yes/no [optional, G2 since ***]
129
+ # limit=number-of-images [optional, G2 since ***]
130
+ # extrafields=yes/no [optional, G2 since 2.12]
131
+ # all_sizes=yes/no [optional, G2 since 2.14]
132
+ def fetch_album_images(set_albumName, params = {})
133
+ params = { :cmd => 'fetch-album-images', :set_albumName => set_albumName }.merge(params)
134
+ send_request(params)
135
+ end
136
+
137
+ # cmd=image-properties
138
+ # protocol_version=***
139
+ # id=item-id
140
+ def image_properties(id, params = {})
141
+ params = { :cmd => 'image-properties', :id => id }.merge(params)
142
+ send_request(params)
143
+ end
144
+
145
+ def status_msg
146
+ "#{@status} - (#{@status_text})"
147
+ end
148
+
149
+ private
150
+
151
+ def build_multipart_query(params, userfile_name)
152
+ params['g2_userfile_name'] = userfile_name
153
+ request = params.map{ |k, v| "Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n#{v}\r\n" }
154
+ content = File.open(userfile_name, 'r'){ |f| f.read }
155
+ request << "Content-Disposition: form-data; name=\"g2_userfile\"; filename=\"#{userfile_name}\"\r\n" +
156
+ "Content-Transfer-Encoding: binary\r\n" +
157
+ "Content-Type: #{@@supported_types[File.extname(userfile_name).downcase]}\r\n\r\n" +
158
+ content + "\r\n"
159
+ request.collect { |p| "--#{@boundary}\r\n#{p}" }.join("") + "--#{@boundary}--"
160
+ end
161
+
162
+ def build_query(params)
163
+ params.map{ |k, v| "#{k}=#{v}" }.join('&')
164
+ end
165
+
166
+ def send_request(params)
167
+ userfile_name = params.delete(:userfile_name)
168
+ post_parameters = prep_params(params)
169
+ headers = {}
170
+ headers['Cookie'] = @cookie_jar.cookies if @cookie_jar.cookies
171
+ if userfile_name && File.file?(userfile_name)
172
+ query = build_multipart_query(post_parameters, userfile_name)
173
+ headers['Content-type'] = "multipart/form-data, boundary=#{@boundary}" if @boundary
174
+ else
175
+ query = build_query(post_parameters)
176
+ end
177
+ res = post(query, headers)
178
+ case res
179
+ when Net::HTTPSuccess, Net::HTTPRedirection
180
+ handle_response(res)
181
+ else
182
+ res.error!
183
+ end
184
+ end
185
+
186
+ def post(query, headers = {}, retries = 3)
187
+ begin
188
+ Net::HTTP.start(@uri.host, @uri.port) do |h|
189
+ h.post(@uri.path, query, headers)
190
+ end
191
+ rescue Exception => e
192
+ puts "Error during POST (#{retries} retries remain): #{e}"
193
+ throw e if retries == 0
194
+ post(query, headers, retries - 1)
195
+ end
196
+ end
197
+
198
+ def prep_params(params)
199
+ result = {}
200
+ result = result.merge(@base_params)
201
+ params.each_pair do |name, value|
202
+ result["g2_form[#{name}]"] = value
203
+ end
204
+ result['g2_authToken'] = @auth_token if @auth_token
205
+ result
206
+ end
207
+
208
+ def handle_response(res)
209
+ @cookie_jar.add(res.header.get_fields('set-cookie'))
210
+ @last_response = {}
211
+ begin
212
+ header = false
213
+ res.body.each do |line|
214
+ header = true if line.chomp == '#__GR2PROTO__'
215
+ next unless header # Ignore debug output
216
+ next if line =~ /^#/ # Ignore comments
217
+ name, *values = line.strip.split(/\s*=\s*/)
218
+ @last_response[name.strip] = values.join('=')
219
+ end
220
+ rescue Exception => e
221
+ puts "Error parsing response:\n#{res.body}"
222
+ throw e
223
+ end
224
+ @auth_token ||= @last_response['auth_token']
225
+ puts 'WARN: no auth token in response (using last)' unless @last_response['auth_token']
226
+ @status = @last_response['status'].to_i
227
+ @status_text = @last_response['status_text']
228
+ puts status_msg
229
+ puts "WARN: #{STATUS_DESCRIPTIONS[@status]}" unless @status == GR_STAT_SUCCESS
230
+ @last_response
231
+ end
232
+ end
233
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gallery-remote
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Carl Leiby
8
+ - Matt Walker
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-18 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: gallery-remote is an implementation of the Gallery Remote protocol in Ruby.
18
+ email: matt.r.walker@gmail.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - Rakefile
27
+ - README
28
+ - LICENSE.md
29
+ - lib/cookie_jar.rb
30
+ - lib/gallery/album.rb
31
+ - lib/gallery/gallery.rb
32
+ - lib/gallery/image.rb
33
+ - lib/gallery/remote.rb
34
+ - lib/gallery.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/mrwalker/gallery-remote
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project: gallery-remote
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: A Ruby client for the Gallery2 photo gallery system
63
+ test_files: []
64
+