Пример #1
0
        public void SendQueuedAPIEvents(bool eddiIsBeta)
        {
            List <InaraAPIEvent> queue = new List <InaraAPIEvent>();

            // The `GetConsumingEnumerable` method blocks the thread while the underlying collection is empty
            // If we haven't extracted events to send to Inara, this will wait / pause background sync until `queuedAPIEvents` is no longer empty.
            foreach (var pendingEvent in queuedAPIEvents.GetConsumingEnumerable())
            {
                queue.Add(pendingEvent);
                if (queue.Count > 0 && queuedAPIEvents.Count == 0)
                {
                    break;
                }
            }
            InaraConfiguration inaraConfiguration = InaraConfiguration.FromFile();

            if (checkAPIcredentialsOk(inaraConfiguration))
            {
                var responses = SendEventBatch(queue, inaraConfiguration, eddiIsBeta);
                if (responses != null && responses.Count > 0)
                {
                    inaraConfiguration.lastSync = queue.Max(e => e.eventTimeStamp);
                    inaraConfiguration.ToFile();
                }
            }
        }
Пример #2
0
        /// <summary>
        /// Obtain credentials from a file.  If the file name is not supplied the the default
        /// path of Constants.Data_DIR\inara.json is used
        /// </summary>
        public static InaraConfiguration FromFile(string filename = null)
        {
            if (filename == null)
            {
                filename = Constants.DATA_DIR + @"\inara.json";
            }

            InaraConfiguration configuration = new InaraConfiguration();

            if (File.Exists(filename))
            {
                try
                {
                    string data = Files.Read(filename);
                    if (data != null)
                    {
                        configuration = JsonConvert.DeserializeObject <InaraConfiguration>(data);
                    }
                }
                catch (Exception ex)
                {
                    Logging.Debug("Failed to read Inara configuration", ex);
                }
            }
            if (configuration == null)
            {
                configuration = new InaraConfiguration();
            }

            configuration.dataPath = filename;
            return(configuration);
        }
Пример #3
0
        public void SendAPIEvents(List <InaraAPIEvent> queue)
        {
            InaraConfiguration inaraConfiguration = InaraConfiguration.FromFile();

            if (checkAPIcredentialsOk(inaraConfiguration))
            {
                var responses = SendEventBatch(queue, inaraConfiguration);
                if (responses != null && responses.Count > 0)
                {
                    inaraConfiguration.lastSync = queue.Max(e => e.eventTimeStamp);
                    inaraConfiguration.ToFile();
                }
            }
        }
Пример #4
0
        public async void SendQueuedAPIEventsAsync()
        {
            List <InaraAPIEvent> queue = new List <InaraAPIEvent>();

            while (Instance.queuedAPIEvents.TryDequeue(out InaraAPIEvent pendingEvent))
            {
                queue.Add(pendingEvent);
            }
            if (queue.Count > 0)
            {
                await Task.Run(() => Instance.SendEventBatch(ref queue));

                Instance.lastSync = queue.Max(e => e.eventTimeStamp);
                InaraConfiguration inaraConfiguration = InaraConfiguration.FromFile();
                inaraConfiguration.lastSync = Instance.lastSync;
                inaraConfiguration.ToFile();
            }
        }
Пример #5
0
 private bool checkAPIcredentialsOk(InaraConfiguration inaraConfiguration)
 {
     if (!inaraConfiguration.isAPIkeyValid)
     {
         Logging.Warn("Background sync skipped: API key is invalid.");
         invalidAPIkey?.Invoke(inaraConfiguration, new EventArgs());
         return(false);
     }
     if (string.IsNullOrEmpty(inaraConfiguration.apiKey))
     {
         Logging.Info("Background sync skipped: API key not set.");
         return(false);
     }
     if (string.IsNullOrEmpty(inaraConfiguration.commanderName))
     {
         Logging.Debug("Background sync skipped: Commander name not set.");
         return(false);
     }
     return(true);
 }
Пример #6
0
        protected InaraService()
        {
            // Set up the Inara service
            InaraConfiguration inaraCredentials = InaraConfiguration.FromFile();

            if (inaraCredentials == null)
            {
                return;
            }

            // commanderName: In-game CMDR name of user (not set by user, get this from journals or
            // cAPI to ensure it is a correct in-game name to avoid future problems). It is recommended
            // to be always set when no generic API key is used (otherwise some events may not work).
            commanderName = inaraCredentials.commanderName;

            // commanderFrontierID: Commander's unique Frontier ID (is provided by journals since 3.3)
            // in the format: 'F123456'. When not known, set nothing.
            commanderFrontierID = inaraCredentials.commanderFrontierID;

            lastSync = inaraCredentials.lastSync;
            apiKey   = inaraCredentials.apiKey;
            if (!string.IsNullOrEmpty(apiKey) && !string.IsNullOrEmpty(commanderName))
            {
                // fully configured
                Logging.Info("Configuring EDDI access to Inara profile data");
            }
            else
            {
                apiKey = readonlyAPIkey;
                if (string.IsNullOrEmpty(inaraCredentials.apiKey))
                {
                    Logging.Info("Configuring Inara service for limited access: API key not set.");
                }

                if (string.IsNullOrEmpty(commanderName))
                {
                    Logging.Info("Configuring Inara service for limited access: Commander name not detected.");
                }
            }
        }
