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 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
- for (importName, importTable) in tdb.importTables:
981
- try:
982
- processImportFile(tdenv, tempDB, Path(importName), importTable)
983
- except FileNotFoundError:
984
- tdenv.DEBUG0(
985
- "WARNING: processImportFile found no {} file", importName
986
- )
987
- except StopIteration:
988
- tdenv.NOTE(
989
- "{} exists but is empty. "
990
- "Remove it or add the column definition line.",
991
- importName
992
- )
993
-
994
- tempDB.commit()
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
- processPricesFile(tdenv, tempDB, pricesPath)
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.",
@@ -8,8 +8,7 @@ import itertools
8
8
  import typing
9
9
 
10
10
  if typing.TYPE_CHECKING:
11
- from typing import Any, Optional
12
- from collections.abc import Callable
11
+ from typing import Any, Callable, Optional # noqa
13
12
 
14
13
 
15
14
  class ColumnFormat:
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
+
@@ -1,71 +1,179 @@
1
- import sys
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
- Helper class that describes a simple text-based progress bar.
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
- def __init__(self, maxValue, width, start=0, prefix=""):
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
- Arguments:
11
- maxValue
12
- Last value we can reach (100%).
13
- width
14
- Number of '='s characters for 100%
15
- start
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.start = start
21
- self.maxValue = maxValue
22
- self.width = width
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.progress = -1
25
- self.prefix = prefix
26
- self.textLen = 0
27
- self.mask = '\r' + self.prefix + "[{{:<{width}}}]".format(width=width)
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
- def increment(self, value, postfix=""):
31
- """
32
- Increment the progress bar's internal counter by 'value',
33
- and if this changes the progress step, re-draw the bar.
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
- Returns:
42
- False if the progress bar did not redraw,
43
- True if the progress bar was redrawn,
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
- self.value = min(self.maxValue, self.value + value)
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 clear(self):
121
+ def increment(self, value: Optional[float] = None, description: Optional[str] = None, *, progress: Optional[float] = None) -> None:
65
122
  """
66
- Remove the current progress bar, if any
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.textLen:
69
- fin = "\r{:{width}}\r".format('', width=self.textLen)
70
- sys.stdout.write(fin)
71
- sys.stdout.flush()
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 = _file_line_count(listings)
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
- with listings_path.open("r", encoding="utf-8", errors="ignore") as fh:
246
- prog = pbar.Progress(total, 50)
247
-
248
- cursor: Optional[sqlite3.Cursor] = db.cursor()
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, postfix = lambda value, total: f" {(value / total * 100):.0f}% {value} / {total}")
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
- db.commit()
262
- cur_station, skip_station, cursor = station_id, False, db.cursor()
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
- self.tdenv.DEBUG1(f"Marking {cur_station} as no longer 'live' (old={last_modified}, listing={listing_time}).")
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
- self.tdenv.DEBUG1(f"Deleting old listing data for {cur_station} (old={last_modified}, listing={listing_time}).")
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
- self.tdenv.DEBUG1(f"Inserting new listing data for {station_id}.")
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
- prog.clear()
310
-
311
- # Do a final commit to be sure
312
- db.commit()
313
-
314
- self.tdenv.NOTE("Optimizing database...")
315
- db.execute("VACUUM")
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, station_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,
@@ -930,176 +930,173 @@ class TradeCalc:
930
930
  odyssey = odyssey,
931
931
  )
932
932
 
