using System;
using System.Collections.Generic;
using TradeIdeas.TIProData.Interfaces;
namespace TradeIdeas.TIProData
{
public class SymbolDetailsCacheManager : ISymbolDetailsCacheManager
{
private readonly ISendManager _sendManager;
public SymbolDetailsCacheManager(ISendManager sendManager)
{
_sendManager = sendManager;
}
public interface Info
{
///
/// The original request.
///
string Symbol { get; }
///
/// Might be null.
///
string CompanyName { get; }
///
/// Might be null.
///
string Exchange { get; }
}
///
/// Look for the given data. Immediately return something, whether we have data or
/// not.
///
/// If we have not already requested data for this symbol, we will request it now,
/// so it will be in the cache soon. You can use this to preload the cache with
/// symbols that we expect people to request soon. This object will not send more
/// than one request to the server even if this object receives multiple requests.
///
///
///
/// An info object. This might or might not have any data. This might or might not
/// change from a state of no data to having data based on another thread. (This will
/// never change from having data back to no data.)
///
public Info Request(String symbol)
{
return RequestInternal(symbol);
}
private Item RequestInternal(String symbol)
{
Item result;
lock (_items)
{
if (!_items.TryGetValue(symbol, out result))
{
result = new Item(symbol, _items, _sendManager);
_items.Add(symbol, result);
}
}
return result;
}
///
/// Request info. We will call the callback whenever the info is avaialble. The
/// callback can come in any thread. In particular, if we already have the data
/// in the cache, the callback might happen in the current thread, before this function
/// returns.
///
/// This will automatically combine multiple requests, if appropriate. I.e. if three
/// different parts of the code all want the same data, they can each make a seperate
/// request to this object. This object will make sure only one request makes it to
/// the server.
///
///
///
public void Request(String symbol, Action callback)
{
RequestInternal(symbol).addListener(callback);
}
private class Item : Info, TopListRequest.Listener
{
public string Symbol { get; private set; }
public string CompanyName { get; set; }
public string Exchange { get; set; }
///
/// Null means we've already made the callback, so we shouldn't add anything
/// to the list.
///
private List> _listeners = new List>();
///
/// Notify this listener when we get data.
///
/// If we already have data, notify him immediately, in the current thread,
/// before we return.
///
/// If we call the listener immediately, any exceptions will be reported
/// like normal. If we call the callback at a later time, any exceptions
/// might be reported to the debugger but are otherwise ignored.
///
///
public void addListener(Action listener)
{
System.Diagnostics.Debug.Assert(null != listener);
lock (_mutex)
{
if (null != _listeners)
{ // Do the callback later.
_listeners.Add(listener);
listener = null;
}
}
if (null != listener)
// Do the callback now.
listener(this);
}
private readonly Object _mutex;
private readonly ISendManager _sendManager;
public Item(String symbol, Object mutex, ISendManager sendManager)
{
Symbol = symbol;
_mutex = mutex;
_sendManager = sendManager;
RequestFromServer();
}
private void RequestFromServer()
{ // Single symbol requests are much faster than general reqests. If someone
// accidentally sets Symbol to null, that will be a slow request and it won't
// give you what you expect.
System.Diagnostics.Debug.Assert(null != Symbol);
TopListRequest r = new TopListRequest();
r.SingleSymbol = Symbol;
r.Streaming = false;
r.UseDatabase = false;
// We have a lot of flexibility in the keys. By using the standard values that
// the alerts and top lists normally use, then we can use
// StreamingAlertsManager.GetCompanyName() and friends to parse the result.
// TODO there should be a common place for constants for the internal codes, like
// "D_Name". We have constants for the wire names like "c_D_Name" but that's
// not appropriate here.
r.AddExtraColumn("D_Name");
r.AddExtraColumn("D_Exch");
r.Send(_sendManager, this);
}
void TopListRequest.Listener.OnDisconnect(TopListRequest.Token requestToken)
{ // The request failed. Presumably a network error. Try it again.
requestToken.GetRequest().Send(_sendManager, this);
}
void TopListRequest.Listener.OnRowData(List rows, DateTime? start, DateTime? end, TopListRequest.Token requestToken)
{
System.Diagnostics.Debug.Assert(null != rows); // That was possible in a previous version of TopListRequest.
if (rows.Count == 0)
// Could be a lot of things. Maybe this symbol doesn't exist. Maybe there was something
// wrong with our request.
System.Diagnostics.Debug.Print("Could not find data for " + Symbol);
else if (rows.Count > 1)
// Seems unlikely. Some sort of mistake.
System.Diagnostics.Debug.Print("Found " + rows.Count + " rows for " + Symbol + ", expecting 1.");
else
{
var row = rows[0];
CompanyName = row.GetCompanyName(null);
Exchange = row.GetExchange(null);
}
List> toNotify = null;
lock (_mutex)
{
toNotify = _listeners;
_listeners = null;
}
if (null == toNotify)
{ // We've already notified the listeners. We should only receive one callback from the
// top list, so we should never get here. But if the client is slow enough, this might
// happen. If it happens a lot, it's probably a programming error.
System.Diagnostics.Debug.Print("Multiple calls in SymbolDetailsCache!");
}
else
{ // This might be overkill. Normally you'd just have a variable of a delegate type
// and use += to add more listeners. I don't like the way that handles exceptions.
// The first listener to throw an exception will prevent any of the other listeners
// from receiving an exception. That seems especially bad because in this case
// we will throw away any exceptions! Iterating over my own list allows me to
// insulate the callbacks from one another.
foreach (var callback in toNotify)
try
{
callback(this);
}
catch (Exception ex)
{
System.Diagnostics.Debug.Print("Error in callback in SymbolDetailsCache: " + ex);
}
}
}
void TopListRequest.Listener.OnMetaData(TopListRequest.Token requestToken)
{ // We don't expect to see anything here. However, the client API doesn't enforce that.
// And the API is in flux in general. Better just to ignore this if we see it.
}
}
///
/// This item is used as a mutex. Don't modify _items without locking it first.
/// Also, don't do a modify Item.loaded or Item.listeners without holding this mutex.
///
private readonly Dictionary _items = new Dictionary();
}
}