Compare commits
4 Commits
v2.65.2.30
...
v2.65.2.34
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b9c51db04 | |||
| b1c9b57ef7 | |||
| 5c01ecb2d1 | |||
| 564ba1d85d |
11
.dockerignore
Normal file
11
.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
||||
.venv
|
||||
.git
|
||||
dist
|
||||
build
|
||||
__pycache__
|
||||
*.pyc
|
||||
.pytest_cache
|
||||
*.zip
|
||||
*.spec
|
||||
imgs
|
||||
dist_assets
|
||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements-server.txt .
|
||||
RUN pip install --no-cache-dir -r requirements-server.txt
|
||||
|
||||
COPY . .
|
||||
EXPOSE 9123
|
||||
|
||||
CMD ["python", "pyfa_server.py"]
|
||||
36
build.sh
36
build.sh
@@ -23,10 +23,38 @@ rm -rf build dist
|
||||
echo "Building binary with PyInstaller..."
|
||||
uv run pyinstaller pyfa.spec
|
||||
|
||||
# Headless CLI exe (console) into main dist folder
|
||||
if [ -f dist/pyfa_headless/pyfa-headless.exe ]; then
|
||||
cp dist/pyfa_headless/pyfa-headless.exe dist/pyfa/
|
||||
# Sim server exe (console) into main dist folder
|
||||
if [ -f dist/pyfa_server/pyfa-server.exe ]; then
|
||||
cp dist/pyfa_server/pyfa-server.exe dist/pyfa/
|
||||
fi
|
||||
|
||||
# Docker image (Python server)
|
||||
DOCKER_REPO="${DOCKER_REPO:-docker.site.quack-lab.dev}"
|
||||
IMAGE_NAME="${IMAGE_NAME:-pyfa-server}"
|
||||
COMMIT_SHA=$(git rev-parse --short HEAD)
|
||||
IMAGE_BASE="${DOCKER_REPO}/${IMAGE_NAME}"
|
||||
|
||||
echo ""
|
||||
echo "Building Docker image..."
|
||||
docker build -t "${IMAGE_BASE}:${COMMIT_SHA}" .
|
||||
|
||||
docker tag "${IMAGE_BASE}:${COMMIT_SHA}" "${IMAGE_BASE}:latest"
|
||||
|
||||
TAGS=$(git tag --points-at HEAD 2>/dev/null || true)
|
||||
if [ -n "$TAGS" ]; then
|
||||
while IFS= read -r tag; do
|
||||
[ -n "$tag" ] && docker tag "${IMAGE_BASE}:${COMMIT_SHA}" "${IMAGE_BASE}:${tag}"
|
||||
done <<< "$TAGS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Build complete! dist/pyfa/pyfa.exe (GUI), dist/pyfa/pyfa-headless.exe (HTTP server POST /simulate :9123)"
|
||||
echo "Build complete! dist/pyfa/pyfa.exe (GUI), dist/pyfa/pyfa-server.exe (POST /simulate :9123)"
|
||||
echo ""
|
||||
echo "Docker image built as:"
|
||||
echo " - ${IMAGE_BASE}:${COMMIT_SHA}"
|
||||
echo " - ${IMAGE_BASE}:latest"
|
||||
if [ -n "$TAGS" ]; then
|
||||
while IFS= read -r tag; do
|
||||
[ -n "$tag" ] && echo " - ${IMAGE_BASE}:${tag}"
|
||||
done <<< "$TAGS"
|
||||
fi
|
||||
|
||||
43
config.py
43
config.py
@@ -1,7 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import wx
|
||||
|
||||
from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, \
|
||||
StreamHandler, TimedRotatingFileHandler, WARNING
|
||||
@@ -67,20 +66,34 @@ LOGLEVEL_MAP = {
|
||||
CATALOG = 'lang'
|
||||
|
||||
|
||||
slotColourMapDark = {
|
||||
FittingSlot.LOW: wx.Colour(44, 36, 19), # yellow = low slots 24/13
|
||||
FittingSlot.MED: wx.Colour(28, 39, 51), # blue = mid slots 8.1/9.5
|
||||
FittingSlot.HIGH: wx.Colour(53, 31, 34), # red = high slots 6.5/11.5
|
||||
FittingSlot.RIG: '',
|
||||
FittingSlot.SUBSYSTEM: ''}
|
||||
errColorDark = wx.Colour(70, 20, 20)
|
||||
slotColourMap = {
|
||||
FittingSlot.LOW: wx.Colour(250, 235, 204), # yellow = low slots
|
||||
FittingSlot.MED: wx.Colour(188, 215, 241), # blue = mid slots
|
||||
FittingSlot.HIGH: wx.Colour(235, 204, 209), # red = high slots
|
||||
FittingSlot.RIG: '',
|
||||
FittingSlot.SUBSYSTEM: ''}
|
||||
errColor = wx.Colour(204, 51, 51)
|
||||
def get_slotColourMapDark():
|
||||
import wx
|
||||
return {
|
||||
FittingSlot.LOW: wx.Colour(44, 36, 19), # yellow = low slots 24/13
|
||||
FittingSlot.MED: wx.Colour(28, 39, 51), # blue = mid slots 8.1/9.5
|
||||
FittingSlot.HIGH: wx.Colour(53, 31, 34), # red = high slots 6.5/11.5
|
||||
FittingSlot.RIG: '',
|
||||
FittingSlot.SUBSYSTEM: ''}
|
||||
|
||||
|
||||
def get_errColorDark():
|
||||
import wx
|
||||
return wx.Colour(70, 20, 20)
|
||||
|
||||
|
||||
def get_slotColourMap():
|
||||
import wx
|
||||
return {
|
||||
FittingSlot.LOW: wx.Colour(250, 235, 204), # yellow = low slots
|
||||
FittingSlot.MED: wx.Colour(188, 215, 241), # blue = mid slots
|
||||
FittingSlot.HIGH: wx.Colour(235, 204, 209), # red = high slots
|
||||
FittingSlot.RIG: '',
|
||||
FittingSlot.SUBSYSTEM: ''}
|
||||
|
||||
|
||||
def get_errColor():
|
||||
import wx
|
||||
return wx.Colour(204, 51, 51)
|
||||
|
||||
|
||||
def getClientSecret():
|
||||
|
||||
5
docker-compose.yml
Normal file
5
docker-compose.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
services:
|
||||
pyfa-server:
|
||||
image: docker.site.quack-lab.dev/pyfa-server:latest
|
||||
ports:
|
||||
- "9123:9123"
|
||||
@@ -3,7 +3,7 @@ from logbook import Logger
|
||||
|
||||
import gui.builtinMarketBrowser.pfSearchBox as SBox
|
||||
import gui.globalEvents as GE
|
||||
from config import slotColourMap, slotColourMapDark
|
||||
from config import get_slotColourMap, get_slotColourMapDark
|
||||
from eos.saveddata.module import Module
|
||||
from eos.const import FittingSlot
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES, CHARGES_FOR_FIT
|
||||
@@ -412,7 +412,7 @@ class ItemView(Display):
|
||||
|
||||
def columnBackground(self, colItem, item):
|
||||
if self.sFit.serviceFittingOptions["colorFitBySlot"]:
|
||||
colorMap = slotColourMapDark if isDark() else slotColourMap
|
||||
colorMap = get_slotColourMapDark() if isDark() else get_slotColourMap()
|
||||
return colorMap.get(Module.calculateSlot(item)) or self.GetBackgroundColour()
|
||||
else:
|
||||
return self.GetBackgroundColour()
|
||||
|
||||
@@ -42,7 +42,7 @@ from gui.utils.staticHelpers import DragDropHelper
|
||||
from gui.utils.dark import isDark
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
from config import slotColourMap, slotColourMapDark, errColor, errColorDark
|
||||
from config import get_slotColourMap, get_slotColourMapDark, get_errColor, get_errColorDark
|
||||
from gui.fitCommands.helpers import getSimilarModPositions
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -766,9 +766,9 @@ class FittingView(d.Display):
|
||||
|
||||
def slotColour(self, slot):
|
||||
if isDark():
|
||||
return slotColourMapDark.get(slot) or self.GetBackgroundColour()
|
||||
return get_slotColourMapDark().get(slot) or self.GetBackgroundColour()
|
||||
else:
|
||||
return slotColourMap.get(slot) or self.GetBackgroundColour()
|
||||
return get_slotColourMap().get(slot) or self.GetBackgroundColour()
|
||||
|
||||
def refresh(self, stuff):
|
||||
"""
|
||||
@@ -812,7 +812,7 @@ class FittingView(d.Display):
|
||||
hasRestrictionOverriden = not hasRestrictionOverriden
|
||||
|
||||
if slotMap[mod.slot] or hasRestrictionOverriden: # Color too many modules as red
|
||||
self.SetItemBackgroundColour(i, errColorDark if isDark() else errColor)
|
||||
self.SetItemBackgroundColour(i, get_errColorDark() if isDark() else get_errColor())
|
||||
elif sFit.serviceFittingOptions["colorFitBySlot"]: # Color by slot it enabled
|
||||
self.SetItemBackgroundColour(i, self.slotColour(mod.slot))
|
||||
|
||||
|
||||
@@ -346,20 +346,7 @@ class CargoInfo:
|
||||
return makeReprStr(self, ['itemID', 'amount'])
|
||||
|
||||
|
||||
def activeStateLimit(itemIdentity):
|
||||
item = Market.getInstance().getItem(itemIdentity)
|
||||
if {
|
||||
'moduleBonusAssaultDamageControl', 'moduleBonusIndustrialInvulnerability',
|
||||
'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
|
||||
'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
|
||||
'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
|
||||
'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively',
|
||||
'cargoScan', 'shipScan', 'surveyScan', 'targetSpectrumBreakerBonus',
|
||||
'interdictionNullifierBonus', 'warpCoreStabilizerActive',
|
||||
'industrialItemCompression'
|
||||
}.intersection(item.effects):
|
||||
return FittingModuleState.ONLINE
|
||||
return FittingModuleState.ACTIVE
|
||||
from service.port.active_state import activeStateLimit
|
||||
|
||||
|
||||
def droneStackLimit(fit, itemIdentity):
|
||||
|
||||
26
pyfa.spec
26
pyfa.spec
@@ -79,7 +79,7 @@ a = Analysis(['pyfa.py'],
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
|
||||
a_headless = Analysis(['pyfa_headless.py'],
|
||||
a_server = Analysis(['pyfa_server.py'],
|
||||
pathex=pathex,
|
||||
binaries=[],
|
||||
datas=added_files,
|
||||
@@ -92,7 +92,7 @@ a_headless = Analysis(['pyfa_headless.py'],
|
||||
cipher=block_cipher)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
pyz_headless = PYZ(a_headless.pure, a_headless.zipped_data, cipher=block_cipher)
|
||||
pyz_server = PYZ(a_server.pure, a_server.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
@@ -108,12 +108,12 @@ exe = EXE(
|
||||
contents_directory='app',
|
||||
)
|
||||
|
||||
# Headless: server only. POST /simulate on port 9123.
|
||||
exe_headless = EXE(
|
||||
pyz_headless,
|
||||
a_headless.scripts,
|
||||
# Sim server. POST /simulate on port 9123.
|
||||
exe_server = EXE(
|
||||
pyz_server,
|
||||
a_server.scripts,
|
||||
exclude_binaries=True,
|
||||
name='pyfa-headless',
|
||||
name='pyfa-server',
|
||||
debug=debug,
|
||||
strip=False,
|
||||
upx=upx,
|
||||
@@ -131,14 +131,14 @@ coll = COLLECT(
|
||||
name='pyfa',
|
||||
)
|
||||
|
||||
coll_headless = COLLECT(
|
||||
exe_headless,
|
||||
a_headless.binaries,
|
||||
a_headless.zipfiles,
|
||||
a_headless.datas,
|
||||
coll_server = COLLECT(
|
||||
exe_server,
|
||||
a_server.binaries,
|
||||
a_server.zipfiles,
|
||||
a_server.datas,
|
||||
strip=False,
|
||||
upx=upx,
|
||||
name='pyfa_headless',
|
||||
name='pyfa_server',
|
||||
)
|
||||
|
||||
if platform.system() == 'Darwin':
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Headless sim daemon only. POST /simulate with JSON body.
|
||||
from scripts.pyfa_cli_stats import _run_http_server
|
||||
|
||||
_run_http_server(9123, None)
|
||||
5
pyfa_server.py
Normal file
5
pyfa_server.py
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Sim server. POST /simulate with JSON body.
|
||||
from scripts.pyfa_sim import _run_http_server
|
||||
|
||||
_run_http_server(9123, None)
|
||||
28
release.sh
28
release.sh
@@ -57,4 +57,32 @@ curl -X POST \
|
||||
|
||||
rm "${ZIP}"
|
||||
|
||||
# Push Docker image
|
||||
DOCKER_REPO="${DOCKER_REPO:-docker.site.quack-lab.dev}"
|
||||
IMAGE_NAME="${IMAGE_NAME:-pyfa-server}"
|
||||
COMMIT_SHA=$(git rev-parse --short HEAD)
|
||||
IMAGE_BASE="${DOCKER_REPO}/${IMAGE_NAME}"
|
||||
|
||||
echo ""
|
||||
echo "Pushing Docker images..."
|
||||
docker push "${IMAGE_BASE}:${COMMIT_SHA}"
|
||||
docker push "${IMAGE_BASE}:latest"
|
||||
TAGS=$(git tag --points-at HEAD 2>/dev/null || true)
|
||||
if [ -n "$TAGS" ]; then
|
||||
while IFS= read -r tag; do
|
||||
[ -n "$tag" ] && docker push "${IMAGE_BASE}:${tag}"
|
||||
done <<< "$TAGS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Docker image pushed as:"
|
||||
echo " - ${IMAGE_BASE}:${COMMIT_SHA}"
|
||||
echo " - ${IMAGE_BASE}:latest"
|
||||
if [ -n "$TAGS" ]; then
|
||||
while IFS= read -r tag; do
|
||||
[ -n "$tag" ] && echo " - ${IMAGE_BASE}:${tag}"
|
||||
done <<< "$TAGS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Release complete! ${ZIP} uploaded to ${TAG}"
|
||||
|
||||
14
requirements-server.txt
Normal file
14
requirements-server.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
logbook==1.7.0.post0
|
||||
numpy==1.26.2
|
||||
matplotlib==3.8.2
|
||||
python-dateutil==2.8.2
|
||||
requests==2.31.0
|
||||
sqlalchemy==1.4.50
|
||||
cryptography==42.0.4
|
||||
markdown2==2.4.11
|
||||
packaging==23.2
|
||||
roman==4.1
|
||||
beautifulsoup4==4.12.2
|
||||
pyyaml==6.0.1
|
||||
python-jose==3.3.0
|
||||
requests-cache==1.1.1
|
||||
@@ -8,6 +8,25 @@ import eos.config
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.utils.spoolSupport import SpoolOptions, SpoolType
|
||||
|
||||
# POST /simulate request and response shape; included in 400 response body
|
||||
SIMULATE_SCHEMA = {
|
||||
"request": {
|
||||
"fit": "string (required). EFT or multi-line text export of a single fit, e.g. [ShipName, FitName] followed by modules/drones/etc.",
|
||||
"projected_fits": "array (optional). Each element: { \"fit\": string, \"count\": positive integer }.",
|
||||
"command_fits": "array (optional). Each element: { \"fit\": string }.",
|
||||
},
|
||||
"response_200": {
|
||||
"fit": {"id": "int", "name": "string", "ship_type": "string"},
|
||||
"resources": {"hardpoints": {}, "drones": {}, "fighters": {}, "calibration": {}, "powergrid": {}, "cpu": {}, "cargo": {}},
|
||||
"defense": {"hp": {}, "ehp": {}, "resonance": {}, "tank": {}, "effective_tank": {}, "sustainable_tank": {}, "effective_sustainable_tank": {}},
|
||||
"capacitor": {"capacity": {}, "recharge": {}, "use": {}, "delta": {}, "stable": {}, "state": {}, "neutralizer_resistance": {}},
|
||||
"firepower": {"weapon_dps": {}, "drone_dps": {}, "total_dps": {}, "weapon_volley": {}, "drone_volley": {}, "total_volley": {}, "weapons": []},
|
||||
"remote_reps_outgoing": {"current": {}, "pre_spool": {}, "full_spool": {}},
|
||||
"targeting_misc": {"targets_max": {}, "target_range": {}, "scan_resolution": {}, "scan_strength": {}, "scan_type": {}, "jam_chance": {}, "drone_control_range": {}, "speed": {}, "align_time": {}, "signature_radius": {}, "warp_speed": {}, "max_warp_distance": {}, "probe_size": {}, "cargo_capacity": {}, "cargo_used": {}},
|
||||
"price": {"ship": {}, "fittings": {}, "drones_and_fighters": {}, "cargo": {}, "character": {}, "total": {}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _init_pyfa(savepath: str | None) -> None:
|
||||
config.debug = False
|
||||
@@ -363,41 +382,57 @@ def compute_stats(payload: dict, savepath: str | None = None) -> dict:
|
||||
|
||||
|
||||
def _run_http_server(port: int, savepath: str | None) -> None:
|
||||
def send_error_json(code: int, msg: str, traceback_str: str | None = None):
|
||||
body = {"error": msg, "schema": SIMULATE_SCHEMA}
|
||||
if traceback_str:
|
||||
body["traceback"] = traceback_str
|
||||
return json.dumps(body, indent=2) + "\n"
|
||||
|
||||
class SimulateHandler(BaseHTTPRequestHandler):
|
||||
def send_error(self, code: int, message: str | None = None, explain: str | None = None):
|
||||
msg = message or explain or "Error"
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(send_error_json(code, msg).encode("utf-8"))
|
||||
|
||||
def _reply_error(self, code: int, msg: str, traceback_str: str | None = None):
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(send_error_json(code, msg, traceback_str).encode("utf-8"))
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == "/simulate":
|
||||
self._reply_error(405, "Method not allowed. Use POST.")
|
||||
else:
|
||||
self._reply_error(404, "Not found. POST /simulate with JSON body.")
|
||||
|
||||
def do_POST(self):
|
||||
if self.path != "/simulate":
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
self._reply_error(404, "Not found. POST /simulate with JSON body.")
|
||||
return
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length).decode("utf-8")
|
||||
try:
|
||||
payload = json.loads(body)
|
||||
except json.JSONDecodeError as exc:
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(("Invalid JSON: %s" % exc).encode("utf-8"))
|
||||
self._reply_error(400, "Invalid JSON: %s" % exc)
|
||||
return
|
||||
if not isinstance(payload, dict):
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Top-level JSON must be an object")
|
||||
self._reply_error(400, "Top-level JSON must be an object")
|
||||
return
|
||||
try:
|
||||
output = compute_stats(payload, savepath)
|
||||
except ValueError as e:
|
||||
self.send_response(400)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(str(e).encode("utf-8"))
|
||||
self._reply_error(400, str(e))
|
||||
return
|
||||
except Exception as e:
|
||||
self.send_response(500)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(str(e).encode("utf-8"))
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
sys.stderr.write(tb)
|
||||
sys.stderr.flush()
|
||||
self._reply_error(500, str(e), tb)
|
||||
return
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
@@ -28,12 +28,8 @@ from xml.etree import ElementTree
|
||||
from xml.dom import minidom
|
||||
import gzip
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import config
|
||||
import eos.db
|
||||
from service.esi import Esi
|
||||
|
||||
from eos.saveddata.implant import Implant as es_Implant
|
||||
from eos.saveddata.character import Character as es_Character, Skill
|
||||
@@ -42,7 +38,12 @@ from eos.const import FittingSlot as es_Slot
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
_t = wx.GetTranslation
|
||||
|
||||
|
||||
def _t(s):
|
||||
import wx
|
||||
return wx.GetTranslation(s)
|
||||
|
||||
|
||||
class CharacterImportThread(threading.Thread):
|
||||
|
||||
@@ -97,6 +98,7 @@ class CharacterImportThread(threading.Thread):
|
||||
pyfalog.error(e)
|
||||
continue
|
||||
|
||||
import wx
|
||||
wx.CallAfter(self.callback)
|
||||
|
||||
def stop(self):
|
||||
@@ -132,6 +134,7 @@ class SkillBackupThread(threading.Thread):
|
||||
with open(path, mode='w', encoding='utf-8') as backupFile:
|
||||
backupFile.write(backupData)
|
||||
|
||||
import wx
|
||||
wx.CallAfter(self.callback)
|
||||
|
||||
def stop(self):
|
||||
@@ -386,6 +389,7 @@ class Character:
|
||||
|
||||
def apiFetchCallback(self, guiCallback, e=None):
|
||||
eos.db.commit()
|
||||
import wx
|
||||
wx.CallAfter(guiCallback, e)
|
||||
|
||||
@staticmethod
|
||||
@@ -501,6 +505,7 @@ class UpdateAPIThread(threading.Thread):
|
||||
try:
|
||||
char = eos.db.getCharacter(self.charID)
|
||||
|
||||
from service.esi import Esi
|
||||
sEsi = Esi.getInstance()
|
||||
sChar = Character.getInstance()
|
||||
ssoChar = sChar.getSsoCharacter(char.ID)
|
||||
|
||||
@@ -22,7 +22,6 @@ import datetime
|
||||
from time import time
|
||||
from weakref import WeakSet
|
||||
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
import eos.db
|
||||
@@ -235,6 +234,7 @@ class Fit:
|
||||
@classmethod
|
||||
def getCommandProcessor(cls, fitID):
|
||||
if fitID not in cls.processors:
|
||||
import wx
|
||||
cls.processors[fitID] = wx.CommandProcessor(maxCommands=100)
|
||||
return cls.processors[fitID]
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ import threading
|
||||
from collections import OrderedDict
|
||||
from itertools import chain
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from logbook import Logger
|
||||
from sqlalchemy.sql import or_
|
||||
|
||||
@@ -38,7 +36,12 @@ from service.settings import SettingsProvider
|
||||
from utils.cjk import isStringCjk
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
_t = wx.GetTranslation
|
||||
|
||||
|
||||
def _t(s):
|
||||
import wx
|
||||
return wx.GetTranslation(s)
|
||||
|
||||
|
||||
# Event which tells threads dependent on Market that it's initialized
|
||||
mktRdy = threading.Event()
|
||||
@@ -77,6 +80,7 @@ class ShipBrowserWorkerThread(threading.Thread):
|
||||
set_ = sMkt.getShipList(id_)
|
||||
cache[id_] = set_
|
||||
|
||||
import wx
|
||||
wx.CallAfter(callback, (id_, set_))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
@@ -170,6 +174,7 @@ class SearchWorkerThread(threading.Thread):
|
||||
for item in all_results:
|
||||
if sMkt.getPublicityByItem(item):
|
||||
item_IDs.add(item.ID)
|
||||
import wx
|
||||
wx.CallAfter(callback, sorted(item_IDs))
|
||||
|
||||
def scheduleSearch(self, text, callback, filterName=None):
|
||||
@@ -268,7 +273,7 @@ class Market:
|
||||
self.les_grp = types_Group()
|
||||
self.les_grp.ID = -1
|
||||
self.les_grp.name = "Limited Issue Ships"
|
||||
self.les_grp.displayName = _t("Limited Issue Ships")
|
||||
self.les_grp.displayName = "Limited Issue Ships"
|
||||
self.les_grp.published = True
|
||||
ships = self.getCategory("Ship")
|
||||
self.les_grp.category = ships
|
||||
|
||||
12
service/port/active_state.py
Normal file
12
service/port/active_state.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Module-state logic shared by port (EFT import) and gui (fit commands).
|
||||
# Uses gamedata (item type + attributes), no hardcoded effect names.
|
||||
|
||||
from eos.const import FittingModuleState
|
||||
from service.market import Market
|
||||
|
||||
|
||||
def activeStateLimit(itemIdentity):
|
||||
item = Market.getInstance().getItem(itemIdentity)
|
||||
if not item.isType("active") or item.getAttribute("activationBlocked", 0) > 0:
|
||||
return FittingModuleState.ONLINE
|
||||
return FittingModuleState.ACTIVE
|
||||
@@ -47,6 +47,8 @@ SLOT_ORDER = (FittingSlot.LOW, FittingSlot.MED, FittingSlot.HIGH, FittingSlot.RI
|
||||
OFFLINE_SUFFIX = '/offline'
|
||||
NAME_CHARS = r'[^,/\[\]]' # Characters which are allowed to be used in name
|
||||
|
||||
from service.port.active_state import activeStateLimit
|
||||
|
||||
|
||||
class MutationExportData:
|
||||
|
||||
@@ -240,7 +242,6 @@ def exportCargo(cargos):
|
||||
|
||||
|
||||
def importEft(lines):
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
lines = _importPrepare(lines)
|
||||
try:
|
||||
fit = _importCreateFit(lines)
|
||||
@@ -877,7 +878,6 @@ class AbstractFit:
|
||||
self.getContainerBySlot(m.slot).append(m)
|
||||
|
||||
def __makeModule(self, itemSpec):
|
||||
from gui.fitCommands.helpers import activeStateLimit
|
||||
# Mutate item if needed
|
||||
m = None
|
||||
if itemSpec.mutationIdx in self.mutations:
|
||||
|
||||
@@ -38,7 +38,6 @@ from service.port.eft import (
|
||||
isValidImplantImport, isValidBoosterImport)
|
||||
from service.port.esi import exportESI, importESI
|
||||
from service.port.multibuy import exportMultiBuy
|
||||
from service.port.shipstats import exportFitStats
|
||||
from service.port.xml import importXml, exportXml
|
||||
from service.port.muta import parseMutant, parseDynamicItemString, fetchDynamicItem
|
||||
|
||||
@@ -346,4 +345,5 @@ class Port:
|
||||
|
||||
@staticmethod
|
||||
def exportFitStats(fit, callback=None):
|
||||
return exportFitStats(fit, callback=callback)
|
||||
from service.port.shipstats import exportFitStats as _exportFitStats
|
||||
return _exportFitStats(fit, callback=callback)
|
||||
|
||||
@@ -24,7 +24,6 @@ import timeit
|
||||
from itertools import chain
|
||||
|
||||
import math
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from eos import db
|
||||
@@ -51,6 +50,8 @@ class Price:
|
||||
sources = {}
|
||||
|
||||
def __init__(self):
|
||||
# Import market sources so they register; avoid at module level so server path never loads them
|
||||
from service.marketSources import evemarketdata, fuzzwork, cevemarket, evetycoon # noqa: F401
|
||||
# Start price fetcher
|
||||
self.priceWorkerThread = PriceWorkerThread()
|
||||
self.priceWorkerThread.daemon = True
|
||||
@@ -253,6 +254,7 @@ class PriceWorkerThread(threading.Thread):
|
||||
if len(requests) > 0:
|
||||
Price.fetchPrices(requests, fetchTimeout, validityOverride)
|
||||
|
||||
import wx
|
||||
wx.CallAfter(callback)
|
||||
queue.task_done()
|
||||
|
||||
@@ -273,7 +275,3 @@ class PriceWorkerThread(threading.Thread):
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
# Import market sources only to initialize price source modules, they register on their own
|
||||
from service.marketSources import evemarketdata, fuzzwork, cevemarket, evetycoon # noqa: E402
|
||||
|
||||
@@ -24,7 +24,6 @@ import urllib.error
|
||||
import urllib.parse
|
||||
import json
|
||||
from collections import namedtuple
|
||||
import wx
|
||||
|
||||
from logbook import Logger
|
||||
|
||||
@@ -582,6 +581,7 @@ class LocaleSettings:
|
||||
@classmethod
|
||||
def supported_languages(cls):
|
||||
"""Requires the application to be initialized, otherwise wx.Translation isn't set."""
|
||||
import wx
|
||||
pyfalog.info(f'using "{config.CATALOG}" to fetch languages, relatively base path "{os.getcwd()}"')
|
||||
return {x: wx.Locale.FindLanguageInfo(x) for x in wx.Translations.Get().GetAvailableTranslations(config.CATALOG)}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -7,7 +6,7 @@ import tempfile
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(os.path.realpath(os.path.join(script_dir, "..")))
|
||||
|
||||
from scripts import pyfa_cli_stats # noqa: E402
|
||||
from scripts import pyfa_sim # noqa: E402
|
||||
|
||||
|
||||
ISHTAR_SPIDER_FIT = """[Ishtar, Spider]
|
||||
@@ -41,7 +40,7 @@ Berserker II x5
|
||||
def test_ishtar_spider_remote_armor_reps():
|
||||
payload = {"fit": ISHTAR_SPIDER_FIT}
|
||||
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp:
|
||||
data = pyfa_cli_stats.compute_stats(payload, tmp)
|
||||
data = pyfa_sim.compute_stats(payload, tmp)
|
||||
armor_rps = data["remote_reps_outgoing"]["current"]["armor"]
|
||||
assert armor_rps is not None
|
||||
assert int(round(armor_rps)) == 171
|
||||
Reference in New Issue
Block a user