bits 0.1.1-mswin32
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/bits.rb +410 -0
- data/test/test_bits.rb +132 -0
- metadata +41 -0
data/lib/bits.rb
ADDED
@@ -0,0 +1,410 @@
|
|
1
|
+
#
|
2
|
+
# An interface for the "Background Intelligent Transfer Service" (BITS) in MS Windows
|
3
|
+
#
|
4
|
+
# A more decent way to interface with BITS would be the COM interface it provides. I did not get it to work, so I had to revert to pasing the bitsadmin command.
|
5
|
+
#
|
6
|
+
# Prereqs (formally stated in ../bits.gemspec):
|
7
|
+
# - "Background Intelligent Transfer Service" (BITS) f�r Windows (KB842773)
|
8
|
+
# - bitsadmin tool, available at http://www.microsoft.com/downloads/details.aspx?amp;displaylang=en&familyid=49AE8576-9BB9-4126-9761-BA8011FABF38&displaylang=en
|
9
|
+
#
|
10
|
+
# Author:: Nicolas E. Rabenau (mailto:nerab@gmx.at)
|
11
|
+
# Copyright:: Copyright (c) 2005 Nicolas E. Rabenau
|
12
|
+
# License:: Distributed under the same terms as Ruby
|
13
|
+
#
|
14
|
+
module BITS
|
15
|
+
#
|
16
|
+
# the basic bitsadmin string that is used in all methods
|
17
|
+
#
|
18
|
+
BITSADMIN = "bitsadmin /rawreturn /nowrap"
|
19
|
+
|
20
|
+
#
|
21
|
+
# The BITS Manager bundles all static BITS operations that are independent from an individual job
|
22
|
+
#
|
23
|
+
class Manager
|
24
|
+
|
25
|
+
#
|
26
|
+
# returns a hash of all BITS jobs where the job's name is the key and the Job itself is the value. If there is more
|
27
|
+
# than one job with the same name the value is an array of all jobs with that name.
|
28
|
+
#
|
29
|
+
def Manager.jobs
|
30
|
+
jobs = Hash.new
|
31
|
+
|
32
|
+
for jobString in `#{BITS::BITSADMIN} /list`.split(/\n/)
|
33
|
+
job = Job.new(jobString)
|
34
|
+
|
35
|
+
# a job's name is not guaranteed to be unique
|
36
|
+
if jobs[job.name].nil?
|
37
|
+
# value has not been assigned before
|
38
|
+
jobs[job.name] = job
|
39
|
+
else
|
40
|
+
if jobs[job.name].is_a? Array
|
41
|
+
# value already contains an array
|
42
|
+
jobs[job.name] << job
|
43
|
+
elsif jobs[job.name].is_a? Job
|
44
|
+
# value contains a Job
|
45
|
+
existingValue = jobs[job.name]
|
46
|
+
jobs[job.name] = Array.new
|
47
|
+
jobs[job.name] << existingValue
|
48
|
+
jobs[job.name] << job
|
49
|
+
else
|
50
|
+
# should never happen. If it does, there is something terribly wrong, so we bail out fast
|
51
|
+
raise "Unexpected class of value (#{jobs[job.name].class}) for jobs['#{job.name}']"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
jobs
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# returns a hash of all BITS jobs where the job's id is the key and the Job itself is the value
|
61
|
+
#
|
62
|
+
def Manager.jobs_by_id
|
63
|
+
jobs = Hash.new
|
64
|
+
|
65
|
+
for jobString in `#{BITS::BITSADMIN} /list`.split(/\n/)
|
66
|
+
job = Job.new(jobString)
|
67
|
+
jobs[job.id] = job
|
68
|
+
end
|
69
|
+
|
70
|
+
jobs
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# create a new job with name
|
75
|
+
#
|
76
|
+
# It is arguable whether this method should be Job.create or Manager.createJob. I decided to use the latter.
|
77
|
+
#
|
78
|
+
def Manager.createJob(name)
|
79
|
+
if "" == name
|
80
|
+
raise "Job name missing"
|
81
|
+
end
|
82
|
+
|
83
|
+
Job.new(`#{BITS::BITSADMIN} /create "#{name}"`)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# deletes all jobs
|
88
|
+
#
|
89
|
+
def Manager.cancelAllJobs
|
90
|
+
`#{BITS::BITSADMIN} /reset`
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# returns the version number of the underlying BITS
|
95
|
+
#
|
96
|
+
# For troubleshooting, we could use "bitsadmin /util /version /verbose"
|
97
|
+
#
|
98
|
+
def Manager.version
|
99
|
+
parsed = `bitsadmin`.scan /\d\.\d/
|
100
|
+
parsed[0]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Models the description of a downloadable file, consisting of an URL and the local name where the result is going to be stored
|
106
|
+
#
|
107
|
+
class FileDescriptor
|
108
|
+
attr_accessor :remote_url, :local_name, :bytes_transferred, :bytes_total
|
109
|
+
|
110
|
+
#
|
111
|
+
# builds a new FileDescription with the URL and the optional local name
|
112
|
+
#
|
113
|
+
def initialize(url, local_name = nil)
|
114
|
+
@remote_url = url
|
115
|
+
@local_name = local_name
|
116
|
+
@bytes_transferred = 0
|
117
|
+
@bytes_total = 'UNKNOWN'
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_s
|
121
|
+
"#{@remote_url} -> #{@local_name}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Models a BITS job
|
127
|
+
#
|
128
|
+
# A job can be modified through set methods, changes will be effective immediately.
|
129
|
+
#
|
130
|
+
class Job
|
131
|
+
#
|
132
|
+
# used to refer to the job in BITS (primary key)
|
133
|
+
#
|
134
|
+
attr_reader :id
|
135
|
+
|
136
|
+
#
|
137
|
+
# builds a new Job from the supplied id. The job must already exist in BITS
|
138
|
+
#
|
139
|
+
# Ideally this method would have only module visibility in order to prevent instantiations of a job outside Manager.createJob
|
140
|
+
#
|
141
|
+
def initialize(job_id)
|
142
|
+
if %r!([A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12})! =~ job_id
|
143
|
+
@id = $1
|
144
|
+
else
|
145
|
+
raise JobParseException, "Unable to parse '#{job_id}' into a job description. Maybe the format has changed?"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def files
|
150
|
+
output = `#{BITS::BITSADMIN} /listfiles {#{@id}}`
|
151
|
+
|
152
|
+
# bitsadmin typically produces something like this:
|
153
|
+
# 0 / UNKNOWN WORKING http://remote -> c:\temp\local.file
|
154
|
+
|
155
|
+
files = Array.new
|
156
|
+
|
157
|
+
for line in output.split(/\n/)
|
158
|
+
if %r!(\d+) / ((\d+)|UNKNOWN) WORKING (.*) -> (.*)! =~ line
|
159
|
+
# print "found 1: >#{$1}< 2: >#{$2}< 3: >#{$3}< 4: >#{$4}< 5: >#{$5}<"
|
160
|
+
p = FileDescriptor.new($4, $5)
|
161
|
+
p.bytes_transferred = $1
|
162
|
+
p.bytes_total = $2
|
163
|
+
files << p
|
164
|
+
else
|
165
|
+
raise FileDescriptorParseException, "Unable to parse '#{output}' into a file descriptor. Maybe the format has changed?"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
files
|
170
|
+
end
|
171
|
+
|
172
|
+
#
|
173
|
+
# adds a file to be downloaded and the local name for the file to the job
|
174
|
+
#
|
175
|
+
def addFile(url, local_name)
|
176
|
+
`bitsadmin /rawreturn /addfile {#{@id}} #{url} #{local_name}`
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# suspends the job
|
181
|
+
#
|
182
|
+
def suspend
|
183
|
+
`#{BITS::BITSADMIN} /suspend {#{@id}}`
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# resumes the job
|
188
|
+
#
|
189
|
+
def resume
|
190
|
+
`#{BITS::BITSADMIN} /resume {#{@id}}`
|
191
|
+
end
|
192
|
+
|
193
|
+
#
|
194
|
+
# cancels the job
|
195
|
+
#
|
196
|
+
def cancel
|
197
|
+
`#{BITS::BITSADMIN} /cancel {#{@id}}`
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# completes the job
|
202
|
+
#
|
203
|
+
def complete
|
204
|
+
`#{BITS::BITSADMIN} /complete {#{@id}}`
|
205
|
+
end
|
206
|
+
|
207
|
+
# returns the job's type
|
208
|
+
def type
|
209
|
+
`#{BITS::BITSADMIN} /gettype {#{@id}}`
|
210
|
+
end
|
211
|
+
|
212
|
+
# returns the job's ACL propagation flags
|
213
|
+
def aclflags
|
214
|
+
`#{BITS::BITSADMIN} /getaclflags {#{@id}}`
|
215
|
+
end
|
216
|
+
|
217
|
+
# returns the size of the job
|
218
|
+
def bytestotal
|
219
|
+
`#{BITS::BITSADMIN} /getbytestotal {#{@id}}`
|
220
|
+
end
|
221
|
+
|
222
|
+
# returns the number of bytes transferred
|
223
|
+
def bytestransferred
|
224
|
+
`#{BITS::BITSADMIN} /getbytestransferred {#{@id}}`
|
225
|
+
end
|
226
|
+
|
227
|
+
# returns the number of files in the job
|
228
|
+
def filestotal
|
229
|
+
`#{BITS::BITSADMIN} /getfilestotal {#{@id}}`
|
230
|
+
end
|
231
|
+
|
232
|
+
# returns the number of files transferred
|
233
|
+
def filestransferred
|
234
|
+
`#{BITS::BITSADMIN} /getfilestransferred {#{@id}}`
|
235
|
+
end
|
236
|
+
|
237
|
+
# returns the job's creation time
|
238
|
+
def creationtime
|
239
|
+
`#{BITS::BITSADMIN} /getcreationtime {#{@id}}`
|
240
|
+
end
|
241
|
+
|
242
|
+
# returns the job's modification time
|
243
|
+
def modificationtime
|
244
|
+
`#{BITS::BITSADMIN} /getmodificationtime {#{@id}}`
|
245
|
+
end
|
246
|
+
|
247
|
+
# returns the job's completion time
|
248
|
+
def completiontime
|
249
|
+
`#{BITS::BITSADMIN} /getcompletiontime {#{@id}}`
|
250
|
+
end
|
251
|
+
|
252
|
+
# returns the job's state
|
253
|
+
def state
|
254
|
+
`#{BITS::BITSADMIN} /getstate {#{@id}}`
|
255
|
+
end
|
256
|
+
|
257
|
+
# returns detailed error information
|
258
|
+
def error
|
259
|
+
`#{BITS::BITSADMIN} /geterror {#{@id}}`
|
260
|
+
end
|
261
|
+
|
262
|
+
# returns the job's owner
|
263
|
+
def owner
|
264
|
+
`#{BITS::BITSADMIN} /getowner {#{@id}}`
|
265
|
+
end
|
266
|
+
|
267
|
+
# returns the job's display name
|
268
|
+
def name
|
269
|
+
`#{BITS::BITSADMIN} /getdisplayname {#{@id}}`
|
270
|
+
end
|
271
|
+
|
272
|
+
# returns the job's description
|
273
|
+
def description
|
274
|
+
`#{BITS::BITSADMIN} /getdescription {#{@id}}`
|
275
|
+
end
|
276
|
+
|
277
|
+
# returns the job's priority
|
278
|
+
def priority
|
279
|
+
`#{BITS::BITSADMIN} /getpriority {#{@id}}`
|
280
|
+
end
|
281
|
+
|
282
|
+
# returns the notify flags
|
283
|
+
def notifyflags
|
284
|
+
`#{BITS::BITSADMIN} /getnotifyflags {#{@id}}`
|
285
|
+
end
|
286
|
+
|
287
|
+
# Determines if notify interface is registered
|
288
|
+
def notifyinterface
|
289
|
+
`#{BITS::BITSADMIN} /getnotifyinterface {#{@id}}`
|
290
|
+
end
|
291
|
+
|
292
|
+
# returns the retry delay in seconds
|
293
|
+
def minretrydelay
|
294
|
+
`#{BITS::BITSADMIN} /getminretrydelay {#{@id}}`
|
295
|
+
end
|
296
|
+
|
297
|
+
# returns the no progress timeout in seconds
|
298
|
+
def noprogresstimeout
|
299
|
+
`#{BITS::BITSADMIN} /getnoprogresstimeout {#{@id}}`
|
300
|
+
end
|
301
|
+
|
302
|
+
# returns an error count for the job
|
303
|
+
def errorcount
|
304
|
+
`#{BITS::BITSADMIN} /geterrorcount {#{@id}}`
|
305
|
+
end
|
306
|
+
|
307
|
+
# returns the proxy usage setting
|
308
|
+
def proxyusage
|
309
|
+
`#{BITS::BITSADMIN} /getproxyusage {#{@id}}`
|
310
|
+
end
|
311
|
+
|
312
|
+
# returns the proxy list
|
313
|
+
def proxylist
|
314
|
+
`#{BITS::BITSADMIN} /getproxylist {#{@id}}`
|
315
|
+
end
|
316
|
+
|
317
|
+
# returns the proxy bypass list
|
318
|
+
def proxybypasslist
|
319
|
+
`#{BITS::BITSADMIN} /getproxybypasslist {#{@id}}`
|
320
|
+
end
|
321
|
+
|
322
|
+
# returns the job's notification command line
|
323
|
+
def notifycmdline
|
324
|
+
`#{BITS::BITSADMIN} /getnotifycmdline {#{@id}}`
|
325
|
+
end
|
326
|
+
|
327
|
+
# returns a String representation of the job
|
328
|
+
def to_s
|
329
|
+
`#{BITS::BITSADMIN} /info {#{@id}}`
|
330
|
+
end
|
331
|
+
|
332
|
+
# attribute writers
|
333
|
+
|
334
|
+
# sets the ACL propagation flags for the job
|
335
|
+
def aclflags=(acl_flags)
|
336
|
+
`#{BITS::BITSADMIN} /setaclflags {#{@id}} #{acl_flags}`
|
337
|
+
end
|
338
|
+
|
339
|
+
# sets the job display name
|
340
|
+
def name=(display_name)
|
341
|
+
`#{BITS::BITSADMIN} /setdisplayname {#{@id}} \"#{display_name}\"`
|
342
|
+
end
|
343
|
+
|
344
|
+
# sets the job description
|
345
|
+
def description=(description)
|
346
|
+
`#{BITS::BITSADMIN} /setdescription {#{@id}} \"#{description}\"`
|
347
|
+
end
|
348
|
+
|
349
|
+
# sets the job priority
|
350
|
+
def priority=(priority)
|
351
|
+
`#{BITS::BITSADMIN} /setpriority {#{@id}} #{priority}`
|
352
|
+
end
|
353
|
+
|
354
|
+
# sets the notify flags
|
355
|
+
def notifyflags=(notify_flags)
|
356
|
+
`#{BITS::BITSADMIN} /setnotifyflags {#{@id}} #{notify_flags}`
|
357
|
+
end
|
358
|
+
|
359
|
+
# sets the retry delay in seconds
|
360
|
+
def minretrydelay=(retry_delay)
|
361
|
+
`#{BITS::BITSADMIN} /setminretrydelay {#{@id}} #{retry_delay}`
|
362
|
+
end
|
363
|
+
|
364
|
+
# sets the no progress timeout in seconds
|
365
|
+
def noprogresstimeout=(timeout)
|
366
|
+
`#{BITS::BITSADMIN} /setnoprogresstimeout {#{@id}} #{timeout}`
|
367
|
+
end
|
368
|
+
|
369
|
+
# sets the proxy usage
|
370
|
+
def proxysettings=(usage)
|
371
|
+
`#{BITS::BITSADMIN} /setproxysettings {#{@id}} #{usage}`
|
372
|
+
end
|
373
|
+
|
374
|
+
# sets a program to execute for notification, and optionally parameters. The program name and parameters can be NULL.
|
375
|
+
# IMPORTANT: if parameters are non-NULL, then the program name should be the first parameter.
|
376
|
+
#
|
377
|
+
# For some reason I didn't get this assignment operation to work with more than a single parameter, therefore I made
|
378
|
+
# it a "setXxx" operation. Not elegant, but it works.
|
379
|
+
#
|
380
|
+
def setnotifycmdline(program_name, program_parameters = "")
|
381
|
+
`#{BITS::BITSADMIN} /setnotifycmdline {#{@id}} #{program_name} \"#{program_parameters}\"`
|
382
|
+
end
|
383
|
+
|
384
|
+
# adds credentials to a job. <target> may be either SERVER or PROXY, <scheme> may be BASIC, DIGEST, NTLM, NEGOTIATE, or PASSPORT.
|
385
|
+
def credentials=(target, scheme, username, password)
|
386
|
+
`#{BITS::BITSADMIN} /setcredentials {#{@id}} #{target} #{scheme} #{username} #{password}`
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
#
|
391
|
+
# Generic parse exception, base class for concrete parse exceptions
|
392
|
+
#
|
393
|
+
class ParseException < Exception
|
394
|
+
|
395
|
+
end
|
396
|
+
|
397
|
+
#
|
398
|
+
# Exception that occurs in case parsing a Job specification fails
|
399
|
+
#
|
400
|
+
class JobParseException < ParseException
|
401
|
+
|
402
|
+
end
|
403
|
+
|
404
|
+
#
|
405
|
+
# Exception that occurs in case parsing a FileDescriptor specification fails
|
406
|
+
#
|
407
|
+
class FileDescriptorParseException < ParseException
|
408
|
+
|
409
|
+
end
|
410
|
+
end
|
data/test/test_bits.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require "bits"
|
3
|
+
include BITS
|
4
|
+
|
5
|
+
#
|
6
|
+
# Unit tests for bits.rb
|
7
|
+
#
|
8
|
+
# Author:: Nicolas E. Rabenau (mailto:nerab@gmx.at)
|
9
|
+
# Copyright:: Copyright (c) 2005 Nicolas E. Rabenau
|
10
|
+
# License:: Distributed under the same terms as Ruby
|
11
|
+
#
|
12
|
+
class BITS_Test < Test::Unit::TestCase
|
13
|
+
|
14
|
+
@@RND_CHARS = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
|
15
|
+
|
16
|
+
#
|
17
|
+
# create job with random name
|
18
|
+
#
|
19
|
+
def setup
|
20
|
+
@jobName = self.class.name + "_" + BITS_Test.randomString(8)
|
21
|
+
@job = Manager.createJob(@jobName)
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# remove test job
|
26
|
+
#
|
27
|
+
def teardown
|
28
|
+
@job.cancel
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# test setting and reading the job's name
|
33
|
+
#
|
34
|
+
def test_name
|
35
|
+
assert_equal(@jobName, @job.name)
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# test setting and reading the job's description
|
40
|
+
#
|
41
|
+
def test_description
|
42
|
+
description = "A test job for bits.rb"
|
43
|
+
@job.description = description
|
44
|
+
assert_equal(description, @job.description)
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# tests the Manager's job listing by name
|
49
|
+
#
|
50
|
+
def test_joblist_by_name
|
51
|
+
assert(0 < Manager.jobs.size)
|
52
|
+
assert(Manager.jobs.has_key?(@jobName))
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# tests that the Manager's job listing by name returns all Jobs with duplicate names in an array
|
57
|
+
#
|
58
|
+
def test_joblist_by_name_with_duplicate_jobnames
|
59
|
+
assert(Manager.jobs.has_key?(@jobName))
|
60
|
+
j2 = Manager.createJob(@jobName)
|
61
|
+
assert_equal(Array, Manager.jobs[@jobName].class)
|
62
|
+
assert_equal(2, Manager.jobs[@jobName].size)
|
63
|
+
j2.cancel
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# tests the Manager's job listing by id
|
68
|
+
#
|
69
|
+
def test_joblist_by_id
|
70
|
+
assert(0 < Manager.jobs_by_id.size)
|
71
|
+
assert(Manager.jobs_by_id.has_key?(@job.id))
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# test setting and reading the job's notify commandline
|
76
|
+
#
|
77
|
+
def test_notifycmdline
|
78
|
+
notifycmdline = "net"
|
79
|
+
notifycmdlineparams = "net send * BITS job #{@job.name} (#{@job.id}) successfully completed."
|
80
|
+
@job.setnotifycmdline(notifycmdline, notifycmdlineparams)
|
81
|
+
@job.description = "Testing notifycmdline"
|
82
|
+
assert_equal("the notification command line is '#{notifycmdline}' '#{notifycmdline}'", @job.notifycmdline)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_status_created
|
86
|
+
assert_equal("SUSPENDED", @job.state)
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# test resuming a job without an URL attached, must fail
|
91
|
+
#
|
92
|
+
def test_resume_empty
|
93
|
+
@job.resume
|
94
|
+
assert_equal("SUSPENDED", @job.state)
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# test resuming a job with an URL attached, must not be in suspended or cancelled state
|
99
|
+
#
|
100
|
+
def test_resume_nonempty
|
101
|
+
@job.addFile("http://www.google.com", "c:\\temp\\google.com.html")
|
102
|
+
@job.resume
|
103
|
+
assert_not_equal("SUSPENDED", @job.state)
|
104
|
+
assert_not_equal("CANCELLED", @job.state)
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# test attaching files to the job
|
109
|
+
#
|
110
|
+
def test_files
|
111
|
+
@job.addFile("http://www.google.com", "c:\\temp\\google.com.html")
|
112
|
+
@job.addFile("http://www.heise.de", "c:\\temp\\heise.de.html")
|
113
|
+
assert_equal(@job.files[0].remote_url, "http://www.google.com")
|
114
|
+
assert_equal(@job.files[0].local_name, "c:\\temp\\google.com.html")
|
115
|
+
assert_equal(@job.files[1].remote_url, "http://www.heise.de")
|
116
|
+
assert_equal(@job.files[1].local_name, "c:\\temp\\heise.de.html")
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
#
|
122
|
+
# returns a String of defined length, made of random characters and numbers
|
123
|
+
#
|
124
|
+
def BITS_Test.randomString(length)
|
125
|
+
retVal = Array.new
|
126
|
+
for i in 0..length
|
127
|
+
retVal[i] = @@RND_CHARS[rand(@@RND_CHARS.length)]
|
128
|
+
end
|
129
|
+
|
130
|
+
retVal.to_s
|
131
|
+
end
|
132
|
+
end
|
metadata
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: bits
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2005-10-12
|
8
|
+
summary: A ruby interface for the Background Intelligent Transfer Service (BITS)
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: nerab@gmx.at
|
12
|
+
homepage: http://bits.rubyforge.org
|
13
|
+
rubyforge_project: bits
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: mswin32
|
27
|
+
authors:
|
28
|
+
- Nicolas E. Rabenau
|
29
|
+
files:
|
30
|
+
- lib/bits.rb
|
31
|
+
test_files:
|
32
|
+
- test/test_bits.rb
|
33
|
+
rdoc_options: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
requirements:
|
38
|
+
- Background Intelligent Transfer Service (BITS) f�r Windows (KB842773)
|
39
|
+
- "bitsadmin tool, available at
|
40
|
+
http://www.microsoft.com/downloads/details.aspx?amp;displaylang=en&familyid=49AE8576-9BB9-4126-9761-BA8011FABF38&displaylang=en"
|
41
|
+
dependencies: []
|