Пример #7
0
        public List <InaraCmdr> GetCommanderProfiles(IEnumerable <string> cmdrNames)
        {
            List <InaraCmdr> cmdrs = new List <InaraCmdr>();

            List <InaraAPIEvent> events = new List <InaraAPIEvent>();

            foreach (string cmdrName in cmdrNames)
            {
                events.Add(new InaraAPIEvent(DateTime.UtcNow, "getCommanderProfile", new Dictionary <string, object>()
                {
                    { "searchName", cmdrName }
                }));
            }
            var responses = SendEventBatch(events, InaraConfiguration.FromFile());

            foreach (InaraResponse inaraResponse in responses)
            {
                string jsonCmdr = JsonConvert.SerializeObject(inaraResponse.eventData);
                cmdrs.Add(JsonConvert.DeserializeObject <InaraCmdr>(jsonCmdr));
            }
            return(cmdrs);
        }
Пример #8
0
        private bool validateResponse(InaraResponse inaraResponse, List <InaraAPIEvent> indexedEvents, bool header = false)
        {
            // 200 - Ok
            if (inaraResponse.eventStatus == 200)
            {
                return(true);
            }

            // Anything else - something is wrong.
            Dictionary <string, object> data = new Dictionary <string, object>()
            {
                { "InaraAPIEvent", indexedEvents.Find(e => e.eventCustomID == inaraResponse.eventCustomID) },
                { "InaraResponse", inaraResponse },
                { "Stacktrace", new StackTrace() }
            };

            try
            {
                // 202 - Warning (everything is OK, but there may be multiple results for the input properties, etc.)
                // 204 - 'Soft' error (everything was formally OK, but there are no results for the properties set, etc.)
                if (inaraResponse.eventStatus == 202 || inaraResponse.eventStatus == 204)
                {
                    Logging.Warn("Inara responded with: " + (inaraResponse.eventStatusText ?? "(No response)"), JsonConvert.SerializeObject(data));
                }
                // Other errors
                else if (!string.IsNullOrEmpty(inaraResponse.eventStatusText))
                {
                    if (header)
                    {
                        Logging.Warn("Inara responded with: " + (inaraResponse.eventStatusText ?? "(No response)"), JsonConvert.SerializeObject(data));
                        if (inaraResponse.eventStatusText.Contains("Invalid API key"))
                        {
                            ReEnqueueAPIEvents(indexedEvents);
                            // The Inara API key has been rejected. We'll note and remember that.
                            InaraConfiguration inaraConfiguration = InaraConfiguration.FromFile();
                            inaraConfiguration.isAPIkeyValid = false;
                            inaraConfiguration.ToFile();
                            // Send internal events to the Inara Responder and the UI to handle the invalid API key appropriately
                            invalidAPIkey?.Invoke(inaraConfiguration, new EventArgs());
                        }
                        else if (inaraResponse.eventStatusText.Contains("access to API was temporarily revoked"))
                        {
                            // Note: This can be thrown by over-use of the readonly API key.
                            ReEnqueueAPIEvents(indexedEvents);
                            tooManyRequests = true;
                        }
                    }
                    else
                    {
                        // There may be an issue with a specific API event.
                        // We'll add that API event to a list and omit sending that event again in this instance.
                        Logging.Error("Inara responded with: " + inaraResponse.eventStatusText, data);
                        invalidAPIEvents.Add(indexedEvents.Find(e => e.eventCustomID == inaraResponse.eventCustomID).eventName);
                    }
                }
                else
                {
                    // Inara responded, but no status text description was given.
                    Logging.Error("Inara responded with: ", data);
                }
                return(false);
            }
            catch (Exception e)
            {
                data.Add("Exception", e);
                Logging.Error("Failed to handle Inara server response", data);
                return(false);
            }
        }
Пример #9
0
        private List <InaraAPIEvent> IndexAndFilterAPIEvents(List <InaraAPIEvent> events, InaraConfiguration inaraConfiguration)
        {
            // Flag each event with a unique ID we can use when processing responses
            List <InaraAPIEvent> indexedEvents = new List <InaraAPIEvent>();

            for (int i = 0; i < events.Count; i++)
            {
                InaraAPIEvent indexedEvent = events[i];
                indexedEvent.eventCustomID = i;

                // Exclude and discard events with issues that have returned a code 400 error in this instance.
                if (invalidAPIEvents.Contains(indexedEvent.eventName))
                {
                    continue;
                }

                // Exclude and discard old / stale events
                if (inaraConfiguration?.lastSync > indexedEvent.eventTimeStamp)
                {
                    continue;
                }

                // Inara will ignore the "setCommunityGoal" event while EDDI is in development mode (i.e. beta).
                if (indexedEvent.eventName == "setCommunityGoal" && eddiIsBeta)
                {
                    continue;
                }

                // Note: the Inara Responder does not queue events while the game is in beta.

                indexedEvents.Add(indexedEvent);
            }

            return(indexedEvents);
        }
