protected override object ConnectData(Topic topic, IList <string> topicInfo, ref bool newValues)
 {
     Log("ConnectData: TopicId - {0}, topicInfo: {1}", GetTopicId(topic), string.Join(", ", topicInfo));
     Logger.Debug(">>>>> ConnectData called.");
     _topics.Add((RealStockTopic)topic);
     return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA));
 }
Example #2
0
        protected override object ConnectData(Topic topic, IList <string> topicInfo, ref bool newValues)
        {
            TestArrayTopic testArrayTopic = (TestArrayTopic)topic;

            _topics.Add(testArrayTopic);
            Debug.Print("ConnectData - Prefix {0}", testArrayTopic.Prefix);
            return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA));
        }
Example #3
0
 protected override object ConnectData(Topic topic, System.Collections.Generic.IList <string> topicInfo, ref bool newValues)
 {
     lock (m_TopicMap) {
         string stopic  = topicInfo[0];
         int    topicId = GetTopicId(topic);
         Logr.Log(String.Format("~A ConnectData: {0} - {1}", topicId, stopic));
         m_Subscriptions.Add(topic, stopic);
         m_TopicMap.Add(stopic, topic);
         return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA));
     }
 }
Example #4
0
        public object?GetData(string viewServer, string view, string fields, string?where = default, string?orderBy = default, string?groupBy = default, int?limit = default, int?offset = default)
        {
            if (this._userManager.User == null)
            {
                return("Error: Not logged in.");
            }
            string        location = DataFunctions.GetLocation();
            SelectRequest request  = new SelectRequest
            {
                QueryId    = location,
                Parameters = new SelectRequestParameters
                {
                    View    = view,
                    Fields  = fields,
                    Where   = where,
                    OrderBy = orderBy,
                    GroupBy = groupBy,
                    Limit   = limit,
                    Offset  = offset
                }
            };
            string requestKey = JsonSerializer.Serialize(request, this._serializerOptions).GetHashCode().ToString();

            this._objectCache.Insert(requestKey, request);
            object result = XlCall.RTD(nameof(ViewServerRtdServer), null, viewServer, requestKey);

            if (result == null)
            {
                return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA));
            }
            if (result is string dataKey)
            {
                DataContainer?dataContainer = (DataContainer?)this._objectCache.Extract(dataKey);
                if (dataContainer == null || !(dataContainer.Data is IReadOnlyList <object> list) || list.Count == 0)
                {
                    return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA));
                }
                string[] columns = fields.Split(',');
                object[,] output = new object[list.Count, columns.Length];
                for (int row = 0; row < list.Count; row++)
                {
                    IReadOnlyDictionary <string, object?> item = (IReadOnlyDictionary <string, object?>)list[row];
                    for (int column = 0; column < columns.Length; column++)
                    {
                        output[row, column] = item.TryGetValue(columns[column], out object?value) && value != null
                            ? value
                            : ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNull);
                    }
                }
                return(output);
            }
            return(result);
        }
        // Understanding newValues
        // -----------------------
        // As input: If Excel has a cached value to display, newValues passed in will be false.
        // On return: if newValues is now false, Excel will use cached value if it has one, (else #N/A if passed in as true)
        //            if newValues is now true, Excel will use the value returned by ConnectData.
        object IRtdServer.ConnectData(int topicId, ref Array strings, ref bool newValues)
        {
            try
            {
                // Check for an active topic with the same topicId
                // - this is unexpected, but is reported as a bug in an earlier Excel version.
                // (Thanks ngm)

                // (Does not address the Excel 2010 bug documented here:
                // http://social.msdn.microsoft.com/Forums/en-US/exceldev/thread/ba06ac78-7b64-449b-bce4-9a03ac91f0eb/
                // fixed by hotfix: http://support.microsoft.com/kb/2405840 and SP1. This problem is fixed by the ExcelRtd2010BugHelper. )
                if (_activeTopics.ContainsKey(topicId))
                {
                    using (XlCall.Suspend())
                    {
                        ((IRtdServer)this).DisconnectData(topicId);
                    }
                }

                List <string> topicInfo = new List <string>(strings.Length);
                for (int i = 0; i < strings.Length; i++)
                {
                    topicInfo.Add((string)strings.GetValue(i));
                }

                Topic topic;
                using (XlCall.Suspend())
                {
                    // We create the topic, but what if its value is set here...?
                    topic = CreateTopic(topicId, topicInfo);
                }
                if (topic == null)
                {
                    Logger.RtdServer.Error("Error in RTD server {0} CreateTopic returned null.", GetType().Name);
                    // Not sure what to return here for error. We try the COM error version of #VALUE !?
                    return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue));
                }
                _activeTopics[topicId] = topic;
                using (XlCall.Suspend())
                {
                    return(ConnectData(topic, topicInfo, ref newValues));
                }
            }
            catch (Exception e)
            {
                Logger.RtdServer.Error("Error in RTD server {0} ConnectData: {1}", GetType().Name, e.ToString());
                // Not sure what to return here for error. We try the COM error version of #VALUE !?
                return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue));
            }
        }
