#include "TFileMerger.h"
#include "TUrl.h"
#include "TFile.h"
#include "TUUID.h"
#include "TSystem.h"
#include "TKey.h"
#include "THashList.h"
#include "TObjString.h"
#include "TClass.h"
#include "TMethodCall.h"
#include "Riostream.h"
#include "TFileMergeInfo.h"
#include "TClassRef.h"
#include "TROOT.h"
ClassImp(TFileMerger)
TClassRef R__TH1_Class("TH1");
TClassRef R__TTree_Class("TTree");
TFileMerger::TFileMerger(Bool_t isLocal, Bool_t histoOneGo)
: fOutputFile(0), fFastMethod(kTRUE), fNoTrees(kFALSE), fExplicitCompLevel(kFALSE), fCompressionChange(kFALSE),
fPrintLevel(0),
fLocal(isLocal), fHistoOneGo(histoOneGo)
{
fFileList = new TList;
fFileList->SetOwner(kTRUE);
fMergeList = new TList;
fMergeList->SetOwner(kTRUE);
gROOT->GetListOfCleanups()->Add(this);
}
TFileMerger::~TFileMerger()
{
gROOT->GetListOfCleanups()->Remove(this);
SafeDelete(fFileList);
SafeDelete(fMergeList);
SafeDelete(fOutputFile);
}
void TFileMerger::Reset()
{
fFileList->Clear();
fMergeList->Clear();
}
Bool_t TFileMerger::AddFile(const char *url, Bool_t cpProgress)
{
if (fPrintLevel > 0) {
Printf("Source file %d: %s",fFileList->GetEntries()+1,url);
}
TFile *newfile = 0;
TString localcopy;
if (fLocal) {
TUUID uuid;
localcopy.Form("file:%s/ROOTMERGE-%s.root", gSystem->TempDirectory(), uuid.AsString());
if (!TFile::Cp(url, localcopy, cpProgress)) {
Error("AddFile", "cannot get a local copy of file %s", url);
return kFALSE;
}
newfile = TFile::Open(localcopy, "READ");
} else {
newfile = TFile::Open(url, "READ");
}
if (!newfile) {
if (fLocal)
Error("AddFile", "cannot open local copy %s of URL %s",
localcopy.Data(), url);
else
Error("AddFile", "cannot open file %s", url);
return kFALSE;
} else {
if (fOutputFile && fOutputFile->GetCompressionLevel() != newfile->GetCompressionLevel()) fCompressionChange = kTRUE;
fFileList->Add(newfile);
if (!fMergeList)
fMergeList = new TList;
TObjString *urlObj = new TObjString(url);
fMergeList->Add(urlObj);
return kTRUE;
}
}
Bool_t TFileMerger::OutputFile(const char *outputfile, Bool_t force, Int_t compressionLevel)
{
fExplicitCompLevel = kTRUE;
SafeDelete(fOutputFile);
fOutputFilename = outputfile;
if (!(fOutputFile = TFile::Open(outputfile, (force?"RECREATE":"CREATE"), "", compressionLevel)) || fOutputFile->IsZombie()) {
Error("OutputFile", "cannot open the MERGER output file %s", fOutputFilename.Data());
return kFALSE;
}
return kTRUE;
}
Bool_t TFileMerger::OutputFile(const char *outputfile, Bool_t force )
{
Bool_t res = OutputFile(outputfile,force,1);
fExplicitCompLevel = kFALSE;
return res;
}
void TFileMerger::PrintFiles(Option_t *options)
{
fFileList->Print(options);
}
Bool_t TFileMerger::Merge(Bool_t)
{
if (!fOutputFile) {
TString outf(fOutputFilename);
if (outf.IsNull()) {
outf.Form("file:%s/FileMerger.root", gSystem->TempDirectory());
Info("Merge", "will merge the results to the file %s\n"
"since you didn't specify a merge filename",
TUrl(outf).GetFile());
}
if (!OutputFile(outf.Data())) {
return kFALSE;
}
}
fOutputFile->SetBit(kMustCleanup);
Bool_t result = MergeRecursive(fOutputFile, fFileList);
if (!result) {
Error("Merge", "error during merge of your ROOT files");
} else {
fOutputFile->Close();
}
fOutputFile->ResetBit(kMustCleanup);
SafeDelete(fOutputFile);
TIter next(fFileList);
TFile *file;
while ((file = (TFile*) next())) {
file->Close();
if(fLocal) {
TString p(file->GetPath());
p = p(0, p.Index(':',0));
gSystem->Unlink(p);
}
}
return result;
}
Bool_t TFileMerger::MergeRecursive(TDirectory *target, TList *sourcelist)
{
Bool_t status = kTRUE;
if (fPrintLevel > 0) {
Printf("Target path: %s",target->GetPath());
}
TString path(target->GetPath());
path.Remove(0, path.Last(':') + 2);
Bool_t addDirStat = kTRUE;
if (R__TH1_Class) {
gROOT->ProcessLineFast("TH1::AddDirectoryStatus()");
gROOT->ProcessLine("TH1::AddDirectory(kFALSE);");
}
TDirectory *first_source = (TDirectory*)sourcelist->First();
Int_t nguess = sourcelist->GetSize()+1000;
THashList allNames(nguess);
((THashList*)target->GetList())->Rehash(nguess);
((THashList*)target->GetListOfKeys())->Rehash(nguess);
TFileMergeInfo info(target);
if ((fFastMethod && !fCompressionChange)) {
info.fOptions.Append(" fast");
}
while (first_source) {
TDirectory *current_sourcedir = first_source->GetDirectory(path);
if (!current_sourcedir) {
first_source = (TDirectory*)sourcelist->After(first_source);
continue;
}
TIter nextkey( current_sourcedir->GetListOfKeys() );
TKey *key, *oldkey=0;
while ( (key = (TKey*)nextkey())) {
if (current_sourcedir == target) break;
if (oldkey && !strcmp(oldkey->GetName(),key->GetName())) continue;
if (strcmp(key->GetClassName(),"TProcessID") == 0) { key->ReadObj(); continue;}
if (allNames.FindObject(key->GetName())) continue;
TClass *cl = TClass::GetClass(key->GetClassName());
if (!cl || !cl->InheritsFrom(TObject::Class())) {
Info("MergeRecursive", "cannot merge object type, name: %s title: %s",
key->GetName(), key->GetTitle());
continue;
}
allNames.Add(new TObjString(key->GetName()));
if (fNoTrees && cl->InheritsFrom(R__TTree_Class)) {
continue;
}
TObject *obj = key->ReadObj();
if (!obj) {
Info("MergeRecursive", "could not read object for key {%s, %s}",
key->GetName(), key->GetTitle());
continue;
}
if ( obj->IsA()->InheritsFrom( TDirectory::Class() ) ) {
target->cd();
TDirectory *newdir = target->mkdir( obj->GetName(), obj->GetTitle() );
status = MergeRecursive( newdir, sourcelist);
if (!status) return status;
} else if (obj->IsA()->GetMerge()) {
TList inputs;
Bool_t oneGo = fHistoOneGo && obj->IsA()->InheritsFrom(R__TH1_Class);
TFile *nextsource = (TFile*)sourcelist->After( first_source );
while (nextsource) {
TDirectory *ndir = nextsource->GetDirectory(path);
if (ndir) {
ndir->cd();
TKey *key2 = (TKey*)gDirectory->GetListOfKeys()->FindObject(key->GetName());
if (key2) {
TObject *hobj = key2->ReadObj();
if (!hobj) {
Info("MergeRecursive", "could not read object for key {%s, %s}; skipping file %s",
key->GetName(), key->GetTitle(), nextsource->GetName());
nextsource = (TFile*)sourcelist->After(nextsource);
continue;
}
if (hobj->InheritsFrom(TCollection::Class())) {
((TCollection*)hobj)->SetOwner();
}
hobj->ResetBit(kMustCleanup);
inputs.Add(hobj);
if (!oneGo) {
ROOT::MergeFunc_t func = obj->IsA()->GetMerge();
Long64_t result = func(obj, &inputs, &info);
info.fIsFirst = kFALSE;
if (result < 0) {
Error("MergeRecursive", "calling Merge() on '%s' with the corresponding object in '%s'",
obj->GetName(), nextsource->GetName());
}
inputs.Delete();
}
}
}
nextsource = (TFile*)sourcelist->After( nextsource );
}
if (oneGo) {
ROOT::MergeFunc_t func = obj->IsA()->GetMerge();
func(obj, &inputs, &info);
info.fIsFirst = kFALSE;
inputs.Delete();
}
} else if (obj->InheritsFrom(TObject::Class()) &&
obj->IsA()->GetMethodWithPrototype("Merge", "TCollection*,TFileMergeInfo*") ) {
TList listH;
TString listHargs;
listHargs.Form("(TCollection*)0x%lx,(TFileMergeInfo*)0x%lx", (ULong_t)&listH,(ULong_t)&info);
TFile *nextsource = (TFile*)sourcelist->After( first_source );
while (nextsource) {
TDirectory *ndir = nextsource->GetDirectory(path);
if (ndir) {
ndir->cd();
TKey *key2 = (TKey*)gDirectory->GetListOfKeys()->FindObject(key->GetName());
if (key2) {
TObject *hobj = key2->ReadObj();
if (!hobj) {
Info("MergeRecursive", "could not read object for key {%s, %s}; skipping file %s",
key->GetName(), key->GetTitle(), nextsource->GetName());
nextsource = (TFile*)sourcelist->After(nextsource);
continue;
}
if (hobj->InheritsFrom(TCollection::Class())) {
((TCollection*)hobj)->SetOwner();
}
hobj->ResetBit(kMustCleanup);
listH.Add(hobj);
Int_t error = 0;
obj->Execute("Merge", listHargs.Data(), &error);
info.fIsFirst = kFALSE;
if (error) {
Error("MergeRecursive", "calling Merge() on '%s' with the corresponding object in '%s'",
obj->GetName(), nextsource->GetName());
}
listH.Delete();
}
}
nextsource = (TFile*)sourcelist->After( nextsource );
}
} else if (obj->InheritsFrom(TObject::Class()) &&
obj->IsA()->GetMethodWithPrototype("Merge", "TCollection*") ) {
TList listH;
TString listHargs;
listHargs.Form("((TCollection*)0x%lx)", (ULong_t)&listH);
TFile *nextsource = (TFile*)sourcelist->After( first_source );
while (nextsource) {
TDirectory *ndir = nextsource->GetDirectory(path);
if (ndir) {
ndir->cd();
TKey *key2 = (TKey*)gDirectory->GetListOfKeys()->FindObject(key->GetName());
if (key2) {
TObject *hobj = key2->ReadObj();
if (!hobj) {
Info("MergeRecursive", "could not read object for key {%s, %s}; skipping file %s",
key->GetName(), key->GetTitle(), nextsource->GetName());
nextsource = (TFile*)sourcelist->After(nextsource);
continue;
}
if (hobj->InheritsFrom(TCollection::Class())) {
((TCollection*)hobj)->SetOwner();
}
hobj->ResetBit(kMustCleanup);
listH.Add(hobj);
Int_t error = 0;
obj->Execute("Merge", listHargs.Data(), &error);
if (error) {
Error("MergeRecursive", "calling Merge() on '%s' with the corresponding object in '%s'",
obj->GetName(), nextsource->GetName());
}
listH.Delete();
}
}
nextsource = (TFile*)sourcelist->After( nextsource );
}
} else {
Warning("MergeRecursive", "cannot merge object type (n:'%s', t:'%s') - "
"Merge(TCollection *) not implemented",
obj->GetName(), obj->GetTitle());
TFile *nextsource = (TFile*)sourcelist->After( first_source );
while (nextsource) {
TDirectory *ndir = nextsource->GetDirectory(path);
if (ndir) {
ndir->cd();
TKey *key2 = (TKey*)gDirectory->GetListOfKeys()->FindObject(key->GetName());
if (key2) {
TObject *nobj = key2->ReadObj();
if (!nobj) {
Info("MergeRecursive", "could not read object for key {%s, %s}; skipping file %s",
key->GetName(), key->GetTitle(), nextsource->GetName());
nextsource = (TFile*)sourcelist->After(nextsource);
continue;
}
nobj->ResetBit(kMustCleanup);
if (target->WriteTObject(nobj, key2->GetName(), "SingleKey") <= 0) {
Warning("MergeRecursive", "problems copying object (n:'%s', t:'%s') to output file ",
obj->GetName(), obj->GetTitle());
status = kFALSE;
}
delete nobj;
}
}
nextsource = (TFile*)sourcelist->After( nextsource );
}
}
target->cd();
if(obj->IsA()->InheritsFrom( TDirectory::Class() )) {
} else if (obj->IsA()->InheritsFrom( TCollection::Class() )) {
if ( obj->Write( key->GetName(), TObject::kSingleKey ) <= 0 ) {
status = kFALSE;
}
((TCollection*)obj)->SetOwner();
} else {
if ( obj->Write( key->GetName() ) <= 0) {
status = kFALSE;
}
}
if (obj->IsA()->InheritsFrom(TCollection::Class())) ((TCollection*)obj)->Delete();
oldkey = key;
delete obj;
info.Reset();
}
first_source = (TDirectory*)sourcelist->After(first_source);
}
target->SaveSelf(kTRUE);
if (R__TH1_Class) {
gROOT->ProcessLine(TString::Format("TH1::AddDirectory(%d);",addDirStat));
}
return status;
}
void TFileMerger::RecursiveRemove(TObject *obj)
{
if (obj == fOutputFile) {
Fatal("RecursiveRemove","Output file of the TFile Merger (targetting %s) has been deleted (likely due to a TTree larger than 100Gb)", fOutputFilename.Data());
}
}