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:       # The file with the server URI in case port is 0.
40:       @uriFile = File.join(Dir.getwd, '.tj3d.uri')
41:       # This must must be changed for the communication to work.
42:       @authKey = nil
43:       # Determines whether report IDs are fix IDs or regular expressions that
44:       # match a set of reports.
45:       @regExpMode = false
46: 
47:       @mandatoryArgs = '<command> [arg1 arg2 ...]'
48: 
49:       # This list describes the supported command line commands and their
50:       # parameter.
51:       # :label : The command name
52:       # :args : A list of parameters. If the first character is a '+' the
53:       # parameter must be provided 1 or more times. If the first character is
54:       # a '*' the parameter must be provided 0 or more times. Repeatable and
55:       # optional paramters must follow the mandatory ones.
56:       # :descr : A short description of the command used for the help text.
57:       @commands = [
58:         { :label => 'status',
59:           :args  => [],
60:           :descr => 'Display the status of the available projects' },
61:         { :label => 'terminate',
62:           :args  => [],
63:           :descr => 'Terminate the TaskJuggler daemon' },
64:         { :label => 'add',
65:           :args  => [ 'tjp file', '*tji file'],
66:           :descr => 'Add a new project or update and existing one' },
67:         { :label => 'update',
68:           :args => [],
69:           :descr => 'Reload all projects that have modified files and '+
70:                     'are not being reloaded already' },
71:         { :label => 'remove',
72:           :args  => [ '+project ID' ],
73:           :descr => 'Remove the project with the specified ID from the ' +
74:                     'daemon' },
75:         { :label => 'report',
76:           :args  => [ 'project ID', '+report ID', '!=', '*tji file'],
77:           :descr => 'Generate the report with the provided ID for ' +
78:                     'the project with the given ID'},
79:         { :label => 'list-reports',
80:           :args  => [ 'project ID', '!report ID' ],
81:           :descr => 'List all available reports of the project or those ' +
82:                     'that match the provided report ID' },
83:         { :label => 'check-ts',
84:           :args  => [ 'project ID', 'time sheet' ],
85:           :descr => 'Check the provided time sheet for correctness ' +
86:                     'against the project with the given ID'},
87:         { :label => 'check-ss',
88:           :args  => [ 'project ID', 'status sheet' ],
89:           :descr => 'Check the provided status sheet for correctness ' +
90:                     'against the project with the given ID'}
91:       ]
92:     end

Public Instance Methods

main() click to toggle source
     # File lib/tj3client.rb, line 141
141:     def main
142:       args = super
143:       # Run a first check of the non-option command line arguments.
144:       checkCommand(args)
145:       # Read some configuration variables. Except for the authKey, they are
146:       # all optional.
147:       @rc.configure(self, 'global')
148: 
149:       connectDaemon
150:       retVal = executeCommand(args[0], args[1..1])
151:       disconnectDaemon
152: 
153:       retVal
154:     end
processArguments(argv) click to toggle source
     # File lib/tj3client.rb, line 94
 94:     def processArguments(argv)
 95:       super do
 96:         @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:
 97: 
 98:         # Convert the command list into a help text.
 99:         @commands.each do |cmd|
100:           tail = ''
101:           args = cmd[:args].dup
102:           args.map! do |c|
103:             if c[0] == '*'
104:               "[<#{c[1..-1]}> ...]"
105:             elsif c[0] == '+'
106:               "<#{c[1..-1]}> [<#{c[1..-1]}> ...]"
107:             elsif c[0] == '!'
108:               tail += ']'
109:               "[#{c[1..-1]} "
110:             else
111:               "<#{c}>"
112:             end
113:           end
114:           args = args.join(' ')
115:           @opts.banner += "     #{cmd[:label] + ' ' + args + tail}" +
116:                           "\n\n#{' ' * 10 + format(cmd[:descr], 10)}\n"
117:         end
118:         @opts.on('-p', '--port <NUMBER>', Integer,
119:                  format('Use the specified TCP/IP port')) do |arg|
120:            @port = arg
121:         end
122:         @opts.on('--urifile', String,
123:                  format('If the port is 0, use this file to get the URI ' +
124:                         'of the server.')) do |arg|
125:           @uriFile = arg
126:         end
127:         @opts.on('-r', '--regexp',
128:                 format('The report IDs are not fixed but regular expressions ' +
129:                        'that match a set of reports')) do |arg|
130:           @regExpMode = true
131:         end
132:       end
133:     end

Private Instance Methods

addFiles(tjiFiles) click to toggle source

Transfer the tjiFiles to the reportServer.

     # File lib/tj3client.rb, line 446
446:     def addFiles(tjiFiles)
447:       tjiFiles.each do |file|
448:         begin
449:           unless @reportServer.addFile(@rs_authKey, file)
450:             return false
451:           end
452:         rescue
453:           error("Cannot add file #{file} to ReportServer")
454:         end
455:       end
456:       true
457:     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 415
415:     def callDaemon(command, args)
416:       begin
417:         return @broker.command(@authKey, command, args)
418:       rescue
419:         error("Call to TaskJuggler server on host '#{@host}' " +
420:               "port #{@port} failed: #{$!}")
421:       end
422:     end
checkCommand(args) click to toggle source
     # File lib/tj3client.rb, line 158
