]> git.uio.no Git - u/mrichter/AliRoot.git/blob - PWGLF/FORWARD/trains/GridRailway.C
Merge branch 'feature-movesplit'
[u/mrichter/AliRoot.git] / PWGLF / FORWARD / trains / GridRailway.C
1 /**
2  * @file   GridRailway.C
3  * @author Christian Holm Christensen <cholm@master.hehi.nbi.dk>
4  * @date   Tue Oct 16 19:01:27 2012
5  * 
6  * @brief  Grid Analysis Railway
7  * 
8  * @ingroup pwglf_forward_trains_helper
9  * 
10  */
11 #ifndef GRIDHELPER_C
12 #define GRIDHELPER_C
13 #include "PluginRailway.C"
14 #ifndef __CINT__
15 # include <TUrl.h>
16 # include <TString.h>
17 # include <TGrid.h>
18 # include <AliAnalysisManager.h>
19 # include <AliAnalysisAlien.h>
20 #else
21 class TUrl;
22 class AliAnalysisAlien;
23 #endif
24
25 // ===================================================================
26 /**
27  * Handle analysis on an the Grid
28  * 
29  * This helper is triggered by a URL of the form 
30  *
31  * @code
32  * alien:///<directory>[?<options>][#<pattern>]
33  * @endcode 
34  * where 
35  * <dl>
36  *   <dt>&lt;directory&gt;</dt>
37  *   <dd>Grid directory that holds the data</dd>
38  *   <dt>&lt;treeName&gt;</dt>
39  *   <dd>Tree to loop over</dd>
40  *   <dt>&lt;options&gt;</dt>
41  *   <dd>List of options separated by an &amp;
42  *     <dl>
43  *       <dt><tt>storage=&lt;url&gt;</tt></dt>
44  *       <dd>Specify a non-default storage location for special output
45  *         (e.g., AOD trees).  &lt;url&gt; should be a valid XRootd 
46  *         server URI accessible to the slaves - e.g., 
47  *         <tt>root://lxplus.cern.ch:10930//tmp</tt>.</dd>
48  *       <dt><tt>mode=[default,rec,sim,train,custom]</tt></dt>
49  *       <dd>Set the AliROOT mode.  If not specified <tt>default</tt> 
50  *         is assumed.  See also CreateAliROOTPar</dd>
51  *       <dt><tt>par</tt></dt>
52  *       <dd> Use PAR files</dd>
53  *       <dt><tt>runs=[list or file]</tt></dt>
54  *       <dd>Comma separated list of run numbers, or file(s) containing 
55  *         run numbers</dd> 
56  *       <dt><tt>oper=[FULL,TERMINATE,SUBMIT,OFFLINE,TEST]</tt></dt>
57  *       <dd>How to run the analysis</dd>
58  *       <dt><tt>split=&lt;N&gt;</tt></dt>
59  *       <dd>Maximum number of files per split</dd>
60  *       <dt><tt>merge=&lt;N&gt;</tt></dt>
61  *       <dd>Maximum number of files per merger</dd>
62  *       <dt><tt>mc</tt></dt>
63  *       <dd>Scan also for MC files (<tt>galice.root</tt>, 
64  *          <tt>Kinematics.root</tt>, and <tt>TrackRefs.root</tt>) when 
65  *          scanning &lt;datadir&gt;</dd>
66  *       <dt><tt>pattern=&lt;GLOB&gt;</tt></dt>
67  *       <dd>Shell glob pattern that files must check when scanning 
68  *         &lt;datadir&gt;</dd>
69  *     </dl>
70  *   </dd>
71  * </dl>  
72  *
73  * @ingroup pwglf_forward_trains_helper
74  */
75 struct GridRailway : public PluginRailway
76 {
77   /** 
78    * Constructor 
79    * 
80    * @param url  Url 
81    * @param verbose Verbosity level
82    */
83   GridRailway(const TUrl& url, Int_t verbose)
84     : PluginRailway(url, verbose), fRuns()
85   {
86     // Note, split, merge, and ttl are by default set to values
87     // optimized for AOD production on real PbPb data.
88     //
89     // TTL shouldn't be much smaller than 4h10m.  Split and merge
90     // shouldn't be much larger than 75, but probably not smaller than
91     // 50.
92     fOptions.Add("oper", "FULL|TERMINATE|SUBMIT", "Analysis operation", "FULL");
93     fOptions.Add("split",  "N|max",  "Max number of files before split","50");
94     fOptions.Add("merge",  "N|max",  "Max number of files for merge",   "50");
95     fOptions.Add("run",    "RUNS",   "Range, list, and/or file of runs", "");
96     fOptions.Add("alien",  "VERSION","Alien API version",              "V1.1x");
97     fOptions.Add("ttl",    "N|max",  "Time to live",                   "6h");
98     fOptions.Add("pattern","GLOB",   "File/directory name pattern", "");
99     fOptions.Add("concat", "Concatenate all runs");
100     fOptions.Add("exclude", "GLOB","Comma separated list of merge excludes","");
101   }
102   GridRailway(const GridRailway& o)
103     : PluginRailway(o), fRuns()
104   {}
105   GridRailway& operator=(const GridRailway& o)
106   {
107     if (&o == this) return *this;
108     PluginRailway::operator=(o);
109     return *this;
110   }
111   virtual ~GridRailway() {}
112   /** 
113    * Get the mode identifier 
114    * 
115    * @return Always kProof
116    */
117   virtual UShort_t Mode() const { return kGrid; }
118   /**
119    * Get the mode string used for AliAnalysisManager::StartAnalysis
120    */
121   virtual const char* ModeString() const { return "grid"; }
122   /** 
123    * Set-up done before task set-ups 
124    * 
125    * @return true on success 
126    */
127   virtual UShort_t Operation() const 
128   {
129     if (!fOptions.Has("oper")) return kFull;
130     const TString& oper = fOptions.Get("oper");
131     if      (oper.EqualTo("FULL",      TString::kIgnoreCase)) return kFull;
132     else if (oper.EqualTo("OFFLINE",   TString::kIgnoreCase)) return kOffline;
133     else if (oper.EqualTo("SUBMIT",    TString::kIgnoreCase)) return kSubmit;
134     else if (oper.EqualTo("TERMINATE", TString::kIgnoreCase)) return kTerminate;
135     else if (oper.EqualTo("TEST",      TString::kIgnoreCase)) return kTest;
136     return kFull;
137   }
138   void StoreRun(Int_t r)
139   {
140     TObject* o = new TObject;
141     o->SetUniqueID(r);
142     fRuns.Add(o);
143   }
144   /**
145    * Read run numbers 
146    *
147    * @return Number of registered runs 
148    */
149   virtual Int_t RegisterRuns()
150   {
151     if (!fOptions.Find("run")) {
152       Error("GridRailway::RegisterRuns", "No runs specified");
153       return -1;
154     }
155     Int_t       nRuns  = 0;
156     TString     runs   = fOptions.Get("run");
157     TObjArray*  tokens = runs.Tokenize(",+:");
158     TObjString* part   = 0;
159     TIter       next(tokens);
160     Bool_t      range  = false;
161     Bool_t      individual = false;
162     // Info("GridRailway::RegisterRuns", "Runs specified are %s", runs.Data());
163     while ((part = static_cast<TObjString*>(next()))) {
164       TString& s = part->String();
165       if (s.Contains("-")) { // Run range 
166         if (range) { 
167           Warning("GridRailway::RegisterRuns", "Run range already specified, "
168                   "ignoring %s", s.Data());
169           continue;
170         }
171         if (individual) { 
172           Warning("GridRailway::RegisterRuns", 
173                   "Run ranges and individual run specs do not mix, "
174                   "ignoring %s", s.Data());
175           continue;
176         }
177         TObjArray* ranges = s.Tokenize("-");
178         if (ranges->GetEntriesFast() > 2) { 
179           Warning("GridRailway::RegisterRuns", "Invalid run range: %s", 
180                   s.Data());
181           ranges->Delete();
182           continue;
183         }
184         Int_t first = static_cast<TObjString*>(ranges->At(0))->String().Atoi();
185         Int_t last  = static_cast<TObjString*>(ranges->At(1))->String().Atoi();
186         nRuns       = last-first+1;
187         // Info("GridRailway::RegisterRuns", "Run range %d -> %d", first, last);
188         fHandler->SetRunRange(first, last);
189         ranges->Delete();
190         range = true;
191         for (Int_t r = first; r <= last; r++) StoreRun(r);
192         continue;
193       }
194       if (s.IsDigit()) { // single run
195         if (range) { 
196           Warning("GridRailway::RegisterRuns", 
197                   "Run ranges and individual run specs do not mix, "
198                   "ignoring %s", s.Data());
199           continue;
200         }
201         // Info("GridHandler::RegisterRuns", "Adding run %s", s.Data());
202         fHandler->AddRunNumber(s.Atoi());
203         StoreRun(s.Atoi());
204         nRuns++;
205         individual = true;
206         continue;
207       }
208       if (range) { 
209         Warning("GridRailway::RegisterRuns", "Run ranges and list file "
210                 "do not mix, ignoring %s", s.Data());
211         continue;
212       }
213
214       // We assume this part is a file 
215       // Info("GridRailway::RegisterRuns", "Reading runs from %s", s.Data());
216       std::ifstream in(s.Data());
217       if (!in) { 
218         s.Prepend("../");
219         in.open(s.Data());
220         if (!in) {
221           Warning("GridRailway::RegisterRuns", "Failed to open %s", s.Data());
222           continue;
223         }
224       }
225       while (!in.eof()) {
226         TString lne;
227         lne.ReadLine(in);
228
229         TString bare = lne.Strip(TString::kBoth);
230         if (bare[0] == '#') continue;
231
232         TObjArray* ltokens = bare.Tokenize(" \t,");
233         TIter lnext(ltokens);
234         TObjString* str = 0;
235         while ((str = static_cast<TObjString*>(lnext()))) {
236           const TString& token = str->String();
237           if (!token.IsDigit()) continue;
238           
239           int r = token.Atoi();
240           fHandler->AddRunNumber(r);
241           StoreRun(r);
242           nRuns++;
243         }
244         ltokens->Delete();
245       }
246 #if 0
247       while (!in.eof()) { 
248         Int_t r;
249         in >> r;
250         // Info("GridRailway::RegisterRuns", "Read %d, adding", r);
251         fHandler->AddRunNumber(r);
252         StoreRun(r);
253         nRuns++;
254         Char_t c;
255         in >> c;
256         if (in.bad()) break;
257       }
258 #endif
259       individual = true;
260       in.close();
261     }
262     return nRuns;
263   }
264   /** 
265    * Executed before setting up tasks 
266    * 
267    * @return true on success 
268    */
269   virtual Bool_t PreSetup() 
270   {
271     if (!PluginRailway::PreSetup()) return false;
272     
273     // --- Add system library dir to load path -----------------------
274     gSystem->AddDynamicPath("/usr/lib");
275
276     // --- Open a connection to the grid -----------------------------
277     if (!TGrid::Connect(Form("%s://", fUrl.GetProtocol()))) { 
278       Error("GridRailway::PreSetup", "Failed to connect to AliEN");
279       return false;
280     }
281     if (!gGrid || !gGrid->IsConnected()) { 
282       Error("GridRailway::PreSetup", "Failed to connect to AliEN");
283       return false;
284     }
285
286     return true;
287   }
288   /** 
289    * Set-up done after the task set-ups 
290    *
291    * @return true on success 
292    */
293   virtual Bool_t PostSetup() 
294   {
295     // Info("GridRailway::PostSetup", "Calling super.PostSetup");
296     if (!PluginRailway::PostSetup()) return false;
297
298     // --- API version -----------------------------------------------
299     fHandler->SetAPIVersion(fOptions.Get("alien"));
300     
301     // --- Get the name ----------------------------------------------
302     // Info("GridRailway", "Proceeding with plugin setup");
303     AliAnalysisManager* mgr = AliAnalysisManager::GetAnalysisManager();
304     TString name(mgr->GetName());
305
306     // --- Set the operation to do (TEST, SUBMIT, TERMINATE, FULL) ---
307     TString operation("FULL");
308     if (fOptions.Has("oper")) operation = fOptions.Get("oper");
309     fHandler->SetRunMode(operation);
310
311     // --- Add the run numbers ---------------------------------------
312     fHandler->SetRunPrefix(fOptions.Has("mc") ? "%d" : "%09d");
313     Int_t nRun = RegisterRuns();
314
315     // --- Do not test copying ---------------------------------------
316     fHandler->SetCheckCopy(false);
317     
318     // --- Set output to be per run ----------------------------------
319     fHandler->SetOutputToRunNo(true); 
320
321     // --- Set the job tag -------------------------------------------
322     fHandler->SetJobTag(name);
323
324     // --- Set number of test files - used in test mode only ---------
325     fHandler->SetNtestFiles(1);
326
327     // --- Set the Time-To-Live --------------------------------------
328     if (fOptions.Has("ttl")) { 
329       TString sttl = fOptions.Get("ttl");
330       if (!sttl.EqualTo("max")) {
331         Int_t   ttl  = 0;
332         if (sttl.IsDigit()) ttl = sttl.Atoi();
333         else { 
334           // Parse string of the form <DAYS>d<HOURS>h<MINUTES>m<SECONDS>s
335           Int_t id = sttl.Index("d", 0);
336           if (id == kNPOS) id = -1;
337           else { 
338             TString sdays(sttl(0,id));
339             ttl += 24 * 60 * 60 * sdays.Atoi();
340           }
341           Int_t ih = sttl.Index("h", id+1);
342           if (ih == kNPOS) ih = id;
343           else { 
344             TString shour(sttl(id+1,ih-id-1));
345             ttl += 60 * 60 * shour.Atoi();
346           }
347           Int_t im = sttl.Index("m", ih+1);
348           if (im == kNPOS) im = ih;
349           else { 
350             TString smin(sttl(ih+1, im-ih-1));
351             ttl += 60 * smin.Atoi();
352           }
353           Int_t is = sttl.Index("s", im+1);
354           if (is != kNPOS) { 
355             TString ssec(sttl(im+1, is-im-1));
356             ttl += ssec.Atoi();
357           }
358         }
359         if (ttl != 0) fHandler->SetTTL(ttl);
360         else 
361           Warning("", "Option ttl given but no value found");
362       }
363     }
364     
365     // --- Re-submit failed jobs as long as the ratio of failed jobs -
366     // --- is this percentage.
367     fHandler->SetMasterResubmitThreshold(95);
368
369     // --- Set the input format --------------------------------------
370     fHandler->SetInputFormat("xml-single");
371
372     // --- Set names of generated files ------------------------------
373     fHandler->SetAnalysisMacro(Form("%s.C", name.Data()));
374     fHandler->SetJDLName(Form("%s.jdl", name.Data()));
375     fHandler->SetExecutable(Form("%s.sh", name.Data()));
376     
377     // ---- Set the job price !? -------------------------------------
378     fHandler->SetPrice(1);
379
380     // --- Set whether to merge via JDL ------------------------------
381     fHandler->SetMergeViaJDL(true);
382     
383     // --- Fast read otion -------------------------------------------
384     fHandler->SetFastReadOption(false);
385
386     // --- Whether to overwrite existing output ----------------------
387     fHandler->SetOverwriteMode(true);
388
389     // --- Set the executable binary name and options ----------------
390     fHandler->SetExecutableCommand("aliroot -b -q -x");
391
392     // --- Split by storage element - must be lower case! ------------
393     fHandler->SetSplitMode("se");
394
395     // --- How much to split -----------------------------------------
396     if (fOptions.Has("split")) { 
397       if (!fOptions.Get("split").EqualTo("max")) {
398         fHandler->SetSplitMaxInputFileNumber(fOptions.AsInt("split"));
399       }
400     }
401     // --- Merge parameters ------------------------------------------
402     if (fOptions.Has("merge")) { 
403       if (!fOptions.Get("merge").EqualTo("max")) { 
404         fHandler->SetMaxMergeFiles(fOptions.AsInt("merge"));
405       }
406     }
407     TString exclude="AliAOD.root *EventStat*.root *event_stat*.root";
408     if (fOptions.Has("exclude")) { 
409       TString exOpt = fOptions.Get("exclude");
410       exOpt.ReplaceAll(",", " ");
411       exclude.Append(" ");
412       exclude.Append(exOpt);
413     }
414     fHandler->SetMergeExcludes(exclude);
415     
416     // --- Set number of runs per master - 1 or all ------------------
417     fHandler->SetNrunsPerMaster(fOptions.Has("concat") ? nRun+1 : 1);
418
419
420     // --- Enable default outputs ------------------------------------
421     fHandler->SetDefaultOutputs(true);
422
423     // --- Keep log files ------------------------------------------
424     fHandler->SetKeepLogs();
425
426     // --- Set the working directory to be the trains name (with -----
427     // --- special characters replaced by '_' and the date appended),
428     // --- and also set the output directory (relative to working
429     // --- directory)
430     fHandler->SetGridWorkingDir(name.Data());
431     fHandler->SetGridOutputDir("output");
432     fHandler->SetGridDataDir(fUrl.GetFile());
433
434     // --- Get the tree name and set the file pattern ----------------
435     TString pattern;
436     if (fOptions.Has("pattern")) pattern = fOptions.Get("pattern");
437     else {
438       TString treeName(fUrl.GetAnchor());
439       if (treeName.IsNull()) { 
440         Warning("GridRailway::PreSetup", "No tree name specified, assuming T");
441         treeName = "T";
442       }
443       if      (treeName.EqualTo("esdTree")) pattern = "AliESD";
444       else if (treeName.EqualTo("aodTree")) pattern = "AliAOD";
445     }
446     fHandler->SetDataPattern(pattern);
447
448     // --- Loop over defined containers in the analysis manager, and -
449     // --- declare these as outputs
450     TString listOfAODs  = "";
451     TString listOfHists = "";
452     TString listOfTerms = "";
453
454     TObjArray*  outs[] = { mgr->GetOutputs(), mgr->GetParamOutputs(), 0 };
455     TObjArray** out    = outs;
456     while (*out) {
457       AliAnalysisDataContainer* cont = 0;
458       TIter nextCont(*out);
459       while ((cont = static_cast<AliAnalysisDataContainer*>(nextCont()))) {
460         TString outName(cont->GetFileName());
461         Bool_t   term = (*out == outs[1]);
462         TString& list = (outName == "default" ? listOfAODs : 
463                          !term ? listOfHists : listOfTerms);
464         if (outName == "default") { 
465           if (!mgr->GetOutputEventHandler()) continue; 
466           
467           outName = mgr->GetOutputEventHandler()->GetOutputFileName();
468         }
469         if (list.Contains(outName)) continue;
470         if (!list.IsNull()) list.Append(",");
471         list.Append(outName);
472       }
473       out++;
474     }
475     TString extra = mgr->GetExtraFiles();
476     if (!extra.IsNull()) { 
477       if (!listOfAODs.IsNull()) listOfAODs.Append("+");
478       extra.ReplaceAll(" ", ",");
479       listOfAODs.Append(extra);
480    }
481
482 #if 0
483     Int_t nReplica = 2;
484     TString outArchive = Form("stderr, stdout@disk=%d", nReplica);
485     if (!listOfHists.IsNull()) 
486       outArchive.Append(Form(" hist_archive.zip:%s@disk=%d", 
487                              listOfHists.Data(), nReplica));
488     if (!listOfAODs.IsNull()) 
489       outArchive.Append(Form(" aod_archive.zip:%s@disk=%d", 
490                              listOfAODs.Data(), nReplica));
491     // Disabled for now 
492     // plugin->SetOutputArchive(outArchive);
493 #endif 
494
495     if (listOfAODs.IsNull() && listOfHists.IsNull()) 
496       Fatal("PostSetup", "No outputs defined");
497     if (!listOfTerms.IsNull()) 
498       fHandler->SetTerminateFiles(listOfTerms);
499     
500     return true;
501   };
502   /** 
503    * Start the analysis 
504    * 
505    * @param nEvents Number of events to analyse 
506    * 
507    * @return The return value of AliAnalysisManager::StartAnalysis
508    */
509   virtual Long64_t Run(Long64_t nEvents=-1) 
510   {
511     AliAnalysisManager* mgr = AliAnalysisManager::GetAnalysisManager();
512     if (nEvents == 0) return 0;
513     Long64_t ret = mgr->StartAnalysis("grid", nEvents);
514
515 #if 1
516     std::ofstream outJobs(Form("%s.jobid", mgr->GetName()));
517     outJobs << fHandler->GetGridJobIDs() << std::endl;
518     outJobs.close();
519
520     std::ofstream outStages(Form("%s.stage", mgr->GetName()));
521     outStages << fHandler->GetGridStages() << std::endl;
522     outStages.close();
523 #endif
524     return ret;
525   }
526   /** 
527    * Link an auxilary file to working directory 
528    * 
529    * @param name Name of the file
530    * @param copy  Whether to copy or not 
531    * 
532    * @return true on success
533    */
534   virtual Bool_t AuxFile(const TString& name, bool copy=false)
535   {
536     if (!Railway::AuxFile(name, copy)) return false;
537     // We need to add this file as an additional 'library', so that the 
538     // file is uploaded to the users Grid working directory. 
539     fHandler->AddAdditionalLibrary(gSystem->BaseName(name.Data()));
540     return true;
541   }
542   /** 
543    * Get the output (directory)
544    *
545    */
546   virtual TString OutputPath() const
547   {
548     TString ret;
549     if (!fHandler) {
550       Warning("GridRailway::OutputLocation", "No AliEn handler");
551       return ret;
552     }
553     ret = fHandler->GetGridOutputDir();
554     if (ret.BeginsWith("/")) return ret;
555
556     AliAnalysisManager* mgr = AliAnalysisManager::GetAnalysisManager();
557     if (!mgr) { 
558       Warning("GridRailway::OutputLocation", "No analysis manager");
559       return ret;
560     }
561     ret.Prepend(Form("%s/",  mgr->GetName()));
562     if (gGrid) 
563       ret.Prepend(Form("%s/", gGrid->GetHomeDirectory())); 
564     
565     return ret;
566   }
567   /** 
568    * @return URL help string
569    */
570   virtual const Char_t* UrlHelp() const 
571   {
572     return "alien:///<datadir>[?<options>][#<treeName>]";
573   }
574   /** 
575    * @return Short description
576    */
577   virtual const char* Desc() const { return "AliEn"; }
578   /** 
579    * Write auxillary ROOT (and possible shell) script for more 
580    * (post-)processing e.g., terminate
581    * 
582    * @param escaped        Escaped name  
583    */
584   void AuxSave(const TString& escaped, 
585                Bool_t /*asShellScript*/) 
586   {
587     // Write plug-in to file 
588     TFile* plug = TFile::Open(Form("%s_plugin.root", escaped.Data()), 
589                               "RECREATE");
590     fHandler->Write("plugin");
591     plug->Close();
592     
593     TIter       nextLib(&fExtraLibs);
594     TObjString* lib = 0;
595     TString     libs;
596     while ((lib = static_cast<TObjString*>(nextLib()))) {
597       if (!libs.IsNull()) libs.Append(" ");
598       libs.Append(lib->String());
599     }
600     TIter       nextPar(&fExtraPars);
601     TObjString* par = 0;
602     TString     pars;
603     while ((par = static_cast<TObjString*>(nextPar()))) {
604       if (!pars.IsNull()) pars.Append(" ");
605       pars.Append(par->String());
606     }
607     TIter       nextSrc(&fExtraSrcs);
608     TObjString* src = 0;
609     TString     srcs;
610     while ((src = static_cast<TObjString*>(nextSrc()))) {
611       if (!srcs.IsNull()) srcs.Append(" ");
612       srcs.Append(src->String());
613     }
614     TString macDir("$ALICE_ROOT/PWGLF/FORWARD/trains");
615     std::ofstream t("Terminate.C");
616     if (!t) { 
617       Error("GridRailway::AuxSave", "Failed to make terminate ROOT script");
618       return;
619     }
620
621     t << "// Generated by GridRailway\n"
622       << "Bool_t Terminate(Bool_t localMerge=false)\n"
623       << "{\n"
624       << "  TString name = \"" << escaped << "\";\n"
625       << "  TString libs = \"" << libs << "\";\n"
626       << "  TString pars = \"" << pars << "\";\n"
627       << "  TString srcs = \"" << srcs << "\";\n\n"
628       << "  gSystem->Load(\"libANALYSIS\");\n"
629       << "  gSystem->Load(\"libANALYSISalice\");\n"
630       << "  gSystem->AddIncludePath(\"-I$ALICE_ROOT/include\");\n\n"
631       << "  gROOT->LoadMacro(\"" << macDir << "/GridTerminate.C+g\");\n\n"
632       << "  return GridTerminate(name,libs,pars,srcs,localMerge);\n"
633       << "}\n"
634       << "// EOF\n"
635       << std::endl;
636     t.close();
637
638     TString runs;
639     TString format(fOptions.Has("mc") ? "%d" : "%09d");
640     if (fOptions.Has("concat")) {
641       Int_t first = fRuns.First()->GetUniqueID();
642       Int_t last  = fRuns.Last()->GetUniqueID();
643       TString fmt(format); 
644       fmt.Append("_"); 
645       fmt.Append(format);
646       if (!runs.IsNull()) runs.Append(" ");
647       runs.Append(TString::Format(fmt, first, last));
648     }
649     else {
650       TIter next(&fRuns);
651       TObject* o = 0;
652       while ((o = next())) { 
653         if (!runs.IsNull()) runs.Append(" ");
654         runs.Append(Form(format, o->GetUniqueID()));
655       }
656     }
657
658     std::ofstream d("Download.C");
659     if (!d) { 
660       Error("GridRailway::AuxSave", "Failed to make ROOT script Download.C");
661       return;
662     }
663     d << "// Generated by GridRailway\n"
664       << "void Download(Bool_t unpack=true)\n"
665       << "{\n"
666       << "  TString base = \"" << fUrl.GetProtocol() << "://" 
667       << OutputPath() << "\";\n"
668       << "  TString runs = \"" << runs << "\";\n\n"
669       << "  gROOT->LoadMacro(\"" << macDir << "/GridDownload.C\");\n\n"
670       << "  GridDownload(base, runs, unpack);\n"
671       << "}\n"
672       << "// EOF\n"
673       << std::endl;
674     d.close();
675
676     std::ofstream w("Watch.C");
677     if (!w) {
678       Error("GridRailway::AuxSave", "Failed to make ROOT script Watch.C");
679       return;
680     }
681     w << "// Generated by GridRailway\n"
682       << "void Watch(Bool_t batch=false, Int_t delay=5*60)\n"
683       << "{\n"
684       << "  TString name = \"" << escaped << "\";\n"
685       << "  gROOT->LoadMacro(\"" << macDir << "/GridWatch.C+g\");\n\n"
686       << "  GridWatch(name,batch,delay);\n"
687       << "}\n"
688       << "// EOF\n"
689       << std::endl;
690     w.close();
691
692   }
693   TList fRuns;
694 };
695 #endif
696 //
697 // EOF
698 //