Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAPI: Enable use of Legacy host when applicable. #1747

Merged
merged 12 commits into from
Dec 14, 2022
Merged
4 changes: 4 additions & 0 deletions .mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
follow_imports = skip
ignore_missing_imports = True
scripts_are_modules = True
; Without this bare `mypy <file>` may get warnings for e.g.
; `<var> = <value>`
; i.e. no typing info.
check-untyped-defs = True
; platform = darwin
12 changes: 3 additions & 9 deletions EDMarketConnector.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from os import chdir, environ
from os.path import dirname, join
from time import localtime, strftime, time
from typing import TYPE_CHECKING, Optional, Tuple, Union
from typing import TYPE_CHECKING, Literal, Optional, Tuple, Union

# Have this as early as possible for people running EDMarketConnector.exe
# from cmd.exe or a bat file or similar. Else they might not be in the correct
Expand Down Expand Up @@ -1013,11 +1013,6 @@ def capi_request_data(self, event=None) -> None: # noqa: CCR001
self.status['text'] = _('CAPI query aborted: GameVersion unknown')
return

if not monitor.is_live_galaxy():
logger.warning("Dropping CAPI request because this is the Legacy galaxy, which is not yet supported")
self.status['text'] = 'CAPI for Legacy not yet supported'
return

if not monitor.system:
logger.trace_if('capi.worker', 'Aborting Query: Current star system unknown')
# LANG: CAPI queries aborted because current star system name unknown
Expand Down Expand Up @@ -1210,8 +1205,7 @@ def capi_handle_response(self, event=None): # noqa: C901, CCR001
monitor.state['Loan'] = capi_response.capi_data['commander'].get('debt', 0)

# stuff we can do when not docked
# TODO: Use plug.notify_capi_legacy if Legacy host
err = plug.notify_newdata(capi_response.capi_data, monitor.is_beta)
err = plug.notify_capidata(capi_response.capi_data, monitor.is_beta)
self.status['text'] = err and err or ''
if err:
play_bad = True
Expand Down Expand Up @@ -1357,7 +1351,7 @@ def crewroletext(role: str) -> str:

# Ensure the ship type/name text is clickable, if it should be.
if monitor.state['Modules']:
ship_state = True
ship_state: Literal['normal', 'disabled'] = tk.NORMAL

else:
ship_state = tk.DISABLED
Expand Down
80 changes: 68 additions & 12 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ liable to change without notice.

`from prefs import prefsVersion` - to allow for versioned preferences.

