helix 0.0.0.pre

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/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2012, Twistage
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of Twistage nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL Twistage BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,169 @@
1
+ http://nestacms.com/docs/creating-content/markdown-cheat-sheet
2
+ http://support.mashery.com/docs/customizing_your_portal/Markdown_Cheat_Sheet
3
+
4
+ # Helix
5
+
6
+ The Helix gem allows developers to easily connect to and manipulate the Twistage API.
7
+
8
+ Documentation
9
+ -------------
10
+
11
+ You should find the documentation for your version of helix on [Rubygems](https://rubygems.org/gems/helix).
12
+
13
+ Install
14
+ --------
15
+
16
+ ```shell
17
+ gem install helix
18
+ ```
19
+ or add the following line to Gemfile:
20
+
21
+ ```ruby
22
+ gem 'helix'
23
+ ```
24
+ and run `bundle install` from your shell.
25
+
26
+ Install From Repo
27
+ -----------------
28
+ Using sudo:
29
+ ```shell
30
+ git clone git@github.com:Twistage/helix.git
31
+ gem build helix.gemspec
32
+ sudo gem i helix-*.gem
33
+ ```
34
+
35
+ RVM or root support:
36
+ ```shell
37
+ git clone git@github.com:Twistage/helix.git
38
+ gem build helix.gemspec
39
+ gem i helix-*.gem
40
+ ```
41
+
42
+ Rebuilding the gem, use the first for sudo, the second for RVM or root:
43
+ ```shell
44
+ rake reinstall_helix
45
+ rake reinstall_helix_rvm
46
+ ```
47
+
48
+ Using gem in a Gemfile
49
+ ```shell
50
+ gem 'helix', :git => git@github.com:Twistage/helix.git
51
+ ```
52
+
53
+
54
+
55
+ Supported Ruby versions
56
+ -----------------------
57
+
58
+ 1.9.3
59
+ 1.9.2
60
+
61
+ How To
62
+ ------
63
+
64
+ ###Setup YAML
65
+ ```yaml
66
+ site: 'http://service.twistage.com'
67
+ user: 'my_account@twistage.com'
68
+ password: 'password123'
69
+ company: 'my_company'
70
+ license_key: '141a86b5c4091
71
+ ```
72
+ Load the YAML file as your config.
73
+ ```ruby
74
+ Helix::Config.load("path/to/yaml.yml")
75
+ ```
76
+ ####Current CRUD methods supported by all models
77
+ .create
78
+ .find
79
+ \#update
80
+ \#destroy
81
+
82
+ ####Current models
83
+ Videos, Images, Albums, Tracks, Playlists
84
+
85
+ ###Videos
86
+ #####Required fields: title, description, library, company, and source.
87
+ ```ruby
88
+ video = Helix::Video.create!( title: 'New Video',
89
+ description: 'A video of new things',
90
+ source: 'http://somesource.com/source.mp4'
91
+ company: 'some_company',
92
+ library: 'some_library')
93
+ video.update({title: "New Title"})
94
+ another_video = Helix::Video.find(some_guid)
95
+ another_video.destroy
96
+ ```
97
+ ###Albums
98
+ #####Required fields: title, library, company.
99
+ ```ruby
100
+ album = Helix::Album.create!( title: 'New Album',
101
+ description: 'A album of new things',
102
+ source: 'http://somesource.com/source.mp4'
103
+ company: 'some_company',
104
+ library: 'some_library')
105
+ #Update for album is not currently supported
106
+ another_album = Helix::Album.find(some_guid)
107
+ another_album.destroy
108
+ ```
109
+ ###Images
110
+ #####Required fields: title, description, library, company, and source.
111
+ ```ruby
112
+ image = Helix::Image.create!( title: 'New Image',
113
+ description: 'A image of new things',
114
+ source: 'http://somesource.com/source.jpg'
115
+ company: 'some_company',
116
+ library: 'some_library')
117
+ image.update({title: "New Title"})
118
+ another_image = Helix::Image.find(some_guid)
119
+ another_image.destroy
120
+ ```
121
+ ###Tracks
122
+ #####Required fields: title, description, library, company, and source.
123
+ ```ruby
124
+ track = Helix::Track.create!( title: 'New Track',
125
+ description: 'A track of new things',
126
+ source: 'http://somesource.com/source.mp3'
127
+ company: 'some_company',
128
+ library: 'some_library')
129
+ track.update({title: "New Title"})
130
+ another_track = Helix::Track.find(some_guid)
131
+ another_track.destroy
132
+ ```
133
+ ###Playlists
134
+ #####Required fields: title, library, company.
135
+ ```ruby
136
+ playlist = Helix::Playlist.create!( title: 'New Playlist',
137
+ description: 'A playlist of new things',
138
+ source: 'http://somesource.com/source.mp4'
139
+ company: 'some_company',
140
+ library: 'some_library')
141
+ playlist.update({title: "New Title"})
142
+ another_playlist = Helix::Playlist.find(some_guid)
143
+ another_playlist.destroy
144
+ ```
145
+
146
+ More Information
147
+ ----------------
148
+
149
+ * [Rubygems](https://rubygems.org/gems/helix)
150
+ * [Issues](https://github.com/twistage/helix/issues)
151
+
152
+ Contributing
153
+ ------------
154
+
155
+ How to contribute
156
+
157
+ Credits
158
+ -------
159
+
160
+ Helix was written by Kevin Baird and Michael Wood.
161
+
162
+ Helix is maintained and funded by [Twistage, inc](http://twistage.com)
163
+
164
+ The names and logos for twistage are trademarks of twistage, inc.
165
+
166
+ License
167
+ -------
168
+
169
+ Helix is Copyright © 2008-2012 Twistage, inc.
@@ -0,0 +1,27 @@
1
+ require 'helix/base'
2
+
3
+ module Helix
4
+
5
+ class Album < Base
6
+
7
+ # The class name, to be used by supporting classes. Such as Config which uses
8
+ # this method as a way to build URLs.
9
+ #
10
+ #
11
+ # @example
12
+ # Helix::Album.media_type_sym #=> :album
13
+ #
14
+ # @return [Symbol] Name of the class.
15
+ def self.media_type_sym; :album; end
16
+
17
+ # Currently update is unsupported for album.
18
+ #
19
+ # @param [Hash] opts an array can be passed in so it remains functionally similiar to other update calls.
20
+ # @return [Exception] "Albums Update is not currently supported."
21
+ def update(opts={})
22
+ raise "Albums Update is not currently supported."
23
+ end
24
+
25
+ end
26
+
27
+ end
data/lib/helix/base.rb ADDED
@@ -0,0 +1,185 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+ require 'yaml'
4
+ require 'crack'
5
+
6
+ module Helix
7
+ class Base
8
+
9
+ unless defined?(self::METHODS_DELEGATED_TO_CLASS)
10
+ METHODS_DELEGATED_TO_CLASS = [ :guid_name, :media_type_sym, :plural_media_type ]
11
+ end
12
+
13
+ attr_accessor :attributes, :config
14
+
15
+ # Creates a new record via API and then returns an instance of that record.
16
+ #
17
+ # Example is using Video class since Video inherits from Base. This won't
18
+ # normally be called as Helix::Base.create
19
+ #
20
+ # @example
21
+ # Helix::Video.create({title: "My new video"})
22
+ #
23
+ # @param [Hash] attributes a hash containing the attributes used in the create
24
+ # @return [Base] An instance of Helix::Base
25
+ def self.create(attributes={})
26
+ config = Helix::Config.instance
27
+ #xml = '<?xml version="1.0" encoding="UTF-8"?>
28
+ # <add>
29
+ # <list>
30
+ # <entry>
31
+ # <src>http://www.minhreigen.com/videos/play.mp4</src>
32
+ # <title>Summer camp dance</title>
33
+ # <description>This one is the best</description>
34
+ # </entry>
35
+ # </list>
36
+ # </add>'.gsub!(/\s{2,}|\n/, "")
37
+ url = config.build_url(media_type: plural_media_type,
38
+ format: :xml)
39
+ response = RestClient.post(url, attributes.merge(signature: config.signature(:update)))
40
+ attrs = Crack::XML.parse(response)
41
+ self.new(attributes: attrs[media_type_sym.to_s], config: config)
42
+ end
43
+
44
+ # Finds and returns a record in instance form for a class, through
45
+ # guid lookup.
46
+ #
47
+ # @example
48
+ # video_guid = "8e0701c142ab1"
49
+ # video = Helix::Video.find(video_guid)
50
+ #
51
+ # @param [String] guid an id in guid form.
52
+ # @return [Base] An instance of Helix::Base
53
+ def self.find(guid)
54
+ config = Helix::Config.instance
55
+ item = self.new(attributes: { guid_name => guid }, config: config)
56
+ item.load
57
+ end
58
+
59
+ # Fetches all accessible records, places them into instances, and returns
60
+ # them as an array.
61
+ #
62
+ # @example
63
+ # Helix::Video.find_all(query: 'string_to_match') #=> [video1,video2]
64
+ #
65
+ # @param [Hash] opts a hash of options for parameters passed into the HTTP GET
66
+ # @return [Array] The array of instance objects for a class.
67
+ def self.find_all(opts)
68
+ data_sets = get_data_sets(opts)
69
+ return [] if data_sets.nil?
70
+ data_sets.map { |attrs| self.new(attributes: attrs) }
71
+ end
72
+
73
+ def self.get_data_sets(opts)
74
+ config = Helix::Config.instance
75
+ url = config.build_url(format: :json)
76
+ # We allow opts[:sig_type] for internal negative testing only.
77
+ raw_response = config.get_response(url, {sig_type: :view}.merge(opts))
78
+ data_sets = raw_response[plural_media_type]
79
+ end
80
+
81
+ # Creates a string that associates to the class id.
82
+ #
83
+ # @example
84
+ # Helix::Video.guid_name #=> "video_id"
85
+ #
86
+ # @return [String] The guid name for a specific class.
87
+ def self.guid_name
88
+ "#{self.media_type_sym}_id"
89
+ end
90
+
91
+ # Creates a string associated with a class name pluralized
92
+ #
93
+ # @example
94
+ # Helix::Video.plural_media_type #=> "videos"
95
+ #
96
+ # @return [String] The class name pluralized
97
+ def self.plural_media_type
98
+ "#{self.media_type_sym}s"
99
+ end
100
+
101
+ METHODS_DELEGATED_TO_CLASS.each do |meth|
102
+ define_method(meth) { |*args| self.class.send(meth, *args) }
103
+ end
104
+
105
+ def initialize(opts)
106
+ @attributes = opts[:attributes]
107
+ @config = opts[:config]
108
+ end
109
+
110
+ # Deletes the record of the Helix::Base instance.
111
+ #
112
+ # @example
113
+ # video = Helix::Video.create({title: "Some Title"})
114
+ # video.destroy
115
+ #
116
+ # @return [String] The response from the HTTP DELETE call.
117
+ def destroy
118
+ memo_cfg = config
119
+ url = memo_cfg.build_url(format: :xml, guid: guid, media_type: plural_media_type)
120
+ RestClient.delete(url, params: {signature: memo_cfg.signature(:update)})
121
+ end
122
+
123
+ # Creates a string that associates to the class id.
124
+ #
125
+ # @example
126
+ # video = Helix::Video.create({title: "My new title"})
127
+ # video.guid #=> "9e0989v234sf4"
128
+ #
129
+ # @return [String] The guid for the class instance.
130
+ def guid
131
+ @attributes[guid_name]
132
+ end
133
+
134
+ # Loads in the record from a HTTP GET response.
135
+ #
136
+ # @param [Hash] opts a hash of attributes to update the instance with.
137
+ # @return [Base] Returns an instance of the class.
138
+ def load(opts={})
139
+ memo_cfg = config
140
+ url = memo_cfg.build_url(format: :json, guid: self.guid, media_type: plural_media_type)
141
+ # We allow opts[:sig_type] for internal negative testing only.
142
+ raw_attrs = memo_cfg.get_response(url, {sig_type: :view}.merge(opts))
143
+ @attributes = massage_raw_attrs(raw_attrs)
144
+ self
145
+ end
146
+ alias_method :reload, :load
147
+
148
+ # Raises an error for missing method calls.
149
+ #
150
+ # @param [Symbol] method_sym The method attempting to be called.
151
+ # @return [String] An error for the method attempting to be called.
152
+ def method_missing(method_sym)
153
+ begin
154
+ @attributes[method_sym.to_s]
155
+ rescue
156
+ raise NoMethodError, "#{method_sym} is not recognized within #{self.class.to_s}'s @attributes"
157
+ end
158
+ end
159
+
160
+ # Updates instance and record with attributes passed in.
161
+ #
162
+ # @example
163
+ # video = Helix::Video.find(video_guid)
164
+ # video.update({title: "My new title"})
165
+ #
166
+ # @param [Hash] opts a hash of attributes to update the instance with.
167
+ # @return [Base] Returns an instance of the class after update.
168
+ def update(opts={})
169
+ memo_cfg = config
170
+ url = memo_cfg.build_url(format: :xml, guid: guid, media_type: plural_media_type)
171
+ params = {signature: memo_cfg.signature(:update)}.merge(media_type_sym => opts)
172
+ RestClient.put(url, params)
173
+ self
174
+ end
175
+
176
+ private
177
+
178
+ def massage_raw_attrs(raw_attrs)
179
+ # FIXME: Albums JSON output is embedded as the only member of an Array.
180
+ proper_hash = raw_attrs.respond_to?(:has_key?) && raw_attrs.has_key?(guid_name)
181
+ proper_hash ? raw_attrs : raw_attrs.first
182
+ end
183
+
184
+ end
185
+ end
@@ -0,0 +1,150 @@
1
+ require 'helix/video'
2
+ require 'helix/track'
3
+ require 'helix/album'
4
+ require 'helix/image'
5
+ require 'singleton'
6
+
7
+ module Helix
8
+
9
+ class Config
10
+
11
+ include Singleton
12
+
13
+ unless defined?(self::DEFAULT_FILENAME)
14
+ DEFAULT_FILENAME = './helix.yml'
15
+ SCOPES = %w(reseller company library)
16
+ SIG_DURATION = 1200 # in minutes
17
+ TIME_OFFSET = 1000 * 60 # 1000 minutes, lower to give some margin of error
18
+ VALID_SIG_TYPES = [ :ingest, :update, :view ]
19
+ end
20
+
21
+ attr_accessor :credentials
22
+
23
+ # Creates a singleton of itself, setting the config
24
+ # to a specified YAML file. If no file is specified the default
25
+ # helix.yml file is used.
26
+ #
27
+ # @example
28
+ # Helix::Config.load("/some/path/my_yaml.yml")
29
+ # video = Helix::Video.find("8e0701c142ab1") #Uses my_yaml.yml
30
+ #
31
+ # @param [String] yaml_file_location the yaml file used for config
32
+ # @return [Helix::Config] config returns singleton of Helix::Config
33
+ def self.load(yaml_file_location = DEFAULT_FILENAME)
34
+ config = self.instance
35
+ config.instance_variable_set(:@filename, yaml_file_location)
36
+ config.instance_variable_set(:@credentials, YAML.load(File.open(yaml_file_location)))
37
+ config
38
+ end
39
+
40
+ # Creates additional URL stubbing that can be used in conjuction
41
+ # with the base_url to create RESTful URLs
42
+ #
43
+ # @param [String] base_url the base part of the URL to be used
44
+ # @param [Hash] opts a hash of options for building URL additions
45
+ # @return [String] The full RESTful URL string object
46
+ def add_sub_urls(base_url, opts)
47
+ guid, action = [:guid, :action].map { |sub| opts[sub] }
48
+ url = "#{base_url}/#{opts[:media_type]}"
49
+ url += "/#{guid}" if guid
50
+ url += "/#{action}" if action
51
+ "#{url}.#{opts[:format]}"
52
+ end
53
+
54
+ # Creates a full RESTful URL to be used for HTTP requests.
55
+ #
56
+ # @param [Hash] opts a hash of options for building URL
57
+ # @return [String] The full RESTful URL string object
58
+ def build_url(opts={})
59
+ opts[:format] ||= :json
60
+ opts[:media_type] ||= :videos
61
+ base_url = get_base_url(opts)
62
+ url = add_sub_urls(base_url, opts)
63
+ end
64
+
65
+ # Creates the base url with information collected from credentials.
66
+ #
67
+ # @param [Hash] opts a hash of options for building URL
68
+ # @return [String] The base RESTful URL string object
69
+ def get_base_url(opts)
70
+ creds = credentials
71
+ base_url = creds['site']
72
+ reseller, company, library = SCOPES.map { |scope| creds[scope] }
73
+ base_url += "/resellers/#{reseller}" if reseller
74
+ if company
75
+ base_url += "/companies/#{company}"
76
+ base_url += "/libraries/#{library}" if library
77
+ end
78
+ base_url
79
+ end
80
+
81
+ # Creates additional URL stubbing that can be used in conjuction
82
+ # with the base_url to create RESTful URLs
83
+ #
84
+ # @param [String] url the base part of the URL to be used
85
+ # @param [Hash] opts a hash of options for building URL additions
86
+ # @return [String] The full RESTful URL string object
87
+ def get_response(url, opts={})
88
+ sig_type = opts.delete(:sig_type)
89
+ params = opts.merge(signature: signature(sig_type, opts))
90
+ response = RestClient.get(url, params: params)
91
+ JSON.parse(response)
92
+ end
93
+
94
+ # Fetches the signature for a specific license key.
95
+ #
96
+ # @param [Symbol] sig_type The type of signature required for calls.
97
+ # @return [String] The signature needed to pass around for calls.
98
+ def signature(sig_type, opts={})
99
+ prepare_signature_memoization
100
+ memo_sig = existing_sig_for(sig_type)
101
+ return memo_sig if memo_sig
102
+ unless VALID_SIG_TYPES.include?(sig_type)
103
+ raise ArgumentError, error_message_for(sig_type)
104
+ end
105
+
106
+ lk = license_key
107
+ @signature_expiration_for[lk][sig_type] = Time.now + TIME_OFFSET
108
+ @signature_for[lk][sig_type] = RestClient.get(url_for(sig_type, opts))
109
+ end
110
+
111
+ private
112
+
113
+ def error_message_for(sig_type)
114
+ "I don't understand '#{sig_type}'. Please give me one of :ingest, :update, or :view."
115
+ end
116
+
117
+ def existing_sig_for(sig_type)
118
+ return if sig_expired_for?(sig_type)
119
+ @signature_for[license_key][sig_type]
120
+ end
121
+
122
+ def license_key
123
+ @credentials['license_key']
124
+ end
125
+
126
+ def prepare_signature_memoization
127
+ lk = license_key
128
+ @signature_for ||= {}
129
+ @signature_expiration_for ||= {}
130
+ @signature_for[lk] ||= {}
131
+ @signature_expiration_for[lk] ||= {}
132
+ end
133
+
134
+ def sig_expired_for?(sig_type)
135
+ expires_at = @signature_expiration_for[license_key][sig_type]
136
+ return true if expires_at.nil?
137
+ expires_at <= Time.now
138
+ end
139
+
140
+ def url_for(sig_type, opts={})
141
+ contributor, library_id = [:contributor, :library_id].map { |key| opts[key] }
142
+ url = "#{credentials['site']}/api/#{sig_type}_key?licenseKey=#{credentials['license_key']}&duration=#{SIG_DURATION}"
143
+ url += "&contributor=#{contributor}" if contributor
144
+ url += "&library_id=#{library_id}" if library_id
145
+ url
146
+ end
147
+
148
+ end
149
+
150
+ end
@@ -0,0 +1,19 @@
1
+ require 'helix/base'
2
+
3
+ module Helix
4
+
5
+ class Image < Base
6
+
7
+ # The class name, to be used by supporting classes. Such as Config which uses
8
+ # this method as a way to build URLs.
9
+ #
10
+ #
11
+ # @example
12
+ # Helix::Image.media_type_sym #=> :image
13
+ #
14
+ # @return [Symbol] Name of the class.
15
+ def self.media_type_sym; :image; end
16
+
17
+ end
18
+
19
+ end