In Files

Class Index [+]

Quicksearch

TaskJuggler::Tj3Client

The Tj3Client class provides the primary interface to the TaskJuggler daemon. It exposes a rich commandline interface that supports key operations like add/removing a project, generating a report or checking a time or status sheet. All connections are made via DRb and tj3client requires a properly configured tj3d to work.

Public Class Methods

new() click to toggle source
    # File lib/tj3client.rb, line 30
30:     def initialize
31:       super
32: 
33:       # For security reasons, this will probably not change. All DRb
34:       # operations are limited to localhost only. The client and the sever
35:       # must have access to the identical file system.
36:       @host = '127.0.0.1'
37:       # The default port. 'T' and 'J' in ASCII decimal
38:       @port = 8474
39:       # This must must be changed for the communication to work.
40:       @authKey = nil
41:       # Determines whether report IDs are fix IDs or regular expressions that
42:       # match a set of reports.
43:       @regExpMode = false
44: 
45:       @mandatoryArgs = '<command> [arg1 arg2 ...]'
46: 
47:       # This list describes the supported command line commands and their
48:       # parameter.
49:       # :label : The command name
50:       # :args : A list of parameters. If the first character is a '+' the
51:       # parameter must be provided 1 or more times. If the first character is
52:       # a '*' the parameter must be provided 0 or more times. Repeatable and
53:       # optional paramters must follow the mandatory ones.
54:       # :descr : A short description of the command used for the help text.
55:       @commands = [
56:         { :label => 'status',
57:           :args  => [],
58:           :descr => 'Display the status of the available projects' },
59:         { :label => 'terminate',
60:           :args  => [],
61:           :descr => 'Terminate the TaskJuggler daemon' },
62:         { :label => 'add',
63:           :args  => [ 'tjp file', '*tji file'],
64:           :descr => 'Add a new project or update and existing one' },
65:         { :label => 'remove',
66:           :args  => [ '+project ID' ],
67:           :descr => 'Remove the project with the specified ID from the daemon' },
68:         { :label => 'report',
69:           :args  => [ 'project ID', '+report ID', '!=', '*tji file'],
70:           :descr => 'Generate the report with the provided ID for ' +
71:                     'the project with the given ID'},
72:         { :label => 'list-reports',
73:           :args  => [ 'project ID', '!report ID' ],
74:           :descr => 'List all available reports of the project or those ' +
75:                     'that match the provided report ID' },
76:         { :label => 'check-ts',
77:           :args  => [ 'project ID', 'time sheet' ],
78:           :descr => 'Check the provided time sheet for correctness' +
79:                     'against the project with the given ID'},
80:         { :label => 'check-ss',
81:           :args  => [ 'project ID', 'status sheet' ],
82:           :descr => 'Check the provided status sheet for correctness ' +
83:                     'against the project with the given ID'}
84:       ]
85:     end

Public Instance Methods

main() click to toggle source
     # File lib/tj3client.rb, line 129
129:     def main
130:       args = super
131:       # Run a first check of the non-option command line arguments.
132:       checkCommand(args)
133:       # Read some configuration variables. Except for the authKey, they are
134:       # all optional.
135:       @rc.configure(self, 'global')
136: 
137:       connectDaemon
138:       retVal = executeCommand(args[0], args[1..1])
139:       disconnectDaemon
140: 
141:       retVal
142:     end
processArguments(argv) click to toggle source
     # File lib/tj3client.rb, line 87
 87:     def processArguments(argv)
 88:       super do
 89:         @opts.banner += The TaskJuggler client is used to send commands and data to the TaskJugglerdaemon. The communication is done via TCP/IP.The following commands are supported:
 90: 
 91:         # Convert the command list into a help text.
 92:         @commands.each do |cmd|
 93:           tail = ''
 94:           args = cmd[:args].dup
 95:           args.map! do |c|
 96:             if c[0] == '*'
 97:               "[<#{c[1..-1]}> ...]"
 98:             elsif c[0] == '+'
 99:               "<#{c[1..-1]}> [<#{c[1..-1]}> ...]"