Пример #10
0
        // If you need to do some testing on Inara's API, please set the `isDeveloped` boolean header property to true.
        public List <InaraResponse> SendEventBatch(List <InaraAPIEvent> events, InaraConfiguration inaraConfiguration)
        {
            // We always want to return a list from this method (even if it's an empty list) rather than a null value.
            List <InaraResponse> inaraResponses = new List <InaraResponse>();

            try
            {
                if (inaraConfiguration is null)
                {
                    inaraConfiguration = InaraConfiguration.FromFile();
                }
                List <InaraAPIEvent> indexedEvents = IndexAndFilterAPIEvents(events, inaraConfiguration);
                if (indexedEvents.Count > 0)
                {
                    var           client       = new RestClient("https://inara.cz/inapi/v1/");
                    var           request      = new RestRequest(Method.POST);
                    InaraSendJson inaraRequest = new InaraSendJson()
                    {
                        header = new Dictionary <string, object>()
                        {
                            // Per private conversation with Artie and per Inara API docs, the `isDeveloped` property
                            // should (counterintuitively) be set to true when the an application is in development.
                            // Quote: `it's "true" because the app "is (being) developed"`
                            // Quote: `isDeveloped is meant as "the app is currently being developed and may be broken`
                            { "appName", "EDDI" },
                            { "appVersion", Constants.EDDI_VERSION.ToString() },
                            { "isDeveloped", eddiIsBeta },
                            { "commanderName", !string.IsNullOrEmpty(inaraConfiguration?.commanderName) ? inaraConfiguration.commanderName : (eddiIsBeta ? "TestCmdr" : null) },
                            { "commanderFrontierID", !string.IsNullOrEmpty(inaraConfiguration?.commanderFrontierID) ? inaraConfiguration.commanderFrontierID : null },
                            { "APIkey", !string.IsNullOrEmpty(inaraConfiguration?.apiKey) ? inaraConfiguration.apiKey : readonlyAPIkey }
                        },
                        events = indexedEvents
                    };
                    request.RequestFormat = DataFormat.Json;
                    request.AddBody(inaraRequest); // uses JsonSerializer

                    Logging.Debug("Sending to Inara: " + client.BuildUri(request).AbsoluteUri);
                    var clientResponse = client.Execute <InaraResponses>(request);
                    if (clientResponse.IsSuccessful)
                    {
                        InaraResponses response = clientResponse.Data;
                        if (validateResponse(response.header, indexedEvents, true))
                        {
                            foreach (InaraResponse inaraResponse in response.events)
                            {
                                if (validateResponse(inaraResponse, indexedEvents))
                                {
                                    inaraResponses.Add(inaraResponse);
                                }
                            }
                        }
                    }
                    else
                    {
                        // Inara may return null as it undergoes a nightly maintenance cycle where the servers go offline temporarily.
                        Logging.Warn("Unable to connect to the Inara server.", clientResponse.ErrorMessage);
                        ReEnqueueAPIEvents(events);
                    }
                }
            }
            catch (Exception ex)
            {
                Logging.Error("Sending data to the Inara server failed.", ex);
            }
            return(inaraResponses);
        }
Пример #11
0
        private List <InaraAPIEvent> IndexAndFilterAPIEvents(List <InaraAPIEvent> events, InaraConfiguration inaraConfiguration)
        {
            // If we don't have a commander name then only use `get` events and re-enqueue the rest.
            if (string.IsNullOrEmpty(inaraConfiguration.commanderName))
            {
                var commanderUpdateEvents = events.Where(e => !e.eventName.StartsWith("get")).ToList();
                ReEnqueueAPIEvents(commanderUpdateEvents);
                events = events.Except(commanderUpdateEvents).ToList();
            }

            // Flag each event with a unique ID we can use when processing responses
            List <InaraAPIEvent> indexedEvents = new List <InaraAPIEvent>();

            for (int i = 0; i < events.Count; i++)
            {
                InaraAPIEvent indexedEvent = events[i];
                indexedEvent.eventCustomID = i;

                // Exclude and discard events with issues that have returned a code 400 error in this instance.
                if (invalidAPIEvents.Contains(indexedEvent.eventName))
                {
                    continue;
                }

                // Exclude and discard old / stale events
                if (inaraConfiguration?.lastSync > indexedEvent.eventTimestamp)
                {
                    continue;
                }

                // Inara will ignore the "setCommunityGoal" event while EDDI is in development mode (i.e. beta).
                if (indexedEvent.eventName == "setCommunityGoal" && eddiIsBeta)
                {
                    continue;
                }

                // Note: the Inara Responder does not queue events while the game is in beta.

                indexedEvents.Add(indexedEvent);
            }

            return(indexedEvents);
        }