René Nyffenegger's collection of things on the web
René Nyffenegger on Oracle - Most wanted - Feedback -
 

class JOSReader [JoS Reader]

This class is (so to speak) the top level class for the Joel on Software reader.
The WinMain is in this file as well.

The header file

/*
   JOSReader.h

   Copyright (C) 2004-2005 René Nyffenegger

   This source code is provided 'as-is', without any express or implied
   warranty. In no event will the author be held liable for any damages
   arising from the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this source code must not be misrepresented; you must not
      claim that you wrote the original source code. If you use this source code
      in a product, an acknowledgment in the product documentation would be
      appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
      misrepresented as being the original source code.

   3. This notice may not be removed or altered from any source distribution.

   JOSReader.h is part of "Joel on Software reader".
   The most current version of Joel on Software reader can be found at 
   http://www.adp-gmbh.ch/misc/jos_reader/index.html

   René Nyffenegger rene.nyffenegger@adp-gmbh.ch
*/

#ifndef JOS_READER_H__
#define JOS_READER_H__

#include "SQLiteWrapper.h"

extern SQLiteWrapper sqlite;

#endif

The implementation file

/*
   JOSReader.cpp

   Copyright (C) 2004-2005 René Nyffenegger

   This source code is provided 'as-is', without any express or implied
   warranty. In no event will the author be held liable for any damages
   arising from the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this source code must not be misrepresented; you must not
      claim that you wrote the original source code. If you use this source code
      in a product, an acknowledgment in the product documentation would be
      appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
      misrepresented as being the original source code.

   3. This notice may not be removed or altered from any source distribution.

   JOSReader.cpp is part of "Joel on Software reader".
   The most current version of Joel on Software reader can be found at 
   http://www.adp-gmbh.ch/misc/jos_reader/index.html

   René Nyffenegger rene.nyffenegger@adp-gmbh.ch
*/

// TODO: raus mit NL
#define NL "\n"

#include "JOSReader.h"

#include <windows.h>
#include <vector>
#include <string>
#include <iostream>
#include "HTMLWindow.h"
#include "stdhelpers.h"

#include "Group.h"

#define TODO_FLUSHFILE_DEBUG
#ifdef  TODO_FLUSHFILE_DEBUG
#include "win32_FlushToFile.h"
#endif

SQLiteWrapper sqlite;

class JOSReader : public HTMLWindow {
  public:
    JOSReader(HINSTANCE hInstance) : HTMLWindow (
       "Joel on Software reader", 
       hInstance
   ) 
    {
      TraceFunc("JOSReader::JOSReader"); 
    }

  private: 
  std::map<std::string, Group*> groups_;

  Group* curGroup_;

  //TODO, should be named something like getGroup_create_if_necessary
  //
  // FindGroup finds an instance of a Group. If this instance has not yet been created, this method creates it.
  Group* FindGroup(std::string const& short_group_name, std::string const& title) {
    std::map<std::string, Group*>::iterator iGroup = groups_.find(short_group_name);
    if (iGroup == groups_.end()) {
      HCURSOR old_cursor = ::SetCursor(::LoadCursor(0, IDC_WAIT));
      // TODO: redundant: short_group_name!
      groups_[short_group_name]=new Group(short_group_name, title);
      ::SetCursor(old_cursor);
    }
    // TODO: move following line into if statement.
    iGroup = groups_.find(short_group_name);
  
    return iGroup->second;
  }

  std::string CreateInterestedLinkYes(
    std::string short_group_name,
    std::string topic_id,
    std::string replies,
    std::string name
  ) {

    std::string group_and_topic = 
          "group_name="     + short_group_name  +  
          "&amp;topic_id="  + topic_id          + 
          "&amp;replies="   + replies           ;

    std::string ret;

    ret += "<a class=y href='link://set_interest/?interested=" + StringFrom(Topic::YES) + "&amp;" + group_and_topic + "'>";
    ret += name;  
    ret += "</a>";

    return ret;
  }