100:             elsif c[0] == '!'
101:               tail += ']'
102:               "[#{c[1..-1]} "
103:             else
104:               "<#{c}>"
105:             end
106:           end
107:           args = args.join(' ')
108:           @opts.banner += "     #{cmd[:label] + ' ' + args + tail}" +
109:                           "\n\n#{' ' * 10 + format(cmd[:descr], 10)}\n"
110:         end
111:         @opts.on('-p', '--port <NUMBER>', Integer,
112:                  format('Use the specified TCP/IP port')) do |arg|
113:            @port = arg
114:         end
115:         @opts.on('-r', '--regexp',
116:                 format('The report IDs are not fixed but regular expressions ' +
117:                        'that match a set of reports')) do |arg|
118:           @regExpMode = true
119:         end
120:       end
121:     end

Private Instance Methods

addFiles(tjiFiles) click to toggle source

Transfer the tjiFiles to the reportServer.

     # File lib/tj3client.rb, line 418
418:     def addFiles(tjiFiles)
419:       tjiFiles.each do |file|
420:         begin
421:           unless @reportServer.addFile(@rs_authKey, file)
422:             return false
423:           end
424:         rescue
425:           error("Cannot add file #{file} to ReportServer")
426:         end
427:       end
428:       true
429:     end
callDaemon(command, args) click to toggle source

Call the TaskJuggler daemon (ProjectBroker) and execute the provided command with the provided arguments.

     # File lib/tj3client.rb, line 387
387:     def callDaemon(command, args)
388:       begin
389:         return @broker.command(@authKey, command, args)
390:       rescue
391:         error("Call to TaskJuggler server on host '#{@host}' " +
392:               "port #{@port} failed: #{$!}")
393:       end
394:     end
checkCommand(args) click to toggle source
     # File lib/tj3client.rb, line 146
146:     def checkCommand(args)
147:       if args.empty?
148:         errorMessage = 'You must specify a command!'
149:       else
150:         errorMessage = "Unknown command #{args[0]}"
151:         @commands.each do |cmd|
152:           # The first value of args is the command name.
153:           if cmd[:label] == args[0]
154:             # Find out how many arguments we need to have and if that's a
155:             # lower limit or a fixed value.
156:             minArgs = 0
157:             varArgs = false
158:             cmd[:args].each do |arg|
159:               # Arguments starting with '+' must have 1 or more showings.
160:               # Arguments starting with '*' may show up 0 or more times.
161:               minArgs += 1 unless '!*'.include?(arg[0])
162:               varArgs = true if '!*+'.include?(arg[0])
163:             end
164:             return true if args.length - 1 >= minArgs
165:             errorMessage = "Command #{args[0]} must have " +
166:                            "#{varArgs ? 'at least ' : ''}#{minArgs} " +
167:                            'arguments'
168:           end
169:         end
170:       end
171: 
172:       error(errorMessage)
173:     end
connectDaemon() click to toggle source
     # File lib/tj3client.rb, line 175
175:     def connectDaemon
176:       unless @authKey
177:         $stderr.puts You must set an authentication key in the configuration file. Create a filenamed .taskjugglerrc or taskjuggler.rc that contains at least the followinglines. Replace 'your_secret_key' with some random character sequence._global:  authKey: your_secret_key
178:       end
179: 
180:       # We try to play it safe here. The client also starts a DRb server, so
181:       # we need to make sure it's constricted to localhost only. We require
182:       # the DRb server for the standard IO redirection to work.
183:       $SAFE = 1
184:       DRb.install_acl(ACL.new(] deny all
185:                                   allow 127.0.0.1 ]))
186:       DRb.start_service('druby://127.0.0.1:0')
187: 
188:       begin
189:         # Get the ProjectBroker object from the tj3d.
190:         @broker = DRbObject.new(nil, "druby://#{@host}:#{@port}")
191:         # Client and server should always come from the same Gem. Since we
192:         # restict communication to localhost, that's probably not a problem.
193:         if (check = @broker.apiVersion(@authKey, 1)) < 0
194:           error('This client is too old for the server. Please ' +
195:                 'upgrade to a more recent version of the software.')
196:         elsif check == 0
197:           error('Authentication failed. Please check your authentication ' +
198:                 'key to match the server key.')
199:         end
200:       rescue
201:         error("TaskJuggler server on host '#{@host}' port " +
202:               "#{@port} is not responding")
203:       end
204:     end
connectToProjectServer() click to toggle source
     # File lib/tj3client.rb, line 325
