//=-- DetailedBreakout.cpp - Breakout for a "Details" page... ----------------= // // PLUGIN DESCRIPTION: // This plugin is meant to operate on some sort of a "Details" page, where // it lists all (or at least most) of the accesses that have occured for // the day. As such, this plugin should only be used on relatively low // volume web pages. // // PLUGIN PARAMETERS: // Filter: A filter corresponding to the hits you want shown! // Mode: Either "Flat" or "Breakout". Default=Breakout // DominateKey: The top-level key. If set to "Host" (the default) the // plugin output lists the pages that each host visited. If // set to "Page", then the plugin outputs the hosts that visit // each page. // FoldReloads: If "True" or "1", duplicate consecutive hits to a page // from a host are combined onto a single line. Otherwise, // list on seperate lines. Default=True. // DepthLimit: Limit the maximum URL depth to a value. Default=0 (disable) // MaxKeys: Limit the number of dominate keys to display... // MaxKeyEntries: Limit the number of entries per dominate key. // // PLUGIN AUTHOR: // Chris Lattner, Author of Magicstats (sabre@nondot.org) // // PLUGIN HOMEPAGE: // http://www.nondot.org/MagicStats/Plugins/DetailedBreakout/ // // PLUGIN VERSION / COMPATIBILITY: // DetailedBreakout v1.0 / MagicStats 2.0+ // //=---------------------------------------------------------------------------= // This file is Copyright (c) 2000 Chris Lattner and stuff. //=---------------------------------------------------------------------------= #include "PagePlugin.h" #include "SerializeEx.h" #include "Table.h" #include "DetailedBreakout.h" INIT_PLUGIN(DetailedBreakout); DetailedBreakout::DetailedBreakout(int &CE) { Flags = FAutoFilter | FIgnoreErrors; DominateKey = AccessFormatPlugin::FEHost; SecondaryKey = AccessFormatPlugin::FEURL; ResetState(); CE = 0; } // The full name consists of all of the data settings that alter the data that // is kept track of and stored. void DetailedBreakout::GetFullName(VarTable &Params, String &fullName, int updateFreq) { String Filt; if (Params["Filter"] != 0) { Filt = Params["Filter"]->GetStringValue(); } else { Filt = DefFilter.ToParamStr(); } fullName = String::IntToStr(updateFreq) + Name + Filt; } void DetailedBreakout::ProcessAccess(AccessFormatPlugin &A) { const String &Key = A.GetField(DominateKey); const String &Sec = A.GetField(SecondaryKey); const String &Ref = A.GetReferrer(); // Keep track of the key... KeyRec &KeyRecord = DataTable[Key]; KeyRecord.AddToTail(HitRec(A.GetDate(), HitInfo(Sec, Ref))); } void DetailedBreakout::ResetState() { DataTable.Clear(); // Free all of the data in the linked list... DataTable.SetDefault(KeyRec()); } void DetailedBreakout::AddExplodedDirectories(String Path, String &T, int Depth, BreakoutRenderer::Tree &TheTree) { String CurDir; LinkedList Fields; while (T.Length()) { int Count = T.strchr('/'); if (Count == -1) break; // Shouldn't happen with URLs... garbage? CurDir = T; CurDir.Left(Count+1); T.Right(T.Length()-Count-1); Path += CurDir; // cout << "Path = '" << Path << "' CD = '" << CurDir << "'\n"; if (Path == String("http://")) continue; if (Path == String("http:/")) continue; if (CurDir.Length() > 1) CurDir.Left(Count); // Strip off the trailing /... Fields.AddToTail(String("" + CurDir + ""); TheTree.AddToTail(DataPair >(Depth, Fields)); Fields.RemoveHead(); Depth++; } } void DetailedBreakout::OutputHTML(ostream &O, VarTable &Params) { int CutOff = 3, Percent = 1; // Default to 3% int ModeOutLine = 1; int FoldReloads = 1; int ShowReferrer = 1; if (Params.InTable("Mode")) { ModeOutLine = CSString(Params["Mode"]->GetStringValue()) == CSString("Outline"); } if (Params.InTable("FoldReloads")) FoldReloads = VTExp::IsTrue(Params["FoldReloads"]); if (Params.InTable("ShowReferrer")) ShowReferrer = VTExp::IsTrue(Params["ShowReferrer"]); if (Params.InTable("CutoffValue")) { String Val = Params["CutoffValue"]->GetStringValue(); if (Val.Length() > 0) { CutOff = Val.atoi(); if (Val[Val.Length()-1] != '%') // Not Percentage? Percent = 0; } } LinkedList > DataList; DataTable.ConvertToList(DataList); if (DataList.GetLength() == 0) { O << "

No hits yet...

