source: XIOS/dev/dev_ym/XIOS_COUPLING/src/node/file.cpp @ 1872

Last change on this file since 1872 was 1872, checked in by ymipsl, 4 years ago

Some update on XIOS_COUPLING branch...

YM

  • Property copyright set to
    Software name : XIOS (Xml I/O Server)
    http://forge.ipsl.jussieu.fr/ioserver
    Creation date : January 2009
    Licence : CeCCIL version2
    see license file in root directory : Licence_CeCILL_V2-en.txt
    or http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
    Holder : CEA/LSCE (Laboratoire des Sciences du CLimat et de l'Environnement)
    CNRS/IPSL (Institut Pierre Simon Laplace)
    Project Manager : Yann Meurdesoif
    yann.meurdesoif@cea.fr
File size: 44.5 KB
Line 
1#include "file.hpp"
2
3#include "attribute_template.hpp"
4#include "object_template.hpp"
5#include "group_template.hpp"
6#include "object_factory.hpp"
7#include "context.hpp"
8#include "context_server.hpp"
9#include "nc4_data_output.hpp"
10#include "nc4_data_input.hpp"
11#include "calendar_util.hpp"
12#include "date.hpp"
13#include "message.hpp"
14#include "type.hpp"
15#include "xios_spl.hpp"
16#include "context_client.hpp"
17#include "mpi.hpp"
18#include "timer.hpp"
19#include "server.hpp"
20
21namespace xios {
22
23   /// ////////////////////// Dfinitions ////////////////////// ///
24
25   CFile::CFile(void)
26      : CObjectTemplate<CFile>(), CFileAttributes()
27      , vFieldGroup(), data_out(), enabledFields(), fileComm(MPI_COMM_NULL)
28      , isOpen(false), read_client(0), checkRead(false), allZoneEmpty(false)
29   {
30     setVirtualFieldGroup(CFieldGroup::create(getId() + "_virtual_field_group"));
31     setVirtualVariableGroup(CVariableGroup::create(getId() + "_virtual_variable_group"));
32   }
33
34   CFile::CFile(const StdString & id)
35      : CObjectTemplate<CFile>(id), CFileAttributes()
36      , vFieldGroup(), data_out(), enabledFields(), fileComm(MPI_COMM_NULL)
37      , isOpen(false), read_client(0), checkRead(false), allZoneEmpty(false)
38    {
39      setVirtualFieldGroup(CFieldGroup::create(getId() + "_virtual_field_group"));
40      setVirtualVariableGroup(CVariableGroup::create(getId() + "_virtual_variable_group"));
41    }
42
43   CFile::~CFile(void)
44   { /* Ne rien faire de plus */ }
45
46   ///---------------------------------------------------------------
47  //! Get name of file
48   StdString CFile::GetName(void)   { return (StdString("file")); }
49   StdString CFile::GetDefName(void){ return (CFile::GetName()); }
50   ENodeType CFile::GetType(void)   { return (eFile); }
51
52   //----------------------------------------------------------------
53
54   const StdString CFile::getFileOutputName(void) const
55   TRY
56   {
57     return (name.isEmpty() ? getId() : name) + (name_suffix.isEmpty() ? StdString("") :  name_suffix.getValue());
58   }
59   CATCH
60
61   //----------------------------------------------------------------
62   /*!
63   \brief Get data writer object.
64   Each enabled file in xml represents a physical netcdf file.
65   This function allows to access the data writer object.
66   \return data writer object.
67   */
68   std::shared_ptr<CDataOutput> CFile::getDataOutput(void) const
69   TRY
70   {
71      return data_out;
72   }
73   CATCH
74
75   /*!
76   \brief Get data reader object.
77   Each enabled file in xml represents a physical netcdf file.
78   This function allows to access the data reader object.
79   \return data reader object.
80   */
81   std::shared_ptr<CDataInput> CFile::getDataInput(void) const
82   TRY
83   {
84      return data_in;
85   }
86   CATCH
87
88   /*!
89   \brief Get virtual field group
90      In each file, there always exists a field group which is the ancestor of all
91   fields in the file. This is considered be virtual because it is created automatically during
92   file initialization and it normally doesn't appear on xml file
93   \return Pointer to field group
94   */
95   CFieldGroup* CFile::getVirtualFieldGroup(void) const
96   TRY
97   {
98      return (this->vFieldGroup);
99   }
100   CATCH
101
102   /*!
103   \brief Get virtual variable group
104      In each file, there always exists a variable group which is the ancestor of all
105   variable in the file. This is considered be virtual because it is created automatically during
106   file initialization and it normally doesn't appear on xml file
107   \return Pointer to variable group
108   */
109   CVariableGroup* CFile::getVirtualVariableGroup(void) const
110   TRY
111   {
112      return (this->vVariableGroup);
113   }
114   CATCH
115
116   //! Get all fields of a file
117   std::vector<CField*> CFile::getAllFields(void) const
118   TRY
119   {
120      return (this->vFieldGroup->getAllChildren());
121   }
122   CATCH
123
124   //! Get all variables of a file
125   std::vector<CVariable*> CFile::getAllVariables(void) const
126   TRY
127   {
128      return (this->vVariableGroup->getAllChildren());
129   }
130   CATCH
131
132   //----------------------------------------------------------------
133   /*!
134   \brief Get all enabled fields of file
135      A field is considered to be enabled if it fullfil these conditions: it is enabled, inside a enabled file
136   and its own level is not larger than file output level.
137   \param [in] default_outputlevel default value output level of file
138   \param [in] default_level default value level of field
139   \param [in] default_enabled flag determine by default if field is enabled
140   \return Vector of pointers of enabled fields
141   */
142   std::vector<CField*> CFile::getEnabledFields(int default_outputlevel,
143                                                int default_level,
144                                                bool default_enabled)
145   TRY
146   {
147      if (!this->enabledFields.empty()) return (this->enabledFields);
148
149      const int _outputlevel = (!output_level.isEmpty()) ? output_level.getValue() : default_outputlevel;
150      std::vector<CField*>::iterator it;
151      this->enabledFields = this->getAllFields();
152
153      std::vector<CField*> newEnabledFields;
154
155      for ( it = this->enabledFields.begin(); it != this->enabledFields.end(); it++ )
156      {
157         if (!(*it)->enabled.isEmpty()) // Si l'attribut 'enabled' est dfini ...
158         {
159            if (! (*it)->enabled.getValue()) continue;
160         }
161         else // Si l'attribut 'enabled' n'est pas dfini ...
162         {
163            if (!default_enabled) continue;
164         }
165
166         if (!(*it)->level.isEmpty()) // Si l'attribut 'level' est dfini ...
167         {
168            if ((*it)->level.getValue() > _outputlevel) continue;
169         }
170         else // Si l'attribut 'level' n'est pas dfini ...
171         {
172            if (default_level > _outputlevel) continue;
173         }
174
175         newEnabledFields.push_back(*it);
176      }
177      enabledFields = newEnabledFields;
178
179      return (this->enabledFields);
180   }
181   CATCH_DUMP_ATTR
182
183   //----------------------------------------------------------------
184   //! Change virtual field group to a new one
185   void CFile::setVirtualFieldGroup(CFieldGroup* newVFieldGroup)
186   TRY
187   {
188      this->vFieldGroup = newVFieldGroup;
189   }
190   CATCH_DUMP_ATTR
191
192   //! Change virtual variable group to new one
193   void CFile::setVirtualVariableGroup(CVariableGroup* newVVariableGroup)
194   TRY
195   {
196      this->vVariableGroup = newVVariableGroup;
197   }
198   CATCH_DUMP_ATTR
199
200   //----------------------------------------------------------------
201   bool CFile::isSyncTime(void)
202   TRY
203   {
204     CContext* context = CContext::getCurrent();
205     const CDate& currentDate = context->calendar->getCurrentDate();
206     if (!sync_freq.isEmpty())
207     {
208       if (lastSync + sync_freq.getValue() < currentDate)
209       {
210         lastSync = currentDate;
211         return true;
212        }
213      }
214      return false;
215    }
216    CATCH_DUMP_ATTR
217
218   //! Initialize a file in order to write into it
219   void CFile::initWrite(void)
220   TRY
221   {
222      CContext* context = CContext::getCurrent();
223      const CDate& currentDate = context->calendar->getCurrentDate();
224
225      lastSync  = currentDate;
226      lastSplit = currentDate;
227      if (!split_freq.isEmpty())
228      {
229        StdString keySuffix("CFile::"+getFileOutputName()+"::") ; 
230        if (context->registryIn->foundKey(keySuffix+"splitStart") && context->registryIn->foundKey(keySuffix+"splitEnd"))
231        {
232          CDate savedSplitStart(*context->getCalendar()), savedSplitEnd(*context->getCalendar());
233          context->registryIn->getKey(keySuffix+"splitStart", savedSplitStart);
234          context->registryIn->getKey(keySuffix+"splitEnd",   savedSplitEnd);
235
236          if (savedSplitStart <= lastSplit && lastSplit <= savedSplitEnd)
237            lastSplit = savedSplitStart;
238        }
239      }
240      isOpen = false;     
241
242//      if (!record_offset.isEmpty() && record_offset < 0)
243//        ERROR("void CFile::initFile(void)",
244//              "Invalid 'record_offset', this attribute cannot be negative.");
245      const int recordOffset = record_offset.isEmpty() ? 0 : record_offset;
246
247      set<StdString> setAxis;
248      set<StdString> setDomains;
249
250      std::vector<CField*>::iterator it, end = this->enabledFields.end();
251      for (it = this->enabledFields.begin(); it != end; it++)
252      {
253         CField* field = *it;         
254         std::vector<CAxis*> vecAxis = field->getGrid()->getAxis();
255         for (size_t i = 0; i < vecAxis.size(); ++i)
256           setAxis.insert(vecAxis[i]->getAxisOutputName());
257         std::vector<CDomain*> vecDomains = field->getGrid()->getDomains();
258         for (size_t i = 0; i < vecDomains.size(); ++i)
259           setDomains.insert(vecDomains[i]->getDomainOutputName());
260
261         field->resetNStep(recordOffset);
262      }
263      nbAxis = setAxis.size();
264      nbDomains = setDomains.size();
265
266      // create sub communicator for file
267      createSubComFile();
268
269      // if (time_counter.isEmpty()) time_counter.setValue(time_counter_attr::centered);
270      if (time_counter_name.isEmpty()) time_counter_name = "time_counter";
271    }
272    CATCH_DUMP_ATTR
273
274    //! Initialize a file in order to write into it
275    void CFile::initRead(void)
276    TRY
277    {
278      CContext* context = CContext::getCurrent();
279      if (checkRead) return;
280      //createSubComFile();
281      allZoneEmpty = false; 
282      MPI_Comm_dup(context->intraComm_, &fileComm) ;
283      checkRead = true;
284    }
285    CATCH_DUMP_ATTR
286
287    /*!
288      Create a sub communicator in which processes participate in reading/opening file
289    */
290    void CFile::createSubComFile()
291    TRY
292    {
293      CContext* context = CContext::getCurrent();
294
295      // create sub communicator for file
296      allZoneEmpty = true;     
297      std::vector<CField*>::iterator it, end = this->enabledFields.end();
298      for (it = this->enabledFields.begin(); it != end; it++)
299      {
300         CField* field = *it;
301         bool nullGrid = (nullptr == field->getGrid());
302         allZoneEmpty &= nullGrid ? false : !field->getGrid()->doGridHaveDataToWrite();
303      }
304
305      int color = allZoneEmpty ? 0 : 1;
306      MPI_Comm_split(context->intraComm_, color, context->intraCommRank_, &fileComm);
307      if (allZoneEmpty) MPI_Comm_free(&fileComm);
308    }
309    CATCH_DUMP_ATTR
310
311    /*
312       Check condition to write into a file
313       For now, we only use the level-2 server to write files (if this mode is activated)
314       or classical server to do this job.
315    */
316    void CFile::checkWriteFile(void)
317    TRY
318    {
319      CContext* context = CContext::getCurrent();
320      // Done by classical server or secondary server
321      // This condition should be changed soon
322//ym      if (CServer::serverLevel == 0 || CServer::serverLevel == 2)
323       if (context->getServiceType()==CServicesManager::IO_SERVER || context->getServiceType()==CServicesManager::OUT_SERVER)
324       {
325        if (mode.isEmpty() || mode.getValue() == mode_attr::write)
326        {
327          CTimer::get("Files : create headers").resume();
328          if (!isOpen) createHeader();
329          CTimer::get("Files : create headers").suspend();
330          checkSync();
331        }       
332        checkSplit(); // REally need this?
333      }
334    }
335    CATCH_DUMP_ATTR
336
337    /*
338       Check condition to read from a file
339       For now, we only use the level-1 server to write files (if this mode is activated)
340       or classical server to do this job.
341       This function can be used by client for reading metadata
342    */
343    void CFile::checkReadFile(void)
344    TRY
345    {
346      CContext* context = CContext::getCurrent();
347      // Done by classical server or secondary server
348      // TODO: This condition should be changed soon. It only works with maximum number of level as 2
349
350//ym      if (CServer::serverLevel == 0 || CServer::serverLevel == 1)
351// ym client must doing it also
352//      if (context->getServiceType()==CServicesManager::IO_SERVER || context->getServiceType()==CServicesManager::GATHERER)
353//      {
354        if (!mode.isEmpty() && mode.getValue() == mode_attr::read)
355        {
356          CTimer::get("Files : open headers").resume();
357         
358          if (!isOpen) openInReadMode();
359
360          CTimer::get("Files : open headers").suspend();
361        }
362        //checkSplit(); // Really need for reading?
363 //     }
364    }
365    CATCH_DUMP_ATTR
366
367    /*!
368      Verify if a process participates in an opening-file communicator
369      \return true if the process doesn't participate in opening file
370    */
371    bool CFile::isEmptyZone()
372    TRY
373    {
374      return allZoneEmpty;
375    }
376    CATCH_DUMP_ATTR
377
378    /*!
379    \brief Verify if synchronisation should be done
380        If syn option is enabled, syn frequence and current time will be used to
381    calculate the moment to syn file(s)
382    \return True if it is the moment to synchronize file, otherwise false
383    */
384   bool CFile::checkSync(void)
385   TRY
386   {
387     CContext* context = CContext::getCurrent();
388     const CDate& currentDate = context->calendar->getCurrentDate();
389     if (!sync_freq.isEmpty())
390     {
391       if (lastSync + sync_freq.getValue() <= currentDate)
392       {
393         lastSync = currentDate;
394         data_out->syncFile();
395         return true;
396        }
397      }
398      return false;
399    }
400   CATCH_DUMP_ATTR
401
402    /*!
403    \brief Verify if splitting should be done
404        If split option is enabled, split frequence and current time will be used to
405    calculate the moment to split file
406    \return True if it is the moment to split file, otherwise false
407    */
408    bool CFile::checkSplit(void)
409    TRY
410    {
411      CContext* context = CContext::getCurrent();
412      const CDate& currentDate = context->calendar->getCurrentDate();
413      if (!split_freq.isEmpty())
414      {
415        if (currentDate > lastSplit + split_freq.getValue())
416        {
417          lastSplit = lastSplit + split_freq.getValue();
418          std::vector<CField*>::iterator it, end = this->enabledFields.end();
419          for (it = this->enabledFields.begin(); it != end; it++)
420          {
421            (*it)->resetNStep();
422            (*it)->resetNStepMax();
423          }
424          if (mode.isEmpty() || mode.getValue() == mode_attr::write)
425            createHeader();
426          else
427            openInReadMode();
428          return true;
429        }
430      }
431      return false;
432    }
433    CATCH_DUMP_ATTR
434
435   /*!
436   \brief Create header of netcdf file
437   There are some information to fill in header of each netcdf.
438   */
439   void CFile::createHeader(void)
440   TRY
441   {
442      CContext* context = CContext::getCurrent();
443     
444      if (!allZoneEmpty)
445      {
446         StdString filename = getFileOutputName();
447
448// determine splitting format in the file name  : firstPart%start_date%middlePart%end_date%lastPart
449
450         std::string strStartDate="%start_date%" ;
451         std::string strEndDate="%end_date%" ;
452
453         std::string firstPart ;
454         std::string middlePart ;
455         std::string lastPart ;
456         size_t pos1, pos2 ;
457         bool hasStartDate=false ;
458         bool hasEndDate=false ;
459         bool hasSplit = (!split_freq.isEmpty());
460                 
461         pos1=filename.find(strStartDate) ;
462         if (pos1!=std::string::npos)
463         {
464           firstPart=filename.substr(0,pos1) ;
465           pos1+=strStartDate.size() ;
466           hasStartDate=true ;
467         }
468         else pos1=0 ;
469
470         pos2=filename.find(strEndDate,pos1) ;
471         if (pos2!=std::string::npos)
472         {
473           middlePart=filename.substr(pos1,pos2-pos1) ;
474           pos2+=strEndDate.size() ;
475           lastPart=filename.substr(pos2,filename.size()-pos2) ;
476           hasEndDate=true ;
477         }
478         else middlePart=filename.substr(pos1,filename.size()) ;
479
480         if (!hasStartDate && !hasEndDate)
481         {
482           hasStartDate=true ;
483           hasEndDate=true;
484           firstPart=middlePart ;
485           if (hasSplit) firstPart +="_";
486           middlePart="-" ;
487         }
488   
489         StdOStringStream oss;
490
491         if (!split_freq.isEmpty())
492         {
493           CDate split_start ;
494           CDate splitEnd ;
495           if (!split_start_offset.isEmpty()) split_start=lastSplit + split_start_offset ;
496           else split_start=lastSplit ;
497
498           splitEnd = lastSplit + split_freq ;
499           if (!split_last_date.isEmpty())
500           {
501             CDate splitLastDate=CDate::FromString(split_last_date,*CContext::getCurrent()->getCalendar()) ;
502             if( splitLastDate < splitEnd)  splitEnd=splitLastDate ;
503           }
504           
505           if (!split_end_offset.isEmpty()) splitEnd = splitEnd + split_end_offset;
506           else splitEnd = splitEnd - 1 * Second;
507
508           string splitFormat;
509           if (split_freq_format.isEmpty())
510           {
511             CDuration splitFreq = split_freq.getValue();
512             splitFreq.solveTimeStep(*CContext::getCurrent()->getCalendar());
513             if (splitFreq.second != 0) splitFormat = "%y%mo%d%h%mi%s";
514             else if (splitFreq.minute != 0) splitFormat = "%y%mo%d%h%mi";
515             else if (splitFreq.hour != 0) splitFormat = "%y%mo%d%h";
516             else if (splitFreq.day != 0) splitFormat = "%y%mo%d";
517             else if (splitFreq.month != 0) splitFormat = "%y%mo";
518             else splitFormat = "%y";
519           }
520           else splitFormat = split_freq_format;
521
522           oss << firstPart ;
523           if (hasStartDate) oss << split_start.getStr(splitFormat) ;
524           oss << middlePart ;
525           if (hasEndDate) oss << splitEnd.getStr(splitFormat);
526           oss << lastPart ;
527
528           StdString keySuffix("CFile::"+getFileOutputName()+"::") ; 
529           context->registryOut->setKey(keySuffix+"splitStart", lastSplit);
530           context->registryOut->setKey(keySuffix+"splitEnd",   splitEnd);
531         }
532         else oss<<firstPart<<lastPart ;
533
534        bool append = !this->append.isEmpty() && this->append.getValue();
535
536         bool useClassicFormat = !format.isEmpty() && format == format_attr::netcdf4_classic;
537         bool useCFConvention = convention.isEmpty() || convention == convention_attr::CF;
538
539         bool multifile = true;
540         if (!type.isEmpty())
541         {
542           if (type == type_attr::one_file) multifile = false;
543           else if (type == type_attr::multiple_file) multifile = true;
544
545         }
546#ifndef USING_NETCDF_PAR
547         if (!multifile)
548         {
549            info(0) << "!!! Warning -> Using non parallel version of netcdf, switching in multiple_file mode for file : " << filename << " ..." << endl;
550            multifile = true;
551          }
552#endif
553         if (multifile)
554         {
555            int commSize, commRank;
556            MPI_Comm_size(fileComm, &commSize);
557            MPI_Comm_rank(fileComm, &commRank);
558
559            if (context->intraCommSize_ > 1)
560            {
561              oss << "_" ;
562              int width=0; int n = commSize-1;
563              while (n != 0) { n = n / 10; width++;}
564              if (!min_digits.isEmpty())
565                if (width < min_digits) width = min_digits;
566              oss.width(width);
567              oss.fill('0');
568              oss << right << commRank;
569            }
570         }
571         oss << ".nc";
572
573         bool isCollective = par_access.isEmpty() ||  par_access == par_access_attr::collective;
574
575         if (isOpen) data_out->closeFile();
576
577        data_out = std::shared_ptr<CDataOutput>(new CNc4DataOutput(this, oss.str(), append, useClassicFormat, useCFConvention,
578                                                              fileComm, multifile, isCollective, time_counter_name));
579        isOpen = true;
580
581        data_out->writeFile(CFile::get(this));
582
583        if (!useCFConvention) sortEnabledFieldsForUgrid();
584
585        // Do not recreate the file structure if opening an existing file
586        if (!data_out->IsInAppendMode())
587        {
588          std::vector<CField*>::iterator it, end = this->enabledFields.end();
589          for (it = this->enabledFields.begin(); it != end; it++)
590          {
591            CField* field = *it;
592            this->data_out->writeFieldGrid(field);
593          }
594          this->data_out->writeTimeDimension();
595
596          for (it = this->enabledFields.begin(); it != end; it++)
597          {
598            CField* field = *it;
599            this->data_out->writeFieldTimeAxis(field);
600          }
601         
602          for (it = this->enabledFields.begin(); it != end; it++)
603          {
604            CField* field = *it;
605            this->data_out->writeField(field);
606          }
607
608          vector<CVariable*> listVars = getAllVariables();
609          for (vector<CVariable*>::iterator it = listVars.begin(); it != listVars.end(); it++)
610            this->data_out->writeAttribute(*it);
611
612          this->data_out->definition_end();
613        }
614        else
615        {
616          // check time axis even in append mode
617          std::vector<CField*>::iterator it, end = this->enabledFields.end();
618          for (it = this->enabledFields.begin(); it != end; it++)
619          {
620            CField* field = *it;
621            this->data_out->writeFieldTimeAxis(field);
622          }
623        }
624      }
625   }
626   CATCH_DUMP_ATTR
627
628  /*!
629  \brief Open an existing NetCDF file in read-only mode
630  */
631  void CFile::openInReadMode()
632  TRY
633  {
634    CContext* context = CContext::getCurrent();
635    MPI_Comm readComm = this->fileComm;
636
637    if (!allZoneEmpty)
638    {
639      StdString filename = getFileOutputName();
640      StdOStringStream oss;
641      oss << filename;
642
643      if (!split_freq.isEmpty())
644      {
645        string splitFormat;
646        if (split_freq_format.isEmpty())
647        {
648          CDuration splitFreq = split_freq.getValue();
649          splitFreq.solveTimeStep(*CContext::getCurrent()->getCalendar());
650          if (splitFreq.second != 0) splitFormat = "%y%mo%d%h%mi%s";
651          else if (splitFreq.minute != 0) splitFormat = "%y%mo%d%h%mi";
652          else if (splitFreq.hour != 0) splitFormat = "%y%mo%d%h";
653          else if (splitFreq.day != 0) splitFormat = "%y%mo%d";
654          else if (splitFreq.month != 0) splitFormat = "%y%mo";
655          else splitFormat = "%y";
656        }
657        else splitFormat = split_freq_format;
658        oss << "_" << lastSplit.getStr(splitFormat)
659        << "-" << (lastSplit + split_freq.getValue() - 1 * Second).getStr(splitFormat);
660      }
661
662      bool multifile = true;
663      if (!type.isEmpty())
664      {
665        if (type == type_attr::one_file) multifile = false;
666        else if (type == type_attr::multiple_file) multifile = true;
667      }
668  #ifndef USING_NETCDF_PAR
669      if (!multifile)
670      {
671        info(0) << "!!! Warning -> Using non parallel version of netcdf, switching in multiple_file mode for file : " << filename << " ..." << endl;
672        multifile = true;
673      }
674  #endif
675      if (multifile)
676      {
677        int commSize, commRank;
678        MPI_Comm_size(readComm, &commSize);
679        MPI_Comm_rank(readComm, &commRank);
680
681        if (context->intraCommSize_ > 1)
682        {
683          oss << "_";
684          int width = 0, n = commSize - 1;
685          while (n != 0) { n = n / 10; width++; }
686          if (!min_digits.isEmpty() && width < min_digits)
687            width = min_digits;
688          oss.width(width);
689          oss.fill('0');
690          oss << right << commRank;
691        }
692      }
693      oss << ".nc";
694
695      bool isCollective = par_access.isEmpty() || par_access == par_access_attr::collective;
696      bool readMetaDataPar = true;
697      if (context->getServiceType()==CServicesManager::CLIENT) readMetaDataPar = (read_metadata_par.isEmpty()) ? false : read_metadata_par;
698
699      if (isOpen) data_out->closeFile();
700      bool ugridConvention = !convention.isEmpty() ? (convention == convention_attr::UGRID) : false;
701      if (time_counter_name.isEmpty())
702        data_in = std::shared_ptr<CDataInput>(new CNc4DataInput(oss.str(), readComm, multifile, isCollective, readMetaDataPar, ugridConvention));
703      else
704        data_in = std::shared_ptr<CDataInput>(new CNc4DataInput(oss.str(), readComm, multifile, isCollective, readMetaDataPar, ugridConvention, time_counter_name));
705      isOpen = true;
706    }
707  }
708  CATCH_DUMP_ATTR
709
710   //! Close file
711   void CFile::close(void)
712   TRY
713   {
714     if (!allZoneEmpty)
715       if (isOpen)
716       {
717         if (mode.isEmpty() || mode.getValue() == mode_attr::write)
718          this->data_out->closeFile();
719         else
720          this->data_in->closeFile();
721        isOpen = false;
722       }
723      if (fileComm != MPI_COMM_NULL) MPI_Comm_free(&fileComm);
724   }
725   CATCH_DUMP_ATTR
726
727   //----------------------------------------------------------------
728
729   void CFile::readAttributesOfEnabledFieldsInReadMode()
730   TRY
731   {
732     if (enabledFields.empty()) return;
733
734     // Just check file and try to open it
735     if (time_counter_name.isEmpty()) time_counter_name = "time_counter";
736
737     checkReadFile();
738
739     for (int idx = 0; idx < enabledFields.size(); ++idx)
740     {
741        // First of all, find out which domain and axis associated with this field
742        enabledFields[idx]->solveGridReference();
743
744        // Read attributes of domain and axis from this file
745        this->data_in->readFieldAttributesMetaData(enabledFields[idx]);
746
747        // Now complete domain and axis associated with this field
748        enabledFields[idx]->solveGenerateGrid();
749
750        // Read necessary value from file
751        this->data_in->readFieldAttributesValues(enabledFields[idx]);
752
753        // Fill attributes for base reference
754        enabledFields[idx]->solveGridDomainAxisBaseRef();
755     }
756
757     // Now everything is ok, close it
758     close();
759   }
760   CATCH_DUMP_ATTR
761
762   void CFile::readFieldAttributesMetaData(CField* field)
763   {
764     this->data_in->readFieldAttributesMetaData(field);
765   }
766
767   void CFile::readFieldAttributesValues(CField* field)
768   {
769     this->data_in->readFieldAttributesValues(field);
770   }
771   /*!
772   \brief Parse xml file and write information into file object
773   \param [in] node xmld node corresponding in xml file
774   */
775   void CFile::parse(xml::CXMLNode & node)
776   TRY
777   {
778      SuperClass::parse(node);
779
780      if (node.goToChildElement())
781      {
782        do
783        {
784           if (node.getElementName()=="field" || node.getElementName()=="field_group") this->getVirtualFieldGroup()->parseChild(node);
785           else if (node.getElementName()=="variable" || node.getElementName()=="variable_group") this->getVirtualVariableGroup()->parseChild(node);
786        } while (node.goToNextElement());
787        node.goToParentElement();
788      }
789   }
790   CATCH_DUMP_ATTR
791
792   //----------------------------------------------------------------
793
794   /*!
795   \brief Represent a file in form of string with all its info
796   \return String
797   */
798   StdString CFile::toString(void) const
799   TRY
800   {
801      StdOStringStream oss;
802
803      oss << "<" << CFile::GetName() << " ";
804      if (this->hasId())
805         oss << " id=\"" << this->getId() << "\" ";
806      oss << SuperClassAttribute::toString() << ">" << std::endl;
807      if (this->getVirtualFieldGroup() != NULL)
808         oss << *this->getVirtualFieldGroup() << std::endl;
809      oss << "</" << CFile::GetName() << " >";
810      return (oss.str());
811   }
812   CATCH
813
814   //----------------------------------------------------------------
815
816   /*!
817   \brief Find all inheritace among objects in a file.
818   \param [in] apply (true) write attributes of parent into ones of child if they are empty
819                     (false) write attributes of parent into a new container of child
820   \param [in] parent
821   */
822   void CFile::solveDescInheritance(bool apply, const CAttributeMap * const parent)
823   TRY
824   {
825      SuperClassAttribute::setAttributes(parent,apply);
826      this->getVirtualFieldGroup()->solveDescInheritance(apply, NULL);
827      this->getVirtualVariableGroup()->solveDescInheritance(apply, NULL);
828   }
829   CATCH_DUMP_ATTR
830
831   //----------------------------------------------------------------
832
833   /*!
834   \brief Resolve all reference of active fields.
835      In order to know exactly which data each active field has, a search for all its
836   reference to find its parents or/and its base reference object must be done. Moreover
837   during this search, there are some information that can only be sent to server AFTER
838   all information of active fields are created on server side, e.g: checking mask or index
839   \param [in] sendToServer: Send all info to server (true) or only a part of it (false)
840   */
841   void CFile::solveOnlyRefOfEnabledFields(void)
842   TRY
843   {
844     int size = this->enabledFields.size();
845     for (int i = 0; i < size; ++i)
846     {
847       this->enabledFields[i]->solveOnlyReferenceEnabledField();
848     }
849   }
850   CATCH_DUMP_ATTR
851
852   void CFile::checkGridOfEnabledFields()
853   TRY
854   { 
855     int size = this->enabledFields.size();
856     for (int i = 0; i < size; ++i)
857     {
858       this->enabledFields[i]->checkGridOfEnabledFields();
859     }
860   }
861   CATCH_DUMP_ATTR
862
863   void CFile::sendGridComponentOfEnabledFields()
864   TRY
865   { 
866     int size = this->enabledFields.size();
867     for (int i = 0; i < size; ++i)
868     {
869       this->enabledFields[i]->sendGridComponentOfEnabledFields();
870     }
871   }
872   CATCH_DUMP_ATTR
873
874   /*!
875   \brief Sorting domains with the same name (= describing the same mesh) in the decreasing order of nvertex for UGRID files.
876   This insures that the domain with the highest nvertex is written first and thus all known mesh connectivity is generated at once by this domain.
877   */
878   void CFile::sortEnabledFieldsForUgrid()
879   TRY
880   {
881     int size = this->enabledFields.size();
882     std::vector<int> domainNvertices;
883     std::vector<StdString> domainNames;
884
885     for (int i = 0; i < size; ++i)
886     {
887       std::vector<CDomain*> domain = this->enabledFields[i]->getRelGrid()->getDomains();
888       if (domain.size() != 1)
889       {
890         ERROR("void CFile::sortEnabledFieldsForUgrid()",
891               "A domain, and only one, should be defined for grid "<< this->enabledFields[i]->getRelGrid()->getId() << ".");
892       }
893       StdString domainName = domain[0]->getDomainOutputName();
894       int nvertex;
895       if (domain[0]->nvertex.isEmpty())
896       {
897         ERROR("void CFile::sortEnabledFieldsForUgrid()",
898               "Attributes nvertex must be defined for domain "<< domain[0]->getDomainOutputName() << ".");
899       }
900       else
901         nvertex = domain[0]->nvertex;
902
903       for (int j = 0; j < i; ++j)
904       {
905         if (domainName == domainNames[j] && nvertex > domainNvertices[j])
906         {
907           CField* tmpSwap = this->enabledFields[j];
908           this->enabledFields[j] = this->enabledFields[i];
909           this->enabledFields[i] = tmpSwap;
910           domainNames.push_back(domainNames[j]);
911           domainNames[j] = domainName;
912           domainNvertices.push_back(domainNvertices[j]);
913           domainNvertices[j] = nvertex;
914         }
915         else
916         {
917           domainNames.push_back(domainName);
918           domainNvertices.push_back(nvertex);
919         }
920       }
921       if (i==0)
922       {
923         domainNames.push_back(domainName);
924         domainNvertices.push_back(nvertex);
925       }
926     }
927   }
928   CATCH_DUMP_ATTR
929
930   void CFile::sendGridOfEnabledFields()
931   TRY
932   { 
933     int size = this->enabledFields.size();
934     for (int i = 0; i < size; ++i)
935     {
936       this->enabledFields[i]->sendGridOfEnabledFields();
937     }
938   }
939   CATCH_DUMP_ATTR
940
941   void CFile::generateNewTransformationGridDest()
942   TRY
943   {
944     int size = this->enabledFields.size();
945     for (int i = 0; i < size; ++i)
946     {
947       this->enabledFields[i]->generateNewTransformationGridDest();
948     }
949   }
950   CATCH_DUMP_ATTR
951
952   /*!
953   \brief Resolve all reference of active fields.
954      In order to know exactly which data each active field has, a search for all its
955   reference to find its parents or/and its base reference object must be done. Moreover
956   during this search, there are some information that can only be sent to server AFTER
957   all information of active fields are created on server side, e.g: checking mask or index
958   \param [in] sendToServer: Send all info to server (true) or only a part of it (false)
959   */
960   void CFile::solveAllRefOfEnabledFieldsAndTransform(void)
961   TRY
962   {
963     int size = this->enabledFields.size();
964     for (int i = 0; i < size; ++i)
965     {       
966      this->enabledFields[i]->solveAllEnabledFieldsAndTransform();
967     }
968   }
969   CATCH_DUMP_ATTR
970
971   /*!
972    * Constructs the filter graph for each active field.
973    *
974    * \param gc the garbage collector to use when building the filter graph
975    */
976   void CFile::buildFilterGraphOfEnabledFields(CGarbageCollector& gc)
977   TRY
978   {
979     int size = this->enabledFields.size();
980     for (int i = 0; i < size; ++i)
981     {
982       this->enabledFields[i]->buildFilterGraph(gc, true);
983     }
984   }
985   CATCH_DUMP_ATTR
986
987   /*!
988    * Post-process the filter graph for each active field.
989    */
990   void CFile::postProcessFilterGraph()
991   TRY
992   {
993     int size = this->enabledFields.size();
994     for (int i = 0; i < size; ++i)
995     {
996       this->enabledFields[i]->checkIfMustAutoTrigger();
997     }
998   }
999   CATCH_DUMP_ATTR
1000
1001   /*!
1002     Prefetching the data for enabled fields read from file.
1003   */
1004   void CFile::prefetchEnabledReadModeFields(void)
1005   TRY
1006   {
1007     if (mode.isEmpty() || mode.getValue() != mode_attr::read)
1008       return;
1009
1010     int size = this->enabledFields.size();
1011     for (int i = 0; i < size; ++i)
1012       this->enabledFields[i]->sendReadDataRequest(CContext::getCurrent()->getCalendar()->getCurrentDate(), getContextClient());
1013   }
1014   CATCH_DUMP_ATTR
1015
1016   /*!
1017     Do all pre timestep operations for enabled fields in read mode:
1018      - Check that the data excepted from server has been received
1019      - Check if some filters must auto-trigger
1020   */
1021   void CFile::doPreTimestepOperationsForEnabledReadModeFields(void)
1022   TRY
1023   {
1024     if (mode.isEmpty() || mode.getValue() != mode_attr::read)
1025       return;
1026
1027     int size = this->enabledFields.size();
1028     for (int i = 0; i < size; ++i)
1029     {
1030       this->enabledFields[i]->checkForLateDataFromServer();
1031       this->enabledFields[i]->autoTriggerIfNeeded();
1032     }
1033   }
1034   CATCH_DUMP_ATTR
1035
1036   /*!
1037     Do all post timestep operations for enabled fields in read mode:
1038      - Prefetch the data read from file when needed
1039   */
1040   void CFile::doPostTimestepOperationsForEnabledReadModeFields(void)
1041   TRY
1042   {
1043     if (mode.isEmpty() || mode.getValue() != mode_attr::read)
1044       return;
1045
1046     int size = this->enabledFields.size();
1047     for (int i = 0; i < size; ++i)
1048     {
1049       this->enabledFields[i]->sendReadDataRequestIfNeeded();
1050     }
1051   }
1052   CATCH_DUMP_ATTR
1053
1054   void CFile::solveFieldRefInheritance(bool apply)
1055   TRY
1056   {
1057      // Rsolution des hritages par rfrence de chacun des champs contenus dans le fichier.
1058      std::vector<CField*> allF = this->getAllFields();
1059      for (unsigned int i = 0; i < allF.size(); i++)
1060         allF[i]->solveRefInheritance(apply);
1061   }
1062   CATCH_DUMP_ATTR
1063
1064   //----------------------------------------------------------------
1065
1066   /*!
1067   \brief Add a field into file.
1068      A field is added into file and it will be written out if the file is enabled and
1069   level of this field is smaller than level_output. A new field won't be created if one
1070   with id has already existed
1071   \param [in] id String identity of new field
1072   \return Pointer to added (or already existed) field
1073   */
1074   CField* CFile::addField(const string& id)
1075   TRY
1076   {
1077     return vFieldGroup->createChild(id);
1078   }
1079   CATCH_DUMP_ATTR
1080
1081   /*!
1082   \brief Add a field group into file.
1083      A field group is added into file and it will play a role as parents for fields.
1084   A new field group won't be created if one with id has already existed
1085   \param [in] id String identity of new field group
1086   \return Pointer to added (or already existed) field group
1087   */
1088   CFieldGroup* CFile::addFieldGroup(const string& id)
1089   TRY
1090   {
1091     return vFieldGroup->createChildGroup(id);
1092   }
1093   CATCH_DUMP_ATTR
1094
1095   /*!
1096   \brief Add a variable into file.
1097      A variable is added into file and if one with id has already existed, pointer to
1098   it will be returned.
1099      Variable as long as attributes are information container of file.
1100   However, whereas attributes are "fixed" information, variables provides a more flexible way to user
1101   to fill in (extra) information for a file.
1102   \param [in] id String identity of new variable
1103   \return Pointer to added (or already existed) variable
1104   */
1105   CVariable* CFile::addVariable(const string& id)
1106   TRY
1107   {
1108     return vVariableGroup->createChild(id);
1109   }
1110   CATCH_DUMP_ATTR
1111
1112   /*!
1113   \brief Add a variable group into file.
1114      A variable group is added into file and it will play a role as parents for variables.
1115   A new variable group won't be created if one with id has already existed
1116   \param [in] id String identity of new variable group
1117   \return Pointer to added (or already existed) variable group
1118   */
1119   CVariableGroup* CFile::addVariableGroup(const string& id)
1120   TRY
1121   {
1122     return vVariableGroup->createChildGroup(id);
1123   }
1124   CATCH_DUMP_ATTR
1125
1126   void CFile::setContextClient(CContextClient* newContextClient)
1127   TRY
1128   {
1129     client = newContextClient;
1130     size_t size = this->enabledFields.size();
1131     for (size_t i = 0; i < size; ++i)
1132     {
1133       this->enabledFields[i]->setContextClient(newContextClient);
1134     }
1135   }
1136   CATCH_DUMP_ATTR
1137
1138   CContextClient* CFile::getContextClient()
1139   TRY
1140   {
1141     return client;
1142   }
1143   CATCH_DUMP_ATTR
1144
1145   void CFile::setReadContextClient(CContextClient* readContextclient)
1146   TRY
1147   {
1148     read_client = readContextclient;
1149   }
1150   CATCH_DUMP_ATTR
1151
1152   CContextClient* CFile::getReadContextClient()
1153   TRY
1154   {
1155     return read_client;
1156   }
1157   CATCH_DUMP_ATTR
1158
1159   /*!
1160   \brief Send a message to create a field on server side
1161   \param[in] id String identity of field that will be created on server
1162   */
1163   void CFile::sendAddField(const string& id, CContextClient* client)
1164   TRY
1165   {
1166      sendAddItem(id, EVENT_ID_ADD_FIELD, client);
1167   }
1168   CATCH_DUMP_ATTR
1169
1170   /*!
1171   \brief Send a message to create a field group on server side
1172   \param[in] id String identity of field group that will be created on server
1173   */
1174   void CFile::sendAddFieldGroup(const string& id, CContextClient* client)
1175   TRY
1176   {
1177      sendAddItem(id, (int)EVENT_ID_ADD_FIELD_GROUP, client);
1178   }
1179   CATCH_DUMP_ATTR
1180
1181   /*!
1182   \brief Receive a message annoucing the creation of a field on server side
1183   \param[in] event Received event
1184   */
1185   void CFile::recvAddField(CEventServer& event)
1186   TRY
1187   {
1188
1189      CBufferIn* buffer = event.subEvents.begin()->buffer;
1190      string id;
1191      *buffer>>id;
1192      get(id)->recvAddField(*buffer);
1193   }
1194   CATCH
1195
1196   /*!
1197   \brief Receive a message annoucing the creation of a field on server side
1198   \param[in] buffer Buffer containing message
1199   */
1200   void CFile::recvAddField(CBufferIn& buffer)
1201   TRY
1202   {
1203      string id;
1204      buffer>>id;
1205      addField(id);
1206   }
1207   CATCH_DUMP_ATTR
1208
1209   /*!
1210   \brief Receive a message annoucing the creation of a field group on server side
1211   \param[in] event Received event
1212   */
1213   void CFile::recvAddFieldGroup(CEventServer& event)
1214   TRY
1215   {
1216
1217      CBufferIn* buffer = event.subEvents.begin()->buffer;
1218      string id;
1219      *buffer>>id;
1220      get(id)->recvAddFieldGroup(*buffer);
1221   }
1222   CATCH
1223
1224   /*!
1225   \brief Receive a message annoucing the creation of a field group on server side
1226   \param[in] buffer Buffer containing message
1227   */
1228   void CFile::recvAddFieldGroup(CBufferIn& buffer)
1229   TRY
1230   {
1231      string id;
1232      buffer>>id;
1233      addFieldGroup(id);
1234   }
1235   CATCH_DUMP_ATTR
1236
1237   /*!
1238   \brief Send messages to duplicate all variables on server side
1239      Because each variable has also its attributes. So first thing to do is replicate
1240   all these attributes on server side. Because variable can have a value, the second thing
1241   is to duplicate this value on server, too.
1242   */
1243   void CFile::sendAddAllVariables(CContextClient* client)
1244   TRY
1245   {
1246     std::vector<CVariable*> allVar = getAllVariables();
1247     std::vector<CVariable*>::const_iterator it = allVar.begin();
1248     std::vector<CVariable*>::const_iterator itE = allVar.end();
1249
1250     for (; it != itE; ++it)
1251     {
1252       this->sendAddVariable((*it)->getId(), client);
1253       (*it)->sendAllAttributesToServer(client);
1254       (*it)->sendValue(client);
1255     }
1256   }
1257   CATCH_DUMP_ATTR
1258
1259   /*!
1260   \brief Send a message to create a variable group on server side
1261   \param[in] id String identity of variable group that will be created on server
1262   \param [in] client client to which we will send this adding action
1263   */
1264   void CFile::sendAddVariableGroup(const string& id, CContextClient* client)
1265   TRY
1266   {
1267      sendAddItem(id, (int)EVENT_ID_ADD_VARIABLE_GROUP, client);
1268   }
1269   CATCH_DUMP_ATTR
1270
1271   /*
1272     Send message to add a variable into a file within a certain client
1273     \param [in] id String identity of a variable
1274     \param [in] client client to which we will send this adding action
1275   */
1276   void CFile::sendAddVariable(const string& id, CContextClient* client)
1277   TRY
1278   {
1279      sendAddItem(id, (int)EVENT_ID_ADD_VARIABLE, client);
1280   }
1281   CATCH_DUMP_ATTR
1282
1283   /*!
1284   \brief Receive a message annoucing the creation of a variable on server side
1285   \param[in] event Received event
1286   */
1287   void CFile::recvAddVariable(CEventServer& event)
1288   TRY
1289   {
1290      CBufferIn* buffer = event.subEvents.begin()->buffer;
1291      string id;
1292      *buffer>>id;
1293      get(id)->recvAddVariable(*buffer);
1294   }
1295   CATCH
1296
1297   /*!
1298   \brief Receive a message annoucing the creation of a variable on server side
1299   \param[in] buffer Buffer containing message
1300   */
1301   void CFile::recvAddVariable(CBufferIn& buffer)
1302   TRY
1303   {
1304      string id;
1305      buffer>>id;
1306      addVariable(id);
1307   }
1308   CATCH_DUMP_ATTR
1309
1310   /*!
1311   \brief Receive a message annoucing the creation of a variable group on server side
1312   \param[in] event Received event
1313   */
1314   void CFile::recvAddVariableGroup(CEventServer& event)
1315   TRY
1316   {
1317
1318      CBufferIn* buffer = event.subEvents.begin()->buffer;
1319      string id;
1320      *buffer>>id;
1321      get(id)->recvAddVariableGroup(*buffer);
1322   }
1323   CATCH
1324
1325   /*!
1326   \brief Receive a message annoucing the creation of a variable group on server side
1327   \param[in] buffer Buffer containing message
1328   */
1329   void CFile::recvAddVariableGroup(CBufferIn& buffer)
1330   TRY
1331   {
1332      string id;
1333      buffer>>id;
1334      addVariableGroup(id);
1335   }
1336   CATCH_DUMP_ATTR
1337
1338   /*!
1339     \brief Sending all active (enabled) fields from client to server.
1340   Each field is identified uniquely by its string identity. Not only should we
1341   send the id to server but also we need to send ids of reference domain and reference axis.
1342   With these two id, it's easier to make reference to grid where all data should be written.
1343   Remark: This function must be called AFTER all active (enabled) files have been created on the server side
1344   */
1345   void CFile::sendEnabledFields(CContextClient* client)
1346   TRY
1347   {
1348     size_t size = this->enabledFields.size();
1349     for (size_t i = 0; i < size; ++i)
1350     {
1351       CField* field = this->enabledFields[i];
1352       this->sendAddField(field->getId(), client);
1353       field->checkTimeAttributes();
1354       field->sendAllAttributesToServer(client);
1355       field->sendAddAllVariables(client);
1356     }
1357   }
1358   CATCH_DUMP_ATTR
1359
1360
1361 
1362   /*!
1363    * Send file attribute, related variable and chield field tree to a given file server.
1364    * \param[in] client : the context client where to send file
1365    */
1366   void CFile::sendFileToFileServer(CContextClient* client)
1367   TRY
1368   {
1369     if (sendFileToFileServer_done_.count(client)!=0) return ;
1370     else sendFileToFileServer_done_.insert(client) ;
1371     
1372     StdString fileDefRoot("file_definition");
1373     CFileGroup* cfgrpPtr = CFileGroup::get(fileDefRoot);
1374     cfgrpPtr->sendCreateChild(this->getId(), client);
1375     this->sendAllAttributesToServer(client);
1376     this->sendAddAllVariables(client);
1377     for(auto field : enabledFields) this->sendAddField(field->getId(), client);
1378   }
1379   CATCH_DUMP_ATTR
1380   /*!
1381   \brief Dispatch event received from client
1382      Whenever a message is received in buffer of server, it will be processed depending on
1383   its event type. A new event type should be added in the switch list to make sure
1384   it processed on server side.
1385   \param [in] event: Received message
1386   */
1387   bool CFile::dispatchEvent(CEventServer& event)
1388   TRY
1389   {
1390      if (SuperClass::dispatchEvent(event)) return true;
1391      else
1392      {
1393        switch(event.type)
1394        {
1395           case EVENT_ID_ADD_FIELD :
1396             recvAddField(event);
1397             return true;
1398             break;
1399
1400           case EVENT_ID_ADD_FIELD_GROUP :
1401             recvAddFieldGroup(event);
1402             return true;
1403             break;
1404
1405            case EVENT_ID_ADD_VARIABLE :
1406             recvAddVariable(event);
1407             return true;
1408             break;
1409
1410           case EVENT_ID_ADD_VARIABLE_GROUP :
1411             recvAddVariableGroup(event);
1412             return true;
1413             break;
1414           default :
1415              ERROR("bool CFile::dispatchEvent(CEventServer& event)", << "Unknown Event");
1416           return false;
1417        }
1418      }
1419   }
1420   CATCH
1421
1422   ///--------------------------------------------------------------
1423   /*!
1424   */
1425   StdString CFile::dumpClassAttributes(void)
1426   {
1427     StdString str;
1428     CContext* context = CContext::getCurrent();
1429     str.append("context=\"");
1430     str.append(context->getId());
1431     str.append("\"");
1432     str.append(" enabled fields=\"");
1433     int size = this->enabledFields.size();
1434     for (int i = 0; i < size; ++i)
1435     {
1436       str.append(this->enabledFields[i]->getId());
1437       str.append(" ");
1438     }
1439     str.append("\"");
1440     return str;
1441   }
1442
1443   ///---------------------------------------------------------------
1444
1445} // namespace xios
Note: See TracBrowser for help on using the repository browser.