158:     def checkCommand(args)
159:       if args.empty?
160:         errorMessage = 'You must specify a command!'
161:       else
162:         errorMessage = "Unknown command #{args[0]}"
163:         @commands.each do |cmd|
164:           # The first value of args is the command name.
165:           if cmd[:label] == args[0]
166:             # Find out how many arguments we need to have and if that's a
167:             # lower limit or a fixed value.
168:             minArgs = 0
169:             varArgs = false
170:             cmd[:args].each do |arg|
171:               # Arguments starting with '+' must have 1 or more showings.
172:               # Arguments starting with '*' may show up 0 or more times.
173:               minArgs += 1 unless '!*'.include?(arg[0])
174:               varArgs = true if '!*+'.include?(arg[0])
175:             end
176:             return true if args.length - 1 >= minArgs
177:             errorMessage = "Command #{args[0]} must have " +
178:                            "#{varArgs ? 'at least ' : ''}#{minArgs} " +
179:                            'arguments'
180:           end
181:         end
182:       end
183: 
184:       error(errorMessage)
185:     end
connectDaemon() click to toggle source
     # File lib/tj3client.rb, line 187
187:     def connectDaemon
188:       unless @authKey
189:         $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
190:       end
191: 
192:       uri = "druby://#{@host}:#{@port}"
193:       if @port == 0
194:         # If the @port is configured to 0, we need to read the URI to connect
195:         # to the server from the .tj3d.uri file that has been generated by the
196:         # server.
197:         begin
198:           uri = File.read(@uriFile).chomp
199:         rescue
200:           error('The server port is configured to be 0, but no ' +
201:                 ".tj3d.uri file can be found: #{$!}")
202:         end
203:       end
204: 
205:       # We try to play it safe here. The client also starts a DRb server, so
206:       # we need to make sure it's constricted to localhost only. We require
207:       # the DRb server for the standard IO redirection to work.
208:       $SAFE = 1
209:       DRb.install_acl(ACL.new(] deny all
210:                                   allow 127.0.0.1 ]))
211:       DRb.start_service('druby://127.0.0.1:0')
212: 
213:       begin
214:         # Get the ProjectBroker object from the tj3d.
215:         @broker = DRbObject.new(nil, uri)
216:         # Client and server should always come from the same Gem. Since we
217:         # restict communication to localhost, that's probably not a problem.
218:         if (check = @broker.apiVersion(@authKey, 1)) < 0
219:           error('This client is too old for the server. Please ' +
220:                 'upgrade to a more recent version of the software.')
221:         elsif check == 0
222:           error('Authentication failed. Please check your authentication ' +
223:                 'key to match the server key.')
224:         end
225:       rescue
226:         error("TaskJuggler server on host '#{@host}' port " +
227:               "#{@port} is not responding")
228:       end
229:     end
connectToProjectServer() click to toggle source
     # File lib/tj3client.rb, line 353
353:     def connectToProjectServer
354:       @ps_uri, @ps_authKey = callDaemon(:addProject, [])
355:       begin
356:         @projectServer = DRbObject.new(nil, @ps_uri)
357:       rescue
358:         error("Can't get ProjectServer object: #{$!}")
359:       end
360:       begin
361:         @projectServer.connect(@ps_authKey, $stdout, $stderr, $stdin, @silent)
362:       rescue
363:         error("Can't connect IO: #{$!}")
364:       end
365:     end
connectToReportServer(projectId) click to toggle source
     # File lib/tj3client.rb, line 375
375:     def connectToReportServer(projectId)
376:       @ps_uri, @ps_authKey = callDaemon(:getProject, projectId)
377:       if @ps_uri.nil?
378:         error("No project with ID #{projectId} loaded")
379:       end
380:       begin
381:         @projectServer = DRbObject.new(nil, @ps_uri)
382:         @rs_uri, @rs_authKey = @projectServer.getReportServer(@ps_authKey)
383:         @reportServer = DRbObject.new(nil, @rs_uri)
384:       rescue
385:         error("Cannot get report server")
386:       end
387:       begin
388:         @reportServer.connect(@rs_authKey, $stdout, $stderr, $stdin, @silent)
389:       rescue
390:         error("Can't connect IO: #{$!}")
391:       end
392:     end
disconnectDaemon() click to toggle source
     # File lib/tj3client.rb, line 238
238:     def disconnectDaemon
239:       @broker = nil
240: 
241:       DRb.stop_service
242:     end
disconnectProjectServer() click to toggle source
     # File lib/tj3client.rb, line 367
367:     def disconnectProjectServer
368:       begin
369:         @projectServer.disconnect(@ps_authKey)
370:       rescue
371:         error("Can't disconnect IO: #{$!}")
372:       end
373:     end
disconnectReportServer() click to toggle source
     # File lib/tj3client.rb, line 394