Example #6
0
            public ViewServerTopic(ViewServerRtdServer server, int topicId, string viewServer, Request request)
                : base(server, topicId, ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA))
            {
                this._connection  = Container.Instance.Resolve <ViewServerConnection>();
                this._objectCache = Container.Instance.Resolve <IObjectCache>();
                this._viewServer  = viewServer;
                switch ((this._request = request).Command)
                {
                case Command.Select:
                    this._connection.DataResponseReceived += this.Connection_DataResponseReceived;
                    break;

                case Command.Metadata:
                    this._connection.MetadataResponseReceived += this.Connection_MetadataResponseReceived;
                    break;
                }
            }
Example #7
0
        protected override object ConnectData(Topic topic, IList <string> topicInfo, ref bool newValues)
        {
            // Retrieve and store the GUID from the topic's first info string - used to hook up to the Async state
            Guid id = new Guid(topicInfo[0]);

            _topicGuids[topic] = id;

            // Create a new ExcelRtdObserver, for the Topic, which will listen to the Observable
            // (Internally also set initial value - #N/A for now)
            ExcelRtdObserver rtdObserver = new ExcelRtdObserver(topic);

            // ... and subscribe it
            AsyncObservableImpl.ConnectObserver(id, rtdObserver);

            // Return something: #N/A for now. Not currently used.
            // TODO: Allow customize?
            return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA));
        }
            object FixValue(object value)
            {
                if (value is ExcelError)
                {
                    value = ExcelErrorUtil.ToComError((ExcelError)value);
                }

                // Long strings will cause the topic update to fail horribly.
                // See: http://social.msdn.microsoft.com/Forums/nl-BE/exceldev/thread/436f1aa4-c950-4486-ba58-22a6a12fbf19
                // We truncate long strings.
                string valueString = value as string;

                if (valueString != null && valueString.Length > 255)
                {
                    value = valueString.Substring(0, 255);
                }

                // CONSIDER: Check valid data types
                return(value);
            }