325:     def connectToProjectServer
326:       @ps_uri, @ps_authKey = callDaemon(:addProject, [])
327:       begin
328:         @projectServer = DRbObject.new(nil, @ps_uri)
329:       rescue
330:         error("Can't get ProjectServer object: #{$!}")
331:       end
332:       begin
333:         @projectServer.connect(@ps_authKey, $stdout, $stderr, $stdin, @silent)
334:       rescue
335:         error("Can't connect IO: #{$!}")
336:       end
337:     end
connectToReportServer(projectId) click to toggle source
     # File lib/tj3client.rb, line 347
347:     def connectToReportServer(projectId)
348:       @ps_uri, @ps_authKey = callDaemon(:getProject, projectId)
349:       if @ps_uri.nil?
350:         error("No project with ID #{projectId} loaded")
351:       end
352:       begin
353:         @projectServer = DRbObject.new(nil, @ps_uri)
354:         @rs_uri, @rs_authKey = @projectServer.getReportServer(@ps_authKey)
355:         @reportServer = DRbObject.new(nil, @rs_uri)
356:       rescue
357:         error("Cannot get report server")
358:       end
359:       begin
360:         @reportServer.connect(@rs_authKey, $stdout, $stderr, $stdin, @silent)
361:       rescue
362:         error("Can't connect IO: #{$!}")
363:       end
364:     end
disconnectDaemon() click to toggle source
     # File lib/tj3client.rb, line 213
213:     def disconnectDaemon
214:       @broker = nil
215: 
216:       DRb.stop_service
217:     end
disconnectProjectServer() click to toggle source
     # File lib/tj3client.rb, line 339
339:     def disconnectProjectServer
340:       begin
341:         @projectServer.disconnect(@ps_authKey)
342:       rescue
343:         error("Can't disconnect IO: #{$!}")
344:       end
345:     end
disconnectReportServer() click to toggle source
     # File lib/tj3client.rb, line 366
366:     def disconnectReportServer
367:       begin
368:         @reportServer.disconnect(@rs_authKey)
369:       rescue
370:         error("Can't disconnect IO: #{$!}")
371:       end
372:       begin
373:         @reportServer.terminate(@rs_authKey)
374:       rescue
375:         error("Report server termination failed: #{$!}")
376:       end
377:       @reportServer = nil
378:       @rs_uri = nil
379:       @rs_authKey = nil
380:       @projectServer = nil
381:       @ps_uri = nil
382:       @ps_authKey = nil
383:     end
error(message) click to toggle source
     # File lib/tj3client.rb, line 436
436:     def error(message)
437:       $stderr.puts "ERROR: #{message}"
438:       exit 1
439:     end
executeCommand(command, args) click to toggle source
     # File lib/tj3client.rb, line 219
