tradedangerous 11.2.1__py3-none-any.whl → 11.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tradedangerous might be problematic. Click here for more details.
- tradedangerous/cache.py +35 -18
- tradedangerous/formatting.py +1 -2
- tradedangerous/fs.py +38 -2
- tradedangerous/misc/progress.py +164 -56
- tradedangerous/plugins/eddblink_plug.py +43 -54
- tradedangerous/templates/TradeDangerous.sql +37 -4
- tradedangerous/tradecalc.py +156 -159
- tradedangerous/transfers.py +26 -60
- tradedangerous/version.py +1 -1
- {tradedangerous-11.2.1.dist-info → tradedangerous-11.3.0.dist-info}/METADATA +1 -1
- {tradedangerous-11.2.1.dist-info → tradedangerous-11.3.0.dist-info}/RECORD +15 -15
- {tradedangerous-11.2.1.dist-info → tradedangerous-11.3.0.dist-info}/LICENSE +0 -0
- {tradedangerous-11.2.1.dist-info → tradedangerous-11.3.0.dist-info}/WHEEL +0 -0
- {tradedangerous-11.2.1.dist-info → tradedangerous-11.3.0.dist-info}/entry_points.txt +0 -0
- {tradedangerous-11.2.1.dist-info → tradedangerous-11.3.0.dist-info}/top_level.txt +0 -0
tradedangerous/cache.py
CHANGED
|
@@ -30,14 +30,17 @@ import sqlite3
|
|
|
30
30
|
import sys
|
|
31
31
|
import typing
|
|
32
32
|
|
|
33
|
+
from functools import partial as partial_fn
|
|
34
|
+
from .fs import file_line_count
|
|
33
35
|
from .tradeexcept import TradeException
|
|
36
|
+
from tradedangerous.misc.progress import Progress, CountingBar
|
|
34
37
|
from . import corrections, utils
|
|
35
38
|
from . import prices
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
# For mypy/pylint type checking
|
|
39
42
|
if typing.TYPE_CHECKING:
|
|
40
|
-
from typing import Any, Optional, TextIO
|
|
43
|
+
from typing import Any, Callable, Optional, TextIO # noqa
|
|
41
44
|
|
|
42
45
|
from .tradeenv import TradeEnv
|
|
43
46
|
|
|
@@ -777,11 +780,14 @@ def deprecationCheckItem(importPath, lineNo, line):
|
|
|
777
780
|
)
|
|
778
781
|
|
|
779
782
|
|
|
780
|
-
def processImportFile(tdenv, db, importPath, tableName):
|
|
783
|
+
def processImportFile(tdenv, db, importPath, tableName, *, line_callback: Optional[Callable] = None, call_args: Optional[dict] = None):
|
|
781
784
|
tdenv.DEBUG0(
|
|
782
785
|
"Processing import file '{}' for table '{}'",
|
|
783
786
|
str(importPath), tableName
|
|
784
787
|
)
|
|
788
|
+
call_args = call_args or {}
|
|
789
|
+
if line_callback:
|
|
790
|
+
line_callback = partial_fn(line_callback, **call_args)
|
|
785
791
|
|
|
786
792
|
fkeySelectStr = (
|
|
787
793
|
"("
|
|
@@ -870,6 +876,8 @@ def processImportFile(tdenv, db, importPath, tableName):
|
|
|
870
876
|
uniqueIndex = {}
|
|
871
877
|
|
|
872
878
|
for linein in csvin:
|
|
879
|
+
if line_callback:
|
|
880
|
+
line_callback()
|
|
873
881
|
if not linein:
|
|
874
882
|
continue
|
|
875
883
|
lineNo = csvin.line_num
|
|
@@ -977,25 +985,34 @@ def buildCache(tdb, tdenv):
|
|
|
977
985
|
tempDB.executescript(sqlScript)
|
|
978
986
|
|
|
979
987
|
# import standard tables
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
988
|
+
with Progress(max_value=len(tdb.importTables) + 1, prefix="Importing", width=25, style=CountingBar) as prog:
|
|
989
|
+
for importName, importTable in tdb.importTables:
|
|
990
|
+
import_path = Path(importName)
|
|
991
|
+
import_lines = file_line_count(import_path, missing_ok=True)
|
|
992
|
+
with prog.sub_task(max_value=import_lines, description=importTable) as child:
|
|
993
|
+
prog.increment(value=1)
|
|
994
|
+
call_args = {'task': child, 'advance': 1}
|
|
995
|
+
try:
|
|
996
|
+
processImportFile(tdenv, tempDB, import_path, importTable, line_callback=prog.update_task, call_args=call_args)
|
|
997
|
+
except FileNotFoundError:
|
|
998
|
+
tdenv.DEBUG0(
|
|
999
|
+
"WARNING: processImportFile found no {} file", importName
|
|
1000
|
+
)
|
|
1001
|
+
except StopIteration:
|
|
1002
|
+
tdenv.NOTE(
|
|
1003
|
+
"{} exists but is empty. "
|
|
1004
|
+
"Remove it or add the column definition line.",
|
|
1005
|
+
importName
|
|
1006
|
+
)
|
|
1007
|
+
prog.increment(1)
|
|
1008
|
+
|
|
1009
|
+
with prog.sub_task(description="Save DB"):
|
|
1010
|
+
tempDB.commit()
|
|
995
1011
|
|
|
996
1012
|
# Parse the prices file
|
|
997
1013
|
if pricesPath.exists():
|
|
998
|
-
|
|
1014
|
+
with Progress(max_value=None, width=25, prefix="Processing prices file"):
|
|
1015
|
+
processPricesFile(tdenv, tempDB, pricesPath)
|
|
999
1016
|
else:
|
|
1000
1017
|
tdenv.NOTE(
|
|
1001
1018
|
"Missing \"{}\" file - no price data.",
|
tradedangerous/formatting.py
CHANGED
tradedangerous/fs.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""This module should handle filesystem related operations
|
|
2
2
|
"""
|
|
3
3
|
from shutil import copy as shcopy
|
|
4
|
-
from os import makedirs
|
|
4
|
+
from os import makedirs, PathLike
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
__all__ = ['copy', 'copyallfiles', 'touch', 'ensurefolder']
|
|
7
|
+
__all__ = ['copy', 'copyallfiles', 'touch', 'ensurefolder', 'file_line_count']
|
|
8
8
|
|
|
9
9
|
def pathify(*args):
|
|
10
10
|
if len(args) > 1 or not isinstance(args[0], Path):
|
|
@@ -99,3 +99,39 @@ def ensurefolder(folder):
|
|
|
99
99
|
except FileExistsError:
|
|
100
100
|
pass
|
|
101
101
|
return folderPath.resolve()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def file_line_count(from_file: PathLike, buf_size: int = 128 * 1024, *, missing_ok: bool = False) -> int:
|
|
105
|
+
""" counts the number of newline characters in a given file. """
|
|
106
|
+
if not isinstance(from_file, Path):
|
|
107
|
+
from_file = Path(from_file)
|
|
108
|
+
|
|
109
|
+
if missing_ok and not from_file.exists():
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
# Pre-allocate a buffer so that we aren't putting pressure on the garbage collector.
|
|
113
|
+
buf = bytearray(buf_size)
|
|
114
|
+
|
|
115
|
+
# Capture it's counting method, so we don't have to keep looking that up on
|
|
116
|
+
# large files.
|
|
117
|
+
counter = buf.count
|
|
118
|
+
|
|
119
|
+
total = 0
|
|
120
|
+
with from_file.open("rb") as fh:
|
|
121
|
+
# Capture the 'readinto' method to avoid lookups.
|
|
122
|
+
reader = fh.readinto
|
|
123
|
+
|
|
124
|
+
# read into the buffer and capture the number of bytes fetched,
|
|
125
|
+
# which will be 'size' until the last read from the file.
|
|
126
|
+
read = reader(buf)
|
|
127
|
+
while read == buf_size: # nominal case for large files
|
|
128
|
+
total += counter(b'\n')
|
|
129
|
+
read = reader(buf)
|
|
130
|
+
|
|
131
|
+
# when 0 <= read < buf_size we're on the last page of the
|
|
132
|
+
# file, so we need to take a slice of the buffer, which creates
|
|
133
|
+
# a new object, thus we also have to lookup count. it's trivial
|
|
134
|
+
# but if you have to do it 10,000x it's definitely not a rounding error.
|
|
135
|
+
return total + buf[:read].count(b'\n')
|
|
136
|
+
|
|
137
|
+
|
tradedangerous/misc/progress.py
CHANGED
|
@@ -1,71 +1,179 @@
|
|
|
1
|
-
import
|
|
1
|
+
from rich.progress import (
|
|
2
|
+
Progress as RichProgress,
|
|
3
|
+
TaskID,
|
|
4
|
+
ProgressColumn,
|
|
5
|
+
BarColumn, DownloadColumn, MofNCompleteColumn, SpinnerColumn,
|
|
6
|
+
TaskProgressColumn, TextColumn, TimeElapsedColumn, TimeRemainingColumn,
|
|
7
|
+
TransferSpeedColumn
|
|
8
|
+
)
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
|
|
11
|
+
from typing import Iterable, Optional, Union, Type # noqa
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BarStyle:
|
|
15
|
+
""" Base class for Progress bar style types. """
|
|
16
|
+
def __init__(self, width: int = 10, prefix: Optional[str] = None, *, add_columns: Optional[Iterable[ProgressColumn]]):
|
|
17
|
+
self.columns = [SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(bar_width=width)]
|
|
18
|
+
if add_columns:
|
|
19
|
+
self.columns.extend(add_columns)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CountingBar(BarStyle):
|
|
23
|
+
""" Creates a progress bar that is counting M/N items to completion. """
|
|
24
|
+
def __init__(self, width: int = 10, prefix: Optional[str] = None):
|
|
25
|
+
my_columns = [MofNCompleteColumn(), TimeElapsedColumn()]
|
|
26
|
+
super().__init__(width, prefix, add_columns=my_columns)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DefaultBar(BarStyle):
|
|
30
|
+
""" Creates a simple default progress bar with a percentage and time elapsed. """
|
|
31
|
+
def __init__(self, width: int = 10, prefix: Optional[str] = None):
|
|
32
|
+
my_columns = [TaskProgressColumn(), TimeElapsedColumn()]
|
|
33
|
+
super().__init__(width, prefix, add_columns=my_columns)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LongRunningCountBar(BarStyle):
|
|
37
|
+
""" Creates a progress bar that is counting M/N items to completion with a time-remaining counter """
|
|
38
|
+
def __init__(self, width: int = 10, prefix: Optional[str] = None):
|
|
39
|
+
my_columns = [MofNCompleteColumn(), TimeElapsedColumn(), TimeRemainingColumn()]
|
|
40
|
+
super().__init__(width, prefix, add_columns=my_columns)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TransferBar(BarStyle):
|
|
44
|
+
""" Creates a progress bar representing a data transfer, which shows the amount of
|
|
45
|
+
data transferred, speed, and estimated time remaining. """
|
|
46
|
+
def __init__(self, width: int = 16, prefix: Optional[str] = None):
|
|
47
|
+
my_columns = [DownloadColumn(), TransferSpeedColumn(), TimeRemainingColumn()]
|
|
48
|
+
super().__init__(width, prefix, add_columns=my_columns)
|
|
49
|
+
|
|
2
50
|
|
|
3
51
|
class Progress:
|
|
4
52
|
"""
|
|
5
|
-
|
|
53
|
+
Facade around the rich Progress bar system to help transition away from
|
|
54
|
+
TD's original basic progress bar implementation.
|
|
6
55
|
"""
|
|
7
|
-
|
|
8
|
-
|
|
56
|
+
def __init__(self,
|
|
57
|
+
max_value: Optional[float] = None,
|
|
58
|
+
width: Optional[int] = None,
|
|
59
|
+
start: float = 0,
|
|
60
|
+
prefix: Optional[str] = None,
|
|
61
|
+
*,
|
|
62
|
+
style: Optional[Type[BarStyle]] = None,
|
|
63
|
+
show: bool = True,
|
|
64
|
+
) -> None:
|
|
9
65
|
"""
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Initial value
|
|
17
|
-
prefix
|
|
18
|
-
Something to print infront of the progress bar
|
|
66
|
+
:param max_value: Last value we can reach (100%).
|
|
67
|
+
:param width: How wide to make the bar itself.
|
|
68
|
+
:param start: Override initial value to non-zero.
|
|
69
|
+
:param prefix: Text to print between the spinner and the bar.
|
|
70
|
+
:param style: Bar-style factory to use for styling.
|
|
71
|
+
:param show: If False, disables the bar entirely.
|
|
19
72
|
"""
|
|
20
|
-
self.
|
|
21
|
-
|
|
22
|
-
|
|
73
|
+
self.show = bool(show)
|
|
74
|
+
if not show:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
if style is None:
|
|
78
|
+
style = DefaultBar
|
|
79
|
+
|
|
80
|
+
self.max_value = 0 if max_value is None else max(max_value, start)
|
|
23
81
|
self.value = start
|
|
24
|
-
self.
|
|
25
|
-
self.
|
|
26
|
-
|
|
27
|
-
|
|
82
|
+
self.prefix = prefix or ""
|
|
83
|
+
self.width = width or 25
|
|
84
|
+
# The 'Progress' itself is a view for displaying the progress of tasks. So we construct it
|
|
85
|
+
# and then create a task for our job.
|
|
86
|
+
style_instance = style(width=self.width, prefix=self.prefix)
|
|
87
|
+
self.progress = RichProgress(
|
|
88
|
+
# What fields to display.
|
|
89
|
+
*style_instance.columns,
|
|
90
|
+
# Hide it once it's finished, update it for us, 4x a second
|
|
91
|
+
transient=True, auto_refresh=True, refresh_per_second=5
|
|
92
|
+
)
|
|
28
93
|
|
|
94
|
+
# Now we add an actual task to track progress on.
|
|
95
|
+
self.task = self.progress.add_task("Working...", total=max_value, start=True)
|
|
96
|
+
if self.value:
|
|
97
|
+
self.progress.update(self.task, advance=self.value)
|
|
29
98
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Attributes:
|
|
36
|
-
value
|
|
37
|
-
The amount to increment the internal counter by
|
|
38
|
-
postfix [optional]
|
|
39
|
-
String or callable to print after the bar
|
|
99
|
+
# And show the task tracker.
|
|
100
|
+
self.progress.start()
|
|
101
|
+
|
|
102
|
+
def __enter__(self):
|
|
103
|
+
""" Context manager.
|
|
40
104
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
105
|
+
Example use:
|
|
106
|
+
|
|
107
|
+
import time
|
|
108
|
+
import tradedangerous.progress
|
|
109
|
+
|
|
110
|
+
# Progress(max_value=100, width=32, style=progress.CountingBar)
|
|
111
|
+
with progress.Progress(100, 32, style=progress.CountingBar) as prog:
|
|
112
|
+
for i in range(100):
|
|
113
|
+
prog.increment(1)
|
|
114
|
+
time.sleep(3)
|
|
44
115
|
"""
|
|
45
|
-
|
|
46
|
-
progress = int(self.width * (self.value - self.start) / self.maxValue)
|
|
47
|
-
if progress == self.progress:
|
|
48
|
-
return False
|
|
49
|
-
|
|
50
|
-
if callable(postfix):
|
|
51
|
-
postfixText = postfix(self.value, self.maxValue)
|
|
52
|
-
else:
|
|
53
|
-
postfixText = postfix
|
|
54
|
-
text = self.mask.format("="*progress) + postfixText + " "
|
|
55
|
-
sys.stdout.write(text)
|
|
56
|
-
sys.stdout.flush()
|
|
57
|
-
|
|
58
|
-
self.progress = progress
|
|
59
|
-
self.textLen = len(text)
|
|
60
|
-
|
|
61
|
-
return True
|
|
116
|
+
return self
|
|
62
117
|
|
|
118
|
+
def __exit__(self, *args, **kwargs):
|
|
119
|
+
self.clear()
|
|
63
120
|
|
|
64
|
-
def
|
|
121
|
+
def increment(self, value: Optional[float] = None, description: Optional[str] = None, *, progress: Optional[float] = None) -> None:
|
|
65
122
|
"""
|
|
66
|
-
|
|
123
|
+
Increase the progress of the bar by a given amount.
|
|
124
|
+
|
|
125
|
+
:param value: How much to increase the progress by.
|
|
126
|
+
:param description: If set, replaces the task description.
|
|
127
|
+
:param progress: Instead of increasing by value, set the absolute progress to this.
|
|
67
128
|
"""
|
|
68
|
-
if self.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
129
|
+
if not self.show:
|
|
130
|
+
return
|
|
131
|
+
if description:
|
|
132
|
+
self.prefix = description
|
|
133
|
+
self.progress.update(self.task, description=description, refresh=True)
|
|
134
|
+
|
|
135
|
+
bump = False
|
|
136
|
+
if not value and progress is not None and self.value != progress:
|
|
137
|
+
self.value = progress
|
|
138
|
+
bump = True
|
|
139
|
+
elif value:
|
|
140
|
+
self.value += value # Update our internal count
|
|
141
|
+
bump = True
|
|
142
|
+
|
|
143
|
+
if self.value >= self.max_value: # Did we go past the end? Increase the end.
|
|
144
|
+
self.max_value += value * 2
|
|
145
|
+
self.progress.update(self.task, description=self.prefix, total=self.max_value)
|
|
146
|
+
bump = True
|
|
147
|
+
|
|
148
|
+
if bump and self.max_value > 0:
|
|
149
|
+
self.progress.update(self.task, description=self.prefix, completed=self.value)
|
|
150
|
+
|
|
151
|
+
def clear(self) -> None:
|
|
152
|
+
""" Remove the current progress bar, if any. """
|
|
153
|
+
# These two shouldn't happen separately, but incase someone tinkers, test each
|
|
154
|
+
# separately and shut them down.
|
|
155
|
+
if not self.show:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
if self.task:
|
|
159
|
+
self.progress.remove_task(self.task)
|
|
160
|
+
self.task = None
|
|
161
|
+
|
|
162
|
+
if self.progress:
|
|
163
|
+
self.progress.stop()
|
|
164
|
+
self.progress = None
|
|
165
|
+
|
|
166
|
+
@contextmanager
|
|
167
|
+
def sub_task(self, description: str, max_value: Optional[int] = None, width: int = 25):
|
|
168
|
+
if not self.show:
|
|
169
|
+
yield
|
|
170
|
+
return
|
|
171
|
+
task = self.progress.add_task(description, total=max_value, start=True, width=width)
|
|
172
|
+
try:
|
|
173
|
+
yield task
|
|
174
|
+
finally:
|
|
175
|
+
self.progress.remove_task(task)
|
|
176
|
+
|
|
177
|
+
def update_task(self, task: TaskID, advance: Union[float, int], description: Optional[str] = None):
|
|
178
|
+
if self.show:
|
|
179
|
+
self.progress.update(task, advance=advance, description=description)
|
|
@@ -4,19 +4,18 @@ https://elite.tromador.com/ to update the Database.
|
|
|
4
4
|
"""
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
from email.utils import parsedate_to_datetime
|
|
7
8
|
from pathlib import Path
|
|
9
|
+
from .. fs import file_line_count
|
|
8
10
|
from .. import plugins, cache, transfers
|
|
9
11
|
from ..misc import progress as pbar
|
|
10
12
|
from ..plugins import PluginException
|
|
11
13
|
|
|
12
|
-
import certifi
|
|
13
14
|
import csv
|
|
14
15
|
import datetime
|
|
15
|
-
from email.utils import parsedate_to_datetime
|
|
16
16
|
import os
|
|
17
17
|
import requests
|
|
18
18
|
import sqlite3
|
|
19
|
-
import ssl
|
|
20
19
|
import typing
|
|
21
20
|
|
|
22
21
|
|
|
@@ -26,41 +25,12 @@ if typing.TYPE_CHECKING:
|
|
|
26
25
|
|
|
27
26
|
# Constants
|
|
28
27
|
BASE_URL = os.environ.get('TD_SERVER') or "https://elite.tromador.com/files/"
|
|
29
|
-
CONTEXT=ssl.create_default_context(cafile=certifi.where())
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
class DecodingError(PluginException):
|
|
33
31
|
pass
|
|
34
32
|
|
|
35
33
|
|
|
36
|
-
def _file_line_count(from_file: Path, bufsize: int = 128 * 1024) -> int:
|
|
37
|
-
""" counts the number of newline characters in a given file. """
|
|
38
|
-
# Pre-allocate a buffer so we aren't putting pressure on the garbage collector.
|
|
39
|
-
buf = bytearray(bufsize)
|
|
40
|
-
|
|
41
|
-
# Capture it's counting method, so we don't have to keep looking that up on
|
|
42
|
-
# large files.
|
|
43
|
-
counter = buf.count
|
|
44
|
-
|
|
45
|
-
total = 0
|
|
46
|
-
with from_file.open("rb") as fh:
|
|
47
|
-
# Capture the 'readinto' method to avoid lookups.
|
|
48
|
-
reader = fh.readinto
|
|
49
|
-
|
|
50
|
-
# read into the buffer and capture the number of bytes fetched,
|
|
51
|
-
# which will be 'size' until the last read from the file.
|
|
52
|
-
read = reader(buf)
|
|
53
|
-
while read == bufsize: # nominal case for large files
|
|
54
|
-
total += counter(b'\n')
|
|
55
|
-
read = reader(buf)
|
|
56
|
-
|
|
57
|
-
# when 0 <= read < bufsize we're on the last page of the
|
|
58
|
-
# file, so we need to take a slice of the buffer, which creates
|
|
59
|
-
# a new object and thus we also have to lookup count. it's trivial
|
|
60
|
-
# but if you have to do it 10,000x it's definitely not a rounding error.
|
|
61
|
-
return total + buf[:read].count(b'\n')
|
|
62
|
-
|
|
63
|
-
|
|
64
34
|
def _count_listing_entries(tdenv: TradeEnv, listings: Path) -> int:
|
|
65
35
|
""" Calculates the number of entries in a listing file by counting the lines. """
|
|
66
36
|
if not listings.exists():
|
|
@@ -68,7 +38,7 @@ def _count_listing_entries(tdenv: TradeEnv, listings: Path) -> int:
|
|
|
68
38
|
return 0
|
|
69
39
|
|
|
70
40
|
tdenv.DEBUG0(f"Getting total number of entries in {listings}...")
|
|
71
|
-
count =
|
|
41
|
+
count = file_line_count(listings)
|
|
72
42
|
if count <= 1:
|
|
73
43
|
if count == 1:
|
|
74
44
|
tdenv.DEBUG0("Listing count of 1 suggests nothing but a header")
|
|
@@ -101,7 +71,6 @@ class ImportPlugin(plugins.ImportPluginBase):
|
|
|
101
71
|
"""
|
|
102
72
|
Plugin that downloads data from eddb.
|
|
103
73
|
"""
|
|
104
|
-
|
|
105
74
|
pluginOptions = {
|
|
106
75
|
'item': "Update Items using latest file from server. (Implies '-O system,station')",
|
|
107
76
|
'rare': "Update RareItems using latest file from server. (Implies '-O system,station')",
|
|
@@ -213,12 +182,16 @@ class ImportPlugin(plugins.ImportPluginBase):
|
|
|
213
182
|
"""
|
|
214
183
|
listings_path = Path(self.dataPath, listings_file).absolute()
|
|
215
184
|
from_live = listings_path != Path(self.dataPath, self.listingsPath).absolute()
|
|
216
|
-
self.tdenv.NOTE("Processing market data from {}: Start time = {}. Live = {}", listings_file, self.now(), from_live)
|
|
217
185
|
|
|
186
|
+
self.tdenv.NOTE("Checking listings")
|
|
218
187
|
total = _count_listing_entries(self.tdenv, listings_path)
|
|
219
188
|
if not total:
|
|
189
|
+
self.tdenv.NOTE("No listings")
|
|
220
190
|
return
|
|
221
191
|
|
|
192
|
+
self.tdenv.NOTE("Processing market data from {}: Start time = {}. Live = {}", listings_file, self.now(), from_live)
|
|
193
|
+
|
|
194
|
+
db = self.tdb.getDB()
|
|
222
195
|
stmt_unliven_station = """UPDATE StationItem SET from_live = 0 WHERE station_id = ?"""
|
|
223
196
|
stmt_flush_station = """DELETE from StationItem WHERE station_id = ?"""
|
|
224
197
|
stmt_add_listing = """
|
|
@@ -235,20 +208,25 @@ class ImportPlugin(plugins.ImportPluginBase):
|
|
|
235
208
|
"""
|
|
236
209
|
|
|
237
210
|
# Fetch all the items IDS
|
|
238
|
-
db = self.tdb.getDB()
|
|
239
211
|
item_lookup = _make_item_id_lookup(self.tdenv, db.cursor())
|
|
240
212
|
station_lookup = _make_station_id_lookup(self.tdenv, db.cursor())
|
|
241
213
|
last_station_update_times = _collect_station_modified_times(self.tdenv, db.cursor())
|
|
242
214
|
|
|
243
215
|
cur_station = None
|
|
216
|
+
is_debug = self.tdenv.debug > 0
|
|
244
217
|
self.tdenv.DEBUG0("Processing entries...")
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
218
|
+
|
|
219
|
+
# Try to find a balance between doing too many commits where we fail
|
|
220
|
+
# to get any benefits from constructing transactions, and blowing up
|
|
221
|
+
# the WAL and memory usage by making massive transactions.
|
|
222
|
+
max_transaction_items, transaction_items = 32 * 1024, 0
|
|
223
|
+
with pbar.Progress(total, 40, prefix="Processing", style=pbar.LongRunningCountBar) as prog,\
|
|
224
|
+
listings_path.open("r", encoding="utf-8", errors="ignore") as fh:
|
|
225
|
+
cursor = db.cursor()
|
|
226
|
+
cursor.execute("BEGIN TRANSACTION")
|
|
249
227
|
|
|
250
228
|
for listing in csv.DictReader(fh):
|
|
251
|
-
prog.increment(1
|
|
229
|
+
prog.increment(1)
|
|
252
230
|
|
|
253
231
|
station_id = int(listing['station_id'])
|
|
254
232
|
if station_id not in station_lookup:
|
|
@@ -258,16 +236,21 @@ class ImportPlugin(plugins.ImportPluginBase):
|
|
|
258
236
|
|
|
259
237
|
if station_id != cur_station:
|
|
260
238
|
# commit anything from the previous station, get a new cursor
|
|
261
|
-
|
|
262
|
-
|
|
239
|
+
if transaction_items >= max_transaction_items:
|
|
240
|
+
cursor.execute("COMMIT")
|
|
241
|
+
transaction_items = 0
|
|
242
|
+
cursor.execute("BEGIN TRANSACTION")
|
|
243
|
+
cur_station, skip_station = station_id, False
|
|
263
244
|
|
|
264
245
|
# Check if listing already exists in DB and needs updated.
|
|
265
246
|
last_modified: int = int(last_station_update_times.get(station_id, 0))
|
|
266
247
|
if last_modified:
|
|
267
248
|
# When the listings.csv data matches the database, update to make from_live == 0.
|
|
268
249
|
if listing_time == last_modified and not from_live:
|
|
269
|
-
|
|
250
|
+
if is_debug:
|
|
251
|
+
self.tdenv.DEBUG1(f"Marking {cur_station} as no longer 'live' (old={last_modified}, listing={listing_time}).")
|
|
270
252
|
cursor.execute(stmt_unliven_station, (cur_station,))
|
|
253
|
+
transaction_items += 1
|
|
271
254
|
skip_station = True
|
|
272
255
|
continue
|
|
273
256
|
|
|
@@ -278,8 +261,10 @@ class ImportPlugin(plugins.ImportPluginBase):
|
|
|
278
261
|
continue
|
|
279
262
|
|
|
280
263
|
# The data from the import file is newer, so we need to delete the old data for this station.
|
|
281
|
-
|
|
264
|
+
if is_debug:
|
|
265
|
+
self.tdenv.DEBUG1(f"Deleting old listing data for {cur_station} (old={last_modified}, listing={listing_time}).")
|
|
282
266
|
cursor.execute(stmt_flush_station, (cur_station,))
|
|
267
|
+
transaction_items += 1
|
|
283
268
|
last_station_update_times[station_id] = listing_time
|
|
284
269
|
|
|
285
270
|
# station skip lasts until we change station id.
|
|
@@ -299,20 +284,24 @@ class ImportPlugin(plugins.ImportPluginBase):
|
|
|
299
284
|
supply_units = int(listing['supply'])
|
|
300
285
|
supply_level = int(listing.get('supply_bracket') or '-1')
|
|
301
286
|
|
|
302
|
-
|
|
287
|
+
if is_debug:
|
|
288
|
+
self.tdenv.DEBUG1(f"Inserting new listing data for {station_id}.")
|
|
303
289
|
cursor.execute(stmt_add_listing, (
|
|
304
290
|
station_id, item_id, listing_time, from_live,
|
|
305
291
|
demand_price, demand_units, demand_level,
|
|
306
292
|
supply_price, supply_units, supply_level,
|
|
307
293
|
))
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
294
|
+
transaction_items += 1
|
|
295
|
+
|
|
296
|
+
# These will take a little while, which has four steps, so we'll make it a counter.
|
|
297
|
+
with pbar.Progress(1, 40, prefix="Saving"):
|
|
298
|
+
# Do a final commit to be sure
|
|
299
|
+
cursor.execute("COMMIT")
|
|
300
|
+
|
|
301
|
+
if self.getOption("optimize"):
|
|
302
|
+
with pbar.Progress(1, 40, prefix="Optimizing"):
|
|
303
|
+
db.execute("VACUUM")
|
|
304
|
+
|
|
316
305
|
self.tdb.close()
|
|
317
306
|
|
|
318
307
|
self.tdenv.NOTE("Finished processing market data. End time = {}", self.now())
|
|
@@ -92,10 +92,9 @@ CREATE TABLE Station
|
|
|
92
92
|
UNIQUE (station_id),
|
|
93
93
|
|
|
94
94
|
FOREIGN KEY (system_id) REFERENCES System(system_id)
|
|
95
|
-
ON UPDATE CASCADE
|
|
96
95
|
ON DELETE CASCADE
|
|
97
|
-
);
|
|
98
|
-
CREATE INDEX idx_station_by_system ON Station (system_id
|
|
96
|
+
) WITHOUT ROWID;
|
|
97
|
+
CREATE INDEX idx_station_by_system ON Station (system_id);
|
|
99
98
|
CREATE INDEX idx_station_by_name ON Station (name);
|
|
100
99
|
|
|
101
100
|
|
|
@@ -226,11 +225,45 @@ CREATE TABLE StationItem
|
|
|
226
225
|
ON UPDATE CASCADE ON DELETE CASCADE,
|
|
227
226
|
FOREIGN KEY (item_id) REFERENCES Item(item_id)
|
|
228
227
|
ON UPDATE CASCADE ON DELETE CASCADE
|
|
229
|
-
);
|
|
228
|
+
) WITHOUT ROWID;
|
|
230
229
|
CREATE INDEX si_mod_stn_itm ON StationItem(modified, station_id, item_id);
|
|
231
230
|
CREATE INDEX si_itm_dmdpr ON StationItem(item_id, demand_price) WHERE demand_price > 0;
|
|
232
231
|
CREATE INDEX si_itm_suppr ON StationItem(item_id, supply_price) WHERE supply_price > 0;
|
|
233
232
|
|
|
233
|
+
-- Not used yet
|
|
234
|
+
CREATE TABLE IF NOT EXISTS StationDemand
|
|
235
|
+
(
|
|
236
|
+
station_id INTEGER NOT NULL,
|
|
237
|
+
item_id INTEGER NOT NULL,
|
|
238
|
+
price INTEGER NOT NULL,
|
|
239
|
+
units INTEGER NOT NULL,
|
|
240
|
+
level INTEGER NOT NULL,
|
|
241
|
+
modified INTEGER NOT NULL,
|
|
242
|
+
from_live INTEGER NOT NULL DEFAULT 0,
|
|
243
|
+
CONSTRAINT pk_StationDemand PRIMARY KEY (station_id, item_id),
|
|
244
|
+
CONSTRAINT fk_StationDemand_station_id_Station FOREIGN KEY (station_id) REFERENCES Station (station_id) ON DELETE CASCADE,
|
|
245
|
+
CONSTRAINT fk_StationDemand_item_id_Item FOREIGN KEY (item_id) REFERENCES Item (item_id) ON DELETE CASCADE
|
|
246
|
+
) WITHOUT ROWID;
|
|
247
|
+
DELETE FROM StationDemand;
|
|
248
|
+
CREATE INDEX idx_StationDemand_item ON StationDemand (item_id);
|
|
249
|
+
|
|
250
|
+
-- Not used yet
|
|
251
|
+
CREATE TABLE IF NOT EXISTS StationSupply
|
|
252
|
+
(
|
|
253
|
+
station_id INTEGER NOT NULL,
|
|
254
|
+
item_id INTEGER NOT NULL,
|
|
255
|
+
price INTEGER NOT NULL,
|
|
256
|
+
units INTEGER NOT NULL,
|
|
257
|
+
level INTEGER NOT NULL,
|
|
258
|
+
modified INTEGER NOT NULL,
|
|
259
|
+
from_live INTEGER NOT NULL DEFAULT 0,
|
|
260
|
+
CONSTRAINT pk_StationSupply PRIMARY KEY (station_id, item_id),
|
|
261
|
+
CONSTRAINT fk_StationSupply_station_id_Station FOREIGN KEY (station_id) REFERENCES Station (station_id) ON DELETE CASCADE,
|
|
262
|
+
CONSTRAINT fk_StationSupply_item_id_Item FOREIGN KEY (item_id) REFERENCES Item (item_id) ON DELETE CASCADE
|
|
263
|
+
) WITHOUT ROWID;
|
|
264
|
+
DELETE FROM StationSupply;
|
|
265
|
+
CREATE INDEX idx_StationSupply_item ON StationSupply (item_id);
|
|
266
|
+
|
|
234
267
|
CREATE VIEW StationBuying AS
|
|
235
268
|
SELECT station_id,
|
|
236
269
|
item_id,
|
tradedangerous/tradecalc.py
CHANGED
|
@@ -930,176 +930,173 @@ class TradeCalc:
|
|
|
930
930
|
odyssey = odyssey,
|
|
931
931
|
)
|
|
932
932
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
tdenv.DEBUG1("Route = {}", route.text(lambda x, y: y))
|
|
940
|
-
|
|
941
|
-
srcStation = route.lastStation
|
|
942
|
-
startCr = credits + int(route.gainCr * safetyMargin)
|
|
943
|
-
|
|
944
|
-
srcSelling = getSelling(srcStation.ID, None)
|
|
945
|
-
srcSelling = tuple(
|
|
946
|
-
values for values in srcSelling
|
|
947
|
-
if values[1] <= startCr
|
|
948
|
-
)
|
|
949
|
-
if not srcSelling:
|
|
950
|
-
tdenv.DEBUG1("Nothing sold/affordable - next.")
|
|
951
|
-
continue
|
|
952
|
-
|
|
953
|
-
if goalSystem:
|
|
954
|
-
origSystem = route.firstSystem
|
|
955
|
-
srcSystem = srcStation.system
|
|
956
|
-
srcDistTo = srcSystem.distanceTo
|
|
957
|
-
goalDistTo = goalSystem.distanceTo
|
|
958
|
-
origDistTo = origSystem.distanceTo
|
|
959
|
-
srcGoalDist = srcDistTo(goalSystem)
|
|
960
|
-
srcOrigDist = srcDistTo(origSystem)
|
|
961
|
-
origGoalDist = origDistTo(goalSystem)
|
|
962
|
-
|
|
963
|
-
if unique:
|
|
964
|
-
uniquePath = route.route
|
|
965
|
-
elif loopInt:
|
|
966
|
-
pos_from_end = 0 - loopInt
|
|
967
|
-
uniquePath = route.route[pos_from_end:-1]
|
|
968
|
-
|
|
969
|
-
stations = (d for d in station_iterator(srcStation)
|
|
970
|
-
if (d.station != srcStation) and
|
|
971
|
-
(d.station.blackMarket == 'Y' if reqBlackMarket else True) and
|
|
972
|
-
(d.station not in uniquePath if uniquePath else True) and
|
|
973
|
-
(d.station in restrictStations if restrictStations else True) and
|
|
974
|
-
(d.station.dataAge and d.station.dataAge <= maxAge if maxAge else True) and
|
|
975
|
-
(((d.system is not srcSystem) if bool(tdenv.unique) else (d.system is goalSystem or d.distLy < srcGoalDist)) if goalSystem else True)
|
|
976
|
-
)
|
|
977
|
-
|
|
978
|
-
if tdenv.debug >= 1:
|
|
933
|
+
with pbar.Progress(max_value=len(routes), width=25, show=tdenv.progress) as prog:
|
|
934
|
+
connections = 0
|
|
935
|
+
getSelling = self.stationsSelling.get
|
|
936
|
+
for route_no, route in enumerate(routes):
|
|
937
|
+
prog.increment(progress=route_no)
|
|
938
|
+
tdenv.DEBUG1("Route = {}", route.text(lambda x, y: y))
|
|
979
939
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
"destSys {}, destStn {}, jumps {}, distLy {}",
|
|
983
|
-
dest.system.dbname,
|
|
984
|
-
dest.station.dbname,
|
|
985
|
-
"->".join(jump.text() for jump in dest.via),
|
|
986
|
-
dest.distLy
|
|
987
|
-
)
|
|
988
|
-
return True
|
|
989
|
-
|
|
990
|
-
stations = (d for d in stations if annotate(d))
|
|
991
|
-
|
|
992
|
-
for dest in stations:
|
|
993
|
-
dstStation = dest.station
|
|
940
|
+
srcStation = route.lastStation
|
|
941
|
+
startCr = credits + int(route.gainCr * safetyMargin)
|
|
994
942
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
943
|
+
srcSelling = getSelling(srcStation.ID, None)
|
|
944
|
+
srcSelling = tuple(
|
|
945
|
+
values for values in srcSelling
|
|
946
|
+
if values[1] <= startCr
|
|
947
|
+
)
|
|
948
|
+
if not srcSelling:
|
|
949
|
+
tdenv.DEBUG1("Nothing sold/affordable - next.")
|
|
998
950
|
continue
|
|
999
|
-
trade = fitFunction(items, startCr, capacity, maxUnits)
|
|
1000
951
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
if
|
|
1019
|
-
|
|
952
|
+
if goalSystem:
|
|
953
|
+
origSystem = route.firstSystem
|
|
954
|
+
srcSystem = srcStation.system
|
|
955
|
+
srcDistTo = srcSystem.distanceTo
|
|
956
|
+
goalDistTo = goalSystem.distanceTo
|
|
957
|
+
origDistTo = origSystem.distanceTo
|
|
958
|
+
srcGoalDist = srcDistTo(goalSystem)
|
|
959
|
+
srcOrigDist = srcDistTo(origSystem)
|
|
960
|
+
origGoalDist = origDistTo(goalSystem)
|
|
961
|
+
|
|
962
|
+
if unique:
|
|
963
|
+
uniquePath = route.route
|
|
964
|
+
elif loopInt:
|
|
965
|
+
pos_from_end = 0 - loopInt
|
|
966
|
+
uniquePath = route.route[pos_from_end:-1]
|
|
967
|
+
|
|
968
|
+
stations = (d for d in station_iterator(srcStation)
|
|
969
|
+
if (d.station != srcStation) and
|
|
970
|
+
(d.station.blackMarket == 'Y' if reqBlackMarket else True) and
|
|
971
|
+
(d.station not in uniquePath if uniquePath else True) and
|
|
972
|
+
(d.station in restrictStations if restrictStations else True) and
|
|
973
|
+
(d.station.dataAge and d.station.dataAge <= maxAge if maxAge else True) and
|
|
974
|
+
(((d.system is not srcSystem) if bool(tdenv.unique) else (d.system is goalSystem or d.distLy < srcGoalDist)) if goalSystem else True)
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
if tdenv.debug >= 1:
|
|
1020
978
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
979
|
+
def annotate(dest):
|
|
980
|
+
tdenv.DEBUG1(
|
|
981
|
+
"destSys {}, destStn {}, jumps {}, distLy {}",
|
|
982
|
+
dest.system.dbname,
|
|
983
|
+
dest.station.dbname,
|
|
984
|
+
"->".join(jump.text() for jump in dest.via),
|
|
985
|
+
dest.distLy
|
|
986
|
+
)
|
|
987
|
+
return True
|
|
1026
988
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
# multiplier *= (1 - penalty)
|
|
989
|
+
stations = (d for d in stations if annotate(d))
|
|
990
|
+
|
|
991
|
+
for dest in stations:
|
|
992
|
+
dstStation = dest.station
|
|
1032
993
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
#
|
|
1039
|
-
# The sigmoid: (-1-(50(x-4))/(1+abs(50(x-4))))/4
|
|
1040
|
-
# ranges between 0 and -0.5 with a drop around x=4,
|
|
1041
|
-
# making it great for penalizing distances > 4Kls.
|
|
1042
|
-
#
|
|
1043
|
-
# The curve: (-1+1/(x+1)^((x+1)/4))/2
|
|
1044
|
-
# ranges between 0 and -0.5 in a smooth arc,
|
|
1045
|
-
# which will be used for making distances
|
|
1046
|
-
# closer to 4Kls get a slightly higher penalty
|
|
1047
|
-
# then distances closer to 1Kls.
|
|
1048
|
-
#
|
|
1049
|
-
# Adding the three together creates a doubly-kinked curve
|
|
1050
|
-
# that ranges from ~0.5 to -1.0, with drops around x=1 and x=4,
|
|
1051
|
-
# which closely matches ksfone's intention without going into
|
|
1052
|
-
# negative numbers and causing problems when we add it to
|
|
1053
|
-
# the multiplier variable. ( 1 + -1 = 0 )
|
|
1054
|
-
#
|
|
1055
|
-
# You can see a graph of the formula here:
|
|
1056
|
-
# https://goo.gl/sn1PqQ
|
|
1057
|
-
# NOTE: The black curve is at a penalty of 0%,
|
|
1058
|
-
# the red curve at a penalty of 100%, with intermediates at
|
|
1059
|
-
# 25%, 50%, and 75%.
|
|
1060
|
-
# The other colored lines show the penalty curves individually
|
|
1061
|
-
# and the teal composite of all three.
|
|
994
|
+
connections += 1
|
|
995
|
+
items = self.getTrades(srcStation, dstStation, srcSelling)
|
|
996
|
+
if not items:
|
|
997
|
+
continue
|
|
998
|
+
trade = fitFunction(items, startCr, capacity, maxUnits)
|
|
1062
999
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1000
|
+
multiplier = 1.0
|
|
1001
|
+
# Calculate total K-lightseconds supercruise time.
|
|
1002
|
+
# This will amortize for the start/end stations
|
|
1003
|
+
dstSys = dest.system
|
|
1004
|
+
if goalSystem and dstSys is not goalSystem:
|
|
1005
|
+
dstGoalDist = goalDistTo(dstSys)
|
|
1006
|
+
# Biggest reward for shortening distance to goal
|
|
1007
|
+
score = 5000 * origGoalDist / dstGoalDist
|
|
1008
|
+
# bias towards bigger reductions
|
|
1009
|
+
score += 50 * srcGoalDist / dstGoalDist
|
|
1010
|
+
# discourage moving back towards origin
|
|
1011
|
+
if dstSys is not origSystem:
|
|
1012
|
+
score += 10 * (origDistTo(dstSys) - srcOrigDist)
|
|
1013
|
+
# Gain per unit pays a small part
|
|
1014
|
+
score += (trade.gainCr / trade.units) / 25
|
|
1015
|
+
else:
|
|
1016
|
+
score = trade.gainCr
|
|
1017
|
+
if lsPenalty:
|
|
1018
|
+
# [kfsone] Only want 1dp
|
|
1019
|
+
|
|
1020
|
+
cruiseKls = int(dstStation.lsFromStar / 100) / 10
|
|
1021
|
+
# Produce a curve that favors distances under 1kls
|
|
1022
|
+
# positively, starts to penalize distances over 1k,
|
|
1023
|
+
# and after 4kls starts to penalize aggressively
|
|
1024
|
+
# http://goo.gl/Otj2XP
|
|
1025
|
+
|
|
1026
|
+
# [eyeonus] As aadler pointed out, this goes into negative
|
|
1027
|
+
# numbers, which causes problems.
|
|
1028
|
+
# penalty = ((cruiseKls ** 2) - cruiseKls) / 3
|
|
1029
|
+
# penalty *= lsPenalty
|
|
1030
|
+
# multiplier *= (1 - penalty)
|
|
1031
|
+
|
|
1032
|
+
# [eyeonus]:
|
|
1033
|
+
# (Keep in mind all this ignores values of x<0.)
|
|
1034
|
+
# The sigmoid: (1-(25(x-1))/(1+abs(25(x-1))))/4
|
|
1035
|
+
# ranges between 0.5 and 0 with a drop around x=1,
|
|
1036
|
+
# which makes it great for giving a boost to distances < 1Kls.
|
|
1037
|
+
#
|
|
1038
|
+
# The sigmoid: (-1-(50(x-4))/(1+abs(50(x-4))))/4
|
|
1039
|
+
# ranges between 0 and -0.5 with a drop around x=4,
|
|
1040
|
+
# making it great for penalizing distances > 4Kls.
|
|
1041
|
+
#
|
|
1042
|
+
# The curve: (-1+1/(x+1)^((x+1)/4))/2
|
|
1043
|
+
# ranges between 0 and -0.5 in a smooth arc,
|
|
1044
|
+
# which will be used for making distances
|
|
1045
|
+
# closer to 4Kls get a slightly higher penalty
|
|
1046
|
+
# then distances closer to 1Kls.
|
|
1047
|
+
#
|
|
1048
|
+
# Adding the three together creates a doubly-kinked curve
|
|
1049
|
+
# that ranges from ~0.5 to -1.0, with drops around x=1 and x=4,
|
|
1050
|
+
# which closely matches ksfone's intention without going into
|
|
1051
|
+
# negative numbers and causing problems when we add it to
|
|
1052
|
+
# the multiplier variable. ( 1 + -1 = 0 )
|
|
1053
|
+
#
|
|
1054
|
+
# You can see a graph of the formula here:
|
|
1055
|
+
# https://goo.gl/sn1PqQ
|
|
1056
|
+
# NOTE: The black curve is at a penalty of 0%,
|
|
1057
|
+
# the red curve at a penalty of 100%, with intermediates at
|
|
1058
|
+
# 25%, 50%, and 75%.
|
|
1059
|
+
# The other colored lines show the penalty curves individually
|
|
1060
|
+
# and the teal composite of all three.
|
|
1061
|
+
|
|
1062
|
+
def sigmoid(x):
|
|
1063
|
+
return x / (1 + abs(x))
|
|
1064
|
+
|
|
1065
|
+
boost = (1 - sigmoid(25 * (cruiseKls - 1))) / 4
|
|
1066
|
+
drop = (-1 - sigmoid(50 * (cruiseKls - 4))) / 4
|
|
1067
|
+
try:
|
|
1068
|
+
penalty = (-1 + 1 / (cruiseKls + 1) ** ((cruiseKls + 1) / 4)) / 2
|
|
1069
|
+
except OverflowError:
|
|
1070
|
+
penalty = -0.5
|
|
1071
|
+
|
|
1072
|
+
multiplier += (penalty + boost + drop) * lsPenalty
|
|
1065
1073
|
|
|
1066
|
-
|
|
1067
|
-
drop = (-1 - sigmoid(50 * (cruiseKls - 4))) / 4
|
|
1068
|
-
try:
|
|
1069
|
-
penalty = (-1 + 1 / (cruiseKls + 1) ** ((cruiseKls + 1) / 4)) / 2
|
|
1070
|
-
except OverflowError:
|
|
1071
|
-
penalty = -0.5
|
|
1074
|
+
score *= multiplier
|
|
1072
1075
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
# Check if it is a better option than we just produced
|
|
1088
|
-
bestTradeScore = bestRoute.score + bestScore
|
|
1089
|
-
newTradeScore = route.score + score
|
|
1090
|
-
if bestTradeScore > newTradeScore:
|
|
1091
|
-
continue
|
|
1092
|
-
if bestTradeScore == newTradeScore:
|
|
1093
|
-
bestLy = btd[4]
|
|
1094
|
-
if bestLy <= dest.distLy:
|
|
1076
|
+
dstID = dstStation.ID
|
|
1077
|
+
try:
|
|
1078
|
+
# See if there is already a candidate for this destination
|
|
1079
|
+
btd = bestToDest[dstID]
|
|
1080
|
+
except KeyError:
|
|
1081
|
+
# No existing candidate, we win by default
|
|
1082
|
+
pass
|
|
1083
|
+
else:
|
|
1084
|
+
bestRoute = btd[1]
|
|
1085
|
+
bestScore = btd[5]
|
|
1086
|
+
# Check if it is a better option than we just produced
|
|
1087
|
+
bestTradeScore = bestRoute.score + bestScore
|
|
1088
|
+
newTradeScore = route.score + score
|
|
1089
|
+
if bestTradeScore > newTradeScore:
|
|
1095
1090
|
continue
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1091
|
+
if bestTradeScore == newTradeScore:
|
|
1092
|
+
bestLy = btd[4]
|
|
1093
|
+
if bestLy <= dest.distLy:
|
|
1094
|
+
continue
|
|
1095
|
+
|
|
1096
|
+
bestToDest[dstID] = (
|
|
1097
|
+
dstStation, route, trade, dest.via, dest.distLy, score
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1103
1100
|
if connections == 0:
|
|
1104
1101
|
raise NoHopsError(
|
|
1105
1102
|
"No destinations could be reached within the constraints."
|
tradedangerous/transfers.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections import deque
|
|
4
3
|
from pathlib import Path
|
|
4
|
+
from urllib.parse import urlparse, unquote
|
|
5
|
+
|
|
5
6
|
from .tradeexcept import TradeException
|
|
6
7
|
from .misc import progress as pbar
|
|
7
8
|
from . import fs
|
|
@@ -16,7 +17,7 @@ import requests
|
|
|
16
17
|
if typing.TYPE_CHECKING:
|
|
17
18
|
import os # for PathLike
|
|
18
19
|
from .tradeenv import TradeEnv
|
|
19
|
-
from typing import Optional, Union
|
|
20
|
+
from typing import Callable, Optional, Union # noqa
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
######################################################################
|
|
@@ -38,15 +39,21 @@ def makeUnit(value):
|
|
|
38
39
|
unitSize /= 1024
|
|
39
40
|
return None
|
|
40
41
|
|
|
42
|
+
|
|
43
|
+
def get_filename_from_url(url: str) -> str:
|
|
44
|
+
""" extracts just the filename from a url. """
|
|
45
|
+
return Path(unquote(urlparse(url).path)).name
|
|
46
|
+
|
|
47
|
+
|
|
41
48
|
def download(
|
|
42
49
|
tdenv: TradeEnv,
|
|
43
50
|
url: str,
|
|
44
51
|
localFile: os.PathLike,
|
|
45
52
|
headers: Optional[dict] = None,
|
|
46
53
|
backup: bool = False,
|
|
47
|
-
shebang: Optional[
|
|
54
|
+
shebang: Optional[Callable] = None,
|
|
48
55
|
chunkSize: int = 4096,
|
|
49
|
-
timeout: int =
|
|
56
|
+
timeout: int = 30,
|
|
50
57
|
*,
|
|
51
58
|
length: Optional[Union[int, str]] = None,
|
|
52
59
|
session: Optional[requests.Session] = None,
|
|
@@ -55,20 +62,11 @@ def download(
|
|
|
55
62
|
Fetch data from a URL and save the output
|
|
56
63
|
to a local file. Returns the response headers.
|
|
57
64
|
|
|
58
|
-
tdenv:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
localFile:
|
|
65
|
-
Name of the local file to open.
|
|
66
|
-
|
|
67
|
-
headers:
|
|
68
|
-
dict() of additional HTTP headers to send
|
|
69
|
-
|
|
70
|
-
shebang:
|
|
71
|
-
function to call on the first line
|
|
65
|
+
:param tdenv: TradeEnv we're working under
|
|
66
|
+
:param url: URL we're fetching (http, https or ftp)
|
|
67
|
+
:param localFile: Name of the local file to open.
|
|
68
|
+
:param headers: dict() of additional HTTP headers to send
|
|
69
|
+
:param shebang: function to call on the first line
|
|
72
70
|
"""
|
|
73
71
|
tdenv.NOTE("Requesting {}".format(url))
|
|
74
72
|
|
|
@@ -108,24 +106,14 @@ def download(
|
|
|
108
106
|
tdenv.NOTE("Downloading {} {}ed data", transfer, encoding)
|
|
109
107
|
tdenv.DEBUG0(str(req.headers).replace("{", "{{").replace("}", "}}"))
|
|
110
108
|
|
|
111
|
-
# Figure out how much data we have
|
|
112
|
-
if length and not tdenv.quiet:
|
|
113
|
-
progBar = pbar.Progress(length, 20)
|
|
114
|
-
else:
|
|
115
|
-
progBar = None
|
|
116
|
-
|
|
117
109
|
actPath = Path(localFile)
|
|
118
110
|
fs.ensurefolder(tdenv.tmpDir)
|
|
119
111
|
tmpPath = Path(tdenv.tmpDir, "{}.dl".format(actPath.name))
|
|
120
112
|
|
|
121
|
-
histogram = deque()
|
|
122
|
-
|
|
123
113
|
fetched = 0
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
]
|
|
128
|
-
with tmpPath.open("wb") as fh:
|
|
114
|
+
started = time.time()
|
|
115
|
+
filename = get_filename_from_url(url)
|
|
116
|
+
with pbar.Progress(max_value=length, width=25, prefix=filename, style=pbar.CountingBar, show=not tdenv.quiet) as prog, tmpPath.open("wb") as fh:
|
|
129
117
|
for data in req.iter_content(chunk_size=chunkSize):
|
|
130
118
|
fh.write(data)
|
|
131
119
|
fetched += len(data)
|
|
@@ -134,29 +122,11 @@ def download(
|
|
|
134
122
|
tdenv.DEBUG0("Checking shebang of {}", bangLine)
|
|
135
123
|
shebang(bangLine)
|
|
136
124
|
shebang = None
|
|
137
|
-
if
|
|
138
|
-
|
|
139
|
-
deltaT = max(now - lastTime, 0.001)
|
|
140
|
-
lastTime = now
|
|
141
|
-
if len(histogram) >= 15:
|
|
142
|
-
histogram.popleft()
|
|
143
|
-
histogram.append(len(data) / deltaT)
|
|
144
|
-
progBar.increment(
|
|
145
|
-
len(data),
|
|
146
|
-
postfix=lambda value, goal: \
|
|
147
|
-
" {:>7s} [{:>7s}/s] {:>3.0f}% {:1s}".format(
|
|
148
|
-
makeUnit(value),
|
|
149
|
-
makeUnit(sum(histogram) / len(histogram)),
|
|
150
|
-
(fetched * 100. / length),
|
|
151
|
-
spinners[spinner]
|
|
152
|
-
)
|
|
153
|
-
)
|
|
154
|
-
if deltaT > 0.200:
|
|
155
|
-
spinner = (spinner + 1) % len(spinners)
|
|
125
|
+
if prog:
|
|
126
|
+
prog.increment(len(data))
|
|
156
127
|
tdenv.DEBUG0("End of data")
|
|
128
|
+
|
|
157
129
|
if not tdenv.quiet:
|
|
158
|
-
if progBar:
|
|
159
|
-
progBar.clear()
|
|
160
130
|
elapsed = (time.time() - started) or 1
|
|
161
131
|
tdenv.NOTE(
|
|
162
132
|
"Downloaded {} of {}ed data {}/s",
|
|
@@ -197,17 +167,13 @@ def get_json_data(url, *, timeout: int = 90):
|
|
|
197
167
|
jsData = req.content
|
|
198
168
|
else:
|
|
199
169
|
totalLength = int(totalLength)
|
|
200
|
-
|
|
170
|
+
filename = get_filename_from_url(url)
|
|
171
|
+
progBar = pbar.Progress(totalLength, 25, prefix=filename)
|
|
172
|
+
|
|
201
173
|
jsData = bytes()
|
|
202
174
|
for data in req.iter_content():
|
|
203
175
|
jsData += data
|
|
204
|
-
progBar.increment(
|
|
205
|
-
len(data),
|
|
206
|
-
postfix=lambda value, goal: \
|
|
207
|
-
" {}/{}".format(
|
|
208
|
-
makeUnit(value),
|
|
209
|
-
makeUnit(goal),
|
|
210
|
-
))
|
|
176
|
+
progBar.increment(len(data))
|
|
211
177
|
progBar.clear()
|
|
212
178
|
|
|
213
179
|
return json.loads(jsData.decode())
|
tradedangerous/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tradedangerous
|
|
3
|
-
Version: 11.
|
|
3
|
+
Version: 11.3.0
|
|
4
4
|
Summary: Trade-Dangerous is a set of powerful trading tools for Elite Dangerous, organized around one of the most powerful trade run optimizers available.
|
|
5
5
|
Home-page: https://github.com/eyeonus/Trade-Dangerous
|
|
6
6
|
Author: eyeonus
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
trade.py,sha256=Skqy0bC47g1KujuYrVwa3T8AfJ9NoLLOZcVjjxJ2daQ,1769
|
|
2
2
|
tradegui.py,sha256=q2HdIdoyeLUpeF2X0hVIGn7sU6T4zOzq1HN0zGvZdyE,788
|
|
3
3
|
tradedangerous/__init__.py,sha256=bwsbE_GyCNsuyGDKnfXAg0RD-ewsWHliySJ5QfCK7h8,1166
|
|
4
|
-
tradedangerous/cache.py,sha256=
|
|
4
|
+
tradedangerous/cache.py,sha256=0Pyvh5XYrNzC7Pj7KzDIioI-Xhydx7JGUXZGcrO1org,37225
|
|
5
5
|
tradedangerous/cli.py,sha256=dLekZ3MTbn9XcSGtE532qZF3iSnsb5G-ddQyErwTv9o,4559
|
|
6
6
|
tradedangerous/corrections.py,sha256=_WLgo1IBWoskrrPFeshRwCOeJ2BeJb_x4tDQ0JdAo-s,1340
|
|
7
7
|
tradedangerous/csvexport.py,sha256=_19bGVnCGJPzvx_8DzLGOs4rqx8Asn7eCVdXKdKu92E,8653
|
|
8
8
|
tradedangerous/edscupdate.py,sha256=To7hL2FuR4A7A4gJfvow_jGl6mlD7aP-Drv1rEkwy-I,17310
|
|
9
9
|
tradedangerous/edsmupdate.py,sha256=9IANIASA8ZKnZsLPBRAh7IArDRsa0jriL2UwjqKJ1fI,14928
|
|
10
|
-
tradedangerous/formatting.py,sha256=
|
|
11
|
-
tradedangerous/fs.py,sha256=
|
|
10
|
+
tradedangerous/formatting.py,sha256=R8GX2Zya1xazLOOBIWlLqUEjIESZJO41UlvhZDbyY4Q,6908
|
|
11
|
+
tradedangerous/fs.py,sha256=41yUdriBSv-rinGEmxjBTKyKH4-RA2bIO4sKCMIwvUA,3861
|
|
12
12
|
tradedangerous/gui.py,sha256=DFsF5zATr-lyJShL6t5kPKvcLLJYkICurzBz0WBa-oQ,43676
|
|
13
13
|
tradedangerous/jsonprices.py,sha256=GJ07fbZSNX5U_6lczWTSk6mWwme_weJbXgE8zcZBnpY,7225
|
|
14
14
|
tradedangerous/mapping.py,sha256=Bf2G8LzP1Bat5k3hFs9XyeI1z3tfUpfeh4nuLP2QJuQ,4122
|
|
15
15
|
tradedangerous/prices.py,sha256=JqiDVrtvvPd5pqE3HdwOHOuFgdAbOR-pt0GLD3ZIXM8,7425
|
|
16
16
|
tradedangerous/submit-distances.py,sha256=xL7fwdbVrb05-zWNH-9nyYBDtV4bfUfP7OBx3NMBc34,11749
|
|
17
17
|
tradedangerous/tools.py,sha256=pp-4WtA12SVaaQHFJFOMTF7EDFRCU2mQeOhC4xoXmEk,1331
|
|
18
|
-
tradedangerous/tradecalc.py,sha256=
|
|
18
|
+
tradedangerous/tradecalc.py,sha256=GlJZ9UwkoQUoayqUGVwlefhQvs-1SxYB5Y6Vkr31nSg,42753
|
|
19
19
|
tradedangerous/tradedb.py,sha256=mitKkS4MczivDK_K7A-IC94hkObUmGWFhwIrh_ts9qw,72282
|
|
20
20
|
tradedangerous/tradeenv.py,sha256=o956HN7-7uzIcNi9vI4zh-L1z5jwBioVARyhhiAlsRQ,11885
|
|
21
21
|
tradedangerous/tradeexcept.py,sha256=aZ-Y31MbkjF7lmAzBAbaMsPPE7FEEfuf4gaX2GvriDk,368
|
|
22
|
-
tradedangerous/transfers.py,sha256=
|
|
22
|
+
tradedangerous/transfers.py,sha256=s0fRWDZCsL-VYm5E4ceTMKcUelcvzcOUelsfg6TjYI8,6000
|
|
23
23
|
tradedangerous/utils.py,sha256=PUPvAEqUyxYGqqQa0b_yfLAvq8YVUxK6HfdS-CxM-Lo,5186
|
|
24
|
-
tradedangerous/version.py,sha256=
|
|
24
|
+
tradedangerous/version.py,sha256=Lf6tzJrBdghRB7hsnuyfhzDziiCbZ8EhcNAAsCHjmVs,646
|
|
25
25
|
tradedangerous/commands/TEMPLATE.py,sha256=MOE69xsZPHPIMBQ-LXicfsOlCZdy-2gPX_nlnwYYil8,2026
|
|
26
26
|
tradedangerous/commands/__init__.py,sha256=3gz2cnXNZNkV1gtZh0dOnCRxBkQHbeIyysRe3bM2WEE,9516
|
|
27
27
|
tradedangerous/commands/buildcache_cmd.py,sha256=jhNSqHX_xX43SiSUMFiKtWpB9v4oeZ0sqfNq6DCrjUs,2181
|
|
@@ -58,11 +58,11 @@ tradedangerous/misc/edsc.py,sha256=SN_da9qZ7H8ozibyhoFVB8nAvwBDPF3Z_PMlt70J2Ro,1
|
|
|
58
58
|
tradedangerous/misc/edsm.py,sha256=equDwO1SY3QTsoJIb3LjiHes4C8Dejaap_TMpYlCm8o,2910
|
|
59
59
|
tradedangerous/misc/importeddbstats.py,sha256=iLAcrFzdwiMm_MnUuaHcT_xGgZF8pfEEd1qljhhWJTU,1787
|
|
60
60
|
tradedangerous/misc/prices-json-exp.py,sha256=Fpm62ugP35ZBqnRs6ekYfS3GoDFYmvLD3b3SFJfaMOI,4944
|
|
61
|
-
tradedangerous/misc/progress.py,sha256=
|
|
61
|
+
tradedangerous/misc/progress.py,sha256=pWBHPA8Xc5uzjufAwCn7mkTrz7UbwSpY46o2BgEi7HU,7058
|
|
62
62
|
tradedangerous/plugins/__init__.py,sha256=TL-OIptlqNENKhoFqkFeBJn_vSw8L0pVaDJgjhaTj7A,7860
|
|
63
63
|
tradedangerous/plugins/edapi_plug.py,sha256=5nqBYmjUceAt-KTfiBn7IEl443R1SsGLDmfVXgbcyms,42262
|
|
64
64
|
tradedangerous/plugins/edcd_plug.py,sha256=JuDtuEM_mN9Sz2H09-qYizM-9N3cuNjgvQy7Y-wHwKw,14412
|
|
65
|
-
tradedangerous/plugins/eddblink_plug.py,sha256
|
|
65
|
+
tradedangerous/plugins/eddblink_plug.py,sha256=V58hRCfjeZ7t3aAIgdzbS5VdO5l39A-9_QKbY7KNJ48,21209
|
|
66
66
|
tradedangerous/plugins/edmc_batch_plug.py,sha256=rrP_lFFxWsba8DPEo0WF2EdCiMoRC7tCT8z62MIvtIo,4173
|
|
67
67
|
tradedangerous/plugins/journal_plug.py,sha256=5HMyoxQ7z42qj7NiL8rDxSyTN9gKikoQjyWzJLD-SYQ,23746
|
|
68
68
|
tradedangerous/plugins/netlog_plug.py,sha256=yUl47l9xt3kGj9oSiY_FZaDGdnQj63oa9MBtSeIy1Zo,13469
|
|
@@ -70,10 +70,10 @@ tradedangerous/plugins/spansh_plug.py,sha256=FiIS9cN2_8VKDrAj8yvkdy1NIni2kEc0ECq
|
|
|
70
70
|
tradedangerous/templates/Added.csv,sha256=8o54civQCcS9y7_DBo0GX196XWRbbREQqKDYTKibsgQ,649
|
|
71
71
|
tradedangerous/templates/Category.csv,sha256=8xwUDcBZE25T6x6dZGlRUMTCqeDLt3a9LXU5h6hRHV8,250
|
|
72
72
|
tradedangerous/templates/RareItem.csv,sha256=F1RhRnTD82PiwrVUO-ai2ErGH2PTqNnQaDw5mcgljXs,10483
|
|
73
|
-
tradedangerous/templates/TradeDangerous.sql,sha256=
|
|
74
|
-
tradedangerous-11.
|
|
75
|
-
tradedangerous-11.
|
|
76
|
-
tradedangerous-11.
|
|
77
|
-
tradedangerous-11.
|
|
78
|
-
tradedangerous-11.
|
|
79
|
-
tradedangerous-11.
|
|
73
|
+
tradedangerous/templates/TradeDangerous.sql,sha256=S9TH1bp0dfJLv90T6lTQ-TD8w8YGEZRMyUn8J1W4G3k,9330
|
|
74
|
+
tradedangerous-11.3.0.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
75
|
+
tradedangerous-11.3.0.dist-info/METADATA,sha256=ctub0LqxjEV1nSRKjKmOwkDrXNnEmGtfae8X8vx87Jk,4435
|
|
76
|
+
tradedangerous-11.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
77
|
+
tradedangerous-11.3.0.dist-info/entry_points.txt,sha256=lrA7U9JHOcNuam2WEK4Hmc3vQ3mrJfsbJCE74qd9au8,62
|
|
78
|
+
tradedangerous-11.3.0.dist-info/top_level.txt,sha256=JEoOVAhg5GfXZ4kHpNontu0RVzek_7P9_jp93f3Pqn8,16
|
|
79
|
+
tradedangerous-11.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|