  std::string CreateInterestedLinkNo(
    std::string short_group_name,
    std::string topic_id,
    std::string replies,
    std::string name
  ) {
    std::string group_and_topic = 
          "group_name="     + short_group_name  +  
          "&amp;topic_id="  + topic_id          + 
          "&amp;replies="   + replies           ;

    std::string ret;

    ret += "<a class=n href='link://set_interest/?interested=" + StringFrom(Topic::NO)  + "&amp;" + group_and_topic + "'>";
    ret += name;
    ret += "</a>&nbsp;&nbsp;";

    return ret;
  }

  // TODO: is BeforeNavigate also called if it starts with link://?
  //
  // Overriding BeforeNavigate
  virtual bool BeforeNavigate(std::string const& url) {
    TraceFunc("JOSReader::BeforeNavigate");
    Trace2("url=", url);

    if (url.substr(0,4) == "http") {
      Trace("url starts with http"); 

      HCURSOR old_cursor = ::SetCursor(::LoadCursor(0, IDC_WAIT));
      ShellExecute(0, 0, url.c_str(), 0, 0, SW_SHOWNORMAL);
      ::SetCursor(old_cursor);
      // Don't proceed with the navigation
      return false;
    }
    Trace("url does not start with http"); 

    return true;
  };

  void RenderTopic(
      std::string const& group_name,
      std::string const& topic_id,
      std::string const& replies,
      std::string      & out_html) 
  {
    Topic*     topic = groups_[group_name]->TopicWithId(topic_id);

    topic->replies_ = replies;

    HCURSOR old_cursor = 0;
    if (! topic->topic_items_read_) {
      old_cursor = ::SetCursor(::LoadCursor(0, IDC_WAIT));
    }

    TopicItem* topicItem;

    out_html+= "<h1>" + topic->title_ + "</h1>";

    while (   (topicItem=topic->NextTopicItem())   ) {
      if (To<int>(topicItem->id_) > To<int>(topic->last_id_seen_)) {
        out_html+="<div class='topic_item'>";
      }
      else {
        out_html+="<div class='topic_item_seen'>";
      }
      out_html+=topicItem->html_;
      out_html+="</div>";

      if (topicItem->recognized_author_) {
        out_html += "<div class='topic_item_author_recognized'>";
      }
      else {
        out_html += "<div class='topic_item_author'>";
      }
      out_html += topicItem->author_;
      out_html += "</div>";
    }

    topic->Save();

    if (old_cursor) ::SetCursor(old_cursor);

    out_html += "<p>";
    out_html += CreateInterestedLinkYes(group_name, topic_id, replies, "interesting");
    out_html += "&nbsp;&nbsp;";
    out_html += CreateInterestedLinkNo(group_name, topic_id, replies, "lame");
    out_html += "&nbsp;&nbsp<a href='link://" + group_name + "/'>Back</a>";
  }

  void RenderGroup(Group* group, std::string& out_html) {
    out_html += std::string("<h1>")+group->title_+"</h1><table>" NL;

    out_html += "<tr><td colspan=4><b>New Topics</b></td></tr>"  NL;

    Topic* topic;

    bool old_topics_reached = false;
    group->RewindTopic();
    while (   (topic=group->NextTopic())   ) {
      if (topic->interested_ != Topic::NO ) {
        std::string group_and_topic = 
          "group_name="     + topic->short_group_name_ +  
          "&amp;topic_id="  + topic->topic_id_   + 
          "&amp;replies="   + topic->replies_;

        if (!old_topics_reached)
        if (To<int>(topic->topic_id_) <=  group->LastTopicIdSeen()) {
          old_topics_reached = true;
          out_html += "<tr><td colspan=4><b>Old Topics</b></td></tr>" NL;
        }

        std::string td;
        if (topic->interested_ == Topic::YES) {
          td = "<td class='interested'>";
        }
        else {
          td= "<td>";
        }

        out_html += "<tr><td>";
        out_html += CreateInterestedLinkYes(topic->short_group_name_, topic->topic_id_, topic->replies_, "Yes");
        out_html += "</td><td>";
        out_html += CreateInterestedLinkNo (topic->short_group_name_, topic->topic_id_, topic->replies_, "No");
        out_html += td;
        out_html += "<a ";

        if (topic->NewTopicItems()) {
          out_html += "class='new_items' ";
          // TODO topic->title_+=" new items";
        }
        else {
          out_html += "class='no_new_items' ";
          // TODO topic->title_+=" No new item";
        }
        
        out_html += "href='link://topic/?" + group_and_topic + "'>" + topic->title_ += "</a></td>";
        out_html += "<td>(<i>" + topic->original_poster_ + "</i>)</td></tr>";
      }
    }
    out_html+="</table>";
  }