933
- prog = pbar.Progress(len(routes), 25)
934
- connections = 0
935
- getSelling = self.stationsSelling.get
936
- for route in routes:
937
- if tdenv.progress:
938
- prog.increment(1)
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
- def annotate(dest):
981
- tdenv.DEBUG1(
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
- connections += 1
996
- items = self.getTrades(srcStation, dstStation, srcSelling)
997
- if not items:
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
- multiplier = 1.0
1002
- # Calculate total K-lightseconds supercruise time.
1003
- # This will amortize for the start/end stations
1004
- dstSys = dest.system
1005
- if goalSystem and dstSys is not goalSystem:
1006
- dstGoalDist = goalDistTo(dstSys)
1007
- # Biggest reward for shortening distance to goal
1008
- score = 5000 * origGoalDist / dstGoalDist
1009
- # bias towards bigger reductions
1010
- score += 50 * srcGoalDist / dstGoalDist
1011
- # discourage moving back towards origin
1012
- if dstSys is not origSystem:
1013
- score += 10 * (origDistTo(dstSys) - srcOrigDist)
1014
- # Gain per unit pays a small part
1015
- score += (trade.gainCr / trade.units) / 25
1016
- else:
1017
- score = trade.gainCr
1018
- if lsPenalty:
1019
- # [kfsone] Only want 1dp
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
- cruiseKls = int(dstStation.lsFromStar / 100) / 10
1022
- # Produce a curve that favors distances under 1kls
1023
- # positively, starts to penalize distances over 1k,
1024
- # and after 4kls starts to penalize aggressively
1025
- # http://goo.gl/Otj2XP
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
- # [eyeonus] As aadler pointed out, this goes into negative
1028
- # numbers, which causes problems.
1029
- # penalty = ((cruiseKls ** 2) - cruiseKls) / 3
1030
- # penalty *= lsPenalty
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
- # [eyeonus]:
1034
- # (Keep in mind all this ignores values of x<0.)
1035
- # The sigmoid: (1-(25(x-1))/(1+abs(25(x-1))))/4
1036
- # ranges between 0.5 and 0 with a drop around x=1,
1037
- # which makes it great for giving a boost to distances < 1Kls.
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
- def sigmoid(x):
1064
- return x / (1 + abs(x))
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
- boost = (1 - sigmoid(25 * (cruiseKls - 1))) / 4
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
- multiplier += (penalty + boost + drop) * lsPenalty
1074
-
1075
- score *= multiplier
1076
-
1077
- dstID = dstStation.ID
1078
- try:
1079
- # See if there is already a candidate for this destination
1080
- btd = bestToDest[dstID]
1081
- except KeyError:
1082
- # No existing candidate, we win by default
1083
- pass
1084
- else:
1085
- bestRoute = btd[1]
1086
- bestScore = btd[5]
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
- bestToDest[dstID] = (
1098
- dstStation, route, trade, dest.via, dest.distLy, score
1099
- )
1100
-
1101
- prog.clear()
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."
@@ -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[str] = None,
54
+ shebang: Optional[Callable] = None,
48
55
  chunkSize: int = 4096,
49
- timeout: int = 90,
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
- TradeEnv we're working under
60
-
61
- url:
62
- URL we're fetching (http, https or ftp)
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
- lastTime = started = time.time()
125
- spinner, spinners = 0, [
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 progBar:
138
- now = time.time()
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
- progBar = pbar.Progress(totalLength, 25)
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
@@ -12,5 +12,5 @@
12
12
  """just keeper of current version"""
13
13
 
14
14
  # TODO: remember to update tests when version changes
15
- __version__ = '11.2.1'
15
+ __version__ = '11.3.0'
16
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tradedangerous
3
- Version: 11.2.1
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=N7z1e2fov003wIh_aHkw1pdByKDPt3FkDZiH_EEPdMc,36080
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=K3XL_mZJ4cAiTTTmJmTXK7ePYzzsSxg7EoBVFh7gj60,6931
11
- tradedangerous/fs.py,sha256=_5OJHIRy_M-WFHEZO4g2EIgqDxdrz4s8tcdDmgG7NME,2494
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=A7peEMiaCjlwFvReSq3E7_Ar0shUoFedQi83ZmOc7uY,42075
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=NdPNzrpa5YuZeo2wBK6X8sy1VyFUaAXb0EtASyVXsuQ,6885
22
+ tradedangerous/transfers.py,sha256=s0fRWDZCsL-VYm5E4ceTMKcUelcvzcOUelsfg6TjYI8,6000
23
23
  tradedangerous/utils.py,sha256=PUPvAEqUyxYGqqQa0b_yfLAvq8YVUxK6HfdS-CxM-Lo,5186
24
- tradedangerous/version.py,sha256=FcS0iiOYPOsVvc2lrO3dCSzLkb3qmtanvTPMfHGFupM,646
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=NKvKP1OSCTpItc1CNxDuEH2A1oGJ6aWSyCdPSAjsG9E,2120
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=-TerJ_jKOHVuqS6P-ehKlwYDJsNNBEvu1nlpCNBsWn0,21414
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=VlQK7QGtEi2brGtWaIZDvKmbJ_vLocD4CJ8h_6kKptU,7808
74
- tradedangerous-11.2.1.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
75
- tradedangerous-11.2.1.dist-info/METADATA,sha256=Cyyx7ogtQbyTfD5sFCcXMD63dGvDlASdlqDN99ICMHs,4435
76
- tradedangerous-11.2.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
77
- tradedangerous-11.2.1.dist-info/entry_points.txt,sha256=lrA7U9JHOcNuam2WEK4Hmc3vQ3mrJfsbJCE74qd9au8,62
78
- tradedangerous-11.2.1.dist-info/top_level.txt,sha256=JEoOVAhg5GfXZ4kHpNontu0RVzek_7P9_jp93f3Pqn8,16
79
- tradedangerous-11.2.1.dist-info/RECORD,,
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,,