resync-client 0.1.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGES.md +10 -0
- data/lib/resync/client.rb +63 -0
- data/lib/resync/client/http_helper.rb +90 -90
- data/lib/resync/client/mixins.rb +1 -0
- data/lib/resync/client/mixins/bitstream_resource.rb +26 -0
- data/lib/resync/client/mixins/bitstream_resource_list.rb +29 -0
- data/lib/resync/client/mixins/client_delegator.rb +38 -0
- data/lib/resync/client/mixins/downloadable.rb +36 -0
- data/lib/resync/client/mixins/link_client_delegate.rb +19 -0
- data/lib/resync/client/mixins/resource_client_delegate.rb +19 -0
- data/lib/resync/client/mixins/zipped_resource.rb +20 -0
- data/lib/resync/client/mixins/zipped_resource_list.rb +26 -0
- data/lib/resync/client/version.rb +1 -1
- data/lib/resync/client/zip.rb +1 -0
- data/lib/resync/client/zip/bitstream.rb +85 -0
- data/lib/resync/client/zip/zip_package.rb +78 -0
- data/lib/resync/client/zip/zip_packages.rb +59 -0
- data/lib/resync/extensions.rb +36 -0
- data/resync-client.gemspec +1 -1
- data/spec/acceptance/example_spec.rb +46 -0
- data/spec/data/resourcedump/changedump.xml +16 -0
- data/spec/data/simulator/capability-list.xml +2 -0
- data/spec/data/simulator/change-list.xml +2 -0
- data/spec/data/simulator/source-description.xml +2 -0
- data/spec/data/simulator/update.txt +1 -0
- data/spec/unit/resync/client/bitstream_spec.rb +84 -80
- data/spec/unit/resync/client/client_spec.rb +64 -3
- data/spec/unit/resync/client/{resync_extensions_spec.rb → extensions_spec.rb} +1 -6
- data/spec/unit/resync/client/http_helper_spec.rb +187 -185
- data/spec/unit/resync/client/zip_package_spec.rb +47 -32
- data/spec/unit/resync/client/zip_packages_spec.rb +42 -38
- data/spec/unit/resync/client/zipped_resource_list_spec.rb +61 -0
- metadata +35 -16
- data/lib/resync/client/bitstream.rb +0 -79
- data/lib/resync/client/client.rb +0 -58
- data/lib/resync/client/downloadable.rb +0 -35
- data/lib/resync/client/dump.rb +0 -26
- data/lib/resync/client/resync_extensions.rb +0 -85
- data/lib/resync/client/zip_package.rb +0 -66
- data/lib/resync/client/zip_packages.rb +0 -51
- data/spec/unit/resync/client/dump_spec.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53bce63e13094ab4ffeec5ff5b3d4365c96eaf9f
|
4
|
+
data.tar.gz: 72becf942fb027a0d02d948e3a98fb28d8a626fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97e28c200dbd29c6896c9c4d3e351f89ba0f3836f8cdf0ccc5e87517cb97fefe869e313311f87f635007a06e3373b9c499668b277d089b5b68c227fe4126559d
|
7
|
+
data.tar.gz: c5b24e9fbb0db69734b5e805077b9da163b86ac84a1c4bf296868979812a49e1efa8b863ca1a7a6a386b148ed8c82f0a68c584f51281437851ff8e4a9af11bf8
|
data/.gitignore
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# 0.2.1
|
2
|
+
|
3
|
+
- Update to depend on [resync](https://github.com/dmolesUC3/resync) 0.1.3
|
4
|
+
- Add more tests for client delegation
|
5
|
+
|
6
|
+
# 0.2.0
|
7
|
+
|
8
|
+
- Use named mixins instead of instance monkey-patching for easier documentation and navigation.
|
9
|
+
- Update to depend on [resync](https://github.com/dmolesUC3/resync) 0.1.2
|
10
|
+
|
1
11
|
# 0.1.2
|
2
12
|
|
3
13
|
- Change the `:zip_packages` extension method on `ResourceDump` and `ChangeDump` to return a lazy enumerable instead of preemptively downloading all packages.
|
data/lib/resync/client.rb
CHANGED
@@ -1,3 +1,66 @@
|
|
1
1
|
require 'resync'
|
2
|
+
require_relative 'extensions'
|
2
3
|
|
3
4
|
Dir.glob(File.expand_path('../client/*.rb', __FILE__), &method(:require))
|
5
|
+
|
6
|
+
module Resync
|
7
|
+
|
8
|
+
# Utility class for retrieving HTTP content and parsing it as ResourceSync documents.
|
9
|
+
class Client
|
10
|
+
|
11
|
+
# ------------------------------------------------------------
|
12
|
+
# Initializer
|
13
|
+
|
14
|
+
# Creates a new +Client+
|
15
|
+
# @param helper [HTTPHelper] the HTTP helper. Defaults to a new HTTP helper with
|
16
|
+
# +resync-client VERSION+ as the User-Agent string.
|
17
|
+
def initialize(helper: HTTPHelper.new(user_agent: "resync-client #{VERSION}"))
|
18
|
+
@helper = helper
|
19
|
+
end
|
20
|
+
|
21
|
+
# ------------------------------------------------------------
|
22
|
+
# Public methods
|
23
|
+
|
24
|
+
# Gets the content of the specified URI and parses it as a ResourceSync document.
|
25
|
+
def get_and_parse(uri)
|
26
|
+
uri = Resync::XML.to_uri(uri)
|
27
|
+
raw_contents = get(uri)
|
28
|
+
doc = XMLParser.parse(raw_contents)
|
29
|
+
doc.client_delegate = self
|
30
|
+
doc
|
31
|
+
end
|
32
|
+
|
33
|
+
# Gets the content of the specified URI as a string.
|
34
|
+
# @param uri [URI, String] the URI to download
|
35
|
+
# @return [String] the content of the URI
|
36
|
+
def get(uri)
|
37
|
+
uri = Resync::XML.to_uri(uri)
|
38
|
+
@helper.fetch(uri: uri)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Gets the content of the specified URI and saves it to a temporary file.
|
42
|
+
# @param uri [URI, String] the URI to download
|
43
|
+
# @return [String] the path to the downloaded file
|
44
|
+
def download_to_temp_file(uri)
|
45
|
+
uri = Resync::XML.to_uri(uri)
|
46
|
+
@helper.fetch_to_file(uri: uri)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Gets the content of the specified URI and saves it to the specified file,
|
50
|
+
# overwriting it if it exists.
|
51
|
+
# @param uri [URI, String] the URI to download
|
52
|
+
# @param path [String] the path to save the download to
|
53
|
+
# @return [String] the path to the downloaded file
|
54
|
+
def download_to_file(uri:, path:)
|
55
|
+
uri = Resync::XML.to_uri(uri)
|
56
|
+
@helper.fetch_to_file(path: path, uri: uri)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Allows a {Client} to act as a {Mixins::ClientDelegator} delegate.
|
60
|
+
# @return [Client] this client
|
61
|
+
def client
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -4,108 +4,108 @@ require 'uri'
|
|
4
4
|
require 'mime-types'
|
5
5
|
|
6
6
|
module Resync
|
7
|
+
class Client
|
8
|
+
# Utility class simplifying GET requests for HTTP/HTTPS resources.
|
9
|
+
class HTTPHelper
|
10
|
+
|
11
|
+
# ------------------------------------------------------------
|
12
|
+
# Constants
|
13
|
+
|
14
|
+
# The default number of redirects to follow before erroring out.
|
15
|
+
DEFAULT_MAX_REDIRECTS = 5
|
16
|
+
|
17
|
+
# ------------------------------------------------------------
|
18
|
+
# Accessors
|
19
|
+
|
20
|
+
# @!attribute [rw] user_agent
|
21
|
+
# @return [String] the User-Agent string to send when making requests
|
22
|
+
attr_accessor :user_agent
|
23
|
+
|
24
|
+
# @!attribute [rw] redirect_limit
|
25
|
+
# @return [Integer] the number of redirects to follow before erroring out
|
26
|
+
attr_accessor :redirect_limit
|
27
|
+
|
28
|
+
# ------------------------------------------------------------
|
29
|
+
# Initializer
|
30
|
+
|
31
|
+
# Creates a new +HTTPHelper+
|
32
|
+
#
|
33
|
+
# @param user_agent [String] the User-Agent string to send when making requests
|
34
|
+
# @param redirect_limit [Integer] the number of redirects to follow before erroring out
|
35
|
+
# (defaults to {DEFAULT_MAX_REDIRECTS})
|
36
|
+
def initialize(user_agent:, redirect_limit: DEFAULT_MAX_REDIRECTS)
|
37
|
+
@user_agent = user_agent
|
38
|
+
@redirect_limit = redirect_limit
|
39
|
+
end
|
7
40
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# @!attribute [rw] user_agent
|
22
|
-
# @return [String] the User-Agent string to send when making requests
|
23
|
-
attr_accessor :user_agent
|
24
|
-
|
25
|
-
# @!attribute [rw] redirect_limit
|
26
|
-
# @return [Integer] the number of redirects to follow before erroring out
|
27
|
-
attr_accessor :redirect_limit
|
28
|
-
|
29
|
-
# ------------------------------------------------------------
|
30
|
-
# Initializer
|
31
|
-
|
32
|
-
# Creates a new +HTTPHelper+
|
33
|
-
#
|
34
|
-
# @param user_agent [String] the User-Agent string to send when making requests
|
35
|
-
# @param redirect_limit [Integer] the number of redirects to follow before erroring out
|
36
|
-
# (defaults to {DEFAULT_MAX_REDIRECTS})
|
37
|
-
def initialize(user_agent:, redirect_limit: DEFAULT_MAX_REDIRECTS)
|
38
|
-
@user_agent = user_agent
|
39
|
-
@redirect_limit = redirect_limit
|
40
|
-
end
|
41
|
-
|
42
|
-
# ------------------------------------------------------------
|
43
|
-
# Public methods
|
44
|
-
|
45
|
-
# Gets the content of the specified URI as a string.
|
46
|
-
# @param uri [URI] the URI to download
|
47
|
-
# @param limit [Integer] the number of redirects to follow (defaults to {#redirect_limit})
|
48
|
-
# @return [String] the content of the URI
|
49
|
-
def fetch(uri:, limit: redirect_limit)
|
50
|
-
make_request(uri, limit) do |success|
|
51
|
-
# not 100% clear why we need an explicit return here; it
|
52
|
-
# doesn't show up in unit tests but it does in example.rb
|
53
|
-
return success.body
|
41
|
+
# ------------------------------------------------------------
|
42
|
+
# Public methods
|
43
|
+
|
44
|
+
# Gets the content of the specified URI as a string.
|
45
|
+
# @param uri [URI] the URI to download
|
46
|
+
# @param limit [Integer] the number of redirects to follow (defaults to {#redirect_limit})
|
47
|
+
# @return [String] the content of the URI
|
48
|
+
def fetch(uri:, limit: redirect_limit)
|
49
|
+
make_request(uri, limit) do |success|
|
50
|
+
# not 100% clear why we need an explicit return here; it
|
51
|
+
# doesn't show up in unit tests but it does in example.rb
|
52
|
+
return success.body
|
53
|
+
end
|
54
54
|
end
|
55
|
-
end
|
56
55
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
56
|
+
# Gets the content of the specified URI and saves it to a file. If no
|
57
|
+
# file path is provided, saves it to a temporary file.
|
58
|
+
# @param uri [URI] the URI to download
|
59
|
+
# @param path [String] the path to save the download to (optional)
|
60
|
+
# @return [String] the path to the downloaded file
|
61
|
+
def fetch_to_file(uri:, path: nil, limit: redirect_limit)
|
62
|
+
make_request(uri, limit) do |success|
|
63
|
+
file = path ? File.new(path, 'w+') : Tempfile.new(['resync-client', ".#{extension_for(success)}"])
|
64
|
+
open file, 'w' do |out|
|
65
|
+
success.read_body { |chunk| out.write(chunk) }
|
66
|
+
end
|
67
|
+
return file.path
|
67
68
|
end
|
68
|
-
return file.path
|
69
69
|
end
|
70
|
-
end
|
71
70
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
71
|
+
# ------------------------------------------------------------
|
72
|
+
# Private methods
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def make_request(uri, limit, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
77
|
+
fail "Redirect limit (#{redirect_limit}) exceeded retrieving URI #{uri}" if limit <= 0
|
78
|
+
req = Net::HTTP::Get.new(uri, 'User-Agent' => user_agent)
|
79
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: (uri.scheme == 'https')) do |http|
|
80
|
+
http.request(req) do |response|
|
81
|
+
case response
|
82
|
+
when Net::HTTPSuccess
|
83
|
+
block.call(response)
|
84
|
+
when Net::HTTPInformation, Net::HTTPRedirection
|
85
|
+
make_request(redirect_uri_for(response, uri), limit - 1, &block)
|
86
|
+
else
|
87
|
+
fail "Error #{response.code}: #{response.message} retrieving URI #{uri}"
|
88
|
+
end
|
89
89
|
end
|
90
90
|
end
|
91
91
|
end
|
92
|
-
end
|
93
92
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
93
|
+
def extension_for(response)
|
94
|
+
content_type = response['Content-Type']
|
95
|
+
mime_type = MIME::Types[content_type].first || MIME::Types['application/octet-stream'].first
|
96
|
+
mime_type.preferred_extension || 'bin'
|
97
|
+
end
|
99
98
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
99
|
+
def redirect_uri_for(response, original_uri)
|
100
|
+
if response.is_a?(Net::HTTPInformation)
|
101
|
+
original_uri
|
102
|
+
else
|
103
|
+
location = response['location']
|
104
|
+
new_uri = URI(location)
|
105
|
+
new_uri.relative? ? original_uri + location : new_uri
|
106
|
+
end
|
107
107
|
end
|
108
|
-
end
|
109
108
|
|
109
|
+
end
|
110
110
|
end
|
111
111
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir.glob(File.expand_path('../mixins/*.rb', __FILE__), &method(:require))
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative '../zip'
|
2
|
+
|
3
|
+
# A resource that refers to a bitsream within a zipped bitstream package.
|
4
|
+
#
|
5
|
+
# @!attribute [rw] zip_package_delegate
|
6
|
+
# @return [ZipPackage] the provider of the containing package,
|
7
|
+
# e.g. its manifest
|
8
|
+
module Resync
|
9
|
+
class Client
|
10
|
+
module Mixins
|
11
|
+
module BitstreamResource
|
12
|
+
attr_accessor :zip_package_delegate
|
13
|
+
|
14
|
+
# @return [ZipPackage] the package containing the bitstream for this resource
|
15
|
+
def containing_package
|
16
|
+
@zip_package_delegate.zip_package
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Bitstream] the bitstream for this resource
|
20
|
+
def bitstream
|
21
|
+
containing_package.bitstream_for(self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../zip'
|
2
|
+
require_relative 'bitstream_resource'
|
3
|
+
|
4
|
+
# A list of resources within a single zipped bitstream package, e.g. as provided
|
5
|
+
# by the package manifest.
|
6
|
+
#
|
7
|
+
# @!attribute [rw] zip_package
|
8
|
+
# @return [ZipPackage] the package.
|
9
|
+
module Resync
|
10
|
+
class Client
|
11
|
+
module Mixins
|
12
|
+
module BitstreamResourceList
|
13
|
+
attr_accessor :zip_package
|
14
|
+
|
15
|
+
# Makes each provided resource a {BitstreamResource}
|
16
|
+
# @param value [Array<Resource>] the resources for this list
|
17
|
+
def resources=(value)
|
18
|
+
super
|
19
|
+
resources.each do |r|
|
20
|
+
class << r
|
21
|
+
prepend BitstreamResource
|
22
|
+
end
|
23
|
+
r.zip_package_delegate = self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../../client'
|
2
|
+
|
3
|
+
# An object that delegates to another to provide a {Client} for downloading
|
4
|
+
# resources and links.
|
5
|
+
#
|
6
|
+
# @!attribute [rw] client_delegate
|
7
|
+
# @return [#client] The client provider.
|
8
|
+
module Resync
|
9
|
+
class Client
|
10
|
+
module Mixins
|
11
|
+
module ClientDelegator
|
12
|
+
attr_accessor :client_delegate
|
13
|
+
|
14
|
+
def client
|
15
|
+
client_delegate.client
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates a one-off delegate wrapper around the specified {Client}
|
19
|
+
# @param value [Client] the client
|
20
|
+
def client=(value)
|
21
|
+
@client_delegate = ClientDelegate.new(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Minimal 'delegate' wrapper around a specified {Client}
|
25
|
+
class ClientDelegate
|
26
|
+
# @return [#client] the client
|
27
|
+
attr_reader :client
|
28
|
+
|
29
|
+
# Creates a new {ClientDelegate} wrapping the specified {Client}
|
30
|
+
# @param client The client to delegate to
|
31
|
+
def initialize(client)
|
32
|
+
@client = client
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'client_delegator'
|
2
|
+
|
3
|
+
# A downloadable resource or link.
|
4
|
+
module Resync
|
5
|
+
class Client
|
6
|
+
module Mixins
|
7
|
+
module Downloadable
|
8
|
+
prepend ClientDelegator
|
9
|
+
|
10
|
+
# Delegates to {Client#get_and_parse} to get the contents of
|
11
|
+
# +:uri+ as a ResourceSync document
|
12
|
+
def get_and_parse # rubocop:disable Style/AccessorMethodName
|
13
|
+
client.get_and_parse(uri)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Delegates to {Client#get} to get the contents of this +:uri+
|
17
|
+
def get # rubocop:disable Style/AccessorMethodName
|
18
|
+
client.get(uri)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Delegates to {Client#download_to_temp_file} to download the
|
22
|
+
# contents of +:uri+ to a file.
|
23
|
+
def download_to_temp_file # rubocop:disable Style/AccessorMethodName
|
24
|
+
client.download_to_temp_file(uri)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Delegates to {Client#download_to_file} to download the
|
28
|
+
# contents of +:uri+ to the specified path.
|
29
|
+
# @param path [String] the path to download to
|
30
|
+
def download_to_file(path)
|
31
|
+
client.download_to_file(uri: uri, path: path)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'client_delegator'
|
2
|
+
|
3
|
+
# A link container that is capable of providing those resources with a {Client}
|
4
|
+
module Resync
|
5
|
+
class Client
|
6
|
+
module Mixins
|
7
|
+
module LinkClientDelegate
|
8
|
+
prepend ClientDelegator
|
9
|
+
|
10
|
+
# Sets this object as the client provider delegate for each link.
|
11
|
+
# @param value [Array<Link>] the links for this list
|
12
|
+
def links=(value)
|
13
|
+
super
|
14
|
+
links.each { |l| l.client_delegate = self }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|