\n"; } // Sort data by primary key... DataList.Sort(); if (!ModeOutLine || DominateKey == AccessFormatPlugin::FEURL) { LinkedList >::Iterator I = DataList.GetIterator(); O << ""; // Loop over each primary key... for (; I; I++) { String DateStr; O << ""; // Iterate over the list of hits now... KeyRec::Iterator J = I->S.GetIterator(); for (; J; J++) { if (UpdateFreq == Plugin::UpdateDaily) J->P.strftime(DateStr, "%r"); else J->P.strftime(DateStr, "%r %m/%d/%Y"); O << "\n"; } } O << "

" << I->P << "
" << J->S.P << "" << DateStr << "" << J->S.S << "
\n"; } else { BreakoutRenderer Renderer(O, Params, Descriptor); LinkedList >::Iterator I = DataList.GetIterator(); // Loop over each primary key... LinkedList Caption; Caption.AddToTail(""); Caption.AddToTail("Time:"); if (ShowReferrer) Caption.AddToTail("Referrer:"); LinkedList ColumnSettings; ColumnSettings.AddToTail(""); // Leave the breakout part alone ColumnSettings.AddToTail("align=center"); // Center the date ColumnSettings.AddToTail("align=left"); // Left align the referrer for (; I; I++) { String DateStr; String OldPath = ""; String OldTopDir = ""; int OldDepth = 0; Caption.RemoveHead(); // Replace host name... Caption.AddToHead(String("" + I->P + ":"); BreakoutRenderer::Tree TheTree; // Iterate over the list of hits now... KeyRec::Iterator J = I->S.GetIterator(); LinkedList Fields; for (; J; J++) { String &URL = J->S.P; int SlashIdx = URL.strrchr('/'); String TopDir = URL; TopDir.Right(TopDir.Length()-SlashIdx-1); String Path = URL; Path.Left(SlashIdx+1); if (URL.Length() == 1) TopDir = '/'; // Don't nuke root int Depth = BreakoutRenderer::CalculateURLDepth(URL); // Did we change directories? If so, we may have to put connector // directories into the tree (without times) to make the path // contiguous... otherwise there could be holes!!! // if (Path != OldPath) { //cout << "Path = " << Path << " OldPath = " << OldPath << endl; if (Path.nCompare(OldPath) != 0) { // Is one a substr of the other? // Path doesn't starts with OldPath... keep choping off of OldPath // a directory at a time until Path and OldPath are substrings of // each other. // int IDX = OldPath.strrchr('/', OldPath.Length()-2); while (IDX != -1 && Path.nCompare(OldPath) != 0) { OldPath.Left(IDX+1); OldDepth--; IDX = IDX > 0 ? OldPath.strrchr('/', IDX-1) : -1; } } if (OldDepth < Depth) { // Check to see if we just went deeper // Okay we just went down a directory or two or three... // fill in the holes if there would be any created here... // String T = Path; T.Right(T.Length() - OldPath.Length()); if (T != OldTopDir + '/') AddExplodedDirectories(OldPath, T, OldDepth, TheTree); } OldPath = Path; OldDepth = Depth; } if (UpdateFreq == Plugin::UpdateDaily) J->P.strftime(DateStr, "%r"); else J->P.strftime(DateStr, "%r %m/%d/%Y"); DateStr.ReplaceSubString(" ", " "); String Referrer(String("" + J->S.S + ""); if (J->S.S.Length() == 0) Referrer = ""; Fields.ClearList(); Fields.AddToTail(String("" + TopDir + ""); Fields.AddToTail(DateStr); if (ShowReferrer) Fields.AddToTail(Referrer); TheTree.AddToTail(DataPair >(Depth, Fields)); OldTopDir = TopDir; } // Write it all out now! Renderer.OutputBreakout(Caption, TheTree, ColumnSettings); O << "

\n"; } } } void DetailedBreakout::LoadState(Serialize &I) { unsigned int Version = 0; String Page; ResetState(); I >> Version; // Check version number... switch (Version) { case 101: // Version 1.01 I >> DataTable; break; case 100: // Version 1.00 - No referrer information! { typedef DataPair OHitRec; typedef LinkedList OKeyRec; Table ODataTable; I >> ODataTable; // Read the old format... DataTable.Clear(); // Convert data to new format... Table::Iterator TI = ODataTable.GetStart(); for (; TI; TI++) { const OKeyRec &KR = TI.GetValue(); LinkedList NewList; OKeyRec::Iterator LI = KR.GetIterator(); for (; LI; LI++) { const OHitRec &Rec = *LI; // Set referrer to "" NewList.AddToTail(HitRec(Rec.P, HitInfo(Rec.S, ""))); } DataTable.SetElementTo(TI.GetKey(), NewList); } } break; default: cout << "Serialized " << Name << " Page plugin is of a version that cannot " << "be deserialized\nwith this version of the plugin. Reseting state.\n"; break; } } void DetailedBreakout::SaveState(Serialize &O) { O << GetCurrentVersion(); // Serialize the Version number O << DataTable; }