  void RenderGroup(std::string const& short_group_name, std::string const& title, std::string& out_html) {
    if (!sqlite.Begin()) {
      ::MessageBox(0, "Begin failed", 0, 0);
    }
    curGroup_=FindGroup(short_group_name, title);

    RenderGroup(curGroup_, out_html);

    if (! sqlite.Commit()) {
      ::MessageBox(0, "Commit failed", 0, 0);
    }
  }

  private:
    std::string CSS() {
      return
      "p     {color:blue; }" NL
      "h1    {color:red; font-size:12px; }" NL
      "body  {background-color:white; font-family:Verdana;margin:0px;font-size:11px;} " NL
      ".topic_item {width=400px;margin-top=10px}" NL
      ".topic_item_seen {width=400px;margin-top=10px;color:#888888;}" NL
      ".topic_item_author {width=400px; font-style:italic;background-color:#ccccff;text-align=right};" NL
      ".topic_item_author_recognized {width=400px; font-style:italic;color:red;background-color:#ccccff;text-align=right};" NL
      "a:visited   {color:#0000ff;}" NL
      "a.y             {color=green;}" NL
      "a.y:visited     {color=green;}" NL
      "a.n             {color=red;}" NL
      "a.n:visited     {color=red;}" NL
      "a.new_items     {font-weight:bold;};" NL
      "a.no_new_items  {font-weight:normal;};" NL
      "tr {vertical-align:top; font-size:11px;}" NL
      "td.interested {background-color:#ffff88;}" NL
      ".menu   {left:0px; width=140px;height=100%;font-size=10px;background-color:#eeeeee;padding=2px;border-right:2px solid #cccccc;};" NL
      ".author {position=absolute;left:0px; width=140px;top=300px;font-size=10px;background-color:#eeeeee;padding=2px;border-right:2px solid #cccccc;};" NL
      ".scroll {overflow:auto;height=100%;width=500px;top=0px;left=140px;position:absolute;background-color:#eeeeee;padding:2px;}" NL
      ".menu_item {margin-bottom:2px;margin-left:5px}" NL
      ;
    }

  std::string HtmlStart() {

    return 
      std::string("<html></head>")  +
      "<style>"                     + 
      CSS()                         + 
      "</style>"                                                                    NL
      "</head>"                                                                     NL
      "<body>"                                                                      NL
      "<div class='menu'>"                                                          NL
      // ---- Menu 
      "<b>Groups:</b>"                                                               NL
      "<div class='menu_item'><a href='link://joel/'>Joel on Software</a></div>"    NL
      "<div class='menu_item'><a href='link://biz/'>Business of Software</a></div>" NL
      "<div class='menu_item'><a href='link://design/'>Design of Software</a></div>" NL
      // "<div class='menu_item'><a href='link://dotnetquestions/'>.NET Questions</a></div>" NL
      //"<div class='menu_item'><a href='link://techinterview/'>TechInterview.org</a></div>" NL
      "<div class='menu_item'><a href='link://off/'>Off topic</a></div>"            NL
      "<div class='author'><b>Programmed by:</b><br><a href='http://www.adp-gmbh.ch'>Ren&eacute; Nyffenegger</a></div>" NL
      "</div>"                                                                      NL
      "<div class='scroll'>";

  }

  std::string HtmlEnd() {
    return "</div></body></html>";
  }

