Line data Source code
1 : #include <fstream>
2 : #include <iostream>
3 : #include <map>
4 : #include <sys/types.h>
5 : #include <sys/stat.h>
6 : #include <ctime>
7 :
8 : #include "AliLog.h"
9 :
10 : #include "AliExternalInfo.h"
11 :
12 : #include "TObjArray.h"
13 : #include "TString.h"
14 : #include "TWebFile.h"
15 : #include "TSystem.h"
16 : #include "TTree.h"
17 : #include "TFile.h"
18 : #include "TChain.h"
19 : #include "TMath.h"
20 :
21 128 : ClassImp(AliExternalInfo)
22 :
23 128 : const TString AliExternalInfo::fgkDefaultConfig="$ALICE_ROOT/STAT/Macros/AliExternalInfo.cfg";
24 :
25 : AliExternalInfo::AliExternalInfo(TString localStorageDirectory, TString configLocation/*, Bool_t copyToLocalStorage*/) :
26 : /*fCopyDataToLocalStorage(copyToLocalStorage),*/
27 0 : TObject(),
28 0 : fConfigLocation(configLocation),
29 0 : fLocalStorageDirectory(localStorageDirectory),
30 0 : fLocationTimeOutMap(),
31 0 : fTree(0x0),
32 0 : fChain(new TChain()),
33 0 : fChainMap(),
34 0 : fMaxCacheSize(-1)
35 0 : {
36 : // use default cache path from Env variable if specified
37 0 : if (gSystem->Getenv("AliExternalInfoCache")!=NULL){
38 0 : if (localStorageDirectory.Length()<2) fLocalStorageDirectory=gSystem->Getenv("AliExternalInfoCache");
39 : }
40 0 : ReadConfig();
41 0 : }
42 :
43 0 : AliExternalInfo::~AliExternalInfo() {}
44 :
45 :
46 : /// Reads the configuration file. Lines beginning with an '#' are ignored.
47 : /// Use the format which is in the config.cfg by default. Adding ressources like the ones already
48 : /// there should work without problems.
49 : void AliExternalInfo::ReadConfig(){
50 0 : TString configFileName=gSystem->ExpandPathName(fConfigLocation.Data());
51 0 : if (gSystem->AccessPathName(configFileName)) {
52 0 : AliError(TString::Format("Could not find config file '%s'", configFileName.Data()));
53 0 : const TString defaultConfigFileName=gSystem->ExpandPathName(fgkDefaultConfig);
54 0 : if (defaultConfigFileName!=configFileName) {
55 0 : AliError("Using default config file instead");
56 0 : configFileName=defaultConfigFileName;
57 : }
58 0 : }
59 :
60 0 : std::ifstream configFile(configFileName);
61 0 : if (!configFile.is_open()) {
62 0 : AliError(TString::Format("Could not open config file '%s'", configFileName.Data()));
63 0 : return;
64 : }
65 :
66 : //
67 0 : std::string line;
68 0 : while (std::getline(configFile, line)){
69 0 : TString temp_line(line.c_str()); // Use TString for easier handling
70 :
71 0 : if (temp_line.EqualTo("")) continue; // Ignore empty lines
72 : // temp_line = temp_line.Strip(TString::EStripType::kBoth, ' '); // Strip trailing and leading spaces
73 0 : temp_line = temp_line.Strip(TString::kBoth, ' '); // Strip trailing and leading spaces
74 0 : if (temp_line.First('#') == 0) continue; // If line starts with a '#' treat is as a comment
75 :
76 0 : TObjArray arrTokens = (*(temp_line.Tokenize(' ')));
77 0 : const TString key(arrTokens.At(0)->GetName());
78 0 : const TString value(arrTokens.At(1)->GetName());
79 :
80 0 : fLocationTimeOutMap[key] = value;
81 0 : }
82 : return;
83 0 : }
84 :
85 : /// Prints out the config which was read in previously. Useful to check if anything went wrong
86 : void AliExternalInfo::PrintConfig(){
87 : // Loop through the map (Would be much easier in c++11)
88 0 : std::cout << "User defined resources are:\n";
89 : // looping over map with const_iterator
90 : typedef std::map<TString,TString>::const_iterator it_type;
91 0 : for(it_type iterator = fLocationTimeOutMap.begin(); iterator != fLocationTimeOutMap.end(); ++iterator) {
92 0 : std::cout << iterator->first << " " << iterator->second << "\n";
93 : }
94 0 : return;
95 : }
96 :
97 :
98 : /// Sets up all variables according to period, pass and type. Extracts information from the config file
99 : void AliExternalInfo::SetupVariables(TString& internalFilename, TString& internalLocation, Bool_t& resourceIsTree, TString& pathStructure, \
100 : TString& detector, TString& rootFileName, TString& treeName, const TString& type, const TString& period, const TString& pass){
101 : // Check if resource is a tree in a root file or not
102 0 : pathStructure = CreatePath(type, period, pass);
103 :
104 : // if (fLocationTimeOutMap.count(type + ".treename") > 0) resourceIsTree = kTRUE;
105 : // else resourceIsTree = kFALSE;
106 0 : if (type.Contains("MonALISA") == kTRUE) resourceIsTree = kFALSE;
107 0 : else resourceIsTree = kTRUE;
108 :
109 : // To distinguish different detector QA you have to add a <det>_ to the trending.root. Here we check the detector!
110 0 : if (type.Contains("QA")) {
111 0 : Int_t firstDotOfType(type.First('.') + 1);
112 0 : Int_t lastCharOfType(type.Length() - 1);
113 0 : detector = type(firstDotOfType, lastCharOfType) + "_";
114 : // std::cout << "DETECTOR: " << detector << std::endl;
115 0 : }
116 :
117 0 : rootFileName = fLocationTimeOutMap[type + ".filename"];
118 0 : treeName = fLocationTimeOutMap[type + ".treename"];
119 :
120 : // Create the local path where to store the information of the resource
121 0 : internalLocation += pathStructure;
122 0 : AliInfo(TString::Format("Information will be stored/retrieved in/from %s", internalLocation.Data()));
123 :
124 0 : if (!(period.Last('*') == period.Length() - 1) || !(pass.Last('*') == pass.Length() - 1) || period.Length() == 0){
125 : // std::cout << "mkdir " << internalLocation.Data() << std::endl;
126 0 : gSystem->mkdir(internalLocation.Data(), kTRUE);
127 0 : }
128 :
129 : // Resulting internal path to the file
130 0 : if (resourceIsTree) internalFilename = internalLocation + detector + rootFileName; // e.g data/<year>/<period>/<pass>/<det>_trending.root
131 0 : else internalFilename = internalLocation + rootFileName; // e.g data/<year>/<period>/<pass>/MC.root
132 :
133 0 : return;
134 0 : };
135 :
136 : /// \param type Type of the resource as described in the config file, e.g. QA.TPC, MonALISA.RCT
137 : /// \param period Period, e.g. 'LHC15f'
138 : /// \param pass E.g. 'pass2' or 'passMC'
139 : /// Implements the functionality to download the ressources in the specified storage. If it is not
140 : /// in the form of a tree it creates one. You can use this function or the functions available in
141 : /// the class definition as an abbrevation
142 : /// \return If downloading and creation of tree was successful true, else false
143 : Bool_t AliExternalInfo::Cache(TString type, TString period, TString pass){
144 0 : AliInfo(TString::Format("Caching of %s %s from %s in start path %s", period.Data(), pass.Data(), type.Data(), fLocalStorageDirectory.Data()));
145 :
146 : // initialization of local variables
147 0 : TString internalFilename = ""; // Resulting path to the file
148 0 : TString internalLocation = fLocalStorageDirectory; // Gets expanded in this function to the right directory
149 0 : TString externalLocation = "";
150 0 : Bool_t resourceIsTree = kFALSE;
151 0 : TString detector = "";
152 0 : TString rootFileName = "";
153 0 : TString treeName = "";
154 0 : TString pathStructure = "";
155 :
156 : // initialization of external variables
157 0 : externalLocation = fLocationTimeOutMap[type + ".location"];
158 :
159 : // Setting up all the local variables
160 0 : SetupVariables(internalFilename, internalLocation, resourceIsTree, pathStructure, detector, rootFileName, treeName, type, period, pass);
161 :
162 : // Checking if resource needs to be downloaded
163 0 : const Bool_t downloadNeeded = IsDownloadNeeded(internalFilename, type);
164 :
165 0 : if (downloadNeeded == kTRUE){
166 : // Download resources in the form of .root files in a tree
167 0 : if (resourceIsTree == kTRUE){
168 0 : externalLocation += pathStructure + rootFileName;
169 0 : AliInfo(TString::Format("Information retrieved from: %s", externalLocation.Data()));
170 :
171 : // Check if external location is a http address or locally accessible
172 : // std::cout << externalLocation(0, 4) << std::endl;
173 0 : TFile *file = TFile::Open(externalLocation);
174 0 : if (file && !file->IsZombie()){ // Checks if webresource is available
175 0 : if (file->Cp(internalFilename)) {
176 0 : AliInfo("Caching successful");
177 0 : return kTRUE;
178 : }
179 : else {
180 0 : AliError("Copying to internal location failed");
181 0 : return kFALSE;
182 : }
183 : }
184 : else {
185 0 : AliError("Ressource not available");
186 0 : return kFALSE;
187 : }
188 : delete file;
189 : }
190 : else {
191 : //Set up external path with period and pass if necessary
192 0 : if (period != "" && pass != ""){
193 0 : externalLocation = TString::Format(externalLocation.Data(), period.Data(), pass.Data());
194 0 : }
195 0 : else if (period != "" && pass == ""){
196 0 : externalLocation = TString::Format(externalLocation.Data(), period.Data());
197 0 : }
198 :
199 0 : TString mifFilePath = ""; // Gets changed in Wget command
200 0 : TString command = Wget(mifFilePath, internalLocation, rootFileName, externalLocation);
201 :
202 0 : std::cout << command << std::endl;
203 0 : gSystem->Exec(command.Data());
204 :
205 : // Store it in a tree inside a root file
206 0 : TTree tree(treeName, treeName);
207 :
208 0 : if ( (tree.ReadFile(mifFilePath, "", '\"')) > 0) {
209 0 : AliInfo("-- Successfully read in tree");
210 : }
211 : else {
212 0 : AliError("-- Error while reading tree");
213 0 : return kFALSE;
214 : }
215 :
216 0 : TFile tempfile(internalFilename, "RECREATE");
217 0 : tempfile.cd();
218 0 : tree.Write();
219 0 : tempfile.Close();
220 0 : AliInfo(TString::Format("Write tree to file: %s", internalFilename.Data()));
221 : return kTRUE;
222 0 : }
223 : }
224 : else {// downloadIsNeeded == kFALSE
225 0 : return kTRUE;
226 : }
227 0 : }
228 :
229 : /// \param type Type of the resource as described in the config file, e.g. QA.TPC, MonALISA.RCT
230 : /// \param period Period, e.g. 'LHC15f'
231 : /// \param pass E.g. 'pass2' or 'passMC'
232 : /// Returns the tree with the information from the corresponding resource
233 : /// \return TTree* with corresponding resource
234 : TTree* AliExternalInfo::GetTree(TString type, TString period, TString pass){
235 0 : TString internalFilename = ""; // Resulting path to the file
236 0 : TString internalLocation = fLocalStorageDirectory; // Gets expanded in this function to the right directory
237 0 : TString externalLocation = "";
238 0 : Bool_t resourceIsTree = kFALSE;
239 0 : TString detector = "";
240 0 : TString rootFileName = "";
241 0 : TString treeName = "";
242 0 : TString pathStructure = "";
243 :
244 : TTree* tree = 0x0;
245 :
246 : // Setting up all the local variables
247 0 : SetupVariables(internalFilename, internalLocation, resourceIsTree, pathStructure, detector, rootFileName, treeName, type, period, pass);
248 :
249 0 : std::cout << "internalFilename: " << internalFilename << " rootFileName: " << rootFileName << std::endl;
250 :
251 0 : if (gSystem->AccessPathName(internalFilename.Data()) == kTRUE) {
252 0 : if (Cache(type, period, pass) == kFALSE) {
253 0 : std::cout << "Caching of ressource was not successful; Nullpointer is returned!\n" << std::endl;
254 0 : return tree;
255 : }
256 : }
257 :
258 : // Creating and returning the tree from the file
259 0 : TFile* treefile = new TFile(internalFilename.Data());
260 :
261 : // ---| loop over possible tree names |---
262 0 : TObjArray *arr = treeName.Tokenize(",");
263 0 : for (Int_t iname=0; iname<arr->GetEntriesFast(); ++iname) {
264 0 : tree = dynamic_cast<TTree*>( treefile->Get(arr->At(iname)->GetName()) );
265 0 : if (tree) break;
266 : }
267 0 : delete arr;
268 :
269 0 : if (tree != 0x0) {
270 0 : AliInfo("-- Successfully read in tree");
271 0 : AddTree(tree, type);
272 0 : } else {
273 0 : AliError("Error while reading tree");
274 : }
275 :
276 0 : const TString cacheSize=fLocationTimeOutMap[type + ".CacheSize"];
277 0 : Long64_t cache=cacheSize.Atoll();
278 0 : if (fMaxCacheSize>0) {
279 0 : if (cache>0) {
280 0 : cache=TMath::Min(fMaxCacheSize, cache);
281 0 : } else {
282 : cache=fMaxCacheSize;
283 : }
284 : }
285 0 : if (cache>0) tree->SetCacheSize(cache);
286 :
287 : return tree;
288 0 : }
289 :
290 : /// \param type Type of the resource as described in the config file, e.g. QA.TPC, MonALISA.RCT
291 : /// \param period Period, e.g. 'LHC15f'. Here you can use wildcards like in 'ls', e.g. 'LHC15*'
292 : /// \param pass E.g. 'pass2' or 'passMC'. Here you can use wildcards like in 'ls', e.g. 'pass*'
293 : /// Returns a chain with the information from the corresponding resources.
294 : /// \return TChain*
295 : TChain* AliExternalInfo::GetChain(TString type, TString period, TString pass){
296 : TChain* chain = 0x0;
297 0 : TString internalFilename = ""; // Resulting path to the file
298 0 : TString internalLocation = fLocalStorageDirectory; // Gets expanded in this function to the right directory
299 0 : TString externalLocation = "";
300 0 : Bool_t resourceIsTree = kFALSE;
301 0 : TString detector = "";
302 0 : TString rootFileName = "";
303 0 : TString treeName = "";
304 0 : TString pathStructure = "";
305 :
306 : // Setting up all the local variables
307 0 : SetupVariables(internalFilename, internalLocation, resourceIsTree, pathStructure, detector, rootFileName, treeName, type, period, pass);
308 :
309 0 : TString cmd = TString::Format("/bin/ls %s", internalFilename.Data());
310 : // std::cout << "==== cmd: " << cmd << std::endl;
311 :
312 0 : TString files=gSystem->GetFromPipe(cmd.Data());
313 0 : TObjArray *arrFiles=files.Tokenize("\n");
314 0 : AliInfo(TString::Format("Files to add to chain: %s", files.Data()));
315 :
316 : //function to get tree namee based on type
317 0 : chain=new TChain(treeName.Data());
318 : // ---| loop over possible tree names |---
319 0 : TObjArray *arrTreeName = treeName.Tokenize(",");
320 :
321 0 : for (Int_t ifile=0; ifile<arrFiles->GetEntriesFast(); ++ifile) {
322 0 : for (Int_t itree=0; itree<arrTreeName->GetEntriesFast(); itree++){
323 0 : TFile *ftemp=TFile::Open(arrFiles->At(ifile)->GetName());
324 0 : if (ftemp==NULL) continue;
325 0 : if (ftemp->GetListOfKeys()->FindObject(arrTreeName->At(itree)->GetName())){
326 0 : chain->AddFile(arrFiles->At(ifile)->GetName(),0, arrTreeName->At(itree)->GetName());
327 :
328 : }
329 0 : delete ftemp;
330 0 : }
331 : }
332 :
333 0 : const TString cacheSize=fLocationTimeOutMap[type + ".CacheSize"];
334 0 : Long64_t cache=cacheSize.Atoll();
335 0 : if (fMaxCacheSize>0) {
336 0 : if (cache>0) {
337 0 : cache=TMath::Min(fMaxCacheSize, cache);
338 0 : } else {
339 : cache=fMaxCacheSize;
340 : }
341 : }
342 0 : if (cache>0) chain->SetCacheSize(cache);
343 :
344 0 : AddChain(type, period, pass);
345 0 : delete arrFiles;
346 0 : delete arrTreeName;
347 : return chain;
348 0 : };
349 :
350 : /// Every tree you create is added to a big tree acting as a friend.
351 : /// You can have access to this tree with the GetFriendsTree() function.
352 : /// @TODO Add 'return false' when adding to the friends tree was not successful
353 : /// \return kTRUE
354 : Bool_t AliExternalInfo::AddTree(TTree* tree, TString type){
355 :
356 0 : if (tree->BuildIndex("run") < 0) tree->BuildIndex("raw_run");
357 0 : TString name = "";
358 :
359 0 : if(type.Contains("QA")){ // use TPC instead of QA.TPC
360 0 : name = type(3, type.Length()-1);
361 : }
362 0 : else if(type.Contains("MonALISA")){
363 0 : name = type(9, type.Length()-1);
364 : }
365 : else {
366 0 : name = type;
367 : }
368 :
369 0 : tree->SetName(name);
370 0 : if (fTree == 0x0) fTree = dynamic_cast<TTree*>(tree->Clone());
371 0 : fTree->AddFriend(tree, name);
372 :
373 0 : AliInfo(TString::Format("Added as friend with the name: %s",name.Data()));
374 : return kTRUE;
375 0 : }
376 : /// \param type Type of the resource as described in the config file, e.g. QA.TPC, MonALISA.RCT
377 : /// \param period Period, e.g. 'LHC15f'. Here you can use wildcards like in 'ls', e.g. 'LHC15*'
378 : /// \param pass E.g. 'pass2' or 'passMC'. Here you can use wildcards like in 'ls', e.g. 'pass*'
379 : /// Not fully working. Should automatically add every downloaded file to a huge chain.
380 : Bool_t AliExternalInfo::AddChain(TString type, TString period, TString pass){
381 : // Adds chain of trees and buildsindexs and addfriends it
382 0 : TString internalFilename = ""; // Resulting path to the file
383 0 : TString internalLocation = fLocalStorageDirectory; // Gets expanded in this function to the right directory
384 0 : TString externalLocation = "";
385 0 : Bool_t resourceIsTree = kFALSE;
386 0 : TString detector = "";
387 0 : TString rootFileName = "";
388 0 : TString treeName = "";
389 0 : TString pathStructure = "";
390 :
391 : // Setting up all the local variables
392 0 : SetupVariables(internalFilename, internalLocation, resourceIsTree, pathStructure, detector, rootFileName, treeName, type, period, pass);
393 0 : AliInfo(TString::Format("Add to internal Chain: %s", internalFilename.Data()));
394 0 : AliInfo(TString::Format("with tree name: %s", treeName.Data()));
395 0 : fChain->AddFile(internalFilename.Data(), TChain::kBigNumber, treeName);
396 :
397 : return kTRUE;
398 0 : }
399 :
400 : /// \param period Period in the form eg LHC15f
401 : /// Generate from eg LHC15f the year 2015
402 : /// \return Year in the form "2015"
403 : const TString AliExternalInfo::GetYearFromPeriod(const TString &period){
404 0 : TString year(period(3,2));
405 0 : year = TString::Format("20%s", year.Data());
406 : return year;
407 0 : }
408 :
409 : /// \param type Type of the resource as described in the config file, e.g. QA.TPC, MonALISA.RCT
410 : /// \param period Period, e.g. 'LHC15f'. Here you can use wildcards like in 'ls', e.g. 'LHC15*'
411 : /// \param pass E.g. 'pass2' or 'passMC'. Here you can use wildcards like in 'ls', e.g. 'pass*'
412 : /// Returns a TString containing the complete directory structure where the root
413 : /// file should be stored/loaded from. eg './data/2015/LHC15f/pass2/'
414 : const TString AliExternalInfo::CreatePath(TString type, TString period, TString pass){
415 : // Create the local path from the type, period and pass of the resource
416 0 : TString internalLocation;
417 : //Check if period is MC and adjust storage hierarchy
418 0 : if (period.Length() == 6 || (period == "" && type != "MonALISA.MC") || type == "MonALISA.ProductionCycleID" || type == "TriggerClasses") { // put everything in the form LHCYYx or with empty period and pass in "data"
419 0 : internalLocation.Append("/data/");
420 : }
421 : else { // everything which is not in the form "LHCYYx" put in /sim/
422 0 : internalLocation.Append("/sim/");
423 : }
424 : // std::cout << "Internal save path 1: " << internalLocation << std::endl;
425 0 : if (period != "" && type != "MonALISA.ProductionCycleID" && type != "QA.rawTPC"){
426 0 : const TString year = GetYearFromPeriod(period);
427 0 : internalLocation += year + "/";
428 : // std::cout << "Internal save path 2: " << internalLocation << std::endl;
429 0 : internalLocation += period + "/";
430 : // std::cout << "Internal save path 3: " << internalLocation << std::endl;
431 0 : if (pass != ""){
432 0 : internalLocation += pass + "/";
433 : // std::cout << "Internal save path 4: " << internalLocation << std::endl;
434 0 : }
435 0 : }
436 0 : else if (type == "QA.rawTPC"){ // only needs the year
437 0 : const TString year = GetYearFromPeriod(period);
438 0 : internalLocation += year + "/";
439 0 : }
440 0 : else if (type == "MonALISA.ProductionCycleID") { // Needed for ProductionCycleIDs
441 0 : internalLocation += "ID_" + period + "/";
442 0 : }
443 : return internalLocation;
444 0 : }
445 :
446 : /// \param file Exact location of the file which should be checked
447 : /// \param type Type of the resource as described in the config file, e.g. QA.TPC, MonALISA.RCT
448 : /// Checks if the file is older than the timeout which is specified in the config file of this
449 : /// specific resource type
450 : /// \returns true if download of the resource is needed and false if not
451 : Bool_t AliExternalInfo::IsDownloadNeeded(TString file, TString type){
452 : Int_t timeOut = 0;
453 0 : timeOut = atoi(fLocationTimeOutMap[type + ".timeout"]);
454 :
455 : // std::cout << "-- Check, if " << file << " is already there" << std::endl;
456 0 : if (gSystem->AccessPathName(file.Data()) == kTRUE) {
457 0 : AliInfo("-- File not found locally --> Caching from remote");
458 0 : return kTRUE;
459 : }
460 : else {
461 : // std::cout << "---- File already downloaded --> Check if older than timelimit" << std::endl;
462 0 : struct stat st;
463 0 : stat(file.Data(), &st);
464 0 : std::time_t timeNow = std::time(0);
465 0 : long int timeFileModified = st.st_mtime;
466 : // std::cout << "------ File is " << timeNow - timeFileModified << " seconds old" << std::endl;
467 0 : if (timeNow - timeFileModified < timeOut) {
468 0 : AliInfo(TString::Format("-- File is %li s old; NOT older than the set timelimit %d s",timeNow - timeFileModified, timeOut));
469 0 : return kFALSE; // if file is younger than the set time limit, it will not be downloaded again
470 : }
471 : else {
472 0 : AliInfo(TString::Format("-- File is %li s old; Older than the set timelimit %d s",timeNow - timeFileModified, timeOut));
473 0 : return kTRUE;
474 : }
475 0 : }
476 0 : }
477 : /// \param mifFilePath Location of the mif-file which is downloaded from Monalisa; Is changed by this function
478 : /// \param internalLocation Directory where the root file is stored
479 : /// \param rootFileName Location of the newly created root file
480 : /// \param externalLocation Location specified in the config file
481 : /// Composes the wget-command in a TString which afterwards then can be executed
482 : /// \return wget-command in a TString
483 : const TString AliExternalInfo::Wget(TString& mifFilePath, const TString& internalLocation, TString rootFileName, const TString& externalLocation){
484 0 : TString command = "";
485 0 : TString certificate("$HOME/.globus/usercert.pem");
486 0 : TString privateKey("$HOME/.globus/userkey.pem");
487 :
488 : // TString internalLocation = internalFilename(0, internalFilename.Last('/') + 1); // path to internal location
489 : // Create path to logfile with same name as the root file
490 0 : TString logFileName = rootFileName.ReplaceAll(".root", ".log");
491 0 : logFileName.Prepend(internalLocation);
492 :
493 0 : mifFilePath = rootFileName.ReplaceAll(".log", ".mif");
494 0 : mifFilePath.Prepend(internalLocation);
495 :
496 0 : command = TString::Format("wget --no-check-certificate --secure-protocol=TLSv1 --certificate=%s --private-key=%s -o %s -O %s \"%s\"",
497 0 : certificate.Data(), privateKey.Data(), logFileName.Data(),
498 0 : mifFilePath.Data(), externalLocation.Data());
499 : return command;
500 0 : }
|