394:     def disconnectReportServer
395:       begin
396:         @reportServer.disconnect(@rs_authKey)
397:       rescue
398:         error("Can't disconnect IO: #{$!}")
399:       end
400:       begin
401:         @reportServer.terminate(@rs_authKey)
402:       rescue
403:         error("Report server termination failed: #{$!}")
404:       end
405:       @reportServer = nil
406:       @rs_uri = nil
407:       @rs_authKey = nil
408:       @projectServer = nil
409:       @ps_uri = nil
410:       @ps_authKey = nil
411:     end
error(message) click to toggle source
     # File lib/tj3client.rb, line 464
464:     def error(message)
465:       $stderr.puts "ERROR: #{message}"
466:       exit 1
467:     end
executeCommand(command, args) click to toggle source
     # File lib/tj3client.rb, line 244
244:     def executeCommand(command, args)
245:       case command
246:       when 'status'
247:         $stdout.puts callDaemon(:status, [])
248:       when 'terminate'
249:         callDaemon(:stop, [])
250:         info('Daemon terminated')
251:       when 'add'
252:         # Ask the daemon to create a new ProjectServer process and return a
253:         # DRbObject to access it.
254:         connectToProjectServer
255:         # Ask the server to load the files in _args_ into the ProjectServer.
256:         begin
257:           res = @projectServer.loadProject(@ps_authKey, [ Dir.getwd, *args ])
258:         rescue
259:           error("Loading of project failed: #{$!}")
260:         end
261:         disconnectProjectServer
262:         return res ? 0 : 1
263:       when 'remove'
264:         args.each do |arg|
265:           unless callDaemon(:removeProject, arg)
266:             error("Project '#{arg}' not found in list")
267:           end
268:         end
269:         info('Project removed')
270:       when 'update'
271:         callDaemon(:update, [])
272:         info('Reload requested')
273:       when 'report'
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:           disconnectReportServer
284:           error('You must provide at least one report ID')
285:         end
286:         # Send the provided .tji files to the ReportServer.
287:         failed = !addFiles(tjiFiles)
288:         # Ask the ReportServer to generate the reports with the provided IDs.
289:         unless failed
290:           reportIds.each do |reportId|
291:             unless @reportServer.generateReport(@rs_authKey, reportId,
292:                                                 @regExpMode, nil)
293:               failed = true
294:               break
295:             end
296:           end
297:         end
298:         # Terminate the ReportServer
299:         disconnectReportServer
300:         return failed ? 1 : 0
301:       when 'list-reports'
302:         # The first value of args is the project ID. The following values
303:         # could be either report IDs or TJI file # names ('.' or '*.tji').
304:         projectId = args.shift
305:         # Ask the ProjectServer to launch a new ReportServer process and
306:         # provide a DRbObject reference to it.
307:         connectToReportServer(projectId)
308: 
309:         reportIds, tjiFiles = splitIdsAndFiles(args)
310:         if reportIds.empty?
311:           # If the user did not provide a report ID we generate a full list.
312:           reportIds = [ '.*' ]
313:           @regExpMode = true
314:         end
315:         # Send the provided .tji files to the ReportServer.
316:         failed = !addFiles(tjiFiles)
317:         # Ask the ReportServer to generate the reports with the provided IDs.
318:         unless failed
319:           reportIds.each do |reportId|
320:             unless @reportServer.listReports(@rs_authKey, reportId, @regExpMode)
321:               failed = true
322:               break
323:             end
324:           end
325:         end
326:         # Terminate the ReportServer
327:         disconnectReportServer
328:         return failed ? 1 : 0
329:       when 'check-ts'
330:         connectToReportServer(args[0])
331:         begin
332:           res = @reportServer.checkTimeSheet(@rs_authKey, args[1])
333:         rescue
334:           error("Time sheet check failed: #{$!}")
335:         end
336:         disconnectReportServer
337:         return res ? 0 : 1
338:       when 'check-ss'
339:         connectToReportServer(args[0])
340:         begin
341:           res = @reportServer.checkStatusSheet(@rs_authKey, args[1])
342:         rescue
343:           error("Status sheet check failed: #{$!}")
344:         end
345:         disconnectReportServer
346:         return res ? 0 : 1
347:       else
348:         raise "Unknown command #{command}"
349:       end
350:       0
351:     end
info(message) click to toggle source
     # File lib/tj3client.rb, line 459
459:     def info(message)
460:       return if @silent
461:       $stdout.puts "#{message}"
462:     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 427
427:     def splitIdsAndFiles(args)
428:       reportIds = []
429:       tjiFiles = []
430:       addToReports = true
431:       args.each do |arg|
432:         if arg == '='
433:           # Switch to tji file list.
434:           addToReports = false
435:         elsif addToReports
436:           reportIds << arg
437:         else
438:           tjiFiles << arg
439:         end
440:       end
441: 
442:       [ reportIds, tjiFiles ]
443:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.