<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,8 +1,9 @@
-#!/usr/bin/perl
+#!/usr/bin/env perl
 
 $timeout = 60;
 
 use Finance::Quote;
+use POSIX qw(strftime localtime time);
 
 $q = Finance::Quote-&gt;new;
 $q-&gt;timeout($timeout);
@@ -10,6 +11,8 @@ $q-&gt;require_labels(qw/price/);
 
 %quotes = $q-&gt;fetch(&quot;nasdaq&quot;, $ARGV[0]);
 if ($quotes{$ARGV[0], &quot;price&quot;}) {
+    print strftime('%Y/%m/%d %H:%M:%S', localtime(time()));
+    print &quot; &quot;, $ARGV[0], &quot; &quot;;
     print &quot;\$&quot;, $quotes{$ARGV[0], &quot;price&quot;}, &quot;\n&quot;;
 } else {
     exit 1;</diff>
      <filename>contrib/getquote.pl</filename>
    </modified>
    <modified>
      <diff>@@ -36,6 +36,10 @@
 
 namespace ledger {
 
+optional&lt;path&gt; commodity_t::price_db;
+long           commodity_t::download_leeway = 86400;
+bool           commodity_t::download_quotes;
+
 void commodity_t::base_t::history_t::add_price(commodity_t&amp;      source,
 					       const datetime_t&amp; date,
 					       const amount_t&amp;	 price,
@@ -105,10 +109,108 @@ bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t&amp;  date
   return false;
 }
 
+optional&lt;price_point_t&gt; commodity_t::parse_commodity_price(char * line)
+{
+  char * date_field_ptr = line;
+  char * time_field_ptr = next_element(date_field_ptr);
+  if (! time_field_ptr) return none;
+  string date_field = date_field_ptr;
+
+  char *     symbol_and_price;
+  datetime_t datetime;
+
+  if (std::isdigit(time_field_ptr[0])) {
+    symbol_and_price = next_element(time_field_ptr);
+    if (! symbol_and_price) return none;
+    datetime = parse_datetime(date_field + &quot; &quot; + time_field_ptr);
+  } else {
+    symbol_and_price = time_field_ptr;
+    datetime = parse_datetime(date_field);
+  }
+
+  string symbol;
+  parse_symbol(symbol_and_price, symbol);
+
+  price_point_t point;
+  point.when = datetime;
+  point.price.parse(symbol_and_price);
+  VERIFY(point.price.valid());
+
+  if (commodity_t * commodity =
+      amount_t::current_pool-&gt;find_or_create(symbol)) {
+    commodity-&gt;add_price(point.when, point.price, true);
+    commodity-&gt;add_flags(COMMODITY_KNOWN);
+    return point;
+  }
+
+  return none;
+}
+
+
+optional&lt;price_point_t&gt;
+commodity_t::download_quote(const optional&lt;commodity_t&amp;&gt;&amp; commodity) const
+{
+  DEBUG(&quot;commodity.download&quot;, &quot;downloading quote for symbol &quot; &lt;&lt; symbol());
+#if defined(DEBUG_ON)
+  if (commodity)
+    DEBUG(&quot;commodity.download&quot;,
+	  &quot;  in terms of commodity &quot; &lt;&lt; commodity-&gt;symbol());
+#endif
+
+  char buf[256];
+  buf[0] = '\0';
+
+  string getquote_cmd(&quot;getquote \&quot;&quot;);
+  getquote_cmd += symbol();
+  getquote_cmd += &quot;\&quot; \&quot;&quot;;
+  if (commodity)
+    getquote_cmd += commodity-&gt;symbol();
+  getquote_cmd += &quot;\&quot;&quot;;
+
+  DEBUG(&quot;commodity.download&quot;, &quot;invoking command: &quot; &lt;&lt; getquote_cmd);
+
+  bool success = true;
+  if (FILE * fp = popen(getquote_cmd.c_str(), &quot;r&quot;)) {
+    if (std::feof(fp) || ! std::fgets(buf, 255, fp))
+      success = false;
+    if (pclose(fp) != 0)
+      success = false;
+  } else {
+    success = false;
+  }
+
+  if (success &amp;&amp; buf[0]) {
+    char * p = std::strchr(buf, '\n');
+    if (p) *p = '\0';
+
+    DEBUG(&quot;commodity.download&quot;, &quot;downloaded quote: &quot; &lt;&lt; buf);
+
+    optional&lt;price_point_t&gt; point = parse_commodity_price(buf);
+
+    if (point) {
+      if (price_db) {
+#if defined(__GNUG__) &amp;&amp; __GNUG__ &lt; 3
+	ofstream database(*price_db, ios::out | ios::app);
+#else
+	ofstream database(*price_db, std::ios_base::out | std::ios_base::app);
+#endif
+	database &lt;&lt; &quot;P &quot; &lt;&lt; format_datetime(point-&gt;when, string(&quot;%Y/%m/%d %H:%M:%S&quot;))
+		 &lt;&lt; &quot; &quot; &lt;&lt; symbol() &lt;&lt; &quot; &quot; &lt;&lt; point-&gt;price &lt;&lt; std::endl;
+      }
+      return point;
+    }
+  } else {
+    throw_(std::runtime_error,
+	   _(&quot;Failed to download price for '%1' (command: \&quot;getquote %2\&quot;)&quot;)
+	   &lt;&lt; symbol() &lt;&lt; symbol());
+  }
+  return none;
+}
+
 optional&lt;price_point_t&gt;
   commodity_t::base_t::history_t::
-    find_price(const optional&lt;datetime_t&gt;&amp;   moment,
-	       const optional&lt;datetime_t&gt;&amp;   oldest
+    find_price(const optional&lt;datetime_t&gt;&amp; moment,
+	       const optional&lt;datetime_t&gt;&amp; oldest
 #if defined(DEBUG_ON)
 	       , const int indent
 #endif
@@ -185,16 +287,6 @@ optional&lt;price_point_t&gt;
     }
   }
 
-#if 0
-  if (! has_flags(COMMODITY_NOMARKET) &amp;&amp; parent().get_quote) {
-    if (optional&lt;amount_t&gt; quote = parent().get_quote
-	(*this, age, moment,
-	 (hist &amp;&amp; hist-&gt;prices.size() &gt; 0 ?
-	  (*hist-&gt;prices.rbegin()).first : optional&lt;datetime_t&gt;())))
-      return *quote;
-  }
-#endif
-
   if (! found) {
 #if defined(DEBUG_ON)
     DEBUG_INDENT(&quot;commodity.prices.find&quot;, indent);
@@ -351,7 +443,29 @@ optional&lt;price_point_t&gt;
     DEBUG_INDENT(&quot;commodity.prices.find&quot;, indent);
     DEBUG(&quot;commodity.prices.find&quot;,
 	  &quot;  found price &quot; &lt;&lt; best.price &lt;&lt; &quot; from &quot; &lt;&lt; best.when);
+    DEBUG(&quot;commodity.download&quot;,
+	  &quot;found price &quot; &lt;&lt; best.price &lt;&lt; &quot; from &quot; &lt;&lt; best.when);
+    if (moment)
+      DEBUG(&quot;commodity.download&quot;, &quot;moment = &quot; &lt;&lt; *moment);
+    DEBUG(&quot;commodity.download&quot;, &quot;leeway = &quot; &lt;&lt; download_leeway);
+    if (moment)
+      DEBUG(&quot;commodity.download&quot;,
+	    &quot;slip.moment = &quot; &lt;&lt; (*moment - best.when).total_seconds());
+    else
+      DEBUG(&quot;commodity.download&quot;,
+	    &quot;slip.now = &quot; &lt;&lt; (CURRENT_TIME() - best.when).total_seconds());
 #endif
+    if (download_quotes &amp;&amp;
+	! source.has_flags(COMMODITY_NOMARKET) &amp;&amp;
+	((! moment &amp;&amp;
+	  (CURRENT_TIME() - best.when).total_seconds() &gt; download_leeway) ||
+	 (moment &amp;&amp;
+	  (*moment - best.when).total_seconds() &gt; download_leeway))) {
+      DEBUG(&quot;commodity.download&quot;,
+	    &quot;attempting to download a more current quote...&quot;);
+      if (optional&lt;price_point_t&gt; quote = source.download_quote(commodity))
+	return quote;
+    }
     return best;
   }
   return none;
@@ -368,7 +482,8 @@ optional&lt;commodity_t::base_t::history_t&amp;&gt;
 #if 0
       // jww (2008-09-20): Document which option switch to use here
       throw_(commodity_error,
-	     _(&quot;Cannot determine price history: prices known for multiple commodities (use -x)&quot;));
+	     _(&quot;Cannot determine price history: &quot;
+	       &quot;prices known for multiple commodities (use -x)&quot;));
 #endif
     comm = (*histories.begin()).first;
   } else {</diff>
      <filename>src/commodity.cc</filename>
    </modified>
    <modified>
      <diff>@@ -86,7 +86,6 @@ public:
     struct history_t
     {
       history_map prices;
-      ptime	  last_lookup;
 
       void add_price(commodity_t&amp;	source,
 		     const datetime_t&amp;	date,
@@ -332,6 +331,15 @@ public:
   // Methods related to parsing, reading, writing, etc., the commodity
   // itself.
 
+  static optional&lt;path&gt; price_db;
+  static long           download_leeway;
+  static bool           download_quotes;
+
+  static optional&lt;price_point_t&gt; parse_commodity_price(char * line);
+
+  optional&lt;price_point_t&gt;
+  download_quote(const optional&lt;commodity_t&amp;&gt;&amp; commodity = none) const;
+
   static void parse_symbol(std::istream&amp; in, string&amp; symbol);
   static void parse_symbol(char *&amp; p, string&amp; symbol);
   static string parse_symbol(std::istream&amp; in) {</diff>
      <filename>src/commodity.h</filename>
    </modified>
    <modified>
      <diff>@@ -415,10 +415,21 @@ void global_scope_t::normalize_report_options(const string&amp; verb)
 
   report_t&amp; rep(report());
 
-  // jww (2009-02-09): These global are a hack, but hard to avoid.
+  // jww (2009-02-09): These globals are a hack, but hard to avoid.
   item_t::use_effective_date		= rep.HANDLED(effective);
   rep.session.commodity_pool-&gt;keep_base = rep.HANDLED(base);
 
+  commodity_t::download_quotes = rep.session.HANDLED(download);
+
+  if (rep.session.HANDLED(price_exp_))
+    commodity_t::download_leeway =
+      rep.session.HANDLER(price_exp_).value.as_long();
+
+  if (rep.session.HANDLED(price_db_))
+    commodity_t::price_db = rep.session.HANDLER(price_db_).str();
+  else
+    commodity_t::price_db = none;
+
   if (rep.HANDLED(date_format_)) {
     output_datetime_format = rep.HANDLER(date_format_).str() + &quot; %H:%M:%S&quot;;
     output_date_format     = rep.HANDLER(date_format_).str();</diff>
      <filename>src/global.cc</filename>
    </modified>
    <modified>
      <diff>@@ -474,9 +474,6 @@ option_t&lt;report_t&gt; * report_t::lookup_option(const char * p)
   case 'Y':
     OPT_CH(yearly);
     break;
-  case 'Z':
-    OPT_CH(price_exp_);
-    break;
   case 'a':
     OPT(abbrev_len_);
     else OPT(account_);
@@ -557,7 +554,6 @@ option_t&lt;report_t&gt; * report_t::lookup_option(const char * p)
     else OPT(lots);
     else OPT(lots_actual);
     else OPT_ALT(tail_, last_);
-    else OPT_ALT(price_exp_, leeway_);
     break;
   case 'm':
     OPT(market);
@@ -582,7 +578,6 @@ option_t&lt;report_t&gt; * report_t::lookup_option(const char * p)
     else OPT(plot_amount_format_);
     else OPT(plot_total_format_);
     else OPT(price);
-    else OPT(price_exp_);
     else OPT(prices_format_);
     else OPT(pricesdb_format_);
     else OPT(print_format_);</diff>
      <filename>src/report.cc</filename>
    </modified>
    <modified>
      <diff>@@ -255,7 +255,6 @@ public:
     HANDLER(plot_amount_format_).report(out);
     HANDLER(plot_total_format_).report(out);
     HANDLER(price).report(out);
-    HANDLER(price_exp_).report(out);
     HANDLER(prices_format_).report(out);
     HANDLER(pricesdb_format_).report(out);
     HANDLER(print_format_).report(out);
@@ -619,8 +618,6 @@ public:
       parent-&gt;HANDLER(amount_).set_expr(string(&quot;--price&quot;), &quot;price&quot;);
     });
 
-  OPTION(report_t, price_exp_); // -Z
-
   OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
       on(none,
 	 &quot;%-.9(date) %-8(account) %(justify(scrub(display_amount), 12, &quot;</diff>
      <filename>src/report.h</filename>
    </modified>
    <modified>
      <diff>@@ -219,6 +219,12 @@ void session_t::clean_accounts()
 option_t&lt;session_t&gt; * session_t::lookup_option(const char * p)
 {
   switch (*p) {
+  case 'Q':
+    OPT_CH(download); // -Q
+    break;
+  case 'Z':
+    OPT_CH(price_exp_);
+    break;
   case 'a':
     OPT_(account_); // -a
     break;
@@ -232,17 +238,15 @@ option_t&lt;session_t&gt; * session_t::lookup_option(const char * p)
     OPT(input_date_format_);
     break;
   case 'l':
-    OPT(leeway_);
+    OPT_ALT(price_exp_, leeway_);
     break;
   case 'p':
     OPT(price_db_);
+    else OPT(price_exp_);
     break;
   case 's':
     OPT(strict);
     break;
-  case 'Q':
-    OPT_CH(download); // -Q
-    break;
   }
   return NULL;
 }</diff>
      <filename>src/session.cc</filename>
    </modified>
    <modified>
      <diff>@@ -105,10 +105,10 @@ public:
   {
     HANDLER(account_).report(out);
     HANDLER(download).report(out);
-    HANDLER(leeway_).report(out);
     HANDLER(file_).report(out);
     HANDLER(input_date_format_).report(out);
     HANDLER(price_db_).report(out);
+    HANDLER(price_exp_).report(out);
     HANDLER(strict).report(out);
   }
 
@@ -124,8 +124,8 @@ public:
   OPTION(session_t, download); // -Q
 
   OPTION__
-  (session_t, leeway_,
-   CTOR(session_t, leeway_) { value = 24L * 3600L; }
+  (session_t, price_exp_, // -Z
+   CTOR(session_t, price_exp_) { value = 24L * 3600L; }
    DO_(args) {
      value = args[1].to_long() * 60L;
    });</diff>
      <filename>src/session.h</filename>
    </modified>
    <modified>
      <diff>@@ -455,67 +455,18 @@ void instance_t::price_conversion_directive(char * line)
   }
 }
 
-namespace {
-  void parse_symbol(char *&amp; p, string&amp; symbol)
-  {
-    if (*p == '&quot;') {
-      char * q = std::strchr(p + 1, '&quot;');
-      if (! q)
-	throw parse_error(_(&quot;Quoted commodity symbol lacks closing quote&quot;));
-      symbol = string(p + 1, 0, q - p - 1);
-      p = q + 2;
-    } else {
-      char * q = next_element(p);
-      symbol = p;
-      if (q)
-	p = q;
-      else
-	p += symbol.length();
-    }
-    if (symbol.empty())
-      throw parse_error(_(&quot;Failed to parse commodity&quot;));
-  }
-}
-
 void instance_t::price_xact_directive(char * line)
 {
-  char * date_field_ptr = skip_ws(line + 1);
-  char * time_field_ptr = next_element(date_field_ptr);
-  if (! time_field_ptr) return;
-  string date_field = date_field_ptr;
-
-  char *     symbol_and_price;
-  datetime_t datetime;
-
-  if (std::isdigit(time_field_ptr[0])) {
-    symbol_and_price = next_element(time_field_ptr);
-    if (! symbol_and_price) return;
-    datetime = parse_datetime(date_field + &quot; &quot; + time_field_ptr,
-			      current_year);
-  } else {
-    symbol_and_price = time_field_ptr;
-    datetime = parse_datetime(date_field, current_year);
-  }
-
-  string symbol;
-  parse_symbol(symbol_and_price, symbol);
-  amount_t price(symbol_and_price);
-  VERIFY(price.valid());
-
-  if (commodity_t * commodity =
-      amount_t::current_pool-&gt;find_or_create(symbol)) {
-    commodity-&gt;add_price(datetime, price, true);
-    commodity-&gt;add_flags(COMMODITY_KNOWN);
-  } else {
-    assert(false);
-  }
+  optional&lt;price_point_t&gt; point =
+    commodity_t::parse_commodity_price(skip_ws(line + 1));
+  assert(point);
 }
 
 void instance_t::nomarket_directive(char * line)
 {
   char * p = skip_ws(line + 1);
   string symbol;
-  parse_symbol(p, symbol);
+  commodity_t::parse_symbol(p, symbol);
 
   if (commodity_t * commodity =
       amount_t::current_pool-&gt;find_or_create(symbol))</diff>
      <filename>src/textual.cc</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>72a2eaa38e68b886a567da2afda8f08b1929e3b6</id>
    </parent>
  </parents>
  <author>
    <name>John Wiegley</name>
    <email>johnw@newartisans.com</email>
  </author>
  <url>http://github.com/jwiegley/ledger/commit/440124eacc9f7fde993e87968ca9e65ffa309f11</url>
  <id>440124eacc9f7fde993e87968ca9e65ffa309f11</id>
  <committed-date>2009-06-23T18:44:07-07:00</committed-date>
  <authored-date>2009-06-23T18:44:07-07:00</authored-date>
  <message>Restored --download, although not done yet

The problem at this point is that it's recording prices in the price
database multiple times; it should only need to download a price for
each commodity once per day.</message>
  <tree>c86839186bee5c4951ad98491d02625e9c276669</tree>
  <committer>
    <name>John Wiegley</name>
    <email>johnw@newartisans.com</email>
  </committer>
</commit>
