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

Public Instance Methods

appMain(args) click to toggle source
     # File lib/taskjuggler/apps/Tj3Client.rb, line 162
162:     def appMain(args)
163:       begin
164:         # Run a first check of the non-optional command line arguments.
165:         checkCommand(args)
166:         # Read some configuration variables. Except for the authKey, they are
167:         # all optional.
168:         @rc.configure(self, 'global')
169: 
170:         connectDaemon
171:         retVal = executeCommand(args[0], args[1..1])
172:         disconnectDaemon
173: 
174:         return retVal
175:       rescue TjRuntimeError
176:         return 1
177:       end
178:     end
processArguments(argv) click to toggle source
     # File lib/taskjuggler/apps/Tj3Client.rb, line 99
 99:     def processArguments(argv)
100:       super do
101:         @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:
102: 
103:         # Convert the command list into a help text.
104:         @commands.each do |cmd|
105:           tail = ''
106:           args = cmd[:args].dup
107:           args.map! do |c|
108:             if c[0] == '*'
109:               "[<#{c[1..-1]}> ...]"
110:             elsif c[0] == '+'
111:               "<#{c[1..-1]}> [<#{c[1..-1]}> ...]"
112:             elsif c[0] == '!'
113:               tail += ']'
114:               "[#{c[1..-1]} "
115:             else
116:               "<#{c}>"
117:             end
118:           end
119:           args = args.join(' ')
120:           @opts.banner += "     #{cmd[:label] + ' ' + args + tail}" +
121:                           "\n\n#{' ' * 10 + format(cmd[:descr], 10)}\n"
122:         end
123:         @opts.on('-p', '--port <NUMBER>', Integer,
124:                  format('Use the specified TCP/IP port')) do |arg|
125:            @port = arg
126:         end
127:         @opts.on('--urifile <FILE>', String,
128:                  format('If the port is 0, use this file to get the URI ' +
129:                         'of the server.')) do |arg|
130:           @uriFile = arg
131:         end
132:         @opts.on('-r', '--regexp',
133:                  format('The report IDs are not fixed but regular expressions ' +
134:                         'that match a set of reports')) do |arg|
135:           @regExpMode = true
136:         end
137:         @opts.on('--unsafe',
138:                  format('Run the program without sandbox protection. This ' +
139:                         'is not recommended for normal operation! It may ' +
140:                         'only be used for debugging or testing ' +
141:                         'purposes.')) do |arg|
142:           @unsafeMode = true
143:         end
144:         @opts.on('--format [FORMAT]', [ :csv, :html, :niku, :tjp ],
145:                  format('Request the report to be generated in the specified' +
146:                         'format. Use multiple options to request multiple ' +
147:                         'formats. Supported formats are csv, html, niku and ' +
148:                         'tjp. By default, the formats specified in the report ' +
149:                         'definition are used.')) do |arg|
150:           @formats = [] unless @formats
151:           @formats << arg
152:         end
153:       end
154:     end

Private Instance Methods

addFiles(tjiFiles) click to toggle source

Transfer the tjiFiles to the reportServer.

     # File lib/taskjuggler/apps/Tj3Client.rb, line 441
441:     def addFiles(tjiFiles)
442:       tjiFiles.each do |file|
443:         begin
444:           unless @reportServer.addFile(@rs_authKey, file)
445:             return false
446:           end
447:         rescue
448:           error("Cannot add file #{file} to ReportServer")
449:         end
450:       end
451:       true
452:     end
callDaemon(command, args) click to toggle source

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

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

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.