Example #9
0
        // Forwarded from XlCall
        // Loads the RTD server with temporary ProgId.
        // CAUTION: Might fail when called from array formula (the first call in every array-group fails).
        //          When it fails, the xlfRtd call returns xlReturnUncalced.
        //          In that case, this function returns null, and does not keep a reference to the created server object.
        //          The next call should then succeed (though a new server object will be created).
        public static bool TryRTD(out object result, string progId, string server, params string[] topics)
        {
            Debug.Print("### RtdRegistration.RTD " + progId);
            // Check if this is any of our business.
            Type rtdServerType;

            if (!string.IsNullOrEmpty(server) || !registeredRtdServerTypes.TryGetValue(progId, out rtdServerType))
            {
                // Just pass on to Excel.
                return(TryCallRTD(out result, progId, null, topics));
            }

            // TODO: Check that ExcelRtdServer with stable ProgId case also works right here -
            //       might need to add to loadedRtdServers somehow

            // Check if already loaded.
            string loadedProgId;

            if (loadedRtdServers.TryGetValue(progId, out loadedProgId))
            {
                if (ExcelRtd2010BugHelper.ExcelVersionHasRtdBug && rtdServerType.IsSubclassOf(typeof(ExcelRtdServer)))
                {
                    ExcelRtd2010BugHelper.RecordRtdCall(progId, topics);
                }
                // Call Excel using the synthetic RtdSrv_xxx (or actual from attribute) ProgId
                return(TryCallRTD(out result, loadedProgId, null, topics));
            }

            // Not loaded already - need to get the Rtd server loaded
            // TODO: Need to reconsider registration here.....
            //       Sometimes need stable ProgIds.
            object rtdServer;

            if (ExcelRtd2010BugHelper.ExcelVersionHasRtdBug && rtdServerType.IsSubclassOf(typeof(ExcelRtdServer)))
            {
                Debug.Print("### Creating Wrapper " + progId);
                rtdServer = new ExcelRtd2010BugHelper(progId, rtdServerType);
            }
            else
            {
                using (XlCall.Suspend())
                {
                    rtdServer = Activator.CreateInstance(rtdServerType);
                }
                ExcelRtdServer excelRtdServer = rtdServer as ExcelRtdServer;
                if (excelRtdServer != null)
                {
                    // Set ProgId so that it can be 'unregistered' (removed from loadedRtdServers) when the RTD server terminates.
                    excelRtdServer.RegisteredProgId = progId;
                }
                else
                {
                    // Make a wrapper if we are not an ExcelRtdServer
                    // (ExcelRtdServer implements exception-handling and XLCall supension itself)
                    rtdServer = new RtdServerWrapper(rtdServer, progId);
                }
            }

            // We pick a new Guid as ClassId for this add-in...
            CLSID clsId = Guid.NewGuid();

            // ... (bad idea - this will cause Excel to try to load this RTD server while it is not registered.)
            // Guid typeGuid = GuidUtilit.CreateGuid(..., DnaLibrary.XllPath + ":" + rtdServerType.FullName);
            // or something based on ExcelDnaUtil.XllGuid
            // string progIdRegistered = "RtdSrv_" + typeGuid.ToString("N");

            // by making a fresh progId, we are sure Excel will try to load when we are ready.
            // Change from RtdSrv.xxx to RtdSrv_xxx to avoid McAfee bug that blocks registry writes with a "." anywhere
            string progIdRegistered = "RtdSrv_" + clsId.ToString("N");

            Debug.Print("RTD - Using ProgId: {0} for type: {1}", progIdRegistered, rtdServerType.FullName);

            try
            {
                using (new SingletonClassFactoryRegistration(rtdServer, clsId))
                    using (new ProgIdRegistration(progIdRegistered, clsId))
                        using (new ClsIdRegistration(clsId, progIdRegistered))
                        {
                            Debug.Print("### About to call TryCallRTD " + progId);
                            if (TryCallRTD(out result, progIdRegistered, null, topics))
                            {
                                // Mark as loaded - ServerTerminate in the wrapper will remove.
                                loadedRtdServers[progId] = progIdRegistered;
                                Debug.Print("### Added to loadedRtdServers " + progId);
                                return(true);
                            }
                            return(false);
                        }
            }
            catch (UnauthorizedAccessException secex)
            {
                Logger.RtdServer.Error("The RTD server of type {0} required by add-in {1} could not be registered.\r\nThis may be due to restricted permissions on the user's HKCU\\Software\\Classes key.\r\nError message: {2}", rtdServerType.FullName, DnaLibrary.CurrentLibrary.Name, secex.Message);
                result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue);
                // Return true to have the #VALUE stick, just as it was before the array-call refactoring
                return(true);
            }
            catch (Exception ex)
            {
                Logger.RtdServer.Error("The RTD server of type {0} required by add-in {1} could not be registered.\r\nThis is an unexpected error.\r\nError message: {2}", rtdServerType.FullName, DnaLibrary.CurrentLibrary.Name, ex.Message);
                Debug.Print("RtdRegistration.RTD exception: " + ex.ToString());
                result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue);
                // Return true to have the #VALUE stick, just as it was before the array-call refactoring
                return(true);
            }
        }
 protected internal Topic(ExcelRtdServer server, int topicId)
 {
     _server  = server;
     _topicId = topicId;
     _value   = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA);
 }
        // Understanding newValues
        // -----------------------
        // As input: If Excel has a cached value to display, newValues passed in will be false.
        // On return: if newValues is now false, Excel will use cached value if it has one, (else #N/A if passed in as true)
        //            if newValues is now true, Excel will use the value returned by ConnectData.
        object IRtdServer.ConnectData(int topicId, ref Array strings, ref bool newValues)
        {
            try
            {
                // Check for an active topic with the same topicId
                // - this is unexpected, but is reported as a bug in an earlier Excel version.
                // (Thanks ngm)

                // (Does not address the Excel 2010 bug documented here:
                // http://social.msdn.microsoft.com/Forums/en-US/exceldev/thread/ba06ac78-7b64-449b-bce4-9a03ac91f0eb/
                // fixed by hotfix: http://support.microsoft.com/kb/2405840 and SP1. This problem is fixed by the ExcelRtd2010BugHelper. )
                if (_activeTopics.ContainsKey(topicId))
                {
                    using (XlCall.Suspend())
                    {
                        ((IRtdServer)this).DisconnectData(topicId);
                    }
                }

                List <string> topicInfo = new List <string>(strings.Length);
                for (int i = 0; i < strings.Length; i++)
                {
                    topicInfo.Add((string)strings.GetValue(i));
                }

                Topic topic;
                using (XlCall.Suspend())
                {
                    // We create the topic, but what if its value is set here...?
                    topic = CreateTopic(topicId, topicInfo);
                }
                if (topic == null)
                {
                    Logger.RtdServer.Error("Error in RTD server {0} CreateTopic returned null.", GetType().Name);
                    // Not sure what to return here for error. We try the COM error version of #VALUE !?
                    return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue));
                }

                // NOTE: 2016-11-04
                //       Before v 0.34 the topic was added to _activeTopics before ConnectData was called
                //       The effect of moving it after (hence that topic is not in _activeTopics during the ConnectData call)
                //       is that a call to UpdateValue during the the ConnectData call will no longer cause an Update call to Excel
                //       (since SetDirty is ignored for topics not in _activeTopics)
                object value;
                using (XlCall.Suspend())
                {
                    value = ConnectData(topic, topicInfo, ref newValues);
                }
                _activeTopics[topicId] = topic;

                // Now we need to ensure that the topic value does indeed agree with the returned value
                // Otherwise we are left with an inconsistent state for future updates.
                // If there's a difference, we do force the update.
                if (!object.Equals(value, topic.Value))
                {
                    // 2020-03-03 v1.1
                    // Changing from topic.UpdateNotify, which no longer (in recent Excel) seems to work on its own.
                    // NOTE: In the unusual case that the FixValue inside Topic makes value == topic.Value, we won't get an update here
                    //       E.g. for long strings that are truncated
                    //
                    topic.UpdateValue(value);
                }
                return(value);
            }
            catch (Exception e)
            {
                Logger.RtdServer.Error("Error in RTD server {0} ConnectData: {1}", GetType().Name, e.ToString());
                // Not sure what to return here for error. We try the COM error version of #VALUE !?
                return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue));
            }
        }
