mrt-ingest 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.
data/.hgignore ADDED
@@ -0,0 +1,4 @@
1
+ .*\.gem
2
+ \.bundle
3
+ Gemfile\.lock
4
+ pkg/.*
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in mrt-ingest.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "fakeweb"
8
+ gem "mocha"
9
+ gem "checkm", ">=0.0.6"
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2011, Regents of the University of California
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
+
7
+ - Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ - Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ - Neither the name of the University of California nor the names of its
13
+ contributors may be used to endorse or promote products derived from this
14
+ software without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ POSSIBILITY OF SUCH DAMAGE.
data/README ADDED
@@ -0,0 +1,29 @@
1
+ = mrt-ingest (ruby)
2
+ Date:: 6 Sept. 2011
3
+ Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
4
+
5
+ == What?
6
+
7
+ A Ruby ingest client for Merritt[http://merritt.cdlib.org/].
8
+
9
+ == Install
10
+
11
+ $ gem build mrt-ingest.gemspec
12
+ $ sudo gem install mrt-ingest-0.0.1.gem
13
+
14
+ == How?
15
+
16
+ require 'rubygems'
17
+ require 'mrt/ingest'
18
+ client = Mrt::Ingest::Client.new("http://merritt.cdlib.org/object/ingest", USERNAME, PASSWORD)
19
+ obj = Mrt::Ingest::IObject.new(:erc => {
20
+ "who" => "Doe, John",
21
+ "what" => "Hello, world",
22
+ "when/created" => "2011" })
23
+ obj.add_component(File.new("/tmp/helloworld_a"))
24
+ obj.add_component(File.new("/tmp/helloworld_b"))
25
+ obj.add_component(URI.parse("http://example.org/xxx"),
26
+ :name => "helloworld_c",
27
+ :digest => Mrt::Ingest::MessageDigest::MD5.new("6f5902ac237024bdd0c176cb93063dc4"))
28
+ obj.start_ingest(client, "demo_merritt_content", "me/My Name")
29
+ obj.finish_ingest()
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ # -*- ruby -*-
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+
5
+ require 'bundler'
6
+ include Rake::DSL
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ task :default => [:test]
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib' << 'test'
12
+ t.pattern = 'test/**/test_*.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ RDoc::Task.new do |rd|
17
+ rd.title = "Merritt Ingest Client"
18
+ rd.rdoc_files.include("README", "lib/**/*.rb")
19
+
20
+ rd.options += ['-f', 'darkfish',]
21
+ end
data/lib/mrt/ingest.rb ADDED
@@ -0,0 +1,16 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ module Mrt
5
+ module Ingest
6
+ autoload :Client, "mrt/ingest/client"
7
+ autoload :IObject, "mrt/ingest/iobject"
8
+ autoload :MessageDigest, "mrt/ingest/message_digest"
9
+ autoload :OneTimeServer, "mrt/ingest/one_time_server"
10
+ autoload :Request, "mrt/ingest/request"
11
+ autoload :Response, "mrt/ingest/response"
12
+
13
+ class IngestException < Exception
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'rubygems'
5
+
6
+ require 'rest-client'
7
+
8
+ module Mrt
9
+ module Ingest
10
+
11
+ # A client for ingesting objects into a Merritt.
12
+ class Client
13
+ def initialize(base_uri, username=nil, password=nil)
14
+ @base_uri = base_uri
15
+ @username = username
16
+ @password = password
17
+ end
18
+
19
+ # Send a request to the client.
20
+ def ingest(ingest_req)
21
+ return Response.new(mk_rest_request(ingest_req).execute())
22
+ end
23
+
24
+ # :nodoc:
25
+ def mk_rest_request(ingest_req)
26
+ args = {
27
+ :method => :post,
28
+ :url => @base_uri,
29
+ :user => @username,
30
+ :password => @password,
31
+ :payload => ingest_req.mk_args(),
32
+ :headers => { :multipart => true } }.delete_if { |k,v| (v.nil? || v == "") }
33
+ return RestClient::Request.new(args)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,144 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'mrt/ingest'
5
+ require 'tempfile'
6
+ require 'uri'
7
+
8
+ module Mrt
9
+ module Ingest
10
+ # Represents a component of an object to ingest. Either a #URI or a
11
+ # #File.
12
+ class Component # :nodoc:
13
+ def initialize(server, where, options)
14
+ @name = options[:name]
15
+ @digest = options[:digest]
16
+ @mime_type = options[:mime_type]
17
+ @size = options[:size]
18
+
19
+ case where
20
+ when File, Tempfile
21
+ @name = File.basename(where.path) if @name.nil?
22
+ @uri = server.add_file(where)[0]
23
+ if @digest.nil? then
24
+ @digest = Mrt::Ingest::MessageDigest::MD5.from_file(where)
25
+ end
26
+ @size = File.size(where.path) if @size.nil?
27
+ when URI
28
+ @name = File.basename(where.to_s) if @name.nil?
29
+ @uri = where
30
+ else
31
+ raise IngestException.new("Trying to add a component that is not a File or URI")
32
+ end
33
+
34
+ end
35
+
36
+ def to_manifest_entry
37
+ (digest_alg, digest_value) = if @digest.nil? then
38
+ ['', '']
39
+ else
40
+ [@digest.type, @digest.value]
41
+ end
42
+ return "#{@uri} | #{digest_alg} | #{digest_value} | #{@size || ''} | | #{@name} | #{@mime_type || '' }\n"
43
+ end
44
+ end
45
+
46
+ # An object prepared for ingest into Merritt.
47
+ class IObject
48
+
49
+ attr_accessor :primary_identifier, :local_identifier, :erc
50
+
51
+ # Options can have the keys :primary_identifier,
52
+ # :local_identifier, :server, or :erc. :erc can be a #File, #Uri
53
+ # or a #Hash of metadata. :server is a #OneTimeServer.
54
+ def initialize(options={})
55
+ @primary_identifier = options[:primary_identifier]
56
+ @local_identifier = options[:local_identifier]
57
+ @erc = options[:erc] || Hash.new
58
+ @components = []
59
+ @server = options[:server] || Mrt::Ingest::OneTimeServer.new
60
+ end
61
+
62
+ # Add a component to the object. where can be either a #URI or a
63
+ # #File. Options is a hash whose keys may be :name, :digest,
64
+ # :mime_type, or :size. If :digest is supplied, it must be a
65
+ # subclass of Mrt::Ingest::MessageDigest::Base. If where is a
66
+ # #File, it will be hosted on an embedded web server.
67
+ def add_component(where, options={})
68
+ @components.push(Component.new(@server, where, options))
69
+ end
70
+
71
+ # Make a Mrt::Ingest::Request object for this mrt-object
72
+ def mk_request(profile, submitter)
73
+ erc_component = case @erc
74
+ when URI, File, Tempfile
75
+ Component.new(@server, @erc, :name => 'mrt-erc.txt')
76
+ when Hash
77
+ uri_str, path = @server.add_file do |f|
78
+ @erc.each_pair do |k, v|
79
+ f.write("#{k}: #{v}\n")
80
+ end
81
+ end
82
+ Component.new(@server,
83
+ URI.parse(uri_str),
84
+ :name => 'mrt-erc.txt',
85
+ :digest => Mrt::Ingest::MessageDigest::MD5.from_file(File.new(path)))
86
+ else
87
+ raise IngestException.new("Bad ERC supplied: must be a URI, File, or Hash")
88
+ end
89
+ manifest_file = Tempfile.new("mrt-ingest")
90
+ mk_manifest(manifest_file, erc_component)
91
+ # reset to beginning
92
+ manifest_file.open
93
+ return Mrt::Ingest::Request.
94
+ new(:file => manifest_file,
95
+ :filename => manifest_file.path.split(/\//).last,
96
+ :type => "object-manifest",
97
+ :submitter => submitter,
98
+ :profile => profile,
99
+ :primary_identifier => @primary_identifier)
100
+ end
101
+
102
+ def start_server # :nodoc:
103
+ return @server.start_server()
104
+ end
105
+
106
+ def join_server # :nodoc:
107
+ return @server.join_server()
108
+ end
109
+
110
+ def stop_server # :nodoc:
111
+ return @server.stop_server()
112
+ end
113
+
114
+ def mk_manifest(manifest, erc_component) # :nodoc:
115
+ manifest.write("#%checkm_0.7\n")
116
+ manifest.write("#%profile http://uc3.cdlib.org/registry/ingest/manifest/mrt-ingest-manifest\n")
117
+ manifest.write("#%prefix | mrt: | http://uc3.cdlib.org/ontology/mom#\n")
118
+ manifest.write("#%prefix | nfo: | http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#\n")
119
+ manifest.write("#%fields | nfo:fileUrl | nfo:hashAlgorithm | nfo:hashValue | nfo:fileSize | nfo:fileLastModified | nfo:fileName | mrt:mimeType\n")
120
+ @components.each { |c|
121
+ manifest.write(c.to_manifest_entry)
122
+ }
123
+ manifest.write(erc_component.to_manifest_entry)
124
+ manifest.write("#%EOF\n")
125
+ end
126
+
127
+ # Begin an ingest on the given client, with a profile and
128
+ # submitter.
129
+ def start_ingest(client, profile, submitter)
130
+ request = mk_request(profile, submitter)
131
+ start_server
132
+ @response = client.ingest(request)
133
+ return @response
134
+ end
135
+
136
+ # Wait for the ingest of this object to finish.
137
+ def finish_ingest
138
+ # XXX Right now we only join the hosting server; in the future
139
+ # we will check the status via the ingest server.
140
+ join_server
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,51 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'digest/md5'
5
+
6
+ module Mrt
7
+ module Ingest
8
+ module MessageDigest
9
+ class Base # :nodoc:
10
+ attr_reader :value, :type
11
+ def initialize(value, type)
12
+ @value = value
13
+ @type = type
14
+ end
15
+ end
16
+
17
+ # Represents a SHA256 digest suitable for a Checkm manifest.
18
+ class SHA256 < Base
19
+ def initialize(value)
20
+ super(value, "sha-256")
21
+ end
22
+ end
23
+
24
+ # Represents an MD5 digest suitable for a Checkm manifest.
25
+ class MD5 < Base
26
+ def initialize(value)
27
+ super(value, "md5")
28
+ end
29
+
30
+ # Generate a digest from a file.
31
+ def self.from_file(file)
32
+ digest = Digest::MD5.new
33
+ File.open(file.path, 'r') do |f|
34
+ buff = ""
35
+ while (f.read(1024, buff) != nil)
36
+ digest << buff
37
+ end
38
+ end
39
+ return Mrt::Ingest::MessageDigest::MD5.new(digest.hexdigest)
40
+ end
41
+ end
42
+
43
+ # Represents a SHA1 digest suitable for a Checkm manifest.
44
+ class SHA1 < Base
45
+ def initialize(value)
46
+ super(value, "sha1")
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,116 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'webrick'
5
+
6
+ # An HTTP server that will serve each file ONCE before shutting down.
7
+ module Mrt
8
+ module Ingest
9
+ class OneTimeServer
10
+ # Find an open port, starting with start and adding one until we get
11
+ # an open port
12
+ def get_open_port(start=8080)
13
+ try_port = start
14
+ while (true)
15
+ begin
16
+ s = TCPServer.open(try_port)
17
+ s.close
18
+ return try_port
19
+ rescue Errno::EADDRINUSE
20
+ try_port = try_port + 1
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize
26
+ @dir = Dir.mktmpdir
27
+ @mutex = Mutex.new
28
+ @known_paths = {}
29
+ @requested = {}
30
+ @port = get_open_port()
31
+ @file_callback = lambda do |req, res|
32
+ @requested[req.path] ||= true
33
+ end
34
+
35
+ config = { :Port => @port }
36
+ @server = WEBrick::HTTPServer.new(config)
37
+ @server.mount("/", WEBrick::HTTPServlet::FileHandler, @dir,
38
+ { :FileCallback=>@file_callback })
39
+ end
40
+
41
+ # Return true if each file has been served.
42
+ def finished?
43
+ Dir.entries(@dir).each do |entry|
44
+ next if (entry == "." || entry == "..")
45
+ if @requested["/#{entry}"].nil? then
46
+ return false
47
+ end
48
+ end
49
+ return true
50
+ end
51
+
52
+ def get_temppath
53
+ tmpfile = Tempfile.new("tmp", @dir)
54
+ tmppath = tmpfile.path
55
+ tmpfile.close!
56
+ @mutex.synchronize do
57
+ if !@known_paths.has_key?(tmppath) then
58
+ # no collision
59
+ @known_paths[tmppath] = true
60
+ return tmppath
61
+ end
62
+ end
63
+ # need to retry, there was a collision
64
+ return get_temppath
65
+ end
66
+
67
+ # Add a file to this server. Returns the URL to use
68
+ # to fetch the file & the file path
69
+ def add_file(sourcefile=nil)
70
+ fullpath = get_temppath()
71
+ path = File.basename(fullpath)
72
+ if !sourcefile.nil? then
73
+ @server.mount("/#{path}",
74
+ WEBrick::HTTPServlet::FileHandler,
75
+ sourcefile.path,
76
+ { :FileCallback=>@file_callback })
77
+ else
78
+ File.open(fullpath, 'w+') do |f|
79
+ yield f
80
+ end
81
+ end
82
+ return "http://#{Socket.gethostname}:#{@port}/#{path}", fullpath
83
+ end
84
+
85
+ def start_server
86
+ @thread = Thread.new do
87
+ @server.start
88
+ end
89
+ return @thread
90
+ end
91
+
92
+ # Stop server unconditionally.
93
+ def stop_server
94
+ @server.shutdown
95
+ @thread.join
96
+ end
97
+
98
+ # Wait for server to finish serving all files.
99
+ def join_server
100
+ # ensure that each file is requested once before shutting down
101
+ while (!self.finished?) do sleep(1) end
102
+ @server.shutdown
103
+ @thread.join
104
+ end
105
+
106
+ # Run the server and wait until each file has been served once.
107
+ # Cleans up files before it returns.
108
+ def run
109
+ start_server()
110
+ join_server()
111
+ # FileUtils.rm_rf(@dir)
112
+ return
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,57 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ module Mrt
5
+ module Ingest
6
+ class RequestException < Exception
7
+ end
8
+
9
+ # Represents a request to be sent to an ingest server.
10
+ class Request
11
+ attr_accessor :creator, :date, :local_identifier,
12
+ :primary_identifier, :profile, :note, :submitter,
13
+ :title, :type
14
+
15
+ # Options is a hash; required are :profile, :submitter, :type.
16
+ # May also include :creator, :date, :digest, :file, :filename,
17
+ # :local_identifier, :primary_identifier, :note, :title.
18
+ def initialize(options)
19
+ @creator = options[:creator]
20
+ @date = options[:date]
21
+ @digest = options[:digest]
22
+ @file = options[:file]
23
+ @filename = options[:filename]
24
+ @local_identifier = options[:local_identifier]
25
+ @primary_identifier = options[:primary_identifier]
26
+ @profile = options[:profile]
27
+ @note = options[:note]
28
+ @submitter = options[:submitter]
29
+ @title = options[:title]
30
+ @type = options[:type]
31
+ [:profile, :submitter, :type].each do |arg|
32
+ raise RequestException.new("#{arg} is required.") if options[arg].nil?
33
+ end
34
+ end
35
+
36
+ # Returns a hash of arguments suitable for sending to a server.
37
+ def mk_args
38
+ return {
39
+ 'creator' => @creator,
40
+ 'date' => @date,
41
+ 'digestType' => ((!@digest.nil? && @digest.type) || nil),
42
+ 'digestValue' => ((!@digest.nil? && @digest.value) || nil),
43
+ 'file' => @file,
44
+ 'filename' => @filename,
45
+ 'localIdentifier' => @local_identifier,
46
+ 'primaryIdentifier' => @primary_identifier,
47
+ 'profile' => @profile,
48
+ 'note' => @note,
49
+ 'responseForm' => 'json',
50
+ 'submitter' => @submitter,
51
+ 'title' => @title,
52
+ 'type' => @type
53
+ }.reject{|k, v| v.nil? || (v == '')}
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'rubygems'
5
+
6
+ require 'json'
7
+ require 'time'
8
+
9
+ module Mrt
10
+ module Ingest
11
+ class Response
12
+ def initialize(data)
13
+ @parsed = JSON.parse(data)['batchState']
14
+ end
15
+
16
+ def batch_id
17
+ return @parsed['batchID']
18
+ end
19
+
20
+ def user_agent
21
+ return @parsed['userAgent']
22
+ end
23
+
24
+ def submission_date
25
+ return Time.parse(@parsed['submissionDate'])
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "mrt/ingest/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "mrt-ingest"
7
+ s.version = "0.0.2"
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Erik Hetzner"]
10
+ s.email = ["erik.hetzner@ucop.edu"]
11
+ s.homepage = "http://bitbucket.org/merritt/mrt-ingest-ruby"
12
+ s.summary = %q{A client for Merritt ingest.}
13
+ s.description = %q{A client for the Merritt ingest system. More details available from http://wiki.ucop.edu/display/curation.}
14
+
15
+ s.add_dependency "json", ">=1.5.0"
16
+ s.add_dependency "rest-client", ">=1.6.0"
17
+
18
+ s.rubyforge_project = "mrt-ingest"
19
+
20
+ s.files = `hg locate`.split("\n")
21
+ s.test_files = `hg locate --include '{spec,features}'`.split("\n")
22
+ s.executables = `hg locate --include bin`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
@@ -0,0 +1,39 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'rubygems'
5
+
6
+ require 'checkm'
7
+ require 'fakeweb'
8
+ require 'mocha'
9
+ require 'mrt/ingest'
10
+ require 'shoulda'
11
+ require 'open-uri'
12
+
13
+ class TestClient < Test::Unit::TestCase
14
+ context "creating a client" do
15
+ should "be able to create an ingest client" do
16
+ client = Mrt::Ingest::Client.new("http://example.org/ingest")
17
+ assert_instance_of(Mrt::Ingest::Client, client)
18
+ end
19
+
20
+ should "be able to create an ingest client with login credentials" do
21
+ client = Mrt::Ingest::Client.new("http://example.org/ingest", "me", "secret")
22
+ assert_instance_of(Mrt::Ingest::Client, client)
23
+ end
24
+ end
25
+
26
+ context "ingest clients" do
27
+ setup do
28
+ @client = Mrt::Ingest::Client.new("http://example.org/ingest", "me", "secret")
29
+ @iobject = Mrt::Ingest::IObject.new
30
+ @ingest_req = @iobject.mk_request("profile", "submitter")
31
+ end
32
+
33
+ should "should create a good rest client request" do
34
+ rest_req = @client.mk_rest_request(@ingest_req)
35
+ assert_equal("me", rest_req.user)
36
+ assert_equal("secret", rest_req.password)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,143 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'rubygems'
5
+
6
+ require 'checkm'
7
+ require 'fakeweb'
8
+ require 'mocha'
9
+ require 'mrt/ingest'
10
+ require 'shoulda'
11
+ require 'open-uri'
12
+
13
+ class TestIObject < Test::Unit::TestCase
14
+ def parse_object_manifest(iobject)
15
+ req = iobject.mk_request("profile", "submitter")
16
+ args = req.mk_args
17
+ return Checkm::Manifest.new(args['file'].read())
18
+ end
19
+
20
+ def write_to_tempfile(content)
21
+ tempfile = Tempfile.new('test_iobject')
22
+ tempfile << content
23
+ tempfile.open
24
+ return tempfile
25
+ end
26
+
27
+ def get_uri_for_name(iobject, name)
28
+ manifest = parse_object_manifest(iobject)
29
+ return manifest.entries.find { |entry|
30
+ entry.values[-2] == name
31
+ }
32
+ end
33
+
34
+ def parse_erc(erc)
35
+ return Hash[erc.map { |l| l.chomp.split(/:\s+/) }]
36
+ end
37
+
38
+ def parse_erc_entry(erc_entry)
39
+ return parse_erc(open(erc_entry.values[0]).read())
40
+ end
41
+
42
+ def check_erc_content(iobject, asserted_erc)
43
+ erc_entry = get_uri_for_name(iobject, "mrt-erc.txt")
44
+ if erc_entry.nil?
45
+ assert(false, "Could not find mrt-erc.txt file!")
46
+ else
47
+ iobject.start_server()
48
+ assert_equal(asserted_erc, parse_erc_entry(erc_entry))
49
+ iobject.stop_server()
50
+ end
51
+ end
52
+
53
+ context "an iobject" do
54
+ setup do
55
+ @iobject = Mrt::Ingest::IObject.new
56
+ end
57
+
58
+ should "be able to add a URI component" do
59
+ @iobject.add_component(URI.parse("http://example.org/file"))
60
+ end
61
+
62
+ should "not be able to add a non-URI component" do
63
+ assert_raise(Mrt::Ingest::IngestException) do
64
+ @iobject.add_component("http://example.org/file")
65
+ end
66
+ end
67
+
68
+ should "be able to make a request" do
69
+ req = @iobject.mk_request("profile", "submitter")
70
+ assert_equal("profile", req.profile)
71
+ assert_equal("submitter", req.submitter)
72
+ end
73
+ end
74
+
75
+ context "the created request" do
76
+ setup do
77
+ @iobject = Mrt::Ingest::IObject.new
78
+ @manifest = parse_object_manifest(@iobject)
79
+ @erc_entry = get_uri_for_name(@iobject, "mrt-erc.txt")
80
+ end
81
+
82
+ should "generate a valid manifest file with more than one line" do
83
+ assert(@manifest.entries.length > 0, "Empty manifest?")
84
+ end
85
+
86
+ should "have a mrt-erc.txt entry, and it should be fetchable" do
87
+ if @erc_entry.nil?
88
+ assert(false, "Could not find mrt-erc.txt file!")
89
+ else
90
+ @iobject.start_server()
91
+ erc_lines = open(@erc_entry.values[0]).read().lines().to_a
92
+ @iobject.stop_server()
93
+ end
94
+ end
95
+ end
96
+
97
+ ERC_CONTENT = <<EOS
98
+ who: John Doe
99
+ what: Something
100
+ when: now
101
+ EOS
102
+
103
+ context "an iobject" do
104
+ should "be able to specify a file for ERC" do
105
+ erc_tempfile = write_to_tempfile(ERC_CONTENT)
106
+ iobject = Mrt::Ingest::IObject.new(:erc=>File.new(erc_tempfile.path))
107
+ check_erc_content(iobject, parse_erc(ERC_CONTENT))
108
+ end
109
+
110
+ should "be able to use a hash for ERC" do
111
+ erc = {
112
+ "who" => "John Doe",
113
+ "what" => "Something",
114
+ "when" => "now" }
115
+ iobject = Mrt::Ingest::IObject.new(:erc=>erc)
116
+ check_erc_content(iobject, erc)
117
+ end
118
+ end
119
+
120
+ FILE_CONTENT = <<EOS
121
+ Hello, world!
122
+ EOS
123
+
124
+ FILE_CONTENT_MD5 = "746308829575e17c3331bbcb00c0898b"
125
+
126
+ context "serving local files" do
127
+ should "be able to add a local file component" do
128
+ iobject = Mrt::Ingest::IObject.new
129
+ tempfile = write_to_tempfile(FILE_CONTENT)
130
+ iobject.add_component(tempfile, {:name => "helloworld" })
131
+ uri_entry = get_uri_for_name(iobject, "helloworld")
132
+ erc_entry = get_uri_for_name(iobject, "mrt-erc.txt")
133
+ manifest = parse_object_manifest(iobject)
134
+ if uri_entry.nil?
135
+ assert(false, "Could not find hosted file URI!")
136
+ else
137
+ iobject.start_server
138
+ assert_equal(FILE_CONTENT, open(uri_entry.values[0]).read())
139
+ iobject.stop_server()
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,36 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'rubygems'
5
+
6
+ require 'fakeweb'
7
+ require 'mocha'
8
+ require 'mrt/ingest'
9
+ require 'shoulda'
10
+
11
+ class TestRequest < Test::Unit::TestCase
12
+ context "when creating a request" do
13
+ setup do
14
+ end
15
+
16
+ should "not supplying a required parameter should raise an exception" do
17
+ assert_raise(Mrt::Ingest::RequestException) do
18
+ Mrt::Ingest::Request.
19
+ new(:submitter => "jd/John Doe",
20
+ :type => "file")
21
+ end
22
+
23
+ assert_raise(Mrt::Ingest::RequestException) do
24
+ Mrt::Ingest::Request.
25
+ new(:profile => "demo_merritt",
26
+ :type => "file")
27
+ end
28
+
29
+ assert_raise(Mrt::Ingest::RequestException) do
30
+ Mrt::Ingest::Request.
31
+ new(:profile => "demo_merritt",
32
+ :submitter => "jd/John Doe")
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ # Author:: Erik Hetzner (mailto:erik.hetzner@ucop.edu)
2
+ # Copyright:: Copyright (c) 2011, Regents of the University of California
3
+
4
+ require 'rubygems'
5
+
6
+ require 'fakeweb'
7
+ require 'mocha'
8
+ require 'mrt/ingest'
9
+ require 'shoulda'
10
+
11
+ class TestResponse < Test::Unit::TestCase
12
+ RESPONSE_JSON = <<EOS
13
+ {
14
+
15
+ "batchState": {
16
+ "batchID":"bid-8c0fa0c2-f3d7-4deb-bd49-f953f6752b59",
17
+ "updateFlag":false,
18
+ "targetQueue":"example.org:2181",
19
+ "batchStatus":"QUEUED",
20
+ "userAgent":"egh/Erik Hetzner",
21
+ "submissionDate":"2011-08-31T15:40:26-07:00",
22
+ "targetQueueNode":"/ingest.example.1",
23
+ "batchProfile": {
24
+ "owner":"ark:/99999/fk4tt4wsh",
25
+ "creationDate":"2010-01-19T13:28:14-08:00",
26
+ "targetStorage": {
27
+ "storageLink":"http://example.org:35121",
28
+ "nodeID":10
29
+ },
30
+ "objectType":"MRT-curatorial",
31
+ "modificationDate":"2010-01-26T23:28:14-08:00",
32
+ "aggregateType":"",
33
+ "objectMinterURL":"https://example.org/ezid/shoulder/ark:/99999/fk4",
34
+ "collection": {
35
+ },
36
+ "profileID":"merritt_content",
37
+ "profileDescription":"Merritt demo content",
38
+ "fixityURL":"http://example.org:33143",
39
+ "contactsEmail": {
40
+ "notification": {
41
+ "contactEmail":"erik.hetzner@example.org"
42
+ }
43
+ },
44
+ "identifierScheme":"ARK",
45
+ "identifierNamespace":"99999",
46
+ "objectRole":"MRT-content"
47
+ }
48
+ }
49
+ }
50
+ EOS
51
+
52
+ context "when creating a response" do
53
+ setup do
54
+ @response = Mrt::Ingest::Response.new(RESPONSE_JSON)
55
+ end
56
+
57
+ should "have the right properties" do
58
+ assert_equal("bid-8c0fa0c2-f3d7-4deb-bd49-f953f6752b59", @response.batch_id)
59
+ assert_equal(Time.at(1314830426), @response.submission_date)
60
+ end
61
+ end
62
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mrt-ingest
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Erik Hetzner
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-12-20 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 1
31
+ - 5
32
+ - 0
33
+ version: 1.5.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rest-client
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 15
45
+ segments:
46
+ - 1
47
+ - 6
48
+ - 0
49
+ version: 1.6.0
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: A client for the Merritt ingest system. More details available from http://wiki.ucop.edu/display/curation.
53
+ email:
54
+ - erik.hetzner@ucop.edu
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files: []
60
+
61
+ files:
62
+ - .hgignore
63
+ - Gemfile
64
+ - LICENSE
65
+ - README
66
+ - Rakefile
67
+ - lib/mrt/ingest.rb
68
+ - lib/mrt/ingest/client.rb
69
+ - lib/mrt/ingest/iobject.rb
70
+ - lib/mrt/ingest/message_digest.rb
71
+ - lib/mrt/ingest/one_time_server.rb
72
+ - lib/mrt/ingest/request.rb
73
+ - lib/mrt/ingest/response.rb
74
+ - mrt-ingest.gemspec
75
+ - test/test_client.rb
76
+ - test/test_iobject.rb
77
+ - test/test_request.rb
78
+ - test/test_response.rb
79
+ homepage: http://bitbucket.org/merritt/mrt-ingest-ruby
80
+ licenses: []
81
+
82
+ post_install_message:
83
+ rdoc_options: []
84
+
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ requirements: []
106
+
107
+ rubyforge_project: mrt-ingest
108
+ rubygems_version: 1.8.12
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: A client for Merritt ingest.
112
+ test_files: []
113
+