219:     def executeCommand(command, args)
220:       case command
221:       when 'status'
222:         $stdout.puts callDaemon(:status, [])
223:       when 'terminate'
224:         callDaemon(:stop, [])
225:         info('Daemon terminated')
226:       when 'add'
227:         # Ask the daemon to create a new ProjectServer process and return a
228:         # DRbObject to access it.
229:         connectToProjectServer
230:         # Ask the server to load the files in _args_ into the ProjectServer.
231:         begin
232:           res = @projectServer.loadProject(@ps_authKey, [ Dir.getwd, *args ])
233:         rescue
234:           error("Loading of project failed: #{$!}")
235:         end
236:         disconnectProjectServer
237:         return res ? 0 : 1
238:       when 'remove'
239:         args.each do |arg|
240:           unless callDaemon(:removeProject, arg)
241:             error("Project '#{arg}' not found in list")
242:           end
243:         end
244:         info('Project removed')
245:       when 'report'
246:         # The first value of args is the project ID. The following values
247:         # could be either report IDs or TJI file # names ('.' or '*.tji').
248:         projectId = args.shift
249:         # Ask the ProjectServer to launch a new ReportServer process and
250:         # provide a DRbObject reference to it.
251:         connectToReportServer(projectId)
252: 
253:         reportIds, tjiFiles = splitIdsAndFiles(args)
254:         if reportIds.empty?
255:           disconnectReportServer
256:           error('You must provide at least one report ID')
257:         end
258:         # Send the provided .tji files to the ReportServer.
259:         failed = !addFiles(tjiFiles)
260:         # Ask the ReportServer to generate the reports with the provided IDs.
261:         unless failed
262:           reportIds.each do |reportId|
263:             unless @reportServer.generateReport(@rs_authKey, reportId,
264:                                                 @regExpMode, nil)
265:               failed = true
266:               break
267:             end
268:           end
269:         end
270:         # Terminate the ReportServer
271:         disconnectReportServer
272:         return failed ? 1 : 0
273:       when 'list-reports'
274:         # The first value of args is the project ID. The following values
275:         # could be either report IDs or TJI file # names ('.' or '*.tji').
276:         projectId = args.shift
277:         # Ask the ProjectServer to launch a new ReportServer process and
278:         # provide a DRbObject reference to it.
279:         connectToReportServer(projectId)
280: 
281:         reportIds, tjiFiles = splitIdsAndFiles(args)
282:         if reportIds.empty?
283:           # If the user did not provide a report ID we generate a full list.
284:           reportIds = [ '.*' ]
285:           @regExpMode = true
286:         end
287:         # Send the provided .tji files to the ReportServer.
288:         failed = !addFiles(tjiFiles)
289:         # Ask the ReportServer to generate the reports with the provided IDs.
290:         unless failed
291:           reportIds.each do |reportId|
292:             unless @reportServer.listReports(@rs_authKey, reportId, @regExpMode)
293:               failed = true
294:               break
295:             end
296:           end
297:         end
298:         # Terminate the ReportServer
299:         disconnectReportServer
300:         return failed ? 1 : 0
301:       when 'check-ts'
302:         connectToReportServer(args[0])
303:         begin
304:           res = @reportServer.checkTimeSheet(@rs_authKey, args[1])
305:         rescue
306:           error("Time sheet check failed: #{$!}")
307:         end
308:         disconnectReportServer
309:         return res ? 0 : 1
310:       when 'check-ss'
311:         connectToReportServer(args[0])
312:         begin
313:           res = @reportServer.checkStatusSheet(@rs_authKey, args[1])
314:         rescue
315:           error("Status sheet check failed: #{$!}")
316:         end
317:         disconnectReportServer
318:         return res ? 0 : 1
319:       else
320:         raise "Unknown command #{command}"
321:       end
322:       0
323:     end
info(message) click to toggle source
     # File lib/tj3client.rb, line 431
431:     def info(message)
432:       return if @silent
433:       $stdout.puts "#{message}"
434:     end
splitIdsAndFiles(args) click to toggle source

Sort the remaining arguments into a report ID and a TJI file list. If .tji files are present, they must be separated from the report ID list by a ’=’.

     # File lib/tj3client.rb, line 399
399:     def splitIdsAndFiles(args)
400:       reportIds = []
401:       tjiFiles = []
402:       addToReports = true
403:       args.each do |arg|
404:         if arg == '='
405:           # Switch to tji file list.
406:           addToReports = false
407:         elsif addToReports
408:           reportIds << arg
409:         else
410:           tjiFiles << arg
411:         end
412:       end
413: 
414:       [ reportIds, tjiFiles ]
415:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.