Example #12
0
        //New RTD subscription callback
        protected override object ConnectData(Topic topic, IList <string> topicInfo, ref bool newValues)
        {
            object result;
            int    topicId = GetTopicId(topic);

            Logging.Log("ConnectData: {0} - {{{1}}}", topicId, string.Join(", ", topicInfo));

            //parse and validate request
            //---

            //count and default parameters
            if (topicInfo.Count < 2)
            {
                return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); //need at least a product and a datapoint
            }
            else if (topicInfo.Count == 2)
            {
                topicInfo.Add("0"); //default to top level of book
            }
            //parse parameters
            string product = topicInfo[0];

            DataPoint dataPoint;
            bool      dataPointOk = Enum.TryParse <DataPoint>(topicInfo[1], out dataPoint);

            int  level;
            bool levelOk = Int32.TryParse(topicInfo[2], out level);

            //return error if parameters are invalid
            if (!dataPointOk || !levelOk)
            {
                return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); //incorrect level or datapoint request
            }
            if (level > 9 || level < 0)
            {
                return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); //level out of range
            }
            //store subscription request
            //---

            //if product has not yet been subscribed, create subscription container
            if (!_topics.ContainsKey(product))
            {
                _topics[product] = new TopicCollection();
            }

            //store subscription request
            _topics[product].TopicItems[Tuple.Create(dataPoint, level)] = topic;
            if (_topicDetails.ContainsKey(topicId))
            {
                Logging.Log("ERROR: duplicate topicId: {0} {1}", topicId, _topicDetails[topicId]);
            }
            _topicDetails.Add(topicId, new TopicSubscriptionDetails(product, dataPoint, level));


            //return data
            //--
            if (dataPoint == DataPoint.Last)
            {
                if (!_cache.Instruments.ContainsKey(product))
                {
                    //may be empty when sheet is first loaded
                    result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorGettingData);             //return "pending data" to Excel
                }
                else
                {
                    //get data from cache
                    BitMexInstrument inst = _cache.Instruments[product];
                    switch (dataPoint)
                    {
                    case DataPoint.Last:
                        result = inst.lastPrice;
                        break;

                    default:
                        result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA);
                        break;
                    }
                }
            }
            else //bid/ask etc
            {
                if (!_cache.MarketData.ContainsKey(product))
                {
                    //if product is not yet in cache, request snapshot
                    //this can happen if a product doesnt update very frequently
                    _api.GetSnapshot(product);
                    result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorGettingData);             //return "pending data" to Excel
                }
                else
                {
                    //get data from cache
                    MarketDataSnapshot snap = _cache.MarketData[product];
                    switch (dataPoint)
                    {
                    case DataPoint.Bid:
                        result = snap.BidDepth[level].Price;
                        break;

                    case DataPoint.BidVol:
                        result = snap.BidDepth[level].Qty;
                        break;

                    case DataPoint.Ask:
                        result = snap.AskDepth[level].Price;
                        break;

                    case DataPoint.AskVol:
                        result = snap.AskDepth[level].Qty;
                        break;

                    default:
                        result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA);
                        break;
                    }
                }
            }

            return(result);
        }