  // TODO: AppLink must be protected
  public:
  virtual void AppLink(std::string const& path, std::string& out_html, const Parameters& params) {

    out_html = HtmlStart();

    try {
      if (path == "joel/") {
        RenderGroup("joel", "Joel on Software", out_html);
      }
      else if (path == "biz/") {
        RenderGroup("biz", "The Business of Software", out_html);
      }
      else if (path == "design/") {
        RenderGroup("design", "Design of Software", out_html);
      }
//      else if (path == "dotnetquestions/") {
//        RenderGroup("dotnetquestions", ".NET Questions", out_html);
//      }
//      else if (path == "techinterview/") {
//        RenderGroup("techinterview", "TechInterview.org", out_html);
//      }
      else if (path == "off/") {
        RenderGroup("off", "Off topic", out_html);
      }
      else if (path == "set_interest/") {

        Topic* topic = groups_[params.Value("group_name")]->TopicWithId(params.Value("topic_id"));
        topic -> interested_ = static_cast<Topic::INTERESTED>(To<int>(params.Value("interested")));
        topic -> Save();

        RenderGroup(curGroup_, out_html);
      }
      else if (path == "topic/") {
        RenderTopic(params.Value("group_name"), params.Value("topic_id"), params.Value("replies"), out_html);
      }
      else {
        ::MessageBox(0, path.c_str(), "Unknown Link", 0);
      }
    }
    catch (const char* e) {
      out_html += "Error: "; 
      out_html += e;
    }
    catch (...) {
      out_html += "Error: "; 
      out_html += "...";
    }

    out_html += HtmlEnd();
    Win32_FlushToFile("result.html", out_html);
  }

  void Start() {
    TraceFunc("JOSReader::Start");
    HTML(HtmlStart() + HtmlEnd());
  }
};

void create_tables_if_necessary() {
  SQLiteWrapper::ResultTable r;

  if (!sqlite.SelectStmt("select count(*) from sqlite_master where tbl_name='topics' and type='table'", r)) {
    ::MessageBox(0, "Error with select count(*) [create_tables_if_necessary]", sqlite.LastError().c_str(), 0);
  }

  if (r.records_[0].fields_[0] != "0") return;

  sqlite.Begin();

  if (! sqlite.DirectStatement("create table groups(short_group_name, last_topic_id_seen)"))                               ::MessageBox(0, "Could not create group", 0, 0);
  if (! sqlite.DirectStatement("create table topics(short_group_name, topic_id, replies_seen, interested, last_id_seen)")) ::MessageBox(0, "Could not create topic", 0, 0);

  SQLiteStatement* stmt = sqlite.Statement("insert into groups values (?, ?)");
  if (stmt) {
    stmt->Bind(0, "joel"); stmt->Bind(1,   "0"); if (!stmt->Execute()) ::MessageBox(0, "failed: insert into group", 0, 0);
    stmt->Bind(0, "biz" ); stmt->Bind(1,   "0"); if (!stmt->Execute()) ::MessageBox(0, "failed: insert into group", 0, 0);
    stmt->Bind(0, "design" ); stmt->Bind(1,   "0"); if (!stmt->Execute()) ::MessageBox(0, "failed: insert into group", 0, 0);
    //stmt->Bind(0, "dotnetquestions" ); stmt->Bind(1,   "0"); if (!stmt->Execute()) ::MessageBox(0, "failed: insert into group", 0, 0);
    //stmt->Bind(0, "techinterview" ); stmt->Bind(1,   "0"); if (!stmt->Execute()) ::MessageBox(0, "failed: insert into group", 0, 0);
    stmt->Bind(0, "off" ); stmt->Bind(1,   "0"); if (!stmt->Execute()) ::MessageBox(0, "failed: insert into group", 0, 0);
  }
  else {
    ::MessageBox(0, sqlite.LastError().c_str(), "insert into groups values", 0);
  }
  sqlite.Commit();

  delete stmt;
}

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR /*lpszCmdLine*/, int /*nCmdShow*/) {
  StartTrace("JOSReader.trace");
  TraceFunc("WinMain");

  if (!sqlite.Open("jos.db")) {
    ::MessageBox(0, "Could not open sqllite db jos.db", 0, 0);
  }

  create_tables_if_necessary();

  JOSReader jos_reader(hInstance);
  
  jos_reader.Size(680, 620);

  jos_reader.Start();

  MSG msg;
  while (GetMessage(&msg, 0, 0, 0)) {
    TranslateMessage(&msg);
    if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) {
      ::SendMessage(jos_reader.hwnd_, msg.message, msg.wParam, msg.lParam);
    }
    DispatchMessage(&msg);
  }

  return 0;
}