`from companion import CAPIData, SERVER_LIVE, SERVER_LEGACY, SERVER_BETA` -
`CAPIData` is the actual type of `data` as passed into `cmdr_data()` and
`cmdr_data_legacy()`.
See [Commander Data from Frontier CAPI](#commander-data-from-frontier-capi))
for further information.

`import edmc_data` (or specific 'from' imports) - This contains various static
data that used to be in other files. You should **not** now import anything
from the original files unless specified as allowed in this section.
Expand Down Expand Up @@ -129,11 +135,15 @@ modules for plugin use:
- sqlite3
- zipfile

And, of course, anything in the [Python Standard Library](https://docs.python.org/3/library/)
will always be available, dependent on the version of Python we're using to
build Windows installed versions. Check the 'Startup' line in an application
[Debug Log File](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#debug-log-files)
for the version of Python being used.
Unfortunately we cannot promise to include every part of the
[Python Standard Library](https://docs.python.org/3/library/) due to issues
with correctly detecting all the modules, and if they're single file or a
package, and perhaps have sub-modules. For now, if you find something is
missing that you need for your plugin, ask us to add it in, and we'll do so on
a 'best efforts' basis.

See [#1327 - ModuleNotFound when creating a new plugin.](https://github.com/EDCD/EDMarketConnector/issues/1327)
for some discussion.


---
Expand Down Expand Up @@ -877,8 +887,14 @@ constants.
---

### Commander Data from Frontier CAPI
If a plugin has a `cmdr_data()` function it gets called when the application
has just fetched fresh Cmdr and station data from Frontier's servers, **but not
for the Legacy galaxy**. See `cmdr_data_legacy()` below for Legacy data
handling.

```python
from companion import CAPIData, SERVER_LIVE, SERVER_LEGACY, SERVER_BETA

def cmdr_data(data, is_beta):
"""
We have new data on our commander
Expand All @@ -887,18 +903,58 @@ def cmdr_data(data, is_beta):
raise ValueError("this isn't possible")

logger.info(data['commander']['name'])
```

This gets called when the application has just fetched fresh Cmdr and station
data from Frontier's servers.
# Determining source galaxy for the data
if data.source_host == SERVER_LIVE:
...

elif data.source_host == SERVER_BETA:
...

elif data.source_host == SERVER_LEGACY:
...
```

| Parameter | Type | Description |
| :-------- | :--------------: | :------------------------------------------------------------------------------------------------------- |
| `data` | `Dict[str, Any]` | `/profile` API response, with `/market` and `/shipyard` added under the keys `marketdata` and `shipdata` |
| `data` | `CAPIData` | `/profile` API response, with `/market` and `/shipyard` added under the keys `marketdata` and `shipdata` |
| `is_beta` | `bool` | If the game is currently in beta |
NB: Actually `data` is a custom type, based on `UserDict`, called `CAPIData`,
and has some extra properties. However, these are for **internal use only**
at this time, especially as there are some caveats about at least one of them.
`CAPIData` is a class, which you can `from companion import CAPIDATA`, and is
based on `UserDict`. The actual data from CAPI queries is thus accessible
via python's normal `data['key']` syntax. However, being a class, it can also
have extra properties, such as `source_host`, as shown above. Plugin authors
are free to use *that* property, **but MUST NOT rely on any other extra
properties present in `CAPIData`, they are for internal use only.**


#### CAPI data for Legacy
When CAPI data has been retrieved from the separate CAPI host for the Legacy
galaxy, because the Journal gameversion indicated the player is playing/last
played in that galaxy, a different function will be called,
`cmdr_data_legacy()`.

```python
def cmdr_data_legacy(data, is_beta):
"""
We have new data on our commander
"""
if data.get('commander') is None or data['commander'].get('name') is None:
raise ValueError("this isn't possible")

logger.info(data['commander']['name'])
```

**IF AND ONLY IF** your code definitely handles the Live/Legacy split itself
then you *may* simply:

```python
def cmdr_data_legacy(data, is_beta):
return cmdr_data(data, is_beta)
```

The core 'eddn' plugin might contain some useful hints about how to handle the
split **but do not rely on any extra properties on `data` unless they are
documented in [Available imports](#available-imports) in this document**.

---

Expand Down
25 changes: 11 additions & 14 deletions companion.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ def _(x): return x
UserDict = collections.UserDict # type: ignore # Otherwise simply use the actual class


# Define custom type for the dicts that hold CAPI data
# CAPIData = NewType('CAPIData', Dict)

capi_query_cooldown = 60 # Minimum time between (sets of) CAPI queries
capi_default_requests_timeout = 10
auth_timeout = 30 # timeout for initial auth
Expand All @@ -67,8 +64,8 @@ class CAPIData(UserDict):
def __init__(
self,
data: Union[str, Dict[str, Any], 'CAPIData', None] = None,
source_host: str = None,
source_endpoint: str = None
source_host: Optional[str] = None,
source_endpoint: Optional[str] = None
) -> None:
if data is None:
super().__init__()
Expand Down Expand Up @@ -652,7 +649,7 @@ def start_frontier_auth(self, access_token: str) -> None:
self.requests_session.headers['User-Agent'] = user_agent
self.state = Session.STATE_OK

def login(self, cmdr: str = None, is_beta: Optional[bool] = None) -> bool:
def login(self, cmdr: Optional[str] = None, is_beta: Optional[bool] = None) -> bool:
"""
Attempt oAuth2 login.

Expand Down Expand Up @@ -763,10 +760,6 @@ def capi_single_query(
:return: The resulting CAPI data, of type CAPIData.
"""
capi_data: CAPIData
if capi_host == SERVER_LEGACY:
logger.warning("Dropping CAPI request because this is the Legacy galaxy")
return capi_data

try:
logger.trace_if('capi.worker', 'Sending HTTP request...')
if conf_module.capi_pretend_down:
Expand Down Expand Up @@ -921,7 +914,6 @@ def capi_station_queries( # noqa: CCR001
break

logger.trace_if('capi.worker', f'Processing query: {query.endpoint}')
capi_data: CAPIData
try:
if query.endpoint == self._CAPI_PATH_STATION:
capi_data = capi_station_queries(query.capi_host)
Expand Down Expand Up @@ -1080,17 +1072,22 @@ def capi_host_for_galaxy(self) -> str:
"""
if self.credentials is None:
# Can't tell if beta or not
logger.warning("Dropping CAPI request because unclear if game beta or not")
return ''

if self.credentials['beta']:
logger.debug(f"Using {SERVER_BETA} because {self.credentials['beta']=}")
return SERVER_BETA

if monitor.is_live_galaxy():
logger.debug(f"Using {SERVER_LIVE} because monitor.is_live_galaxy() was True")
return SERVER_LIVE

# return SERVER_LEGACY # Not Yet
logger.warning("Dropping CAPI request because this is the Legacy galaxy, which is not yet supported")
return ""
else:
logger.debug(f"Using {SERVER_LEGACY} because monitor.is_live_galaxy() was False")
return SERVER_LEGACY

return ''
######################################################################


Expand Down
12 changes: 10 additions & 2 deletions plug.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ def notify_dashboard_entry(
return error


def notify_newdata(
def notify_capidata(
data: companion.CAPIData,
is_beta: bool
) -> Optional[str]:
Expand All @@ -383,13 +383,21 @@ def notify_newdata(
"""
error = None
for plugin in PLUGINS:
cmdr_data = plugin._get_func('cmdr_data')
# TODO: Handle it being Legacy data
if data.source_host == companion.SERVER_LEGACY:
cmdr_data = plugin._get_func('cmdr_data_legacy')

else:
cmdr_data = plugin._get_func('cmdr_data')

if cmdr_data:
try:
newerror = cmdr_data(data, is_beta)
error = error or newerror

except Exception:
logger.exception(f'Plugin "{plugin.name}" failed')

return error


Expand Down
21 changes: 20 additions & 1 deletion plugins/eddn.py
Original file line number Diff line number Diff line change
Expand Up @@ -2408,9 +2408,28 @@ def journal_entry( # noqa: C901, CCR001
return None


def cmdr_data_legacy(data: CAPIData, is_beta: bool) -> Optional[str]:
"""
Process new CAPI data for Legacy galaxy.

Ensuring the correct EDDN `header->gameversion` is achieved by use of
`EDDN.capi_gameversion_from_host_endpoint()` in:

`EDDN.export_outfitting()`
`EDDN.export_shipyard()`
`EDDN.export_outfitting()`

Thus we can just call through to the 'not Legacy' version of this function.
:param data: CAPI data to process.
:param is_beta: bool - True if this is a beta version of the Game.
:return: str - Error message, or `None` if no errors.
"""
return cmdr_data(data, is_beta)


def cmdr_data(data: CAPIData, is_beta: bool) -> Optional[str]: # noqa: CCR001
"""
Process new CAPI data.
Process new CAPI data for not-Legacy galaxy (might be beta).

:param data: CAPI data to process.
:param is_beta: bool - True if this is a beta version of the Game.
Expand Down
5 changes: 3 additions & 2 deletions td.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Export data for Trade Dangerous."""

import pathlib
import sys
import time
from collections import defaultdict
from operator import itemgetter
Expand Down Expand Up @@ -29,9 +30,9 @@ def export(data: CAPIData) -> None:
# codecs can't automatically handle line endings, so encode manually where
# required
with open(data_path / data_filename, 'wb') as h:
# Format described here: https://bitbucket.org/kfsone/tradedangerous/wiki/Price%20Data
# Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data
h.write('#! trade.py import -\n'.encode('utf-8'))
this_platform = 'darwin' and "Mac OS" or system()
this_platform = sys.platform == 'darwin' and "Mac OS" or system()
cmdr_name = data['commander']['name'].strip()
h.write(
f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8')
Expand Down