ns_connector 0.0.6
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/Gemfile +13 -0
- data/Gemfile.lock +80 -0
- data/Guardfile +9 -0
- data/HACKING +31 -0
- data/LICENSE.txt +7 -0
- data/README.rdoc +191 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/ns_connector.rb +4 -0
- data/lib/ns_connector/attaching.rb +42 -0
- data/lib/ns_connector/chunked_searching.rb +111 -0
- data/lib/ns_connector/config.rb +66 -0
- data/lib/ns_connector/errors.rb +79 -0
- data/lib/ns_connector/field_store.rb +19 -0
- data/lib/ns_connector/hash.rb +11 -0
- data/lib/ns_connector/resource.rb +288 -0
- data/lib/ns_connector/resources.rb +3 -0
- data/lib/ns_connector/resources/contact.rb +279 -0
- data/lib/ns_connector/resources/customer.rb +355 -0
- data/lib/ns_connector/resources/invoice.rb +466 -0
- data/lib/ns_connector/restlet.rb +137 -0
- data/lib/ns_connector/sublist.rb +21 -0
- data/lib/ns_connector/sublist_item.rb +25 -0
- data/misc/failed_sublist_saving_patch +547 -0
- data/scripts/run_restlet +25 -0
- data/scripts/test_shell +21 -0
- data/spec/attaching_spec.rb +48 -0
- data/spec/chunked_searching_spec.rb +75 -0
- data/spec/config_spec.rb +43 -0
- data/spec/resource_spec.rb +340 -0
- data/spec/resources/contact_spec.rb +8 -0
- data/spec/resources/customer_spec.rb +8 -0
- data/spec/resources/invoice_spec.rb +20 -0
- data/spec/restlet_spec.rb +135 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/sublist_item_spec.rb +25 -0
- data/spec/sublist_spec.rb +45 -0
- data/spec/support/mock_data.rb +10 -0
- data/support/read_only_test +63 -0
- data/support/restlet.js +384 -0
- data/support/super_dangerous_write_test +85 -0
- metadata +221 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
remote: http://packages.anchor.net.au/gems/
|
4
|
+
specs:
|
5
|
+
addressable (2.3.4)
|
6
|
+
coderay (1.0.9)
|
7
|
+
columnize (0.3.6)
|
8
|
+
crack (0.3.2)
|
9
|
+
debugger (1.5.0)
|
10
|
+
columnize (>= 0.3.1)
|
11
|
+
debugger-linecache (~> 1.2.0)
|
12
|
+
debugger-ruby_core_source (~> 1.2.0)
|
13
|
+
debugger-linecache (1.2.0)
|
14
|
+
debugger-ruby_core_source (1.2.0)
|
15
|
+
diff-lcs (1.2.4)
|
16
|
+
ffi (1.8.1)
|
17
|
+
formatador (0.2.4)
|
18
|
+
git (1.2.5)
|
19
|
+
guard (1.8.0)
|
20
|
+
formatador (>= 0.2.4)
|
21
|
+
listen (>= 1.0.0)
|
22
|
+
lumberjack (>= 1.0.2)
|
23
|
+
pry (>= 0.9.10)
|
24
|
+
thor (>= 0.14.6)
|
25
|
+
guard-rspec (3.0.1)
|
26
|
+
guard (>= 1.8)
|
27
|
+
rspec (~> 2.13)
|
28
|
+
jeweler (1.8.4)
|
29
|
+
bundler (~> 1.0)
|
30
|
+
git (>= 1.2.5)
|
31
|
+
rake
|
32
|
+
rdoc
|
33
|
+
json (1.8.0)
|
34
|
+
listen (1.1.4)
|
35
|
+
rb-fsevent (>= 0.9.3)
|
36
|
+
rb-inotify (>= 0.9)
|
37
|
+
rb-kqueue (>= 0.2)
|
38
|
+
lumberjack (1.0.3)
|
39
|
+
method_source (0.8.1)
|
40
|
+
pry (0.9.12.2)
|
41
|
+
coderay (~> 1.0.5)
|
42
|
+
method_source (~> 0.8)
|
43
|
+
slop (~> 3.4)
|
44
|
+
pry-debugger (0.2.2)
|
45
|
+
debugger (~> 1.3)
|
46
|
+
pry (~> 0.9.10)
|
47
|
+
rake (10.0.4)
|
48
|
+
rb-fsevent (0.9.3)
|
49
|
+
rb-inotify (0.9.0)
|
50
|
+
ffi (>= 0.5.0)
|
51
|
+
rb-kqueue (0.2.0)
|
52
|
+
ffi (>= 0.5.0)
|
53
|
+
rdoc (4.0.1)
|
54
|
+
json (~> 1.4)
|
55
|
+
rspec (2.13.0)
|
56
|
+
rspec-core (~> 2.13.0)
|
57
|
+
rspec-expectations (~> 2.13.0)
|
58
|
+
rspec-mocks (~> 2.13.0)
|
59
|
+
rspec-core (2.13.1)
|
60
|
+
rspec-expectations (2.13.0)
|
61
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
62
|
+
rspec-mocks (2.13.1)
|
63
|
+
slop (3.4.5)
|
64
|
+
thor (0.18.1)
|
65
|
+
webmock (1.11.0)
|
66
|
+
addressable (>= 2.2.7)
|
67
|
+
crack (>= 0.3.2)
|
68
|
+
|
69
|
+
PLATFORMS
|
70
|
+
ruby
|
71
|
+
|
72
|
+
DEPENDENCIES
|
73
|
+
bundler
|
74
|
+
guard-rspec
|
75
|
+
jeweler
|
76
|
+
pry-debugger
|
77
|
+
rake
|
78
|
+
rdoc
|
79
|
+
rspec
|
80
|
+
webmock
|
data/Guardfile
ADDED
data/HACKING
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Install your bundle:
|
2
|
+
|
3
|
+
bundle install
|
4
|
+
|
5
|
+
Run guard:
|
6
|
+
|
7
|
+
bundle exec guard
|
8
|
+
|
9
|
+
Or, run the tests manually:
|
10
|
+
|
11
|
+
bundle exec rspec
|
12
|
+
|
13
|
+
* The restlet lives in support/restlet.rb
|
14
|
+
* There are basic conformance tests for it in support/
|
15
|
+
* You'll want a config in a file somewhere:
|
16
|
+
|
17
|
+
$ cat tmp/ns_config
|
18
|
+
{
|
19
|
+
:account_id => '1234',
|
20
|
+
:email => 'email@site.com',
|
21
|
+
:password => 'secret',
|
22
|
+
:role => '5678',
|
23
|
+
:restlet_url => 'https://rest.netsuite.com/app/site/hosting/restlet.nl?script=setme&deploy=setme'
|
24
|
+
:valid_customer_id => 1234 # A test customer record for conformance tests
|
25
|
+
}
|
26
|
+
|
27
|
+
* All of the development scripts and conformance tests are run the same way,
|
28
|
+
something like:
|
29
|
+
|
30
|
+
scripts/test_shell "$(cat tmp/ns_config)"
|
31
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (C) 2013 Christian Marie
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
= NSConnector intro
|
2
|
+
This library provides an interface to NetSuite via RESTlets, i.e. SuiteScript .
|
3
|
+
This appears to be a quicker and more reliable way of interfacing with NetSuite
|
4
|
+
records than the SOAP API.
|
5
|
+
|
6
|
+
How is this different to the other netsuite connector gems out there?
|
7
|
+
|
8
|
+
* Basically, it uses a javascript RESTlet to communicate as opposed to the SOAP
|
9
|
+
API, so it's completely different and exposes a separate API.
|
10
|
+
* It's not built around auto generated WSDL which can make it easier to design
|
11
|
+
a usable API.
|
12
|
+
* It can be concurrent, so it at least has the potential to scale. You can only
|
13
|
+
make one request at a time per user using the SOAP API.
|
14
|
+
* It's quicker. Not sure why, but the SOAP API is horrendously slow sometimes,
|
15
|
+
RESTlets seem to be quicker, for now.
|
16
|
+
|
17
|
+
== Features
|
18
|
+
* No dependencies
|
19
|
+
* Read write for supported types
|
20
|
+
* Flexible searching
|
21
|
+
* Multithreaded chunked searching for retrieving large datasets
|
22
|
+
* ActiveRecord (vaguely) alike interface
|
23
|
+
* Read only sublist support.
|
24
|
+
* Attaching and detaching records
|
25
|
+
* Invoice PDF generation.
|
26
|
+
|
27
|
+
== Supported NetSuite types
|
28
|
+
It's pretty trivial to add a new one yourself. These have been tested to work:
|
29
|
+
* Contact
|
30
|
+
* Customer
|
31
|
+
* Invoice
|
32
|
+
|
33
|
+
== Installation
|
34
|
+
Install the RESTlet:
|
35
|
+
function post(request) {
|
36
|
+
func = eval(request.code);
|
37
|
+
return func(request);
|
38
|
+
}
|
39
|
+
|
40
|
+
That's the whole RESTlet. Deploy it to NetSuite, ensuring that the POST
|
41
|
+
function is set to 'post'. Note the 'External URL' when deploying it, that is
|
42
|
+
what you will use in the configuration below.
|
43
|
+
|
44
|
+
== Configuration
|
45
|
+
The configuration is stored 'globally' via NSConnector::Config#set_config!
|
46
|
+
|
47
|
+
An example config:
|
48
|
+
NSConnector::Config.set_config!({
|
49
|
+
:account_id => '123',
|
50
|
+
:email => 'email@site',
|
51
|
+
:password => "password",
|
52
|
+
:role => '456',
|
53
|
+
:restlet_url => 'https://netsuite/restlet',
|
54
|
+
})
|
55
|
+
=== Options
|
56
|
+
==== :account_id (mandatory)
|
57
|
+
TODO: Document how to find an account ID.
|
58
|
+
|
59
|
+
==== :email (mandatory)
|
60
|
+
The email address you log into the NetSuite web site with that matches the
|
61
|
+
account_id.
|
62
|
+
|
63
|
+
==== :password (mandatory)
|
64
|
+
Hopefully a secret.
|
65
|
+
|
66
|
+
==== :role (mandatory)
|
67
|
+
Can be found in your cookie when logged in, or deep within the jungle of the
|
68
|
+
user interface.
|
69
|
+
TODO: Document the perilous journey through the jungle.
|
70
|
+
|
71
|
+
==== :use_threads (optional)
|
72
|
+
A bool, set to false to turn off threading when retrieving large result sets.
|
73
|
+
By default we try to split these result sets up into manageable chunks. If you
|
74
|
+
turn this off, they will still be split up, but only one chunk will be
|
75
|
+
retrieved at a time.
|
76
|
+
|
77
|
+
==== :no_threads (optional)
|
78
|
+
An integer, the number of threads to use. By default, 4.
|
79
|
+
|
80
|
+
== CRUD usage
|
81
|
+
Every supported type supports full CRUD via the same standard interface
|
82
|
+
Check the SuiteScript Records Browser [1] for avaliable fields.
|
83
|
+
|
84
|
+
=== Creating
|
85
|
+
To create a record, simply instantiate a new class of that kind and call
|
86
|
+
.save! on it.
|
87
|
+
|
88
|
+
Calling .save! will re-load the saved Record from NetSuite with any other
|
89
|
+
changes that NetSuite's internal logic may have made.
|
90
|
+
|
91
|
+
Example:
|
92
|
+
|
93
|
+
include NSConnector
|
94
|
+
|
95
|
+
c = Contact.new(:firstname => 'name')
|
96
|
+
=> <#NSConnector::Contact:nil>
|
97
|
+
|
98
|
+
c.fields
|
99
|
+
=> [fields...]
|
100
|
+
|
101
|
+
c.lastname = 'abc'
|
102
|
+
=> 'abc'
|
103
|
+
|
104
|
+
c.save!
|
105
|
+
=> true
|
106
|
+
|
107
|
+
c.lastname
|
108
|
+
=> 'Abc'
|
109
|
+
|
110
|
+
=== Reading
|
111
|
+
You can find by any field or by internalId.
|
112
|
+
Example:
|
113
|
+
|
114
|
+
include NSConnector
|
115
|
+
|
116
|
+
# Search by any internal field ID, returns an array of Contacts
|
117
|
+
Contact.search_by('entityId', 42)
|
118
|
+
=> [<#NSConnector::Contact:12>]
|
119
|
+
|
120
|
+
# Fetch one Contact by internalId
|
121
|
+
Contact.find(12)
|
122
|
+
=> <#NSConnector::Contact:12>
|
123
|
+
|
124
|
+
# Fetch all Contacts, this will take a while. (Request will be broken
|
125
|
+
# up into smaller ones and spread across multiple threads).
|
126
|
+
Contact.all
|
127
|
+
=> [rather large array of NSConnector::Contact]
|
128
|
+
|
129
|
+
You can also perform more complex searces.
|
130
|
+
Example:
|
131
|
+
Contact.advanced_search([
|
132
|
+
['email', 'contains', '@'],
|
133
|
+
['entityId', 'lessthanorequalto', '1000']
|
134
|
+
])
|
135
|
+
=> [<#NSConnector::Contact:12>, <#NS...]
|
136
|
+
|
137
|
+
At any time you can check which fields are avaliable in both the class and
|
138
|
+
instances:
|
139
|
+
Contact.fields
|
140
|
+
Contact.new.fields
|
141
|
+
You can also access the raw data store to more easily see what the object
|
142
|
+
contains with:
|
143
|
+
Contact.find(1234).store
|
144
|
+
==== SubLists
|
145
|
+
SubLists are exposed differently by the backend API, but that's pretty
|
146
|
+
transparent to us, simply access them as a read only array:
|
147
|
+
Contact.addressbook.first.city
|
148
|
+
You can see which sublists are avaliable to an object via the sublists
|
149
|
+
accessor:
|
150
|
+
Contact.sublists
|
151
|
+
Customer.new.sublists
|
152
|
+
|
153
|
+
=== Updating
|
154
|
+
Updating records is much like creating new ones. Simply:
|
155
|
+
1. Grab the record (see Reading).
|
156
|
+
c = Contact.find(12)
|
157
|
+
2. Update the record to your liking.
|
158
|
+
c.lastname = 'newname'
|
159
|
+
3. Save the record (see Creating)
|
160
|
+
c.save!
|
161
|
+
=> true
|
162
|
+
|
163
|
+
=== Deleting
|
164
|
+
Deletion can be done by ID or Record.
|
165
|
+
Example:
|
166
|
+
|
167
|
+
1. By ID
|
168
|
+
Contact.delete!(42)
|
169
|
+
=> true
|
170
|
+
2. By Record
|
171
|
+
c = Contact.find(42)
|
172
|
+
c.delete!
|
173
|
+
|
174
|
+
=== Troubleshooting note:
|
175
|
+
Should you run into an error something like:
|
176
|
+
|
177
|
+
NSConnector::Restlet::RuntimeError: Failed to parse response from Restlet as
|
178
|
+
JSON (): A JSON text must at least contain two octets!
|
179
|
+
|
180
|
+
It may just be that your user is being denied access, in which case netsuite
|
181
|
+
seems to believes it is a good idea to simply send a blank response.
|
182
|
+
|
183
|
+
|
184
|
+
== Development notes
|
185
|
+
See HACKING in the repo
|
186
|
+
|
187
|
+
== License
|
188
|
+
MIT
|
189
|
+
|
190
|
+
== References
|
191
|
+
[1] {SuiteScript Records Browser}[https://system.netsuite.com/help/helpcenter/en_US/RecordsBrowser/2013_1/index.html]
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
warn e.message
|
7
|
+
warn "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'rdoc/task'
|
12
|
+
require 'jeweler'
|
13
|
+
require 'rspec/core/rake_task'
|
14
|
+
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = "ns_connector"
|
17
|
+
gem.homepage = ""
|
18
|
+
gem.summary = "An interface to NetSuite records via RESTlets."
|
19
|
+
gem.description = "This library provides an interface to NetSuite via"\
|
20
|
+
"'RESTlets'. This appears to be a quicker and more reliable"\
|
21
|
+
"way of interfacing with NetSuite records than the SOAP API."
|
22
|
+
gem.authors = ["Christian Marie <pingu@anchor.com.au>"]
|
23
|
+
gem.license = 'MIT'
|
24
|
+
end
|
25
|
+
Jeweler::RubygemsDotOrgTasks.new
|
26
|
+
|
27
|
+
task :default => :test
|
28
|
+
RSpec::Core::RakeTask.new :test
|
29
|
+
|
30
|
+
task :deploy_to_hopper => [:build, :rdoc, :test] do
|
31
|
+
`scp pkg/ns_connector-$(cat VERSION).gem packages@hopper.engineroom.anchor.net.au:public_html/gems/gems/`
|
32
|
+
`ssh packages@hopper.engineroom.anchor.net.au "cd /home/packages/public_html/gems && make"`
|
33
|
+
if $?.to_i == 0 then
|
34
|
+
puts "Deploy to hopper successful"
|
35
|
+
else
|
36
|
+
raise RuntimeError, "Deploy failed :("
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Rake::RDocTask.new do |rd|
|
41
|
+
rd.main = "README.rdoc"
|
42
|
+
rd.title = 'NSConnector documentation'
|
43
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
44
|
+
`scp -r html/* packages@hopper.engineroom.anchor.net.au:public_html/gems/docs/ns_connector/`
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.6
|
data/lib/ns_connector.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Provide attach! and detach! methods
|
2
|
+
module NSConnector::Attaching
|
3
|
+
# Attach any number of ids to klass
|
4
|
+
# Arguments::
|
5
|
+
# klass:: target class to attach to, i.e. Contact
|
6
|
+
# attachee_id:: internal id of the record to make the attach(s) to
|
7
|
+
# ids:: array of target ids
|
8
|
+
# attributes:: optional attributes for the attach, i.e. {:role => -5}
|
9
|
+
def attach!(klass, attachee_id, ids, attributes = nil)
|
10
|
+
unless ids.kind_of?(Array) then
|
11
|
+
raise ::ArgumentError,
|
12
|
+
'Expected ids to be an array'
|
13
|
+
end
|
14
|
+
NSConnector::Restlet.execute!(
|
15
|
+
:action => 'attach',
|
16
|
+
:type_id => type_id,
|
17
|
+
:target_type_id => klass.type_id,
|
18
|
+
:attachee_id => attachee_id,
|
19
|
+
:attributes => attributes,
|
20
|
+
:data => ids
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Unattach any number of ids to klass
|
25
|
+
# Arguments::
|
26
|
+
# klass:: target class to detach from, i.e. Contact
|
27
|
+
# attachee_id:: internal id of the record to make the detach(s) from
|
28
|
+
# ids:: array of target class ids
|
29
|
+
def detach!(klass, attachee_id, ids)
|
30
|
+
unless ids.kind_of?(Array) then
|
31
|
+
raise ::ArgumentError,
|
32
|
+
'Expected ids to be an array'
|
33
|
+
end
|
34
|
+
NSConnector::Restlet.execute!(
|
35
|
+
:action => 'detach',
|
36
|
+
:type_id => type_id,
|
37
|
+
:target_type_id => klass.type_id,
|
38
|
+
:attachee_id => attachee_id,
|
39
|
+
:data => ids
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# Provide threaded and non-threaded chunked searching
|
2
|
+
module NSConnector::ChunkedSearching
|
3
|
+
# Retrieve a single chunk, this makes one HTTP connection
|
4
|
+
# Raises:: NSConnector::Errors::EndChunking when there's no more chunks
|
5
|
+
# Returns:: Resource objects
|
6
|
+
def grab_chunk(filters, chunk)
|
7
|
+
NSConnector::Restlet.execute!(
|
8
|
+
:action => 'search',
|
9
|
+
:type_id => type_id,
|
10
|
+
:data => {
|
11
|
+
:filters => filters,
|
12
|
+
:chunk => chunk,
|
13
|
+
}
|
14
|
+
).map do |upstream_store|
|
15
|
+
self.new(upstream_store)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# The basic logic here is, given four threads we have four workers,
|
20
|
+
# those workers keep eating chunks of data specified by the master.
|
21
|
+
# When a worker recieves a EndChunking error, it flags done as true and
|
22
|
+
# everyone wraps up thier work. Pretty simple.
|
23
|
+
def threaded_search_by_chunks(filters)
|
24
|
+
require 'thread'
|
25
|
+
threads = NSConnector::Config[:no_threads].to_i
|
26
|
+
if threads < 1 then
|
27
|
+
raise NSConnector::Config::ArgumentError,
|
28
|
+
"Need more than #{threads} threads"
|
29
|
+
end
|
30
|
+
|
31
|
+
# We bother pre-populating the queue here because locking is
|
32
|
+
# super expensive, on my build of ruby at least.
|
33
|
+
queue = Queue.new
|
34
|
+
(threads - 1).times do |i|
|
35
|
+
queue << i
|
36
|
+
end
|
37
|
+
|
38
|
+
mutex = Mutex.new
|
39
|
+
|
40
|
+
workers = []
|
41
|
+
results = []
|
42
|
+
current_chunk = threads - 1
|
43
|
+
done = false
|
44
|
+
|
45
|
+
# Workers
|
46
|
+
threads.times do
|
47
|
+
workers << Thread.new do
|
48
|
+
until done
|
49
|
+
begin
|
50
|
+
# Avoid a deadlock by popping
|
51
|
+
# off -1 to exit
|
52
|
+
chunk = queue.pop
|
53
|
+
break if chunk == -1
|
54
|
+
|
55
|
+
result = grab_chunk(
|
56
|
+
filters, chunk
|
57
|
+
)
|
58
|
+
rescue NSConnector::Errors::EndChunking
|
59
|
+
done = true
|
60
|
+
break
|
61
|
+
end
|
62
|
+
|
63
|
+
mutex.synchronize do
|
64
|
+
results += result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Master
|
71
|
+
until done
|
72
|
+
if queue.empty? then
|
73
|
+
queue << current_chunk
|
74
|
+
current_chunk += 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
threads.times do
|
79
|
+
queue << -1
|
80
|
+
end
|
81
|
+
|
82
|
+
workers.each do |worker|
|
83
|
+
worker.join
|
84
|
+
end
|
85
|
+
|
86
|
+
return results
|
87
|
+
end
|
88
|
+
|
89
|
+
# Just keep grabbing incremental chunks till we're told to stop.
|
90
|
+
def normal_search_by_chunks(filters)
|
91
|
+
results = []
|
92
|
+
chunk = 0
|
93
|
+
while true
|
94
|
+
begin
|
95
|
+
results += grab_chunk(filters, chunk)
|
96
|
+
chunk += 1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
rescue NSConnector::Errors::EndChunking
|
100
|
+
return results
|
101
|
+
end
|
102
|
+
|
103
|
+
# Search by requesting chunks
|
104
|
+
def search_by_chunks filters
|
105
|
+
if NSConnector::Config[:use_threads] then
|
106
|
+
return threaded_search_by_chunks(filters)
|
107
|
+
else
|
108
|
+
return normal_search_by_chunks(filters)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|