001package votorola.s.line; // Copyright 2007-2011, 2013, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import java.io.*; 004import java.sql.*; 005import java.text.*; 006import java.util.*; 007import java.util.Date; // over java.sql.Date 008import votorola.a.*; 009import votorola.a.count.*; 010import votorola.a.response.line.*; 011import votorola.a.trust.NetworkTrace; 012import votorola.g.*; 013import votorola.g.hold.*; 014import votorola.g.io.*; 015import votorola.g.lang.*; 016import votorola.g.logging.*; 017import votorola.g.mail.*; 018import votorola.g.option.*; 019import votorola.g.sql.*; 020import votorola.g.text.*; 021 022 023/** Main class of the executable <code>vocount</code> - a tool to count the votes. 024 * 025 * @see <a href='../../../../../s/manual.xht#line-vocount' target='_top'>vocount</a> 026 */ 027public @ThreadSafe final class VOCount extends ResultsCompiler 028{ 029 030 // cf. a/trust/VOTrace 031 032 033 /** Runs the tool from the command line. 034 * 035 * @param argv command line argument array 036 */ 037 public static void main( final String[] argv ) 038 { 039 LoggerX.i(VOCount.class).info( "vocount is running with arguments " + Arrays.toString( argv )); 040 new VOCount().run( argv ); 041 } 042 043 044 045//// P r i v a t e /////////////////////////////////////////////////////////////////////// 046 047 048 private ArrayList<File> nominalReadyDirectories( final File vocountDirectory ) 049 { 050 final ArrayList<File> list = new ArrayList<File>(); 051 final File[] snapDirectoryArray = FileX.listFilesNoNull( vocountDirectory, 052 OutputStore.SNAP_DIRECTORY_FILTER ); 053 for( final File snapDirectory: snapDirectoryArray ) 054 { 055 final File[] readyDirectoryArray = FileX.listFilesNoNull( snapDirectory, 056 ReadyDirectory.READY_DIRECTORY_FILTER ); 057 for( final File readyDirectory: readyDirectoryArray ) list.add( readyDirectory ); 058 } 059 // Collections.sort( list ); // sorts on pathnames, with '/' messing up the order 060 //// no need, it's fine on Linux 061 return list; 062 } 063 064 065 066 private void run( final String[] argv ) 067 { 068 final Map<String,Option> optionMap = compileBaseOptions(); 069 { 070 final String name = "churn"; 071 optionMap.put( name, new Option( name, Option.NO_ARGUMENT )); 072 } 073 074 final int aFirstNonOption = GetoptX.parse( "vocount", argv, optionMap ); 075 if( optionMap.get("help").hasOccured() ) 076 { 077 System.out.print 078 ( 079 "Usage: vocount [--verbose] [--etc[=ready|mount|report|umount]] [--churn]\n" + 080 " or vocount snap [--verbose] [--etc[=ready|mount|report|umount]]\n" + 081 " vocount ready [--verbose] [--etc[=mount|report|umount]]\n" + 082 " [~/votorola/out/vocount/snap-YYYY-MD-S]\n" + 083 " vocount mount [--verbose] [--etc[=report|umount]] [--churn]\n" + 084 " [~/votorola/out/vocount/snap-YYYY-MD-S/readyCount-S]\n" + 085 " vocount report [--verbose] [--etc]\n" + 086 " [~/votorola/out/vocount/snap-YYYY-MD-S/readyCount-S]\n" + 087 " vocount umount [--verbose]\n" + 088 " [~/votorola/out/vocount/snap-OLD-YYYY-MD-S/readyCount-OLD-S]\n" + 089 " or vocount ureport [--verbose]\n" + 090 " or vocount status\n" + 091 "Count the votes and mark the results available for reporting.\n" 092 ); 093 return; 094 } 095 096 final int argCount = argv.length - aFirstNonOption; 097 final Action action; // first argument 098 final String argument; // second 099 if( argCount == 0 ) 100 { 101 action = Action.snap; 102 argument = null; 103 final Option etc = optionMap.get( "etc" ); 104 if( !etc.hasOccured() ) etc.addOccurence( Action.END_ACTION_LAST.name() ); 105 } 106 else if( argCount > 2 ) 107 { 108 System.err.println( "vocount: too many arguments" ); 109 System.err.println( GetoptX.createHelpPrompt( "vocount" )); 110 System.exit( 1 ); return; // redundant return, to prevent compiler warnings 111 } 112 else 113 { 114 final String actionArg = argv[aFirstNonOption]; 115 try 116 { 117 action = Action.valueOf( actionArg ); 118 } 119 catch( IllegalArgumentException x ) 120 { 121 System.err.println( "vocount: unrecognized action argument: " + actionArg ); 122 System.err.println( GetoptX.createHelpPrompt( "vocount" )); 123 System.exit( 1 ); return; // redundant return, to prevent compiler warnings 124 } 125 126 if( argCount == 1 ) argument = null; 127 else 128 { 129 assert argCount == 2; 130 argument = argv[aFirstNonOption + 1]; 131 } 132 } 133 run( action, argument, optionMap ); 134 } 135 136 137 138 private void run( final Action action, String argument, final Map<String,Option> optionMap ) 139 { 140 final boolean isEtc = action.isEtc( "vocount", optionMap ); 141 final boolean isVerbose = optionMap.get("verbose").hasOccured(); 142 try 143 { 144 final VoteServer vS = voteServer(); 145 146 // 1. Snap 147 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 148 if( action == Action.snap ) 149 { 150 if( argument != null ) 151 { 152 System.err.println( "vocount: too many arguments" ); 153 System.err.println( GetoptX.createHelpPrompt( "vocount" )); 154 System.exit( 1 ); 155 } 156 157 // timestamp santity check 158 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 159 final File vocountDirectory = new File( vS.outDirectory(), "vocount" ); 160 final long msStart = System.currentTimeMillis(); 161 for( final File d: FileX.listFilesNoNull( vocountDirectory, 162 OutputStore.SNAP_DIRECTORY_FILTER )) 163 { 164 if( d.lastModified() > msStart ) 165 { 166 System.err.println( 167 "vocount, warning: existing snap directory timestamped in future: " + d ); 168 // Problematic because the default for the 'ready' action 169 // depends on timestamps. 170 } 171 } 172 173 // snap 174 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 175 final File snapDirectory = OutputStore.mkdirsY4MDS( vocountDirectory, "snap" ); 176 final File inVoteDirectory = new File( snapDirectory, "_in_vote" ); 177 inVoteDirectory.mkdir(); 178 final SimpleDateFormat iso8601Formatter = 179 new SimpleDateFormat( SimpleDateFormatX.ISO_8601_PATTERN ); 180 final Database database = vsRun().database(); 181 try 182 { 183 synchronized( database ) 184 { 185 final Statement s = database.connection().createStatement(); 186 try 187 { 188 Spool spool = Spool0.i(); 189 final ResultSet r = s.executeQuery( 190 "SELECT serviceName, voterEmail, xml FROM in_vote" 191 + " ORDER BY serviceName" ); 192 try 193 { 194 String serviceName = null; 195 BufferedWriter w = null; 196 final Date date = new Date( 0L ); 197 while( r.next() ) 198 { 199 final String serviceNameKey = r.getString( 1 ); 200 if( !serviceNameKey.equals( serviceName )) 201 { 202 spool.unwind(); 203 spool = new Spool1(); 204 serviceName = serviceNameKey; 205 final File inVoteFile = new File( inVoteDirectory, 206 serviceName + ".xml" ); 207 // separate file per poll, to allow independent 208 // verification of each by remote verifiers. 209 // Also to keep file size within OS limits 210 FileX.ensuredirs( inVoteFile.getParentFile() ); 211 final BufferedWriter writerNew = new BufferedWriter( 212 new OutputStreamWriter( new FileOutputStream( 213 inVoteFile ), "UTF-8" )); 214 spool.add( new Hold() 215 { 216 public void release() 217 { 218 try 219 { 220 writerNew.append( " </in>\n" ); 221 writerNew.close(); 222 } 223 catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); } 224 } 225 }); 226 w = writerNew; 227 w.append( "<?xml version='1.0' encoding='UTF-8'?> <!-- -*-coding: utf-8;-*- -->\n" 228 + "<in serviceName='" + serviceName + "'>\n" ); 229 } 230 final String voterEmail = r.getString(2); 231 String loc = InternetAddressX.localPart( voterEmail ); 232 String dom = voterEmail.substring( loc.length() + 1 ); 233 w.append( " <pos>\n" 234 + " <subj dom='" + dom + "' loc='" + loc + "'/>\n" ); 235 final Vote vote = new Vote( voterEmail, r.getString(3) ); 236 date.setTime( vote.getTime() ); 237 w.append( " <vote t='" ).append( iso8601Formatter.format( date )).append( "'" ); 238 final int dartSector = vote.getDartSector(); 239 if( dartSector != 0 ) w.append( " dS='" ).append( Integer.toString(dartSector) ).append( "'" ); 240 w.append( ">\n" ); 241 final String candidateEmail = vote.getCandidateEmail(); 242 if( candidateEmail != null ) 243 { 244 loc = InternetAddressX.localPart( candidateEmail ); 245 dom = candidateEmail.substring( loc.length() + 1 ); 246 w.append( 247 " <obj dom='" + dom + "' loc='" + loc + "'/>\n" ); 248 } 249 w.append( " </vote>\n" 250 + " </pos>\n" ); 251 } 252 } 253 finally 254 { 255 spool.unwind(); 256 r.close(); 257 } 258 } 259 catch( SQLException x ) 260 { 261 if( !"42P01".equals( x.getSQLState() )) throw x; // 42P01 = UNDEFINED TABLE 262 263 System.err.println( "vocount: OK, but no votes to count: " + x.toString() ); 264 } 265 finally{ s.close(); } 266 } 267 } 268 finally{ database.logAndClearWarnings(); } 269 270 // record snap.xml 271 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 272 final BufferedWriter out = new BufferedWriter( new OutputStreamWriter( 273 new FileOutputStream( new File( snapDirectory, 274 OutputStore.SNAP_SUMMARY_FILENAME )), "UTF-8" )); 275 try 276 { 277 out.append( "<snap msStart='" ); // a.count.Count.msStartSnap() 278 out.append( Long.toString( msStart )); 279 out.append( "' msEnd='" ); // a.count.Count.msEndSnap() 280 out.append( Long.toString( System.currentTimeMillis() )); 281 out.append( "'/>" ); 282 } 283 finally{ out.close(); } 284 285 // ` ` ` 286 if( !isEtc || isVerbose ) System.out.println( snapDirectory ); 287 } 288 289 // 2. Ready 290 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 291 else if( action == Action.ready ) 292 { 293 final File vocountDirectory = new File( vS.outDirectory(), "vocount" ); 294 if( argument == null ) 295 { 296 File snapDirectory = null; // till found 297 for( final File d: FileX.listFilesNoNull( vocountDirectory, 298 OutputStore.SNAP_DIRECTORY_FILTER )) 299 { 300 if( snapDirectory == null 301 || d.lastModified() > snapDirectory.lastModified() ) snapDirectory = d; 302 } 303 if( snapDirectory == null ) 304 { 305 System.err.println( 306 "vocount: nothing to ready, no snap directory exists\n" 307 + " try: vocount snap" ); 308 System.exit( 1 ); 309 } 310 311 argument = snapDirectory.getPath(); 312 if( isVerbose ) 313 { 314 System.out.println( "readying most recently modified snap directory: " 315 + argument ); 316 } 317 } 318 final File snapDirectory = new File( argument ); 319 final File trustReadyToReportLink = vS.scopeTrace().readyToReportLink(); 320 if( !trustReadyToReportLink.exists() ) 321 { 322 System.err.println( 323 "vocount: no compiled registrations, missing symbolic link to ready trace: " 324 + trustReadyToReportLink ); 325 System.exit( 1 ); 326 } 327 328 // timestamp santity check 329 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 330 final long msStart = System.currentTimeMillis(); 331 for( final File r: nominalReadyDirectories( vocountDirectory )) 332 { 333 if( r.lastModified() > msStart ) 334 { 335 System.err.println( 336 "vocount, warning: existing record timestamped in future: " + r ); 337 // Problematic because the default for the 'mount' action 338 // depends on timestamps. 339 } 340 } 341 342 // ready 343 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 344 final File d = OutputStore.mkdirsS( snapDirectory, "readyCount" ); 345 try 346 { 347 ReadyDirectory.createReadyDirectory( d, 348 trustReadyToReportLink.getCanonicalPath()/*i.e. its target*/, 349 vS.pollwiki().pipeRecognizer() ); 350 } 351 catch( final Exception x ) 352 { 353 if( !FileX.deleteRecursive( d )) 354 { 355 System.err.println( 356 "vocount: unable to remove partially constructed ready directory, please delete it manually: " 357 + d ); 358 } 359 throw x; 360 } 361 362 if( !isEtc || isVerbose ) System.out.println( d ); 363 } 364 365 // 3. Mount 366 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 367 else if( action == Action.mount ) 368 { 369 final File vocountDirectory = new File( vS.outDirectory(), "vocount" ); 370 final ArrayList<File> readyDirectoryList = 371 nominalReadyDirectories( vocountDirectory ); 372 if( argument == null ) 373 { 374 ReadyDirectory readyDirectory = null; // till found 375 File nominalReadyDirectory = null; 376 for( final File d: readyDirectoryList ) 377 { 378 final ReadyDirectory r = new ReadyDirectory( d ); // as such 379 if( readyDirectory == null || r.lastModified() 380 > readyDirectory.lastModified() ) 381 { 382 readyDirectory = r; 383 nominalReadyDirectory = d; 384 } 385 } 386 if( readyDirectory == null ) 387 { 388 System.err.println( 389 "vocount: nothing to mount, no snap/readyCount record exists\n" 390 + " try: vocount ready" ); 391 System.exit( 1 ); 392 } 393 394 if( readyDirectory.isMounted() ) 395 { 396 System.err.println( 397 "vocount: already mounted most recently modified record: " 398 + nominalReadyDirectory ); 399 System.exit( 1 ); 400 } 401 402 argument = nominalReadyDirectory.getPath(); 403 if( isVerbose ) 404 { 405 System.out.println( "mounting most recently modified record: " + argument ); 406 } 407 } 408 final String nominalReadyPath = argument; 409 final ReadyDirectory readyDirectory = new ReadyDirectory( nominalReadyPath ); 410 if( readyDirectory.isMounted() ) 411 { 412 System.err.println( "vocount: already mounted: " + nominalReadyPath ); 413 System.exit( 1 ); 414 } 415 416 final votorola.a.trust.ReadyDirectory trustReadyDirectory = 417 new votorola.a.trust.ReadyDirectory( readyDirectory.readyTraceLink() ); 418 if( !trustReadyDirectory.isMounted() ) 419 { 420 System.err.println( "vocount: cannot mount without a mounted trust network\n" 421 + " try: votrace mount " 422 + readyDirectory.readyTraceLink() ); 423 System.exit( 1 ); 424 } 425 426 // timestamp santity check 427 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 428 final long msStart = System.currentTimeMillis(); 429 for( final File d: readyDirectoryList ) 430 { 431 final ReadyDirectory r = new ReadyDirectory( d ); // as such 432 if( !r.isMounted() ) continue; 433 434 if( r.mountedDirectory().lastModified() > msStart ) 435 { 436 System.err.println( 437 "vocount, warning: existing mount timestamped in future: " 438 + r.mountedDirectory() ); 439 // Problematic because the default for the 'report' action 440 // depends on timestamps. 441 } 442 } 443 444 // churn 445 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 446 if( optionMap.get("churn").hasOccured() ) vS.pollwiki().cache().churn(); 447 448 // mount 449 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 450 final NetworkTrace networkTrace = NetworkTrace.readObjectFromSerialFile( 451 trustReadyDirectory ); 452 if( readyDirectory.isInitWarned() ) 453 { 454 System.err.println( "vocount: mount despite logged warnings: " 455 + nominalReadyPath ); 456 } 457 try 458 { 459 final int pollCount = readyDirectory.mount( vsRun(), networkTrace, isVerbose ); 460 if( isVerbose ) 461 { 462 System.out.println(); 463 System.out.print( pollCount ); 464 System.out.print( " poll" ); 465 if( pollCount != 1 ) System.out.print( 's' ); 466 System.out.print( " counted" ); 467 } 468 } 469 finally { if( isVerbose ) System.out.println(); } 470 } 471 472 // 4. Report 473 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 474 else if( action == Action.report ) 475 { 476 final Count.VoteServerScope scopeCount = vS.scopeCount(); 477 final File readyToReportLink = scopeCount.readyToReportLink(); 478 if( argument == null ) 479 { 480 final File vocountDirectory = new File( vS.outDirectory(), "vocount" ); 481 ReadyDirectory readyDirectory = null; // till found 482 File nominalReadyDirectory = null; 483 for( final File d: nominalReadyDirectories( vocountDirectory )) 484 { 485 final ReadyDirectory r = new ReadyDirectory( d ); // as such 486 if( !r.isMounted() ) continue; 487 488 if( readyDirectory == null || r.mountedDirectory().lastModified() 489 > readyDirectory.mountedDirectory().lastModified() ) 490 { 491 readyDirectory = r; 492 nominalReadyDirectory = d; 493 } 494 } 495 496 if( readyDirectory == null ) 497 { 498 System.err.println( "vocount: nothing to report, no count is mounted" ); 499 System.exit( 1 ); 500 } 501 502 if( readyToReportLink.exists() 503 && readyDirectory.equals( readyToReportLink.getCanonicalFile() )) 504 { 505 System.err.println( 506 "vocount: already reporting most recently mounted record: " 507 + nominalReadyDirectory ); 508 System.exit( 1 ); 509 } 510 511 argument = nominalReadyDirectory.getPath(); 512 if( isVerbose ) 513 { 514 System.out.println( "reporting most recently mounted record: " + argument ); 515 } 516 } 517 final String nominalReadyPath = argument; 518 final ReadyDirectory readyDirectory = new ReadyDirectory( nominalReadyPath ); 519 if( readyToReportLink.exists() 520 && readyDirectory.equals( readyToReportLink.getCanonicalFile() )) 521 { 522 System.err.println( "vocount: already reported: " + nominalReadyPath ); 523 System.exit( 1 ); 524 } 525 526 if( !readyDirectory.isMounted() ) 527 { 528 System.err.println( "vocount: cannot report, count is not mounted\n" 529 + " try: vocount mount " + nominalReadyPath ); 530 System.exit( 1 ); 531 } 532 533 FileX.symlink( readyDirectory.getPath(), readyToReportLink.getPath() ); 534 FileX.symlink( readyDirectory.snapDirectory().getName(), 535 scopeCount.snapToReportLink().getPath() ); 536 } 537 538 // 5. Umount 539 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 540 else if( action == Action.umount ) 541 { 542 final File readyToReportLink = vS.scopeCount().readyToReportLink(); 543 if( argument == null ) // unmount all 544 { 545 final File vocountDirectory = new File( vS.outDirectory(), "vocount" ); 546 for( final File nominalReadyDirectory: 547 nominalReadyDirectories( vocountDirectory )) 548 { 549 final ReadyDirectory r = new ReadyDirectory( nominalReadyDirectory ); // as such 550 if( !r.isMounted() ) continue; 551 552 argument = nominalReadyDirectory.getPath(); 553 if( r.equals( readyToReportLink.getCanonicalFile() )) 554 { 555 if( isVerbose ) 556 { 557 System.out.println( "skipping unmount of reported count " 558 + argument ); 559 } 560 continue; 561 } 562 563 if( isVerbose ) System.out.println( "unmounting " + argument ); 564 run( action, argument, optionMap ); 565 } 566 return; 567 } 568 else // unmount one, as specified 569 { 570 final String nominalReadyPath = argument; 571 final ReadyDirectory readyDirectory = new ReadyDirectory( nominalReadyPath ); 572 if( readyToReportLink.exists() 573 && readyDirectory.equals( readyToReportLink.getCanonicalFile() )) 574 { 575 System.err.println( "vocount: cannot unmount, count is currently reported\n" 576 + " try: vocount ureport" ); 577 System.exit( 1 ); 578 } 579 580 if( !readyDirectory.unmount( vsRun() )) 581 { 582 System.err.println( "vocount: not mounted: " + nominalReadyPath ); 583 System.exit( 1 ); 584 } 585 } 586 } 587 588 // *. Ureport 589 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 590 else if( action == Action.ureport ) 591 { 592 if( argument != null ) 593 { 594 System.err.println( "vocount: too many arguments" ); 595 System.err.println( GetoptX.createHelpPrompt( "vocount" )); 596 System.exit( 1 ); 597 } 598 599 final Count.VoteServerScope scopeCount = vS.scopeCount(); 600 final File readyToReportLink = scopeCount.readyToReportLink(); 601 if( !readyToReportLink.exists()) 602 { 603 System.err.println( "vocount: nothing to unreport" ); 604 System.exit( 1 ); 605 } 606 607 if( isVerbose ) 608 { 609 System.out.println( "unreporting " + readyToReportLink.getCanonicalFile() ); // difficult to recover the nominal path in this case, because getCanonicalFile() is the only easy way to de-reference the link 610 } 611 if( !readyToReportLink.delete() ) 612 { 613 System.err.println( 614 "vocount: unable to delete report link, please delete it manually: " 615 + readyToReportLink ); 616 } 617 final File snapToReportLink = scopeCount.snapToReportLink(); 618 if( !snapToReportLink.delete() ) 619 { 620 System.err.println( 621 "vocount: unable to delete snap link, please delete it manually: " 622 + snapToReportLink ); 623 } 624 } 625 626 // *. Status 627 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 628 else if( action == Action.status ) 629 { 630 if( argument != null ) 631 { 632 System.err.println( "vocount: too many arguments" ); 633 System.err.println( GetoptX.createHelpPrompt( "vocount" )); 634 System.exit( 1 ); 635 } 636 637 final File vocountDirectory = new File( vS.outDirectory(), "vocount" ); 638 final File readyDirectoryToReportOrNull; 639 { 640 final File readyToReportLink = vS.scopeCount().readyToReportLink(); 641 readyDirectoryToReportOrNull = readyToReportLink.exists()? 642 readyToReportLink.getCanonicalFile(): null; 643 } 644 final File[] snapDirectoryArray = FileX.listFilesNoNull( vocountDirectory, 645 OutputStore.SNAP_DIRECTORY_FILTER ); 646 Arrays.sort( snapDirectoryArray ); 647 final StringBuilder flagB = new StringBuilder( ReadyDirectory.STATUS_FLAGS_COUNT ); 648 for( final File snapDirectory: snapDirectoryArray ) 649 { 650 final File[] readyDirectoryArray = FileX.listFilesNoNull( snapDirectory, 651 ReadyDirectory.READY_DIRECTORY_FILTER ); 652 if( readyDirectoryArray.length == 0 ) // unreadied snap 653 { 654 ReadyDirectory.statusPrintln( StringBuilderX.clear(flagB), snapDirectory ); 655 continue; 656 } 657 658 Arrays.sort( readyDirectoryArray ); 659 for( final File nominalReadyDirectory: readyDirectoryArray ) 660 { 661 final ReadyDirectory readyDirectory = 662 new ReadyDirectory( nominalReadyDirectory ); // as such 663 readyDirectory.statusPrintln( StringBuilderX.clear(flagB), 664 nominalReadyDirectory, readyDirectoryToReportOrNull ); 665 } 666 } 667 } 668 669 // - - - 670 else assert false; 671 if( isEtc ) 672 { 673 run( /*next*/Action.values()[action.ordinal() + 1], /*argument*/null, optionMap ); 674 } 675 } 676 catch( RuntimeException x ) { throw x; } 677 catch( Exception x ) 678 { 679 System.err.print( "vocount: fatal error" + Votorola.unmessagedDetails(x) + ": " ); 680 // System.err.println( x ); 681 x.printStackTrace( System.err ); 682 System.exit( 1 ); 683 } 684 } 685 686 687}