In Files

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 421
421:     def addFiles(tjiFiles)
422:       tjiFiles.each do |file|
423:         begin
424:           unless @reportServer.addFile(@rs_authKey, file)
425:             return false
426:           end
427:         rescue
428:           error("Cannot add file #{file} to ReportServer")
429:         end
430:       end
431:       true
432:     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 390
390:     def callDaemon(command, args)
391:       begin
392:         return @broker.command(@authKey, command, args)
393:       rescue
394:         error("Call to TaskJuggler server on host '#{@host}' " +
395:               "port #{@port} failed: #{$!}")
396:       end
397:     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 323
323:     def connectToProjectServer
324:       @ps_uri, @ps_authKey = callDaemon(:addProject, [])
325:       begin
326:         @projectServer = DRbObject.new(nil, @ps_uri)
327:       rescue
328:         error("Can't get ProjectServer object: #{$!}")
329:       end
330:       begin
331:         @projectServer.connect(@ps_authKey, $stdout, $stderr, $stdin, @silent)
332:       rescue
333:         error("Can't connect IO: #{$!}")
334:       end
335:     end
connectToReportServer(projectId) click to toggle source
     # File lib/tj3client.rb, line 345
345:     def connectToReportServer(projectId)
346:       @ps_uri, @ps_authKey = callDaemon(:getProject, projectId)
347:       if @ps_uri.nil?
348:         error("No project with ID #{projectId} loaded")
349:       end
350:       begin
351:         @projectServer = DRbObject.new(nil, @ps_uri)
352:         @rs_uri, @rs_authKey = @projectServer.getReportServer(@ps_authKey)
353:         @reportServer = DRbObject.new(nil, @rs_uri)
354:       rescue
355:         error("Cannot get report server")
356:       end
357:       begin
358:         @reportServer.connect(@rs_authKey, $stdout, $stderr, $stdin, @silent)
359:       rescue
360:         error("Can't connect IO: #{$!}")
361:       end
362:     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 337
337:     def disconnectProjectServer
338:       begin
339:         @projectServer.disconnect(@ps_authKey)
340:       rescue
341:         error("Can't disconnect IO: #{$!}")
342:       end
343:     end
disconnectReportServer() click to toggle source
     # File lib/tj3client.rb, line 364
364:     def disconnectReportServer
365:       begin
366:         @reportServer.disconnect(@rs_authKey)
367:       rescue
368:         error("Can't disconnect IO: #{$!}")
369:       end
370:       begin
371:         @reportServer.terminate(@rs_authKey)
372:       rescue
373:         error("Report server termination failed: #{$!}")
374:       end
375:       @reportServer = nil
376:       begin
377:         @projectServer.dropReportServer(@ps_authKey, @rs_uri)
378:       rescue
379:         error("Cannot drop report server: #{$!}")
380:       end
381:       @rs_uri = nil
382:       @rs_authKey = nil
383:       @projectServer = nil
384:       @ps_uri = nil
385:       @ps_authKey = nil
386:     end
error(message) click to toggle source
     # File lib/tj3client.rb, line 434
434:     def error(message)
435:       $stderr.puts "ERROR: #{message}"
436:       exit 1
437:     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:       when 'add'
226:         # Ask the daemon to create a new ProjectServer process and return a
227:         # DRbObject to access it.
228:         connectToProjectServer
229:         # Ask the server to load the files in _args_ into the ProjectServer.
230:         begin
231:           res = @projectServer.loadProject(@ps_authKey, [ Dir.getwd, *args ])
232:         rescue
233:           error("Loading of project failed: #{$!}")
234:         end
235:         disconnectProjectServer
236:         return res ? 0 : 1
237:       when 'remove'
238:         args.each do |arg|
239:           unless callDaemon(:removeProject, arg)
240:             error("Project '#{arg}' not found in list")
241:           end
242:         end
243:       when 'report'
244:         # The first value of args is the project ID. The following values
245:         # could be either report IDs or TJI file # names ('.' or '*.tji').
246:         projectId = args.shift
247:         # Ask the ProjectServer to launch a new ReportServer process and
248:         # provide a DRbObject reference to it.
249:         connectToReportServer(projectId)
250: 
251:         reportIds, tjiFiles = splitIdsAndFiles(args)
252:         if reportIds.empty?
253:           disconnectReportServer
254:           error('You must provide at least one report ID')
255:         end
256:         # Send the provided .tji files to the ReportServer.
257:         failed = !addFiles(tjiFiles)
258:         # Ask the ReportServer to generate the reports with the provided IDs.
259:         unless failed
260:           reportIds.each do |reportId|
261:             unless @reportServer.generateReport(@rs_authKey, reportId,
262:                                                 @regExpMode)
263:               failed = true
264:               break
265:             end
266:           end
267:         end
268:         # Terminate the ReportServer
269:         disconnectReportServer
270:         return failed ? 1 : 0
271:       when 'list-reports'
272:         # The first value of args is the project ID. The following values
273:         # could be either report IDs or TJI file # names ('.' or '*.tji').
274:         projectId = args.shift
275:         # Ask the ProjectServer to launch a new ReportServer process and
276:         # provide a DRbObject reference to it.
277:         connectToReportServer(projectId)
278: 
279:         reportIds, tjiFiles = splitIdsAndFiles(args)
280:         if reportIds.empty?
281:           # If the user did not provide a report ID we generate a full list.
282:           reportIds = [ '.*' ]
283:           @regExpMode = true
284:         end
285:         # Send the provided .tji files to the ReportServer.
286:         failed = !addFiles(tjiFiles)
287:         # Ask the ReportServer to generate the reports with the provided IDs.
288:         unless failed
289:           reportIds.each do |reportId|
290:             unless @reportServer.listReports(@rs_authKey, reportId, @regExpMode)
291:               failed = true
292:               break
293:             end
294:           end
295:         end
296:         # Terminate the ReportServer
297:         disconnectReportServer
298:         return failed ? 1 : 0
299:       when 'check-ts'
300:         connectToReportServer(args[0])
301:         begin
302:           res = @reportServer.checkTimeSheet(@rs_authKey, args[1])
303:         rescue
304:           error("Time sheet check failed: #{$!}")
305:         end
306:         disconnectReportServer
307:         return res ? 0 : 1
308:       when 'check-ss'
309:         connectToReportServer(args[0])
310:         begin
311:           res = @reportServer.checkStatusSheet(@rs_authKey, args[1])
312:         rescue
313:           error("Status sheet check failed: #{$!}")
314:         end
315:         disconnectReportServer
316:         return res ? 0 : 1
317:       else
318:         raise "Unknown command #{command}"
319:       end
320:       0
321:     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 402
402:     def splitIdsAndFiles(args)
403:       reportIds = []
404:       tjiFiles = []
405:       addToReports = true
406:       args.each do |arg|
407:         if arg == '='
408:           # Switch to tji file list.
409:           addToReports = false
410:         elsif addToReports
411:           reportIds << arg
412:         else
413:           tjiFiles << arg
414:         end
415:       end
416: 
417:       [ reportIds, tjiFiles ]
418:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.