Changeset 10898


Ignore:
Timestamp:
06/01/11 11:01:11 (13 years ago)
Author:
lyard
Message:
removed serviceList object, replaced by inheritance from DimServiceInfoList
Location:
trunk/FACT++/src
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/FACT++/src/Fits.cc

    r10814 r10898  
    8282//! @param fitsCounter a pointer to the integer keeping track of the opened FITS files
    8383//! @param out a pointer to the MessageImp that should be used to log errors
    84 //! @param runNumber the runNumber for which this file is opened. -1 means nightly file.
     84//! @param runNumber the runNumber for which this file is opened. 0 means nightly file.
    8585//
    8686void Fits::Open(const string& fileName, const string& tableName, FITS* file, int* fitsCounter, MessageImp* out, int runNumber)
  • trunk/FACT++/src/Fits.h

    r10814 r10898  
    6161public:
    6262        ///current run number being logged
    63         int fRunNumber;
     63                uint32_t fRunNumber;
    6464               
    6565                Fits() :  fFile(NULL),
     
    7676                                         fNumOpenFitsFiles(NULL),
    7777                                         fMess(NULL),
    78                                          fRunNumber(-1)
     78                                         fRunNumber(0)
    7979                 {}
    8080               
  • trunk/FACT++/src/dataLogger.cc

    r10897 r10898  
    5353#include "Description.h"
    5454
    55 #include "DimServiceInfoList.h"
    56 
     55//#include "DimServiceInfoList.h"
     56#include "DimNetwork.h"
    5757//for getting stat of opened files
    5858#include <unistd.h>
     
    109109    string reportName;
    110110    ///the actual run number
    111     int runNumber;
     111    uint32_t runNumber;
    112112    ///the time at which the run number was received
    113113    Time time;
     
    124124        runFitsFile = NULL;
    125125#endif
    126         runNumber = -1;
     126        runNumber = 0;
    127127        //give it a meaningless, 0 time to distinguish with actual, valid times
    128128        time = Time(0,0);
     
    221221    ///the actual dimInfo pointer
    222222    DimStampedInfo* dimInfo;
     223    ///the server
     224    string server;
     225    ///the service
     226    string service;
    223227    ///the converter for outputting the data according to the format
    224228    Converter* fConv;
     
    226230    int* numCopies;
    227231    ///the current run number used by this subscription
    228     int runNumber;
     232    uint32_t runNumber;
    229233    ///copy operator
    230234    void operator = (const SubscriptionType& other)
     
    235239#endif
    236240        dimInfo = other.dimInfo;
     241        server = other.server;
     242        service = other.service;
    237243        numCopies = other.numCopies;
    238244        fConv = other.fConv;
     
    248254#endif
    249255        dimInfo = other.dimInfo;
     256        server = other.server;
     257        service = other.service;
    250258        numCopies = other.numCopies;
    251259        fConv = other.fConv;
     
    258266        dimInfo = info;
    259267        fConv = NULL;
    260         runNumber = -1;
     268        runNumber = 0;
    261269        numCopies = new int(1);
    262270    }
     
    266274        dimInfo = NULL;
    267275        fConv = NULL;
    268         runNumber = -1;
     276        runNumber = 0;
    269277        numCopies = new int(1);
    270278    }
     
    295303};
    296304
    297 class DataLogger : public StateMachineDim, DimInfoHandler
     305class DataLogger : public StateMachineDim, DimServiceInfoList
    298306{
    299307public:
     
    334342    double fMjD;
    335343    ///for obtaining the name of the existing services
    336     ServiceList fServiceList;
     344//    ServiceList fServiceList;
    337345    typedef map<const string, map<string, SubscriptionType> > SubscriptionsListType;
    338346    ///All the services to which we have subscribed to, sorted by server name.
     
    382390    ///DEPREC - configuration of the run number
    383391    int ConfigureRunNumber(const Event& evt);
    384     ///logging method for the messages
    385 //    int LogMessagePlease(const Event& evt);
    386392    ///print the current state of the dataLogger
    387393    int PrintStatePlease(const Event& evt);
    388394    ///checks whether or not the current info being treated is a run number
    389     void CheckForRunNumber(DimInfo* I); 
     395    void CheckForRunNumber(DimInfo* I);
    390396    /// start transition
    391397    int StartPlease();
     
    462468    int OpenRunFile(RunNumberType& run);
    463469    ///add a new run number
    464     void AddNewRunNumber(int newRun, Time time);
     470    void AddNewRunNumber(int64_t newRun, Time time);
    465471    ///removes the oldest run number, and close the relevant files.
    466472    void RemoveOldestRunNumber();
    467     ///checks with fServiceList whether or not the services got updated
    468     bool CheckForServicesUpdate();
    469473    ///retrieves the size of a file
    470474    off_t GetFileSize(string&);
     
    497501    ///Checks if the input osftream is in error state, and if so close it.
    498502    void CheckForOfstreamError(ofstream& out);
     503    /***************************************************
     504    * INHERITED FROM DIMSERVICEINFOLIST
     505    ***************************************************/
     506    ///Add a new service subscription
     507    void AddService(const string&, const string&, const string&, bool);
     508    ///Remove a given service subscription
     509    void RemoveService(const string&, const string&, bool);
     510    ///Remove all the services associated with a given server
     511    void RemoveAllServices(const string&);
    499512}; //DataLogger
    500513
     514// --------------------------------------------------------------------------
     515//
     516//! Add a new service subscription
     517//! @param server the server for which the subscription should be created
     518//! @param service the service for which the subscription should be created
     519//! @param isCmd whether this is a Dim Command or not. Commands are not logged
     520//TODO maybe commands should indeed be logged ?
     521//
     522void DataLogger::AddService(const string& server, const string& service, const string&, bool isCmd)
     523{
     524    //check the given subscription against black and white lists
     525    if (!ShouldSubscribe(server, service))
     526        return;
     527    //dataLogger does not subscribe to commands
     528    if (isCmd)
     529        return;
     530    //do we already have an entry for that server ?
     531    SubscriptionsListType::iterator cSubs = fServiceSubscriptions.find(server);
     532    if (cSubs != fServiceSubscriptions.end())
     533    {//server already here. check its services
     534        if (cSubs->second.find(service) == cSubs->second.end())
     535        {//service not found. Add it
     536            cSubs->second[service].dimInfo = SubscribeToPlease(server, service);
     537            cSubs->second[service].server = server;
     538            cSubs->second[service].service = service;
     539        }
     540        else
     541        {
     542            MessageImp::Error("Service " + server + "/" + service + " is already in the dataLogger's list. ignoring its update.");
     543            return;
     544        }
     545    }
     546    else
     547    {//server not yet in our list. add it
     548        fServiceSubscriptions[server] = map<string, SubscriptionType>();
     549        map<string, SubscriptionType>& liste = fServiceSubscriptions[server];
     550        liste[service].dimInfo = SubscribeToPlease(server, service);
     551        liste[service].server = server;
     552        liste[service].service = service;
     553    }
     554    if (fDebugIsOn)
     555    {
     556        Debug("Added subscription to " + server + "/" + service);
     557    }
     558}
     559// --------------------------------------------------------------------------
     560//
     561//! Remove a given service subscription
     562//! @param server the server for which the subscription should be removed
     563//! @param service the service that should be removed
     564//! @param isCmd whether or not this is a command
     565//
     566void DataLogger::RemoveService(const string& server, const string& service, bool isCmd)
     567{
     568    if (isCmd)
     569        return;
     570
     571    fServiceSubscriptions[server].erase(service);
     572    if (fDebugIsOn)
     573    {
     574        Debug("Removed subscription to " + server + "/" + service);
     575    }
     576}
     577// --------------------------------------------------------------------------
     578//
     579//! Remove all the services associated with a given server
     580//! @param server the server for which all the services should be removed
     581//
     582void DataLogger::RemoveAllServices(const string& server)
     583{
     584    fServiceSubscriptions[server].clear();
     585    if (fDebugIsOn)
     586    {
     587        Debug("Removed all subscriptions to " + server + "/");
     588    }
     589}
    501590// --------------------------------------------------------------------------
    502591//
     
    627716    fileNameWithPath = fRunFilePath + '/' + fileName;
    628717}
     718
    629719// --------------------------------------------------------------------------
    630720//
     
    637727{
    638728    ostringstream groupName;
    639     if (runNumber != -1)
     729    if (runNumber != 0)
    640730    {
    641731        groupName << fRunFilePath << '/' << runNumber << ".fits";
     
    750840        return st.st_size;
    751841
    752     if (errno != 0)
     842    if (errno != 0 && errno != 2)//ignoring error #2: no such file or directory is not an error for new files
    753843    {
    754844        ostringstream str;
     
    10021092             "|Path[string]:Absolute or relative path name where the run files should be stored.");
    10031093
    1004     AddEvent(fConfigRunNumber, "I", kSM_Ready, kSM_NightlyOpen, kSM_WaitingRun, kSM_BadRunConfig, kSM_Logging)
     1094    AddEvent(fConfigRunNumber, "X", kSM_Ready, kSM_NightlyOpen, kSM_WaitingRun, kSM_BadRunConfig, kSM_Logging)
    10051095            (boost::bind(&DataLogger::ConfigureRunNumber, this, _1))
    10061096            ("Configure the run number. Cannot be done in logging state");
    10071097
    1008      //Provide a logging command
    1009      //I get the feeling that I should be going through the EventImp
    1010      //instead of DimCommand directly, mainly because the commandHandler
    1011      //is already done in StateMachineImp.cc
    1012      //Thus I'll simply add a configuration, which I will treat as the logging command
    1013 //     AddEvent(fConfigLog, "C", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_BadRunConfig)
    1014  //            (boost::bind(&DataLogger::LogMessagePlease, this, _1))
    1015  //            ("Log a single message to the log-files."
    1016 //              "|Message[string]:Message to be logged.");
    1017        
    10181098     //Provide a print command
    10191099     ostringstream str;
     
    10231103             (boost::bind(&DataLogger::PrintStatePlease, this, _1))
    10241104             ("Print information about the internal status of the data logger.");
    1025 
    1026      fServiceList.SetHandler(this);
    1027      CheckForServicesUpdate();
    10281105
    10291106     //start the monitoring service
     
    10871164     }
    10881165}
    1089 // --------------------------------------------------------------------------
    1090 //
    1091 //! Checks for changes in the existing services.
    1092 //! Any new service will be added to the service list, while the ones which disappeared are removed.
    1093 //
    1094 //FIXME The service must be udpated so that I get the first notification. This should not be
    1095 bool DataLogger::CheckForServicesUpdate()
    1096 {
    1097     bool serviceUpdated = false;
    1098     //get the current server list
    1099     const vector<string> serverList = fServiceList.GetServerList();
    1100     //first let's remove the servers that may have disapeared
    1101     //can't treat the erase on maps the same way as for vectors. Do it the safe way instead
    1102     vector<string> toBeDeleted;
    1103     for (SubscriptionsListType::iterator cListe = fServiceSubscriptions.begin(); cListe != fServiceSubscriptions.end(); cListe++)
    1104     {
    1105         vector<string>::const_iterator givenServers;
    1106         for (givenServers=serverList.begin(); givenServers!= serverList.end(); givenServers++)
    1107             if (cListe->first == *givenServers)
    1108                 break;
    1109         if (givenServers == serverList.end())//server vanished. Remove it
    1110         {   
    1111             toBeDeleted.push_back(cListe->first);
    1112             serviceUpdated = true;
    1113         }
    1114     }
    1115     for (vector<string>::const_iterator it = toBeDeleted.begin(); it != toBeDeleted.end(); it++)
    1116         fServiceSubscriptions.erase(*it);
    1117     //now crawl through the list of servers, and see if there was some updates
    1118     for (vector<string>::const_iterator i=serverList.begin(); i!=serverList.end();i++)
    1119     {
    1120         //skip the two de-fact excluded services
    1121         //Dim crashes if the publisher subscribes to its own service. This sounds weird, I agree.
    1122         //I agree: hard coded values are bad, but in this case these two entries should always be here otherwise Dim crashes.
    1123         if ((i->find("DIS_DNS") != string::npos) ||
    1124             (i->find("DATA_LOGGER") != string::npos))
    1125             continue;
    1126         //find the current server in our subscription list   
    1127         SubscriptionsListType::iterator cSubs = fServiceSubscriptions.find(*i);
    1128         //get the service list of the current server
    1129         vector<string> cServicesList = fServiceList.GetServiceList(*i);
    1130         if (cSubs != fServiceSubscriptions.end())//if the current server already is in our subscriptions
    1131         {                                         //then check and update our list of subscriptions
    1132             //first, remove the services that may have disapeared.
    1133             map<string, SubscriptionType>::iterator serverSubs;
    1134             vector<string>::const_iterator givenSubs;
    1135             toBeDeleted.clear();
    1136             for (serverSubs=cSubs->second.begin(); serverSubs != cSubs->second.end(); serverSubs++)
    1137             {
    1138                 for (givenSubs = cServicesList.begin(); givenSubs != cServicesList.end(); givenSubs++)
    1139                     if (serverSubs->first == *givenSubs)
    1140                         break;
    1141                 if (givenSubs == cServicesList.end())
    1142                 {
    1143                     toBeDeleted.push_back(serverSubs->first);
    1144                     serviceUpdated = true;
    1145                 }   
    1146             }
    1147             for (vector<string>::const_iterator it = toBeDeleted.begin(); it != toBeDeleted.end(); it++)
    1148                 cSubs->second.erase(*it);
    1149             //now check for new services
    1150             for (givenSubs = cServicesList.begin(); givenSubs != cServicesList.end(); givenSubs++)
    1151             {
    1152                 if (!ShouldSubscribe(*i, *givenSubs))
    1153                     continue;
    1154                 if (cSubs->second.find(*givenSubs) == cSubs->second.end())
    1155                 {//service not found. Add it
    1156                     cSubs->second[*givenSubs].dimInfo = SubscribeToPlease(*i, *givenSubs);
    1157                     serviceUpdated = true;
    1158                 }   
    1159             }
    1160         }
    1161         else //server not found in our list. Create its entry
    1162         {
    1163             fServiceSubscriptions[*i] = map<string, SubscriptionType>();
    1164             map<string, SubscriptionType>& liste = fServiceSubscriptions[*i];
    1165             for (vector<string>::const_iterator j = cServicesList.begin(); j!= cServicesList.end(); j++)
    1166             {
    1167                 if (!ShouldSubscribe(*i, *j))
    1168                     continue;
    1169                 liste[*j].dimInfo = SubscribeToPlease(*i, *j);
    1170                 serviceUpdated = true;
    1171             }
    1172         }   
    1173     }
    1174     return serviceUpdated;
    1175 }
     1166
    11761167// --------------------------------------------------------------------------
    11771168//
     
    12201211
    12211212    DimInfo* I = getInfo();
    1222     SubscriptionsListType::iterator x;
    1223     map<string, SubscriptionType>::iterator y;
     1213
    12241214    if (I==NULL)
    1225     {
    1226         if (CheckForServicesUpdate())
    1227         {
    1228             //services were updated. Notify
    1229             fNumSubAndFitsData.numSubscriptions = 0;
    1230             for (x=fServiceSubscriptions.begin(); x != fServiceSubscriptions.end(); x++)
    1231                 fNumSubAndFitsData.numSubscriptions += x->second.size();
    1232             if (fNumSubAndFitsIsOn)
    1233             {
    1234                 if (fDebugIsOn)
    1235                 {
    1236                     ostringstream str;
    1237                     str << "Updating number of subscriptions service: Num Subs=" << fNumSubAndFitsData.numSubscriptions << " Num open FITS=" << fNumSubAndFitsData.numOpenFits;
    1238                     Debug(str);
    1239                 }
    1240                 fNumSubAndFits->updateService();
    1241             }
    1242         }
    12431215        return;
    1244     }
    12451216    //check if the service pointer corresponds to something that we subscribed to
    12461217    //this is a fix for a bug that provides bad Infos when a server starts
    12471218    bool found = false;
    1248     for (x=fServiceSubscriptions.begin(); x != fServiceSubscriptions.end(); x++)
     1219    SubscriptionsListType::iterator x;
     1220    map<string, SubscriptionType>::iterator y;
     1221     for (x=fServiceSubscriptions.begin(); x != fServiceSubscriptions.end(); x++)
    12491222    {//find current service is subscriptions
    12501223        for (y=x->second.begin(); y!=x->second.end();y++)
     
    12581231    }
    12591232    if (!found)
     1233    {
     1234        DimServiceInfoList::infoHandler();
    12601235        return;
     1236    }
    12611237    if (I->getSize() <= 0)
    12621238        return;
     
    13361312//! @param time the time at which the new run number was issued
    13371313//
    1338 void DataLogger::AddNewRunNumber(int newRun, Time time)
    1339 {
     1314void DataLogger::AddNewRunNumber(int64_t newRun, Time time)
     1315{
     1316
     1317    if (newRun > 0xffffffff)
     1318    {
     1319        Error("New run number too large, out of range. Ignoring.");
     1320        return;
     1321    }
    13401322    if (fDebugIsOn)
    13411323    {
     
    13461328    //Add new run number to run number list
    13471329    fRunNumber.push_back(RunNumberType());
    1348     fRunNumber.back().runNumber = newRun;
     1330    fRunNumber.back().runNumber = uint32_t(newRun);
    13491331    fRunNumber.back().time = time;
     1332
     1333    ostringstream str;
     1334    str << "The new run number is: " << fRunNumber.back().runNumber;
     1335    Message(str);
     1336
    13501337    if (GetCurrentState() != kSM_Logging)
    13511338        return;
     
    13641351    if (strstr(I->getName(), fRunNumberInfo) != NULL)
    13651352    {//assumes that the run number is an integer
    1366         AddNewRunNumber(I->getInt(), Time(I->getTimestamp(), I->getTimestampMillisecs()*1000));
    1367         ostringstream str;
    1368         str << "New run number is " << fRunNumber.back().runNumber;
    1369         Message(str);
     1353        AddNewRunNumber(I->getLonglong(), Time(I->getTimestamp(), I->getTimestampMillisecs()*1000));
    13701354    }
    13711355}
     
    15231507}
    15241508
    1525 // --------------------------------------------------------------------------
    1526 //
    1527 //! write messages to logs.
    1528 /*
    1529 //! @param evt
    1530 //!        the current event to log
    1531 //! @returns
    1532 //!        the new state. Currently, always the current state
    1533 //!
    1534 //! @deprecated
    1535 //!    I guess that this function should not be any longer
    1536 //
    1537 //Otherwise re-write it properly with the MessageImp class
    1538 int DataLogger::LogMessagePlease(const Event& evt)
    1539 {
    1540     if (!fNightlyLogFile.is_open())
    1541         return GetCurrentState();
    1542     Warn("LogMessagePlease has not been checked nor updated since a long while. Undefined behavior to be expected");
    1543     ostringstream header;
    1544     const Time& cTime = evt.GetTime();
    1545     header << evt.GetName() << " " << cTime.Y() << " " << cTime.M() << " " << cTime.D() << " ";
    1546     header << cTime.h() << " " << cTime.m() << " " << cTime.s() << " ";
    1547     header << cTime.ms() << " ";
    1548        
    1549     const Converter conv(Out(), evt.GetFormat());
    1550     if (!conv)
    1551     {
    1552         Error("Couldn't properly parse the format... ignored.");
    1553         return GetCurrentState();
    1554     }
    1555 
    1556     string text;
    1557     try
    1558     {
    1559         text = conv.GetString(evt.GetData(), evt.GetSize());
    1560     }
    1561     catch (const runtime_error &e)
    1562     {
    1563         Out() << kRed << e.what() << endl;
    1564         Error("Couldn't properly parse the data... ignored.");
    1565         return GetCurrentState();
    1566     }
    1567 
    1568     if (text.empty())
    1569         return GetCurrentState();
    1570 
    1571     //replace bizarre characters by white space
    1572     replace(text.begin(), text.end(), '\n', '\\');
    1573     replace_if(text.begin(), text.end(), ptr_fun<int, int>(&iscntrl), ' ');
    1574     if (fDebugIsOn)
    1575     {
    1576         ostringstream str;
    1577         str << "Logging: \"" << header << text << "\"";
    1578         Debug(str.str());   
    1579     }
    1580    
    1581     if (fNightlyLogFile.is_open())
    1582     {
    1583         fNightlyLogFile << header;
    1584         if (!fNightlyLogFile.good())
    1585         {
    1586             Error("An error occured while writing to the run log file. Closing it.");
    1587             if (fNightlyLogFile.is_open())
    1588                 fNightlyLogFile.close();   
    1589         }
    1590     }
    1591     if (fRunLogFile.is_open())
    1592     {
    1593         fRunLogFile << header;
    1594         if (!fRunLogFile.good())
    1595         {
    1596             Error("An error occured while writing to the run log file. Closing it.");
    1597             if (fRunLogFile.is_open())
    1598                 fRunLogFile.close();   
    1599         }
    1600     }
    1601     if (fNightlyLogFile.is_open())
    1602     {
    1603         fNightlyLogFile << text;
    1604         if (!fNightlyLogFile.good())
    1605         {
    1606             Error("An error occured while writing to the run log file. Closing it.");
    1607             if (fNightlyLogFile.is_open())
    1608                 fNightlyLogFile.close();   
    1609         }
    1610     }
    1611     if (fRunLogFile.is_open())
    1612     {
    1613         fRunLogFile << text;
    1614         if (!fRunLogFile.good())
    1615         {
    1616             Error("An error occured while writing to the run log file. Closing it.");
    1617             if (fRunLogFile.is_open())
    1618                 fRunLogFile.close();   
    1619         }
    1620     }
    1621     return GetCurrentState();
    1622 }*/
    16231509// --------------------------------------------------------------------------
    16241510//
     
    18781764int DataLogger::ConfigureRunNumber(const Event& evt)
    18791765{
    1880     AddNewRunNumber(evt.GetInt(), evt.GetTime());
     1766    AddNewRunNumber(evt.GetXtra(), evt.GetTime());
    18811767//    fRunNumber = evt.GetInt();
    1882     ostringstream str;
    1883     str << "The new run number is: " << fRunNumber.back().runNumber;
    1884     Message(str);
    18851768    return GetCurrentState();
    18861769}
     
    20091892            fOpenedNightlyFits[fileNameOnly].push_back(serviceName);
    20101893
    2011         sub.nightlyFile.Open(partialName, serviceName, NULL, &fNumSubAndFitsData.numOpenFits, this, -1);//Out());
     1894        sub.nightlyFile.Open(partialName, serviceName, NULL, &fNumSubAndFitsData.numOpenFits, this, 0);//Out());
    20121895
    20131896        //notify the opening
     
    20251908    }
    20261909    //do the actual file open
    2027     if (!sub.runFile.IsOpen() && (GetCurrentState() == kSM_Logging) && sub.runNumber != -1)
     1910    if (!sub.runFile.IsOpen() && (GetCurrentState() == kSM_Logging) && sub.runNumber != 0)
    20281911    {//buffer for the run file have already been allocated when doing the Nightly file
    20291912        string fileNameOnly;
     
    21562039             dataFormatsLocal.push_back(dataQualifier.str());
    21572040     }
    2158      sub.nightlyFile.InitDataColumns(fServiceList.GetDescriptions(sub.dimInfo->getName()), dataFormatsLocal, sub.dimInfo->getData(), size);
    2159      sub.runFile.InitDataColumns(fServiceList.GetDescriptions(sub.dimInfo->getName()), dataFormatsLocal, sub.dimInfo->getData(), size);
     2041     sub.nightlyFile.InitDataColumns(GetDescription(sub.server, sub.service), dataFormatsLocal, sub.dimInfo->getData(), size);
     2042     sub.runFile.InitDataColumns(GetDescription(sub.server, sub.service), dataFormatsLocal, sub.dimInfo->getData(), size);
    21602043}
    21612044// --------------------------------------------------------------------------
     
    22102093//! @param filesToGroup a map of filenames mapping to table names to be grouped (i.e. a
    22112094//!        single file can contain several tables to group
    2212 //! @param runNumber the run number that should be used for grouping. -1 means nightly group
     2095//! @param runNumber the run number that should be used for grouping. 0 means nightly group
    22132096//
    22142097void DataLogger::CreateFitsGrouping(map<string, vector<string> > & filesToGroup, int runNumber)
     
    22182101        ostringstream str;
    22192102        str << "Creating fits group for ";
    2220         if (runNumber != -1)
     2103        if (runNumber != 0)
    22212104            str << "run files";
    22222105        else
     
    23972280    }
    23982281#ifdef HAVE_FITS
    2399     CreateFitsGrouping(fOpenedNightlyFits, -1);
     2282    CreateFitsGrouping(fOpenedNightlyFits, 0);
    24002283#endif
    24012284    return kSM_Ready;
Note: See TracChangeset for help on using the changeset viewer.