Example #13
0
        // Forwarded from XlCall
        // Loads the RTD server with temporary ProgId.
        public static object RTD(string progId, string server, params string[] topics)
        {
            // Check if this is any of our business.
            if (!string.IsNullOrEmpty(server) || !registeredRtdServerTypes.ContainsKey(progId))
            {
                // Just pass on to Excel.
                return(CallRTD(progId, null, topics));
            }

            // Check if already loaded.
            if (loadedRtdServers.ContainsKey(progId))
            {
                // Call Excel using the synthetic RtdSrv.xxx (or actual from attribute) ProgId
                return(CallRTD(loadedRtdServers[progId], null, topics));
            }

            // Not loaded already - need to get the Rtd server loaded
            // TODO: Need to reconsider registration here.....
            //       Sometimes need stable ProgIds.
            Type   rtdServerType = registeredRtdServerTypes[progId];
            object rtdServer     = Activator.CreateInstance(rtdServerType);

            ExcelRtdServer excelRtdServer = rtdServer as ExcelRtdServer;

            if (excelRtdServer != null)
            {
                // Set ProgId so that it can be 'unregistered' (removed from loadedRtdServers) when the RTD sever terminates.
                excelRtdServer.RegisteredProgId = progId;
            }
            else
            {
                // Make a wrapper if we are not an ExcelRtdServer
                // (ExcelRtdServer implements exception-handling and XLCall supension itself)
                rtdServer = new RtdServerWrapper(rtdServer, progId);
            }

            // We pick a new Guid as ClassId for this add-in...
            CLSID clsId = Guid.NewGuid();

            // ... (bad idea - this will cause Excel to try to load this RTD server while it is not registered.)
            // Guid typeGuid = GuidUtilit.CreateGuid(..., DnaLibrary.XllPath + ":" + rtdServerType.FullName);
            // string progIdRegistered = "RtdSrv." + typeGuid.ToString("N");

            // by making a fresh progId, we are sure Excel will try to load when we are ready.
            string progIdRegistered = "RtdSrv." + clsId.ToString("N");

            Debug.Print("RTD - Using ProgId: {0} for type: {1}", progIdRegistered, rtdServerType.FullName);

            try
            {
                using (new SingletonClassFactoryRegistration(rtdServer, clsId))
                    using (new ProgIdRegistration(progIdRegistered, clsId))
                        using (new ClsIdRegistration(clsId, progIdRegistered))
                        {
                            object result;
                            if (TryCallRTD(out result, progIdRegistered, null, topics))
                            {
                                // Mark as loaded - ServerTerminate in the wrapper will remove.
                                // TODO: Consider multithread race condition...
                                loadedRtdServers[progId] = progIdRegistered;
                            }
                            return(result);
                        }
            }
            catch (UnauthorizedAccessException secex)
            {
                Logging.LogDisplay.WriteLine("The RTD server of type {0} required by add-in {1} could not be registered.\r\nThis may be due to restricted permissions on the user's HKCU\\Software\\Classes key.\r\nError message: {2}", rtdServerType.FullName, DnaLibrary.CurrentLibrary.Name, secex.Message);
                return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue));
            }
        }
Example #14
0
 object?IDataFunctions.GetLastPrice(string?symbol)
 {
     return(string.IsNullOrEmpty(symbol)
         ? ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue)
         : this.GetData("marketdata", "marketdataWindow", "last", $"symbol='{symbol}'"));
 }
Example #15
0
 public override object ToComError(ExcelError excelError)
 {
     return(ExcelErrorUtil.ToComError(excelError));
 }