From b6425001f22f8af4635d6bc05fb4cf170972dfcb Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 5 Jan 2021 16:14:12 +0000 Subject: [PATCH 001/261] Add killswitches.json to releases branch --- killswitches.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 killswitches.json diff --git a/killswitches.json b/killswitches.json new file mode 100644 index 000000000..c1c3f5a29 --- /dev/null +++ b/killswitches.json @@ -0,0 +1,6 @@ +{ + "__COMMENT_READ_ME": "Do *NOT* commit this file to any branch other than `releases` !!! See docs/Killswitches.md", + "version": 1, + "last_updated": "2021-01-05", + "kill_switches": [] +} From da04a9e301db039aa2889412bdc3c94e9352785d Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 12 Jan 2021 16:32:59 +0000 Subject: [PATCH 002/261] appcast: 4.1.6 --- edmarketconnector.xml | 58 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index a696a1e42..50cb38b01 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -168,11 +168,63 @@ - Release 4.1.5 + Release 4.1.6 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

Release 4.1.6

+

We might have finally found the cause of the application hangs during shutdown. +Note that this became easier to track down due to the downtime +for migration of www.edsm.net around 2021-01-11. Before these fixes EDSM's +API not being available would cause an EDMC hang on shutdown.

+
    +
  • +

    We've applied extra paranoia to some of the application shutdown code to +ensure we're not still trying to handle journal events during this sequence.

    +

    We also re-ordered the shutdown sequence, which might help avoid the shutdown +hang.

    +

    If you encounter a shutdown hang then please add a comment and log files to +Application can leave a zombie process on shutdown #678 +to help us track down the cause and fix it.

    +
  • +
  • +

    We now avoid making Tk event_generate() calls whilst the appliction is +shutting down.

    +
  • +
  • +

    Plugins should actively avoid making any sort of Tk event_generate() call +during application shutdown.

    +

    This means using if not config.shutting_down: to gate any code in worker +threads that might attempt this. Also, be sure you're not attempting such +in your plugin_stop() function.

    +

    See plugins/edsm.py and plugins/inara.py for example of the usage.

    +
  • +
  • +

    Any use of plug.show_error() won't actually change the UI status line +during shutdown, but the text you tried to show will be logged instead.

    +
  • +
  • +

    Cargo tracking will now correctly count all instances of the same type of +cargo for different missions. Previously it only counted the cargo for +the last mission requiring that cargo type, as found in Cargo.json.

    +
  • +
  • +

    The loaded contents of Cargo.json can now be found in monitor.state['CargoJSON']. +monitor.state is what is passed to plugins as state in the +journal_entry() call.

    +
  • +
  • +

    Our logging code should now cope with logging from a property.

    +
  • +
  • +

    Logging from any name-mangled method should now work properly.

    +
  • +
  • +

    Miscellaneous updates to PLUGINS.md - mostly to clarify some things.

    +
  • +
+

Release 4.1.5

This is a minor maintenance release, mostly addressing behaviour around process shutdown and startup, along with a couple of small enhancements that @@ -886,10 +938,10 @@ If any of your plugins are listed in that section then they will need updating, ]]> From 01fa174ca324a6ae33a1d6ba2b8df80007142661 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 30 Jan 2021 10:25:03 +0000 Subject: [PATCH 003/261] appcast: Reduce changelogs to bare minimum. This is to see if 3.4.3.0 will then offer the update without crashing. --- edmarketconnector.xml | 842 ------------------------------------------ 1 file changed, 842 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 50cb38b01..3d524be94 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -21,138 +21,6 @@

  • Version increased to 3.4.4.0 / 3.44.
  • URL the application checks for updates changed to point to github,
  • -
-

Release 3.44

-

Release 3.43

-
    -
  • New commodity and modules from “September Update”.
  • -
  • Misc fixes.
  • -
  • More control over plugin widget colors.
  • -
-

The first time that you run the app while playing the game you are redirected to Frontier's authentication website and prompted for your username and password.

-

Release 3.42

-
    -
  • Use EDSY.org address for EDShipyard.
  • -
  • Add advanced multi-cannon from “Bridging the Gap”.
  • -
-

Release 3.41

-
    -
  • Transparent theme window size reduced.
  • -
-

Release 3.40

-
    -
  • Add new modules in 3.4.
  • -
  • Improved authentication when app started with game already running.
  • -
-

Release 3.38

-
    -
  • More authentication fixes.
  • -
  • Send influence and reputation gain to Inara on mission completion.
  • -
-

Release 3.37

-
    -
  • More authentication fixes.
  • -
  • More robust/graceful handling of Frontier Auth and/or cAPI server outages.
  • -
-

Release 3.36

-
    -
  • Fix for forthcoming Frontier authentication changes.
  • -
-

Release 3.35

-
    -
  • Display feedback on successful authentication.
  • -
  • Outfitting and Shipyard data also sent to EDDN on visiting outfitting or shipyard in-game, and tagged with a “Horizons” flag.
  • -
  • Sends your local faction reputation to Inara.
  • -
  • Fix for preferences window appearance on macOS Mojave.
  • -
-

Release 3.33

-
    -
  • More authentication fixes.
  • -
-

Release 3.32

-
    -
  • Fix for token expiry during a session (“Frontier server is down” error).
  • -
  • Force re-authentication if credentials entered for wrong Cmdr.
  • -
  • More logging of OAuth failures.
  • -
-

Release 3.31

-
    -
  • Support for OAuth2-based access to station commodity market, outfitting and shipyard data.
  • -
  • Fix for command-line program.
  • -
  • Improved handling of authentication errors.
  • -
  • Commodity market data also sent to EDDN on visiting the in-game commodity market.
  • -
  • Misc fixes.
  • -
-

Release 3.30

-
    -
  • Support for OAuth2-based access to station commodity market, outfitting and shipyard data.
  • -
  • Commodity market data also sent to EDDN on visiting the in-game commodity market.
  • -
  • Misc fixes.
  • -
-

Release 3.20

-
    -
  • Preliminary support for E:D 3.3.
  • -
  • Support accessing Journal on macOS remotely over SMB.
  • -
  • Misc fixes.
  • -
-

Release 3.12

- -

Release 3.11

-
    -
  • Misc fixes.
  • -
-

Release 3.10

-
    -
  • Support for new ships and modules in E:D 3.1.
  • -
  • Fix for sending ship loadouts with engineered modules with certain secondary effects to Inara.
  • -
  • Add separators between plugins in main window.
  • -
  • Chinese (Simplified) translation courtesy of Cmdr Zhixian Wu.
  • -
  • Portuguese (Portugal) translation courtesy of Carlos Oliveira.
  • -
-

Release 3.06

-
    -
  • Extend localisation support to plugins.
  • -
  • Hungarian translation courtesy of Cmdr Wormhole.
  • -
  • Misc fixes.
  • -
-

Release 3.05

-
    -
  • Fix for “Frontier server is down” error on systems with primary language other than English.
  • -
  • Fix for TD prices file format.
  • -
-

Release 3.04

-
    -
  • Export ship loadout to Coriolis in Journal “Loadout” format.
  • -
  • Fix for “This app requires accurate timestamps” error - get timestamps for cAPI-derived data from cAPI server.
  • -
  • Fix for TCE integration.
  • -
  • Support for “package plugins”.
  • -
-

Release 3.03

-
    -
  • Fixes for stats and plugin display.
  • -
-

Release 3.02

-
    -
  • Choose between eddb, EDSM and Inara for station and shipyard links.
  • -
  • Don't display “Solo” mode in main window.
  • -
  • Fix for saving ship loadout to file when ship name contains punctuation.
  • -
-

Release 3.01

-
    -
  • Various fixes for EDSM, Inara and TCE integrations.
  • -
  • Fix for failure to terminate cleanly.
  • -
  • Switch ship loadout file to journal format.
  • -
-

Release 3.00

-
    -
  • Support for E:D 3.0.
  • -
  • Updates your entire fleet on EDSM and/or Inara whenever you visit the shipyard in game.
  • -
  • Updates your current ship's loadout on EDSM and/or Inara whenever it changes.
  • -
  • Plugin access to your dashboard status.
]]>
@@ -225,716 +93,6 @@ the last mission requiring that cargo type, as found in Cargo.json.

-

Release 4.1.5

-

This is a minor maintenance release, mostly addressing behaviour around -process shutdown and startup, along with a couple of small enhancements that -most users won't notice.

-
    -
  • -

    If there is already an EDMarketConnector.exe process running when trying -to run another instance then that new process will no longer exit silently. -Instead you'll get a pop-up telling you it's detected another process, and -you need to close that pop-up in order for this additional process to then -exit.

    -

    This hopefully makes it obvious when you've got a hung EDMarketConnect.exe -process that you need to kill in order to re-run the program.

    -
  • -
  • -

    In order to gather more information about how and why EDMarketConnector.exe -sometimes doesn't shutdown properly we've added some extra debug logging to -the sequence of clean-up calls performed during shutdown.

    -

    Also, to make it more obvious if the process has hung during shutdown the -UI window is no longer hidden at the start of this shutdown sequence. It -will instead linger, with "Shutting down..." showing in the status line -(translation for this small phrase will be added in a later release).

    -

    If you encounter this shutdown hang then please add a comment to -Application can leave a zombie process on shutdown #678 -to help us track down the cause and fix it.

    -
  • -
  • -

    Cater for 'mangled name' class functions in our logging code. e.g. where -you name a class member with a __ prefix in order to 'hide' it from -out-of-class code.

    -
  • -
  • -

    To help track down the cause of Crashing On Startup #798 -we've added some exception catching in our logging code. If this is -triggered you will see ??:?? in logging output, instead of class and/or -function names.

    -

    If you encounter this then please comment on that bug report to aid us in -tracking down the root cause!

    -
  • -
  • -

    Fixed logging from EDMC.exe so that the -debug log goes into EDMC-debug.log -not EDMarketConnector-debug.log.

    -
  • -
  • -

    Fix EDMC.exe -j handling of file encodings. NB: This command-line -argument isn't listed on EDMC.exe -h as it's intended for developer use -only.

    -
  • -
  • -

    Fix the name of 'Void Opal(s)' so that output of market data to files is -correct.

    -
  • -
  • -

    Fix URL in PLUGINS.md to refer to main, not master branch.

    -
  • -
  • -

    We're able to pull py2exe from PyPi now, so docs/Releasing.md has been -update to reflect this.

    -
  • -
- - -

Release 4.1.4

-

The only change from 4.1.3 is to insert some Windows version checks before -even attempting to set a UTF-8 encoding. We'll now only attempt this if the -user is not on Windows, or is on at least Windows 10 1903.

-

For unknown reasons no exception was being thrown under some circumstances (in -this case running under an earlier Windows 10, but with EDMarketConnector.exe -set to run in Windows 7 compatibility mode for some unknown reason).

- -

Release 4.1.3

-
    -
  • -

    Revert to not setting gdiScaling in the application manifest. This should -fix #734 -and #739.

    -

    A side effect will be that the radio buttons in Preferences > Appearance -for the Theme selection will once more be improperly sized under any UI -scaling. This is a Tcl/Tk bug which they have fixed in their code, but not -yet made a new release containing that fix. We'll have it fixed when Tcl/Tk -release a fixed version and Python releases a fixed version, that we use, -that includes the fixed libraries.

    -
  • -
  • -

    Wraps some ctypes code in a try/except in order to fix -#737. This should -benefit anyone running EDMC under any Wine version that doesn't set the -registry key we check for.

    -

    Note, however, that we recommend running EDMarketConnector natively from -source if using Linux.

    -
  • -
- -

Release 4.1.2

-
    -
  • Minor fix to EDMC.py to revert broken logic trying to detect when there is -neither commodities nor outfitting data for a station.
  • -
- -

Release 4.1.1

-

This release should get the program running again for everyone who had issues -with 4.1.0.

-
    -
  • -

    Catch any exception when we try to set UTF-8 encoding. We'll log where this -fails but the program should continue running.

    -
  • -
  • -

    The use of the tkinter.filedialog code is now contingent on a UTF-8 -encoding being set. If it isn't then we'll revert to the previous -non-tkinter file dialog code. The older OSes that can't handle a UTF-8 -encoding will get that slightly worse file dialog (that was previously -always the case before 4.1.0). Everyone else gets to enjoy the more up to -date file dialog with all the shortcuts etc.

    -
  • -
- -

Release 4.1.0

-

This release contains the result of a lot of code cleanup on several files -and the addition of a proper logging paradigm, which should aid us in tracking -down bugs.

-

None of the code cleanups should change actual program behaviour, but as we -don't yet have the code in a state to have proper tests it's possible we've -broken something.

-
    -
  • -

    The error 'list' object has no attribute 'values' should now be fixed.

    -
  • -
  • -

    This version will attempt to send empty market commodity lists over EDDN. -The benefit of this is it will show when a Fleet Carrier no longer has any -buy or sell orders active.

    -

    At this time the EDDN Gateway will reject these messages. We're catching -and suppressing that (but log a message at TRACE level). If/when the EDDN -schema is updated and the Gateway starts using that this will mean, -e.g. EDDB, can start better tracking Fleet Carrier markets.

    -
  • -
  • -

    We are now explicitly a Unicode application:

    -
      -
    1. -

      A manifest setting in both EDMarketConnector.exe and EDMC.exe now -specifies they're Unicode applications so that they default to using the -UTF-8 codepage.

      -
    2. -
    3. -

      We are now explicitly setting a UTF8 encoding at startup. NB: This is -still necessary so that users running from source code are also using the -UTF-8 encoding, there's no manifest in that scenario.

      -

      This shouldn't have any side effects and has allowed us to switch to -the native tkinter file dialogs rather than some custom code.

      -
    4. -
    -

    If you do encounter errors that might be related to this then it would be -useful to see the logging output that details the Locale settings at -various points during startup. Examples might include incorrect text being -rendered for your language when you have it set, or issues with filenames -and their content, but any of these are unlikely.

    -
  • -
  • -

    EDMarketConnector.exe now has gdiScaling set to true in its manifest. This -results in better Windows OS scaling of the UI (radio buttons scale correctly -now). This might negate the need for our own UI Scaling (see below), but -we're leaving the functionality in for anyone who finds it useful.

    -
  • -
  • -

    New UI Scaling option! Find the setting on the 'Appearance' tab of Settings.

    -
      -
    1. This will only actually take effect after restarting the application.
    2. -
    3. The 'Default' theme's menu names won't be resized due to using the -default font. The other two themes work properly though as they use -a custom font for those texts.
    4. -
    5. As per the note next to the settings bar, "100" means "default", so set -it to that if you decide you don't need the UI scaling.
    6. -
    7. If you select 0 it will become 100 on the next startup.
    8. -
    -

    Plugin Authors: If you are doing per-pixel things in your UI then you'll -want to check config.get('ui_scale') and adjust accordingly. 100 -means default scaling with other values being a percentage relative to -that (so 150 means you need to scale everything x1.5).

    -
  • -
  • -

    Code dealing with Frontier's CAPI was cleaned up, so please report any -issues related to that (mostly when just docked or when you press the Update -button).

    -
  • -
  • -

    We now have proper logging available, using the python module of that name. -Plugin Authors, please change your code to using proper logging, as per the -new 'Logging' section of PLUGINS.md, rather than simple print(...) -statements.

    -
      -
    1. -

      We have a TRACE level of log output. By default this is turned off. -Run either EDMarketConnector or EDMC with --trace flag to enable. This is -intended for use where we need finer-grained tracing to track down a bug, -but the output would be too spammy in normal use.

      -

      To make it easy for users to run with TRACE logging there's a new file -EDMarketConnector - TRACE.bat. Running this should result in the program -running with tracing. Recommended use is to navigate a Windows File -Explorer window to where EDMarketConnector.exe is installed then -double-click this .bat file.

      -
    2. -
    3. -

      EDMC.py has a new --loglevel command-line argument. See EDMC.py -h -for the possible values. It defaults to 'INFO', which, unless there's an -error, should yield the same output as before.

      -
    4. -
    5. -

      EDMC.exe will now log useful startup state information if run with the ---loglevel DEBUG arguments.

      -
    6. -
    7. -

      EDMarketConnector has a new 'Loglevel' setting on the 'Configuration' tab -to change the loglevel. Default is 'INFO' and advised for normal use. -If reporting a bug it will be very helpful to change this to 'DEBUG' and -then reproduce the bug. Changes to this will take effect immediately, no -need for a restart.

      -
    8. -
    9. -

      Both programs not only log to their old locations (console for EDMC, and -%TEMP%\EDMarketConnector.log for the main application), but now also to -a size-limited and rotated logfile inside the folder -%TEMP%\EDMarketConnector\ .

      -
        -
      1. The base filename inside there is EDMarketConnector-debug.log for the -main program and EDMC-debug.log for the command-line program.
      2. -
      3. A new file is only started if/when it reaches the 1 MiB size limit.
      4. -
      5. We'll keep at most 10 backups of each file, so the maximum disk space -used by this will be 22 MiB.
      6. -
      7. Only actually logged output goes to these files, which currently is -far from all the traditional output that goes to the old file/console. -Anything using print(...) will not appear in these new files.
      8. -
      9. These files always default to DEBUG level, whereas the old log file -continues to follow the user-set logging level.
      10. -
      -
    10. -
    11. -

      Default logging level for plugins is DEBUG. This won't change what's -actually logged, it just ensures that everything gets through to the two -channels that then decide what is output.

      -
    12. -
    -
  • -
  • -

    There's a little extra DEBUG logging at startup so we can be sure of some -things like Python version used (pertinent if running from source).

    -
  • -
  • -

    Minor tweak to EDDN plugin logging so we know what message we tried to send -if it fails.

    -
  • -
  • -

    More logging added to companion.py to aid diagnosing Frontier Auth issues.

    -
  • -
  • -

    Extra TRACE level logging added for when we process Location, Docked, -t puFSDJump and CarrierJump events for EDSM. This was added to help track -down the cause of #713.

    -
  • -
-

Translators: There are new strings to translate related to Log Levels -and the new UI Scaling. Thanks to those who already updated!

-

There was a series of betas and release candidates between 4.0.6 and 4.1.0, -see their individual changelogs on -GitHub EDMarketConnector Releases. -All the pertinent changes in them were folded into the text above.

- -

Release 4.0.6

-
    -
  • Correct the three System Provider plugins to not show the next system -in a plotted route instead of the current system.
  • -
- -

Release 4.0.5

-
    -
  • Built using Python 3.7.9.
  • -
  • Fix EDSM plugin so the System provider actually updates the URLs for -jumps to new systems.
  • -
-

In general this cleans up the code for all three System and Station Providers; -EDDB, EDSM, Inara.

- -

Release 4.0.4

-
    -
  • -

    Built using Python 3.7.8. Prior 4.0.x releases used 3.7.7.

    -
  • -
  • -

    Don't crash if no non-default Journal Directory has been set.

    -
  • -
  • -

    Only send to Inara API at most once every 30 seconds. This should avoid -the "Inara 400 Too much requests, slow down, cowboy. ;) ..." message and -being locked out from the API for an hour as a result. Any events that -require data to be sent during the 30s cooldown will be queued and sent when -that timer expires.

    -

    This was caused by previous changes in an attempt to send cargo events -to Inara more often. This fix retains that enhancement.

    -

    Note that if you log out and stop EDMC within 30 seconds you might have -some events not sent. If we tried to force a send then it might hit the -limit when you want to log back in and continue playing. As it is you can -re-run EDMC and log back into the game to ensure Inara is synchronised -properly.

    -
  • -
- - -

Release 4.0.3

-

NB: Anyone who installed a 4.0.3-rcX release candidate version should first -uninstall it before installing this. -
Your settings are safe, they're in either the Registry on Windows, or in a -file outside the install location on other OSes. -
Your third-party plugins should also be safe, because you placed them in -e.g. %LOCALAPPDATA%\EDMarketConnector\plugins, not in the installation -plugins folder, didn't you ?

-

This release contains fixes for a handful of bugs in 4.0.2.0, as well as a -switch to full Semantic Version -strings.

-
    -
  • -

    Switch to Semantic Version strings.

    -
      -
    • As part of this the version check with EDMC.exe -v might now show -some exception/error output if it fails to download and parse the appcast -file. The string it shows, new version available or not, should be the -same format as previously.
    • -
    -
  • -
  • -

    Fix for bug #616 - EDMC Not Showing "Station" after Update -This was caused by changes to the EDDB plugin inadvertently no longer -maintaining some state that it turned out the Inara plugin was depending -on.

    -
      -
    • Inara plugin is now using direct URLs for System and Station links. It -no longer relies on you having entered an Inara API Key.
    • -
    • All three 'provider' plugins (EDDB, EDSM, Inara) should now be using the -same logic for when they update and what they display.
    • -
    • If you Request Docking, whether the request succeeds or not, the -station name will now show and be clickable.
    • -
    • If you Undock, Supercruise away or FSDJump away then any station name -will be replaced with a × (multiply) character. As with unpopulated -systems clicking this will take you either to the system page, or to a -list of stations in the system (depending on provider used).
    • -
    -
  • -
  • -

    A fix for ships without a player-set name using a single (space -character) as their name in the UI, instead of the ship model name.

    -

    See #614 - Ship is not displaying but IS hotlinked.

    -
  • -
  • -

    A fix for some file paths on Linux not understanding ~ as "my home -directory". this should help anyone setting up on linux.

    -

    See #486 - Some info about running on Manjaro.

    -
  • -
  • -

    A new option to use an alternate method of opening a URL for shipyard links. -It's called 'Use alternate URL method' and is located in the 'File' > -'Settings' dialogue on the 'Configuration' tab, next to the dropdown used to -choose shipyard provider. If your setup results in coriolis.io or edsy.org -saying they can't load your build then try toggling this on.

    -

    This method writes a small .html file, -%LOCALAPPDATA%\EDMarketConnector\shipyard.html -(or other-OS equivalent location), and directs your browser to open that. -The file contains a meta refresh redirect to the URL for your build on -your chosen shipyard provider. The file is not deleted after use, so -you can also use this as "let's re-open that last build" facility even -without -EDMC running.

    -

    Please let us know if this doesn't work for you! -Anti-Virus or Software Firewalls might object to the "open .html file, and -then it redirects" workaround.

    -

    See #617 - Ship load out link error.

    -
  • -
  • -

    Translations updated:

    -
      -
    • New phrases were added and the only 100% translated languages are now: -Czech, Finnish, German, Italian, Japanese, Portugese (Brazil), Russian, -Serbian (Latin), Serbian (Latin, Bosnia and Herzegovina).
    • -
    -

    Thank you translators! Please do contribute on -the OneSkyApp project -if you are able to.

    -
  • -
- - -

Release 4.0.2.0

-

Only a minor fix to EDMC.exe

-
    -
  • Restore the reporting of new releases for EDMC.exe -v.
  • -
- -

Release 4.0.1.0

-

This fixes a bug with the EDDB 'System Provider' URLs.

-
    -
  • It was possible to pick up, and use, a bad SystemAddress from the Frontier -CAPI. The CAPI will no longer be used as a source for this.
  • -
  • If we do not yet have a SystemAddress from the Journal we will use the -SystemName instead. This carries the small risk of the player being in one -of the duplicate-name systems, in which case EDDB might not display the -correct system.
  • -
- -

Release 4.0.0.0

-

Developers please note the new Contributing.md -, particularly Git branch structure and tag conventions -.

-
    -
  • -

    This release is based on Python 3.7, not 2.7, so a user might find some of -their plugins stop working. If you have any plugins that do not have the -proper support you'll see a popup about this when you -start the program, at most once every 24 hours. As directed on that -popup you can check the status of -your plugins on 'File' > 'Settings' > 'Plugins' in the new 'Plugins Without -Python 3.x Support:' section.

    -

    If the popup gets annoying then follow the directions to -disable a plugin.

    -

    For any plugins without Python 3.x support you should first ensure you're -using the latest version of that plugin. If that hasn't been updated then -you might want to contact the plugin developer to see if they'll update the -plugin. We've checked many plugins and put them in the appropriate -section of this list.

    -

    Plugin authors should also read the latest Developer Plugin -Documentation -, particularly the section -Available imports -. Let us know if we've missed anything.

    -
  • -
  • -

    New 'Help' > 'About E:D Market Connector' menu item to show the currently -running version. Includes a link to the release notes.

    -
  • -
  • -

    Translations updated:

    -
      -
    • -

      New languages: Serbian (Latin, Bosnia and Herzegovina), -Slovenian (Slovenia) and Swedish.

      -
    • -
    • -

      New phrases were added and the only 100% translated languages are now: -Czech, French, German, Japanese, Polish, Portugese (Brazil), -Portugese (Portugal), Russian, Serbian (Latin), -Serbian (Latin, Bosnia and Herzegovina), Spanish, Swedish (Sweden) -Ukrainian,

      -
    • -
    -

    Thank you translators! Please do contribute on -the OneSkyApp project -if you are able to.

    -
  • -
  • -

    EDDB plugin now uses a system's SystemAddress to construct the URL to view -the system on eddb.io. This removes the need for the systems.p file. -That file will be removed in a future version, plugin authors should not -be relying on its presence.

    -
  • -
  • -

    EDDB plugin now uses a station's MarketID to construct a URL to view the -station on eddb.io. This removes the need for stations.p. That file will -be removed in a future version, plugin authors should not be relying on its -presence.

    -

    NB: It's now using the system's "Population" data from Journal messages to -determine if the system has stations or not. This allows for the x as -station name to be clickable to open the eddb.io page for system when you're -not docked. It's known that some systems with stations have a Population of -"0" and thus won't allow this functionality. This is Frontier's issue, not -EDMC's. If you logged out in a populated system, run EDMC afresh, and use -the 'Update' button you won't see the x until you login fully to the game.

    -
  • -
  • -

    Tweak to Inara plugin so it will send updates via the Inara API more -frequently. Will now send an update, no more often than about once a -minute, if your cargo changes at all. This still won't update if you dock -and quickly buy or sell some cargo, but it's better than it was before. -You can nudge it by waiting a minute then re-opening the Commodities screen, -or indeed performing any other action the logs a new Journal event.

    -
  • -
  • -

    The old 'anonymous' and custom 'uploaderID' options were taken out of -the UI back in December 2018, but the settings lingered in the Windows -Registry. Thus some users would still have been sending an anonymised or -custom 'uploaderID' in EDDN messages with no easy way to de-activate this.

    -

    The EDDN Relay has been forcefully anonymising uploaderID since March -2018 anyway, so this is redundant. Thus the code that performs this -anonymisation has now been removed.

    -
  • -
  • -

    There used to be an option to output commodities data in 'BPC' format, but -it was removed from the UI back in Dec 2016. A few small pieces of code -lingered and they have now been removed. Any plugin that was passing -COMMODITY_BPC to commodity.export() will now break.

    -
  • -
  • -

    Fixed a bug where certain combinations of 'Output' and 'EDDN' options would -lead to all options on both tabs reverting to their defaults.

    -
  • -
  • -

    Fixed a bug where if you copied a Journal file to the live location, -resulting in a "Journal.YYMMDDHHMMss.XX - Copy.log" file, the application -would pick it up as 'new' and potentially re-send duplicate data to all of -EDDN, EDSM and Inara.

    -

    Now the only files the application will take note of must:

    -
      -
    1. Start with Journal. or JournalBeta..
    2. -
    3. Have the 12-digit date/timestamp, followed by a . -
    4. -
    5. Have the 2 digit serial number, followed by a . -
    6. -
    7. Nothing else before the trailing log.
    8. -
    -
  • -
  • -

    Fixed the location of Registry keys for the update checker, WinSparkle:

    -
      -
    • To be under the new EDCD Registry key in -Computer\HKEY_CURRENT_USER\Software\.
    • -
    • To be under EDMarketConnector instead of EDMarketConnector.py inside -there.
    • -
    -
  • -
  • -

    Fixed to throw an exception, rather than a Segmentation Fault, if -run on Linux without DISPLAY properly set.

    -
  • -
  • -

    Fixed EDMC.exe (command line tool) to correctly report the version with --v.

    -
  • -
- - -

Release 3.46

-

This should be the final release of EDMC based on Python 2.7. The next release after this, assuming this one doesn't introduce new bugs, will be based on Python 3.7. Any plugins that users have installed will need to have been updated to work under Python 3.7 by the time that next version of EDMC is released. During EDMC startup, at most once per day, you might see a popup with the text: - -

One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the 'Plugins' tab of 'File' > 'Settings'. You should check if there is an updated version available, else alert the developer that they will need to update the code when EDMC moves to Python 3.x
- -If so, do as it directs and check "File" > "Settings" > "Plugins" tab and see what plugins are listed in the section with the text "Plugins Without Python 3.x Support". -

-

-If any of your plugins are listed in that section then they will need updating, by you or the original developer, to work with Python 3.7. See Migrating To Python 3.7 for more information. -

-

Changes in this version: -

    -
  • The CAPI CLIENT_ID has been changed to one under Athanasius' account, so when users are asked to (re-)authenticate with Frontier they'll see "Elite Dangerous Market Connector (EDCD/Athanasius)" as the application asking for permission. There's been no change to the use of the data Frontier then gives access to.
  • -
  • Updated translations (as of 2019-09-26 in general and 2019-11-04 for Polish).
  • -
  • Linux: Should now appear on task bar whilst in dark mode theme.
  • -
  • INARA: Send correct opponentName for Interdicted and Interdiction events.<.li> -
  • Send SAASignalsFound events to EDDN.
  • -
  • Add Agronomic Treatment introduced for a community goal.
  • -
  • Fix Detailed Surface Scanner rating.
  • -
  • Fix for the "Inara 400 The reputation value exceeds the valid range" error.
  • -
  • Minimum interval between checks for a new version of EDMC has been reduced from 47 hours to 8 hours.
  • -
  • There is a new option, within the 'Configuration' tab, 'Disable Automatic Application Updates Check when in-game' which when active should prevent update checks from showing a popup whilst you're in-game. You can still use "Help" > "Check for updates" to trigger a manual check.
  • -
  • Support added for the journal 'CarrierJump' event, triggered when you're docked on a Fleet Carrier as it performs a jump. This is now sent to: EDDN, Inara, EDSM. NB: EDSM doesn't yet support this event at the time of writing, so will still not track such Carrier Jumps in your Flight Log or current location. Generally when EDSM is updated to handle such new events it will back-process stored unrecognised events.
  • -
-

- -

Release 3.45

-

There was no real 3.45, it was 'burned' testing that updates from 3.44 would work with the new update_feed URL.

- -

Release 3.44

-

CHANGE OF MAINTAINER

-

Due to a lack of time to give the project the attention it needs Marginal has handed over ownership of the EDMarketConnector GitHub repository to the EDCD (Elite Dangerous Community Developers) organisation.

-

Initially Athanasius will now be responsible for maintaining the code, including addressing any Pull Requests and Issues, and making releases. Unfortunately he has no access to hardware running MacOS so can't easily generate builds for that platform or test them. So for the time being releases will be for Windows 10 only. MacOS users are advised to look into running from source (see the github README).

-

Going forwards the intention is to move to the python 3.7 code as soon as possible. To facilitate this there will be one more python 2.7 release in addition to this one, with the main aim of that being to add code to alert the user about any plugins they use that have apparently not been updated to run under python 3.7.

-

See the project GitHub repository's README.md for further information.

-
    -
  • Version increased to 3.4.4.0 / 3.44.
  • -
  • URL the application checks for updates changed to point to github,
  • -
-

Release 3.43

-
    -
  • New commodity and modules from “September Update”.
  • -
  • Increase transparent theme font size.
  • -
  • Misc fixes.
  • -
  • More control over plugin widget colors.
  • -
-

The first time that you run the app while playing the game you are redirected to Frontier's authentication website and prompted for your username and password.

-

Release 3.42

-
    -
  • Use EDSY.org address for EDShipyard.
  • -
  • Fixes for running under Wine on Linux.
  • -
  • Support not always on top with dark theme on Linux.
  • -
  • Add advanced multi-cannon from ”Bridging the Gap”.
  • -
-

Release 3.41

-
    -
  • Transparent theme window size reduced.
  • -
-

Release 3.40

-
    -
  • Use Euro Caps font with transparent theme.
  • -
  • Add new modules in 3.4.
  • -
  • Improved authentication when app started with game already running.
  • -
-

Release 3.38

-
    -
  • More authentication fixes.
  • -
  • Send influence and reputation gain to Inara on mission completion.
  • -
-

Release 3.37

-
    -
  • More authentication fixes.
  • -
  • More robust/graceful handling of Frontier Auth and/or cAPI server outages.
  • -
-

Release 3.36

-
    -
  • Fix for forthcoming Frontier authentication changes.
  • -
  • Fix for installation on non-English systems.
  • -
-

Release 3.35

-
    -
  • Display feedback on successful authentication.
  • -
  • Outfitting and Shipyard data also sent to EDDN on visiting outfitting or shipyard in-game, and tagged with a “Horizons” flag.
  • -
  • Sends your local faction reputation to Inara.
  • -
-

The first time that you run the app while playing the game you are redirected to Frontier's authentication website and prompted for your username and password.

-

Release 3.33

-
    -
  • More authentication fixes.
  • -
-

Release 3.32

-
    -
  • Fix for token expiry during a session (“Frontier server is down” error).
  • -
  • Force re-authentication if credentials entered for wrong Cmdr.
  • -
  • More logging of OAuth failures.
  • -
-

Release 3.31

-
    -
  • Support for OAuth2-based access to station commodity market, outfitting and shipyard data.
  • -
  • Fix for command-line program.
  • -
  • Improved handling of authentication errors.
  • -
  • Commodity market data also sent to EDDN on visiting the in-game commodity market.
  • -
  • Misc fixes.
  • -
-

Release 3.30

-
    -
  • Support for OAuth2-based access to station commodity market, outfitting and shipyard data.
  • -
  • Commodity market data also sent to EDDN on visiting the in-game commodity market.
  • -
  • Misc fixes.
  • -
-

Release 3.20

-
    -
  • Preliminary support for E:D 3.3.
  • -
  • Support accessing Journal on macOS remotely over SMB.
  • -
  • Misc fixes.
  • -
-

Release 3.12

- -

Release 3.11

-
    -
  • Misc fixes.
  • -
-

Release 3.10

-
    -
  • Support for new ships and modules in E:D 3.1.
  • -
  • Fix for sending ship loadouts with engineered modules with certain secondary effects to Inara.
  • -
  • Add separators between plugins in main window.
  • -
  • Chinese (Simplified) translation courtesy of Cmdr Zhixian Wu.
  • -
  • Portuguese (Portugal) translation courtesy of Carlos Oliveira.
  • -
-

Release 3.06

-
    -
  • Extend localisation support to plugins.
  • -
  • Hungarian translation courtesy of Cmdr Wormhole.
  • -
  • Misc fixes.
  • -
-

Release 3.05

-
    -
  • Fix for “Frontier server is down” error on systems with primary language other than English.
  • -
  • Fix for TD prices file format.
  • -
-

Release 3.04

-
    -
  • Export ship loadout to Coriolis in Journal “Loadout” format.
  • -
  • Fix for “This app requires accurate timestamps” error - get timestamps for cAPI-derived data from cAPI server.
  • -
  • Fix for TCE integration.
  • -
  • Support for “package plugins”.
  • -
-

Release 3.03

-
    -
  • Fixes for stats and plugin display.
  • -
-

Release 3.02

-
    -
  • Choose between eddb, EDSM and Inara for station and shipyard links.
  • -
  • Don't display “Solo” mode in main window.
  • -
  • Fix for saving ship loadout to file when ship name contains punctuation.
  • -
-

Release 3.01

-
    -
  • Various fixes for EDSM, Inara and TCE integrations.
  • -
  • Fix for failure to terminate cleanly.
  • -
  • Switch ship loadout file to journal format.
  • -
-

Release 3.00

-
    -
  • Support for E:D 3.0.
  • -
  • Updates your entire fleet on EDSM and/or Inara whenever you visit the shipyard in game.
  • -
  • Updates your current ship's loadout on EDSM and/or Inara whenever it changes.
  • -
  • Plugin access to your dashboard status.
  • -
]]> Date: Fri, 12 Mar 2021 14:41:48 +0000 Subject: [PATCH 004/261] Release 4.2.0: appcast --- edmarketconnector.xml | 78 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 3d524be94..9202e404b 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,81 @@ - Release 4.1.6 + Release 4.2.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

Release 4.2.0

+

This release increases the Minor version due to the major change in how +multiple-instance checking is done.

+
    +
  • +

    Adds Steam and Epic to the list of "audiences" in the Frontier Auth callout +so that you can authorise using those accounts, rather than their associated +Frontier Account details.

    +
  • +
  • +

    New status message "CAPI: No commander data returned" if a /profile +request has no commander in the returned data. This can happen if you +literally haven't yet created a Commander on the account. Previously you'd +get a confusing 'commander' message shown.

    +
  • +
  • +

    Changes the "is there another process already running?" check to be based on +a lockfile in the configured Journals directory. The name of this file is +edmc-journal-lock.txt and upon successful locking it will contain text +like:

    +
    Path: <configured path to your Journals>
    +PID: <process ID of the application>
    +
    +

    The lock will be released and applied to the new directory if you change it +via Settings > Configuration. If the new location is already locked you'll +get a 'Retry/Ignore?' pop-up.

    +

    For most users things will operate no differently, although note that the +multiple instance check does now apply to platforms other than Windows.

    +

    For anyone wanting to run multiple instances of the program this is now +possible via:

    +

    runas /user:<USER> "\"c:\Program Files (x86)\EDMarketConnector\EDMarketConnector.exe\" --force-localserver-for-auth"

    +

    If anything has messed with the backslash characters there then know that you +need to have " (double-quote) around the entire command (path to program .exe +and any extra arguments), and as a result need to place a backslash before +any double-quote characters in the command (such as around the space-including +path to the program).

    +

    I've verified it renders correctly on GitHub.

    +

    The old check was based solely on there being a window present with the title +we expect. This prevented using runas /user:SOMEUSER ... to run a second +copy of the application, as the resulting window would still be within the +same desktop environment and thus be found in the check.

    +

    The new method does assume that the Journals directory is writable by the +user we're running as. This might not be true in the case of sharing the +file system to another host in a read-only manner. If we fail to open the +lock file read-write then the application aborts the checks and will simply +continue running as normal.

    +

    Note that any single instance of EDMarketConnector.exe will still only monitor +and act upon the latest Journal file in the configured location. If you run +Elite Dangerous for another Commander then the application will want to start +monitoring that separate Commander. See wiki:Troubleshooting#i-run-two-instances-of-ed-simultaneously-but-i-cant-run-two-instances-of-edmc +which will be updated when this change is in a full release.

    +
  • +
  • +

    Adds the command-line argument --force-localserver-for-auth. This forces +using a local webserver for the Frontier Auth callback. This should be used +when running multiple instances of the application for all instances +else there's no guarantee of the edmc:// protocol callback reaching the +correct process and Frontier Auth will fail.

    +
  • +
  • +

    Adds the command-line argument --suppress-dupe-process-popup to exit +without showing the warning popup in the case that EDMarketConnector found +another process already running.

    +

    This can be useful if wanting to blindly run both EDMC and the game from a +batch file or similar.

    +
  • +
+ + +

Release 4.1.6

We might have finally found the cause of the application hangs during shutdown. Note that this became easier to track down due to the downtime @@ -96,11 +166,11 @@ the last mission requiring that cargo type, as found in Cargo.json.

]]>
From 9924985f18ca4b54b56d2c0015908ed4cad5dfd7 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 19:38:16 +0000 Subject: [PATCH 005/261] appcast 4.2.1 --- edmarketconnector.xml | 46 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 9202e404b..531ef029e 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,51 @@ - Release 4.2.0 + Release 4.2.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

Release 4.2.1

+

This is a bug-fix release.

+
    +
  • +

    Updated translations. Thanks once again to all those contributing as per +Translations.

    +
  • +
  • +

    PLUGINS.md: Clarify when CargoJSON is populated.

    +
  • +
  • +

    macOS: pip install -r requirements.txt will now include pyobjc so that +running this application works at all. Check the updated Running from +source +for some advice if attempting to run on macOS.

    +
  • +
  • +

    JournalLock: Handle when the Journal directory isn't set at all, rather than +erroring. Fixes #910 - Not launching (Linux).

    +
  • +
  • +

    Extra logging added to track down cause of #909 - Authentication not possible (PC) +. The debug log file might now indicate what's wrong, or we might need +you to run

    +
    "c:\Program Files (x86)\EDMarketConnector/EDMarketConnector.exe" --trace
    +
    +

    in order to increase the log level and gather some extra information. +Caution is advised if sharing a --trace log file as it will now contain +some of the actual auth data returned from Frontier.

    +
  • +
  • +

    Ensure that 'Save Raw Data' will work. Fixes #908 - Raw export of CAPI data broken.

    +
  • +
  • +

    Prevent EDDN plugin from erroring when we determine if the commander has +Horizons. Fixes #907 - Modules is a list not a dict on damaged stations

    +
  • +
+ +

Release 4.2.0

This release increases the Minor version due to the major change in how multiple-instance checking is done.

@@ -166,10 +206,10 @@ the last mission requiring that cargo type, as found in Cargo.json.

]]>
From ec103cea7b487344f5aaa72e0e3c3627debbc932 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 17 Mar 2021 12:13:28 +0000 Subject: [PATCH 006/261] Release 4.2.2: appcast --- edmarketconnector.xml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 531ef029e..00b046e18 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,19 @@ - Release 4.2.1 + Release 4.2.2 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

Release 4.2.2

+

This release contains a minor bug-fix, actually properly checking a station's +ships list before operating on it.

+
    +
  • Check that ships['shipuard_list'] is a dict before trying to use +.values() on it. This fixes the issue with seeing list object has no attribute values in the application status line.
  • +
+

Release 4.2.1

This is a bug-fix release.

    @@ -206,11 +214,11 @@ the last mission requiring that cargo type, as found in Cargo.json.

    ]]> From 46703b234971d9884ab8f00e71ba2b0e644c0009 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 18 Mar 2021 19:02:49 +0000 Subject: [PATCH 007/261] Release 4.2.3: appcast --- edmarketconnector.xml | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 00b046e18..9afbb24b6 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,36 @@ - Release 4.2.2 + Release 4.2.3 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

    Release 4.2.3

    +

    This release mostly addresses an issue when Frontier Authorisation gets stuck +on 'Logging in...' despite completing the authorisation on the Frontier +website.

    +
      +
    • +

      Allow edmc... argument to EDMarketConnector.exe. This should only be +necessary when something has prevented your web browser from invoking the +edmc protocol via DDE.

      +

      If you were encountering the 'Logging in...' issue and still do with this +release then please try running the application via the new +EDMarketConnector - localserver-auth.bat file in the installation +directory.

      +

      This simply runs EDMarketConnector.exe with the +--force-localserver-for-auth command-line argument. This forces the code +to setup and use a webserver on a random port on localhost for the +Frontier Authorisation callback, the same way it already works on +non-Windows platforms.

      +
    • +
    • +

      Add Korean translation to both the application and the installer.

      +
    • +
    + +

    Release 4.2.2

    This release contains a minor bug-fix, actually properly checking a station's ships list before operating on it.

    @@ -214,11 +239,11 @@ the last mission requiring that cargo type, as found in Cargo.json.

    ]]>
    From c4d82dadbc7eeb3cfc5e9613daf5eecfd1f47fe2 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 21 Mar 2021 11:07:34 +0000 Subject: [PATCH 008/261] appcast: Update stale 'master' link to 'main' --- edmarketconnector.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 9afbb24b6..a4187153f 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -17,7 +17,7 @@

    Due to a lack of time to give the project the attention it needs Marginal has handed over ownership of the EDMarketConnector GitHub repository to the EDCD (Elite Dangerous Community Developers) organisation.

    Initially Athanasius will now be responsible for maintaining the code, including addressing any Pull Requests and Issues, and making releases. Unfortunately he has no access to hardware running MacOS so can't easily generate builds for that platform or test them. So for the time being releases will be for Windows 10 only. MacOS users are advised to look into running from source (see the github README).

    Going forwards the intention is to move to the python 3.7 code as soon as possible. To facilitate this there will be one more python 2.7 release in addition to this one, with the main aim of that being to add code to alert the user about any plugins they use that have apparently not been updated to run under python 3.7.

    -

    See the project GitHub repository's README.md for further information.

    +

    See the project GitHub repository's README.md for further information.

    • Version increased to 3.4.4.0 / 3.44.
    • URL the application checks for updates changed to point to github,
    • From 15284a4a8e18b9646c1d5037d9f941fbff2f3ba3 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 23 Mar 2021 18:19:49 +0000 Subject: [PATCH 009/261] Release 4.2.4: appcast --- edmarketconnector.xml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index a4187153f..66b093d9a 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,23 @@ - Release 4.2.3 + Release 4.2.4 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

      Release 4.2.4

      +

      This release fixes one cosmetic bug and prepares for the Odyssey Alpha.

      +
        +
      • Avoid a spurious 'list index out of range' status text. This was caused by +the EDDN plugin running out of data to send.
      • +
      • Add some paranoia in case Odyssey Alpha looks like a live version. This +should prevent sending any alpha data to EDDN, EDSM and Inara.
      • +
      • Reduce some log spam in normal operation below TRACE level.
      • +
      • Updated Korean translation.
      • +
      + +

      Release 4.2.3

      This release mostly addresses an issue when Frontier Authorisation gets stuck on 'Logging in...' despite completing the authorisation on the Frontier @@ -239,11 +251,11 @@ the last mission requiring that cargo type, as found in Cargo.json.

      ]]>
      From dce3c32600e63af18c4ef1e394fa8d55609373d2 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 29 Mar 2021 16:11:15 +0100 Subject: [PATCH 010/261] Release 4.2.5: appcast --- edmarketconnector.xml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 66b093d9a..544fa13d9 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,28 @@ - Release 4.2.4 + Release 4.2.5 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

      Release 4.2.5

      +
        +
      • +

        Support the 'JournalAlpha' files from the Odyssey Alpha. We've confirmed +any data from these is correctly tagged as 'beta' for the is_beta flag +passed to plugins.

        +

        Any data from Odyssey Alpha is sent to EDDN using the test schemas.

        +

        No data from Odyssey Alpha is sent to the EDSM or Inara APIs.

        +
      • +
      • +

        Fix ship loadout export to files to not trip up in the face of file +encoding issues. This relates to the 'Ship Loadout' option on the +'Output' tab of Settings/Preferences.

        +
      • +
      + +

      Release 4.2.4

      This release fixes one cosmetic bug and prepares for the Odyssey Alpha.

        @@ -251,10 +268,10 @@ the last mission requiring that cargo type, as found in Cargo.json.

        ]]> From a494103ffd0b8325de70fa677b17e3d6254340d9 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 29 Mar 2021 16:12:23 +0100 Subject: [PATCH 011/261] Release 4.2.5: appcast size --- edmarketconnector.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 544fa13d9..1379e5c56 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -272,7 +272,7 @@ the last mission requiring that cargo type, as found in Cargo.json.

        sparkle:os="windows" sparkle:installerArguments="/passive LAUNCH=yes" sparkle:version="4.2.5" - length="11386880" + length="11382784" type="application/octet-stream" /> From 7292f0ee504a2e2205890b5925d0115056565181 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 1 Apr 2021 13:18:22 +0100 Subject: [PATCH 012/261] Release 4.2.6: appcast --- edmarketconnector.xml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 1379e5c56..95ad7a4f4 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,27 @@ - Release 4.2.5 + Release 4.2.6 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

        Release 4.2.6

        +

        This release applies a workaround for a game bug to do with late Scan events.

        +
          +
        • +

          EDDN requires that all Scan events are augmented with StarPos (system +co-ordinates). This is taken from the co-ordinates of the current system.

          +

          A sequence of EDDN messages indicated that the game can log a delayed +Scan event for the previous system after having already jumped (FSDJump +event) to another system.

          +

          This application would then erroneously apply the new system's StarPos +to the Scan from the old system.

          +

          This application will now not send such delayed Scan events to EDDN at all.

          +
        • +
        + +

        Release 4.2.5

        • @@ -268,10 +284,10 @@ the last mission requiring that cargo type, as found in Cargo.json.

          ]]> From bebf406e5b41ae87807a07f98da2f60e55158986 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 1 Apr 2021 14:28:17 +0100 Subject: [PATCH 013/261] Release 4.2.7: appcast --- edmarketconnector.xml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 95ad7a4f4..f239c0b32 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,16 @@ - Release 4.2.6 + Release 4.2.7 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

          Release 4.2.7

          +

          Developer error meant that 4.2.6 didn't actually contain the intended fix. +This will, honest. No, it wasn't intended as an April Stupids Day prank.

          + +

          Release 4.2.6

          This release applies a workaround for a game bug to do with late Scan events.

            @@ -284,11 +289,11 @@ the last mission requiring that cargo type, as found in Cargo.json.

            ]]> From af80d39442144efb5e503943125b165105bf6473 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 12 May 2021 16:57:10 +0100 Subject: [PATCH 014/261] 5.0.0: appcast NB: Windows section culled down to only this release. --- edmarketconnector.xml | 532 +++++++++++++++++++++++++++--------------- 1 file changed, 348 insertions(+), 184 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index f239c0b32..0920043ff 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,264 +36,428 @@ - Release 4.2.7 + Release 5.0.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

            Release 4.2.7

            -

            Developer error meant that 4.2.6 didn't actually contain the intended fix. -This will, honest. No, it wasn't intended as an April Stupids Day prank.

            - - -

            Release 4.2.6

            -

            This release applies a workaround for a game bug to do with late Scan events.

            +

            Release 5.0.0

            • -

              EDDN requires that all Scan events are augmented with StarPos (system -co-ordinates). This is taken from the co-ordinates of the current system.

              -

              A sequence of EDDN messages indicated that the game can log a delayed -Scan event for the previous system after having already jumped (FSDJump -event) to another system.

              -

              This application would then erroneously apply the new system's StarPos -to the Scan from the old system.

              -

              This application will now not send such delayed Scan events to EDDN at all.

              +

              We now test against, and package with, Python 3.9.5.

              +

              As a consequence of this we no longer support Windows 7.
              +This is due to +Python 3.9.x itself not supporting Windows 7. +The application (both EDMarketConnector.exe and EDMC.exe) will crash on +startup due to a missing DLL.

              +

              This should have no other impact on users or plugin developers, other +than the latter now being free to use features that were introduced since the +Python 3.7 series.

              +

              Developers can check the contents of the .python-version file +in the source (it's not distributed with the Windows installer) for the +currently used version in a given branch.

            - - -

            Release 4.2.5

            +

            +This Update Is Mandatory

            +

            This release is a mandatory upgrade for the release of Elite Dangerous +Odyssey. Any bug reports against earlier releases, pertaining to Odyssey or +not, will be directed to reproduce them with 5.0.0 or later. There are also +minor bugs in 4.2.7 and earlier that have been fixed in this version. There +will NOT be another 4.2.x release.

            +

            The major version has been incremented not for Odyssey support, but because +we have made some minor breaking changes to the APIs we provide for plugin +developers.

            +

            Due to these plugin API changes (see below) users might need to update their +plugins. A check of all the +Plugins we know about +only found one with an issue related to the move to edmc_data.py, the +developer was informed and the issue addressed.

            +

            Other plugins should, at most, log deprecation warnings about the +config changes (again, see below).

            +

            In the first instance please report any issues with plugins to their +developers, not us. They can contact us about EDMC core code issues if +they find such in their investigations.

            +

            All plugin developers would benefit from having a GitHub account and then +setting up a watch on EDMarketConnector +of at least 'Releases' under 'Custom'.

            +

            NB: If you had any beta or -rc1 of 5.0.0 installed and see anything weird +with this full release it would be advisable to manually uninstall, confirm +the installation directory (default c:\Program Files (x86)\EDMarketConnector) +is empty, and then re-install 5.0.0 to be sure you have a clean, working, +install. Anyone upgrading from 4.2.7 or earlier shouldn't see any issues +with this.

            +

            +Changes and Enhancements

            • -

              Support the 'JournalAlpha' files from the Odyssey Alpha. We've confirmed -any data from these is correctly tagged as 'beta' for the is_beta flag -passed to plugins.

              -

              Any data from Odyssey Alpha is sent to EDDN using the test schemas.

              -

              No data from Odyssey Alpha is sent to the EDSM or Inara APIs.

              +

              If the application detects it's running against a non-live (alpha or beta) +version of the game it will append " (beta)" to the Commander name on the +main UI.

            • -

              Fix ship loadout export to files to not trip up in the face of file -encoding issues. This relates to the 'Ship Loadout' option on the -'Output' tab of Settings/Preferences.

              +

              Updated translations. Once more, thanks to all the translators!

              +
            • +
            • +

              We now sanity check a returned Frontier Authentication token to be sure +it's for the current Commander. If it's not you'll see +Error: customer_id doesn't match! on the bottom status line. Double-check +you're using the correct credentials when authing!

              +
            • +
            • +

              New 'Main window transparency' slider on Settings > Appearance.

              +
            • +
            • +

              New command-line argument for EDMarketConnector.exe --reset-ui. This will:

              +
                +
              1. Reset to the default Theme.
              2. +
              3. Reset the UI transparency to fully opaque.
              4. +
              +

              The intention is this can be used if you've lost sight of the main window +due to tweaking these options.

              +

              There is a new file EDMarketConnector - reset-ui.bat to make utilising +this easy on Windows.

              +
            • +
            • +

              New CL arg for EDMarketConnector.exe --force-edmc-protocol. +This is really only of use to core developers (its purpose being to force +use of the edmc:// protocol for Frontier Auth callbacks, even when not +'frozen').

              +
            • +
            • +

              Linux config will be flushed to disk after any change. This means that +EDMC.py can now actually make use of the latest CAPI auth if it's been +updated by EDMarketConnector.py since that started.

              +

              If you want to run multiple instances of the application under Linux then +please check the updated Troubleshooting: Multi-Accounting +wiki entry.

              +
            • +
            • +

              Linux and macOS: You can now set a font name and size in your config file.
              +Ensuring this is a TTF font, rather than a bitmap font, should allow the +application UI scaling to work.

              +
                +
              1. 'font' - the font name to attempt using
              2. +
              3. 'font_size' - the font size to attempt using.
              4. +
              +

              There is no UI for this in Preferences, you will need to edit your +config file +to set or change it, and then restart the application.

              +

              This is not supported on Windows so as not to risk weird bugs. UI +Scaling works on Windows without this.

              +
            • +
            • +

              We now also cite the git 'short hash' in the version string. For a Windows +install of the application this is sourced from the .gitversion file +(written during the build process).

              +

              When running from source we attempt to use the command git rev-parse --short HEAD +to obtain this. If this doesn't work it will be set to 'UNKNOWN'.

              +
            • +
            • +

              We have added a 'killswitch' feature to turn off specific functionality if it +is found to have a bug. An example use of this would be in an "oh +shit! we're sending bad data to EDDN!" moment so as to protect EDDN +listeners such as EDDB.

              +

              If we ever have to use this we'll announce it clearly and endeavour to +get a fixed version of the program released ASAP. We will NOT be +using this merely to try and get some laggards to upgrade.

              +

              Plugin Developers: See Killswitches.md for more +information about this.

              +
            • +
            • +

              Our logging code will make best efforts to still show class name and +other such fields if it has trouble finding any of the required data for +the calling frame. This means no longer seeing ??:??:?? when there is +an issue with this.

              +
            • +
            • +

              macOS: We've managed to test the latest code on macOS Catalina. Other than +keyboard shortcut support not working +it appears to be working.

              +
            • +
            • +

              We've pulled the latest Coriolis data which might have caused changes to +ship and module names as written out to some files.

            - - -

            Release 4.2.4

            -

            This release fixes one cosmetic bug and prepares for the Odyssey Alpha.

            -
              -
            • Avoid a spurious 'list index out of range' status text. This was caused by -the EDDN plugin running out of data to send.
            • -
            • Add some paranoia in case Odyssey Alpha looks like a live version. This -should prevent sending any alpha data to EDDN, EDSM and Inara.
            • -
            • Reduce some log spam in normal operation below TRACE level.
            • -
            • Updated Korean translation.
            • -
            - - -

            Release 4.2.3

            -

            This release mostly addresses an issue when Frontier Authorisation gets stuck -on 'Logging in...' despite completing the authorisation on the Frontier -website.

            +

            +Odyssey

            +

            Every effort was made during the Odyssey Alphas to ensure that this +application will continue to function correctly with it. As always, make a +Bug Report +if you find anything not working, but be sure to check our +Known Issues first.

            • -

              Allow edmc... argument to EDMarketConnector.exe. This should only be -necessary when something has prevented your web browser from invoking the -edmc protocol via DDE.

              -

              If you were encountering the 'Logging in...' issue and still do with this -release then please try running the application via the new -EDMarketConnector - localserver-auth.bat file in the installation -directory.

              -

              This simply runs EDMarketConnector.exe with the ---force-localserver-for-auth command-line argument. This forces the code -to setup and use a webserver on a random port on localhost for the -Frontier Authorisation callback, the same way it already works on -non-Windows platforms.

              +

              A new UI element 'Suit' now appears below 'Ship' when applicable. It +details the type of suit you currently have equipped and its Loadout name.
              +This UI element is collapsed/hidden if no suit/on-foot state is detected, +e.g. not playing Odyssey.

            • -

              Add Korean translation to both the application and the installer.

              +

              Note that we can only reliably know about Suits and their Loadouts from a +CAPI data pull (which is what we do automatically on docking if +configured to do so, or when you press the 'Update' button). We do +attempt to gather this data from Journal events as well, but if you +switch to a Suit Loadout that hasn't been mentioned in them yet we won't +be able to display that until the next CAPI data pull.

            - - -

            Release 4.2.2

            -

            This release contains a minor bug-fix, actually properly checking a station's -ships list before operating on it.

            +

            If anyone becomes aware of a 'suit loadouts' site/tool, a la Coriolis/EDSY +but for Odyssey Suits, do let us know so we can add support for it! +We're already kicking around ideas to e.g. place JSON text in the clipboard +if the Suit Loadout is clicked.

            +

            +Bug Fixes

              -
            • Check that ships['shipuard_list'] is a dict before trying to use -.values() on it. This fixes the issue with seeing list object has no attribute values in the application status line.
            • +
            • +

              Fix ship loadout export to files to not trip up in the face of file encoding +issues. This relates to the 'Ship Loadout' option on the 'Output' tab of +Settings/Preferences.

              +
            • +
            • +

              Ship Type/Name will now be greyed out, and not clickable, if we don't +currently have loadout information for it. This prevents trying to send an +empty loadout to your shipyard provider.

              +
            • +
            • +

              Bug fixed when handling CAPI-sourced shipyard information. This happens +due to a Frontier bug with not returning shipyard data at all for normal +stations.

              +

              It has been observed that Frontier has fixed this bug for Odyssey.

              +
            • +
            • +

              Don't try to get Ship information from LoadGame event if directly in CQC.

              +
            • +
            • +

              Inara: Don't attempt to send an empty +setCommanderReputationMajorFaction API call. This quietens an error +from the Inara API caused when a Cmdr literally has no Major Faction +Reputation yet.

              +
            - -

            Release 4.2.1

            -

            This is a bug-fix release.

            +

            +Code Clean Up

            • -

              Updated translations. Thanks once again to all those contributing as per -Translations.

              +

              Code pertaining to processing Journal events was reworked and noisy logging +reduced as a consequence.

            • -

              PLUGINS.md: Clarify when CargoJSON is populated.

              +

              A little TRACE logging output has been commented out for now.

            • -

              macOS: pip install -r requirements.txt will now include pyobjc so that -running this application works at all. Check the updated Running from -source -for some advice if attempting to run on macOS.

              +

              The code for File > Status has been cleaned up.

            • -

              JournalLock: Handle when the Journal directory isn't set at all, rather than -erroring. Fixes #910 - Not launching (Linux).

              +

              Localisation code has been cleaned up.

            • -

              Extra logging added to track down cause of #909 - Authentication not possible (PC) -. The debug log file might now indicate what's wrong, or we might need -you to run

              -
              "c:\Program Files (x86)\EDMarketConnector/EDMarketConnector.exe" --trace
              -
              -

              in order to increase the log level and gather some extra information. -Caution is advised if sharing a --trace log file as it will now contain -some of the actual auth data returned from Frontier.

              +

              Code handling the Frontier Authorisation callback on Windows has been +cleaned up.

            • -

              Ensure that 'Save Raw Data' will work. Fixes #908 - Raw export of CAPI data broken.

              +

              A lot of general code cleanup relating to: Inara, outfitting, Frontier +CAPI, hotkey (manual Updates), dashboard (Status.json monitoring), +commodities files, and ED format ship loadout files.

              +
            • +
            +

            +Plugin Developers

            +
              +
            • +

              The files stations.p and systems.p have been removed from the Windows +Installer. These were never intended for third-party use. Their use in +core code was for generating EDDB-id URLs, but we long since changed the +EDDB plugin's handlers for that to use alternate URL formats based on +game IDs or names.

              +

              If you were using either to lookup EDDB IDs for systems and/or stations +then please see how system_url() and station_url() now work in +plugins/eddb.py.

              +

              This change also removed the core (not plugin) eddb.py file which +generated these files. You can find it still in the git history if needs +be. It had gotten to the stage where generating systems.p took many +hours and required 64-bit Python to have any hope of working due to +memory usage.

              +
            • +
            • +

              All static data that is +cleared for use by plugins +is now in the file +edmc_data.py and should be imported from there, not any other module.

              +

              The one thing we didn't move was the 'bracket map' dictionaries in td.py +as they're for use only by the code in that file.

              +

              All future such data will be added to this file, and we'll endeavour not +to make breaking changes to any of it without increasing our Major version.

            • -

              Prevent EDDN plugin from erroring when we determine if the commander has -Horizons. Fixes #907 - Modules is a list not a dict on damaged stations

              +

              config.appversion() is now a function that returns a semantic_version.Version. +In contexts where you're expecting a string this should mostly +just work. If needs be wrap it in str().

              +

              For backwards compatibility with pre-5.0.0 you can use:

            +
                from config import appversion
             
            -
            -

            Release 4.2.0

            -

            This release increases the Minor version due to the major change in how -multiple-instance checking is done.

            + if callable(appversion): + edmc_version = appversion() + else: + edmc_version = appversion
            • -

              Adds Steam and Epic to the list of "audiences" in the Frontier Auth callout -so that you can authorise using those accounts, rather than their associated -Frontier Account details.

              -
            • -
            • -

              New status message "CAPI: No commander data returned" if a /profile -request has no commander in the returned data. This can happen if you -literally haven't yet created a Commander on the account. Previously you'd -get a confusing 'commander' message shown.

              +

              Example plugin +plugintest +updated. This includes an example of how to check core EDMC version if needs +be. This example is also in +PLUGINS.md.

            • -

              Changes the "is there another process already running?" check to be based on -a lockfile in the configured Journals directory. The name of this file is -edmc-journal-lock.txt and upon successful locking it will contain text -like:

              -
              Path: <configured path to your Journals>
              -PID: <process ID of the application>
              -
              -

              The lock will be released and applied to the new directory if you change it -via Settings > Configuration. If the new location is already locked you'll -get a 'Retry/Ignore?' pop-up.

              -

              For most users things will operate no differently, although note that the -multiple instance check does now apply to platforms other than Windows.

              -

              For anyone wanting to run multiple instances of the program this is now -possible via:

              -

              runas /user:<USER> "\"c:\Program Files (x86)\EDMarketConnector\EDMarketConnector.exe\" --force-localserver-for-auth"

              -

              If anything has messed with the backslash characters there then know that you -need to have " (double-quote) around the entire command (path to program .exe -and any extra arguments), and as a result need to place a backslash before -any double-quote characters in the command (such as around the space-including -path to the program).

              -

              I've verified it renders correctly on GitHub.

              -

              The old check was based solely on there being a window present with the title -we expect. This prevented using runas /user:SOMEUSER ... to run a second -copy of the application, as the resulting window would still be within the -same desktop environment and thus be found in the check.

              -

              The new method does assume that the Journals directory is writable by the -user we're running as. This might not be true in the case of sharing the -file system to another host in a read-only manner. If we fail to open the -lock file read-write then the application aborts the checks and will simply -continue running as normal.

              -

              Note that any single instance of EDMarketConnector.exe will still only monitor -and act upon the latest Journal file in the configured location. If you run -Elite Dangerous for another Commander then the application will want to start -monitoring that separate Commander. See wiki:Troubleshooting#i-run-two-instances-of-ed-simultaneously-but-i-cant-run-two-instances-of-edmc -which will be updated when this change is in a full release.

              -
            • -
            • -

              Adds the command-line argument --force-localserver-for-auth. This forces -using a local webserver for the Frontier Auth callback. This should be used -when running multiple instances of the application for all instances -else there's no guarantee of the edmc:// protocol callback reaching the -correct process and Frontier Auth will fail.

              -
            • -
            • -

              Adds the command-line argument --suppress-dupe-process-popup to exit -without showing the warning popup in the case that EDMarketConnector found -another process already running.

              -

              This can be useful if wanting to blindly run both EDMC and the game from a -batch file or similar.

              +

              config.py has undergone a major rewrite. You should no longer be using +config.get(...) or config.getint(...), which will both give a +deprecation warning.
              +Use instead the correct config.get_<type>() function:

              +
                +
              • config.get_list(<key>)
              • +
              • config.get_str(<key>)
              • +
              • config.get_bool(<key>)
              • +
              • config.get_int(<key>)
              • +
              +

              Setting still uses config.set(...).

              +

              So:

              +
                +
              1. Replace all instances of config.get() and config.getint() as above.
              2. +
              3. For ease of maintaining compatibility with pre-5.0.0 versions include +this code in at least one module/file (no harm in it being in all that +manipulate plugin config):
              4. +
            +
            from config import config
            +
            +# For compatibility with pre-5.0.0
            +if not hasattr(config, 'get_int'):
            +    config.get_int = config.getint
             
            +if not hasattr(config, 'get_str'):
            +    config.get_str = config.get
             
            +if not hasattr(config, 'get_bool'):
            +    config.get_bool = lambda key: bool(config.getint(key))
             
            -

            Release 4.1.6

            -

            We might have finally found the cause of the application hangs during shutdown. -Note that this became easier to track down due to the downtime -for migration of www.edsm.net around 2021-01-11. Before these fixes EDSM's -API not being available would cause an EDMC hang on shutdown.

            +if not hasattr(config, 'get_list'): + config.get_list = config.get +
            • -

              We've applied extra paranoia to some of the application shutdown code to -ensure we're not still trying to handle journal events during this sequence.

              -

              We also re-ordered the shutdown sequence, which might help avoid the shutdown -hang.

              -

              If you encounter a shutdown hang then please add a comment and log files to -Application can leave a zombie process on shutdown #678 -to help us track down the cause and fix it.

              +

              Utilising our provided logging from a class-level, i.e. not a solid +instance of a class, property/function will now work.

              +
            • +
            • +

              We now change the current working directory of EDMarketConnector.exe to +its location as soon as possible in its execution. We're also +paranoid about ensuring we reference the full path to the .gitversion file.

              +

              However, no plugin should itself call os.chdir(...) or equivalent. You'll +change the current working directory for all core code and other plugins as +well (it's global to the whole process, not per-thread). Use full +absolute paths instead (pathlib is what to use for this).

              +
            • +
            • +

              The state dict passed to plugins in journal_entry() calls (which is +actually monitor.state in the core code) has received many additions +relating to Odyssey, as well as other fixes and enhancements.

              +
                +
              1. +

                Support has been added for the NavRoute (not Route as v28 of the +official Journal documentation erroneously labels it) Journal event and +its associated file NavRoute.json. See PLUGINS.md:Events documentation

              2. -

                We now avoid making Tk event_generate() calls whilst the appliction is -shutting down.

                +

                Similarly, there is now support for the ModuleInfo event and its +associated ModulesInfo.json file.

              3. -

                Plugins should actively avoid making any sort of Tk event_generate() call -during application shutdown.

                -

                This means using if not config.shutting_down: to gate any code in worker -threads that might attempt this. Also, be sure you're not attempting such -in your plugin_stop() function.

                -

                See plugins/edsm.py and plugins/inara.py for example of the usage.

                +

                state['Credits'] - until now no effort was made to keep this +record of the credits balance up to date after the initial LoadGame +event. This has now been addressed, and the balance should stay in sync +as best it can from the available Journal events. It will always correct +back to the actual balance on each CAPI data pull or game relog/restart.

              4. -

                Any use of plug.show_error() won't actually change the UI status line -during shutdown, but the text you tried to show will be logged instead.

                +

                state['Cargo'] now takes account of any CargoTransfer events. +This was added to the game in the Fleet Carriers update, but also covers +transfers to/from an SRV.

              5. -

                Cargo tracking will now correctly count all instances of the same type of -cargo for different missions. Previously it only counted the cargo for -the last mission requiring that cargo type, as found in Cargo.json.

                +

                state['OnFoot'] is a new boolean, set true whenever we detect +the Cmdr is on-foot, i.e. not in any type of vehicle (Cmdr's own ship, +SRV, multi-crew in another Cmdr's ship, Apex taxi, or a Dropship).

              6. -

                The loaded contents of Cargo.json can now be found in monitor.state['CargoJSON']. -monitor.state is what is passed to plugins as state in the -journal_entry() call.

                +

                state['Suits'] and state['SuitLoadouts'] added as dicts containing +information about the Cmdr's owned Suits and the Loadouts the Cmdr has +defined to utilise them (and on-foot weapons). +Note that in the raw CAPI data these are arrays if all members +contiguously exist, else a dictionary, but we have chosen to always coerce +these to a python dict for simplicity. They will be empty dicts, not +None if there is no data.
                +We use the CAPI data names for keys, not the Journal ones - e.g. slots +for weapons equipped, not Modules. +The id field found on e.g. weapon details in suit loadouts may be None +if we got the data from the Journal rather than the CAPI data. +NB: This data is only guaranteed up to date and correct after a fresh CAPI +data pull, as the current Journal events don't allow for updating it on the +fly (this should change in a future Odyssey patch).

              7. -

                Our logging code should now cope with logging from a property.

                +

                state['SuitCurrent'] and state['SuitLoadoutCurrent'] contain the +obvious "currently in use" data as per the Suits/SuitLoadouts.

              8. -

                Logging from any name-mangled method should now work properly.

                +

                Tracking of the new Odyssey 'Microresources' has been added:

                +
                  +
                1. +Component - dict for 'Ship Locker' inventory.
                2. +
                3. +Item - dict for 'Ship Locker' inventory.
                4. +
                5. +Consumable - dict for 'Ship Locker' inventory.
                6. +
                7. +Data - dict for 'Ship Locker' inventory.
                8. +
                9. +BackPack - on-foot inventory, a dict containing again +dicts for Component, Item, Consumable and Data. +However note that the lack of a Journal event when throwing a grenade, +along with no BackPackMaterials event if logging in on-foot means that +we can't track the BackPack inventory perfectly.
                10. +
                +
              9. +
              +

              See the updated PLUGINS.md file for details.

              +
            • +
            • +

              As Status.json, and thus the EDMC 'dashboard' output now has a 'flags2' +key we have added the associated constants to edmc_data.py with a +Flags2 prefix on the names.

            • -

              Miscellaneous updates to PLUGINS.md - mostly to clarify some things.

              +

              Note that during the Odyssey Alpha it was observed that the CAPI +data['commander']['docked'] boolean was always true if the Cmdr was +in their ship. This is a regression from pre-Odyssey behaviour. The +core EDMC code copes with this. Please add a reproduction to the issue +about this: +PTS CAPI saying Commander is Docked after jumping to new system.

            + ]]>
            From afa99d76a6342fb3e9035116c44716848b452e46 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 17 May 2021 19:00:37 +0100 Subject: [PATCH 015/261] Release 5.0.1: appcast --- edmarketconnector.xml | 80 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 0920043ff..2a786117e 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,83 @@ - Release 5.0.0 + Release 5.0.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

            Release 5.0.1

            +

            The main reason for this release is to add an 'odyssey' boolean flag to all +EDDN messages for the benefit of listeners, e.g. eddb.io, inara.cz, +edsm.net, spansh.co.uk, etc. Please do update so as to make their lives +easier once Odyssey has launched!

            +
              +
            • +

              Translations have been updated again. Thanks to all the contributors. +See wiki:Translations +and Translations welcome +for links and discussion if you want to help.

              +
            • +
            • +

              Changed the error message "Error: Frontier server is down" to +"Error: Frontier CAPI didn't respond" to make it clear this pertains to +the CAPI and not the game servers.

              +
            • +
            +

            +Killswitches

            +

            In the 5.0.0 changelog we said:

            +
            We will **NOT** be using this merely to try and get some + laggards to upgrade.
            +

            However, from now on there is an exception to this. After this +release any subsequent -beta or -rc versions will be killswitched after +their full release is published.

            +

            For example, if we put out a 5.0.2-beta1 and 5.0.2-rc1 before the full +5.0.2, then when 5.0.2 was published we would activate all available +killswitches for versions 5.0.2-beta1 and 5.0.2-rc1. In this example +5.0.1 would not be killswitched as part of this policy (but still +could be if, e.g. a data corruption bug was found in it).

            +

            In general please do not linger on any -beta or -rc release if there +has been a subsequent release. Upgrade to the equivalent full release once it +is published.

            +

            +Plugin Developers

            +
              +
            • +

              Please make the effort to subscribe to GitHub notifications of new +EDMarketConnector releases:

              +
                +
              1. Login to GitHub.
              2. +
              3. Navigate to EDMarketConnector.
              4. +
              5. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on +us). It's currently (2021-05-13) the left-most button of 3 near the +top-right of the page.
              6. +
              7. Click 'Custom'.
              8. +
              9. Ensure 'Releases' is selected.
              10. +
              11. Click 'Apply'.
              12. +
              +

              This way you'll be aware, as early as possible, of any -beta and -rc +changelogs and changes that might affect your work.

              +
            • +
            • +

              state passed to journal_entry() has a new member Odyssey (note the +capital O) which is a boolean indicating if the LoadGame event both has +an Odyssey key, and if so, what the value was. Defaults to False.

              +
            • +
            • +

              PLUGINS.md updated to document the state['Horizons'] flag that has been +present in it since version 3.0 of the game.

              +
            • +
            • +

              The stations.p and systems.p files that were deprecated in 5.0.0 have +now also been removed in git. As this release is made they will no +longer be in the develop, main or stable branches. If you truly +need to find a copy look at the Release/4.2.7 tag, but do read the 5.0.0 +changelog for why we stopped using them and what you can change to also +not need them.

              +
            • +
            +

            Release 5.0.0

            • @@ -453,11 +525,11 @@ about this: ]]> From 993d6a8c1480dfbe2cebd960e4159772cd40b665 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 15:32:36 +0100 Subject: [PATCH 016/261] Release 5.0.2: appcast --- edmarketconnector.xml | 72 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 2a786117e..e750f8784 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,75 @@ - Release 5.0.1 + Release 5.0.2 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

              Release 5.0.2

              +

              This release is primarily aimed at getting the UI "Suit: ..." line working +properly.

              +
                +
              • +

                The "Suit: ..." UI line should now function as best it can given the +available data from the game. It should not appear if you have launched +the Horizons version of the game, even if your account has Odyssey +enabled. You might see "<Unknown>" as the text when this application +does not yet have the required data.

                +
              • +
              • +

                Changed the less than obvious "unable to get endpoint: /profile" error +message to "Frontier CAPI query failure: /profile", and similarly for the +other CAPI endpoints we attempt to access. This new form is potentially +translated, but translators need time to do that.

                +

                In addition the old message "Received error {r.status_code} from server" +has been changed to "Frontier CAPI server error: {r.status_code}" and is +potentially translated.

                +
              • +
              • +

                The filenames used for 'Market data in CSV format file' will now be sane, +and as they were before 5.0.0.

                +
              • +
              • +

                Linux: 'Shipyard provider' will no longer default to showing 'False' if +no specific provider has been selected.

                +
              • +
              +

              +Plugin Developers

              +
                +
              • +

                Extra Flagse values added in the live release of Odyssey have been added to +edmc_data.py.

                +
              • +
              • +

                Odyssey 'BackPack' values should now track better, but might still not be +perfect due to Journal bugs/shortcomings.

                +
              • +
              • +

                state passed to journal_entry() now has a BackpackJSON (note the case) +member which is a copy of the data from the Backpack.json (yes, that's +currently the correct case) file that is written when there's a BackPack +(guess what, yes, that is currently the correct case) event written to +the Journal.

                +
              • +
              • +

                state['Credits'] tracking is almost certainly not perfect. We're +accounting for the credits component of SuitUpgrade now, but there +might be other such we've yet accounted for.

                +
              • +
              • +

                state['Suits'] and associated other keys should now be tracking from +Journal events, where possible, as well as CAPI data.

                +
              • +
              • +

                There is a section in PLUGINS.md about how to package an extra Python +module with your plugin. Note the new caveat in +PLUGINS.md:Avoiding-pitfalls +about the name of your plugin's directory.

                +
              • +
              +

              Release 5.0.1

              The main reason for this release is to add an 'odyssey' boolean flag to all EDDN messages for the benefit of listeners, e.g. eddb.io, inara.cz, @@ -525,11 +589,11 @@ about this: ]]> From ee2ff3830a63cb33ee3e4da2135ea0b6d9b48659 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 27 May 2021 15:30:43 +0100 Subject: [PATCH 017/261] Release 5.0.3: appcast --- edmarketconnector.xml | 83 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index e750f8784..c25d373cb 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,88 @@ - Release 5.0.2 + Release 5.0.3 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

                +
              • +

                We now test against, and package with, Python 3.9.5.

                +

                As a consequence of this we no longer support Windows 7.
                +This is due to +Python 3.9.x itself not supporting Windows 7. +The application (both EDMarketConnector.exe and EDMC.exe) will crash on +startup due to a missing DLL.

                +

                This should have no other impact on users or plugin developers, other +than the latter now being free to use features that were introduced since the +Python 3.7 series.

                +

                Developers can check the contents of the .python-version file +in the source (it's not distributed with the Windows installer) for the +currently used version in a given branch.

                +
              • +
              + +

              Release 5.0.3

              +
                +
              • +

                You can now click on a 'cell' in the "File" > "Status" popup to copy that +text to the clipboard. This was a relatively easy, and non-intrusive, code +change. We'll look at richer, fuller, copy functionality in the future.

                +
              • +
              • +

                Suit names, for all grades, should now be displaying as just the relevant +word, never a symbol, and with the redundant 'suit' word(s) from all +languages removed. Note that Frontier have not translated the +following, so neither do we: "Artemis", "Dominator", "Maverick". The 'Flight +Suit' should, approximately, use the Frontier-supplied translation for +'Flight' in this context. In essence the displayed name is now as short +as possible whilst disambiguating the suit names from each other.

                +
              • +
              +

              +Bug Fixes

              +
                +
              • +

                The check for "source, but with extra changes?" in appversion will now +not cause an error if the "git" command isn't available. Also, the extra +text added to the build number is now ".DIRTY".

                +
              • +
              • +

                Actually properly handle the "you just made progress" version of the +EngineerProgress Journal event, so that it doesn't throw errors.

                +
              • +
              +

              +Plugin Developers

              +
                +
              • +

                The backpack and ship locker tracking of micro-resources might now +actually be correct with respect to 'reality' in-game. This is in part +thanks to Frontier changes to some events in 4.0.0.200.

                +
              • +
              • +

                Suit names will now only be sourced from Journal events if the +application didn't (yet) have the equivalent CAPI data.

                +
              • +
              • +

                The displayed Suit name is stored in an extra "edmcName" key within +state['Suits'] and state['SuitCurrent']. What was found in the +Journal or CAPI data is still present in the "name" and "locName" values.

                +
              • +
              • +

                The "language", "gameversion" and "build" values from the "Fileheader" event +are all now stored in state[] fields. See PLUGINS.md for +updated documentation.

                +
              • +
              • +

                We have a new Contributing.md policy of adding +comments in a defined format when we add or change code such that there's a +'hack', 'magic' or 'workaround' in play. You might find some of this +enlightening going forwards.

                +
              • +
              +

              Release 5.0.2

              This release is primarily aimed at getting the UI "Suit: ..." line working properly.

              @@ -589,10 +666,10 @@ about this: ]]>
              From 571ddf699bc6cea1e33c40359304fa4cc210c41b Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 27 May 2021 17:45:31 +0100 Subject: [PATCH 018/261] Revert "Release 5.0.3: appcast" This reverts commit ee2ff3830a63cb33ee3e4da2135ea0b6d9b48659. --- edmarketconnector.xml | 83 ++----------------------------------------- 1 file changed, 3 insertions(+), 80 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index c25d373cb..e750f8784 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,88 +36,11 @@ - Release 5.0.3 + Release 5.0.2 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -
                -
              • -

                We now test against, and package with, Python 3.9.5.

                -

                As a consequence of this we no longer support Windows 7.
                -This is due to -Python 3.9.x itself not supporting Windows 7. -The application (both EDMarketConnector.exe and EDMC.exe) will crash on -startup due to a missing DLL.

                -

                This should have no other impact on users or plugin developers, other -than the latter now being free to use features that were introduced since the -Python 3.7 series.

                -

                Developers can check the contents of the .python-version file -in the source (it's not distributed with the Windows installer) for the -currently used version in a given branch.

                -
              • -
              - -

              Release 5.0.3

              -
                -
              • -

                You can now click on a 'cell' in the "File" > "Status" popup to copy that -text to the clipboard. This was a relatively easy, and non-intrusive, code -change. We'll look at richer, fuller, copy functionality in the future.

                -
              • -
              • -

                Suit names, for all grades, should now be displaying as just the relevant -word, never a symbol, and with the redundant 'suit' word(s) from all -languages removed. Note that Frontier have not translated the -following, so neither do we: "Artemis", "Dominator", "Maverick". The 'Flight -Suit' should, approximately, use the Frontier-supplied translation for -'Flight' in this context. In essence the displayed name is now as short -as possible whilst disambiguating the suit names from each other.

                -
              • -
              -

              -Bug Fixes

              -
                -
              • -

                The check for "source, but with extra changes?" in appversion will now -not cause an error if the "git" command isn't available. Also, the extra -text added to the build number is now ".DIRTY".

                -
              • -
              • -

                Actually properly handle the "you just made progress" version of the -EngineerProgress Journal event, so that it doesn't throw errors.

                -
              • -
              -

              -Plugin Developers

              -
                -
              • -

                The backpack and ship locker tracking of micro-resources might now -actually be correct with respect to 'reality' in-game. This is in part -thanks to Frontier changes to some events in 4.0.0.200.

                -
              • -
              • -

                Suit names will now only be sourced from Journal events if the -application didn't (yet) have the equivalent CAPI data.

                -
              • -
              • -

                The displayed Suit name is stored in an extra "edmcName" key within -state['Suits'] and state['SuitCurrent']. What was found in the -Journal or CAPI data is still present in the "name" and "locName" values.

                -
              • -
              • -

                The "language", "gameversion" and "build" values from the "Fileheader" event -are all now stored in state[] fields. See PLUGINS.md for -updated documentation.

                -
              • -
              • -

                We have a new Contributing.md policy of adding -comments in a defined format when we add or change code such that there's a -'hack', 'magic' or 'workaround' in play. You might find some of this -enlightening going forwards.

                -
              • -
              -

              Release 5.0.2

              This release is primarily aimed at getting the UI "Suit: ..." line working properly.

              @@ -666,10 +589,10 @@ about this: ]]>
              From f6c6f26cefc217ff9f8ac0bf1aef43b4b2b85714 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 27 May 2021 18:06:10 +0100 Subject: [PATCH 019/261] Revert "Revert "Release 5.0.3: appcast"" This reverts commit 571ddf699bc6cea1e33c40359304fa4cc210c41b. --- edmarketconnector.xml | 83 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index e750f8784..c25d373cb 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,11 +36,88 @@ - Release 5.0.2 + Release 5.0.3 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +
                +
              • +

                We now test against, and package with, Python 3.9.5.

                +

                As a consequence of this we no longer support Windows 7.
                +This is due to +Python 3.9.x itself not supporting Windows 7. +The application (both EDMarketConnector.exe and EDMC.exe) will crash on +startup due to a missing DLL.

                +

                This should have no other impact on users or plugin developers, other +than the latter now being free to use features that were introduced since the +Python 3.7 series.

                +

                Developers can check the contents of the .python-version file +in the source (it's not distributed with the Windows installer) for the +currently used version in a given branch.

                +
              • +
              + +

              Release 5.0.3

              +
                +
              • +

                You can now click on a 'cell' in the "File" > "Status" popup to copy that +text to the clipboard. This was a relatively easy, and non-intrusive, code +change. We'll look at richer, fuller, copy functionality in the future.

                +
              • +
              • +

                Suit names, for all grades, should now be displaying as just the relevant +word, never a symbol, and with the redundant 'suit' word(s) from all +languages removed. Note that Frontier have not translated the +following, so neither do we: "Artemis", "Dominator", "Maverick". The 'Flight +Suit' should, approximately, use the Frontier-supplied translation for +'Flight' in this context. In essence the displayed name is now as short +as possible whilst disambiguating the suit names from each other.

                +
              • +
              +

              +Bug Fixes

              +
                +
              • +

                The check for "source, but with extra changes?" in appversion will now +not cause an error if the "git" command isn't available. Also, the extra +text added to the build number is now ".DIRTY".

                +
              • +
              • +

                Actually properly handle the "you just made progress" version of the +EngineerProgress Journal event, so that it doesn't throw errors.

                +
              • +
              +

              +Plugin Developers

              +
                +
              • +

                The backpack and ship locker tracking of micro-resources might now +actually be correct with respect to 'reality' in-game. This is in part +thanks to Frontier changes to some events in 4.0.0.200.

                +
              • +
              • +

                Suit names will now only be sourced from Journal events if the +application didn't (yet) have the equivalent CAPI data.

                +
              • +
              • +

                The displayed Suit name is stored in an extra "edmcName" key within +state['Suits'] and state['SuitCurrent']. What was found in the +Journal or CAPI data is still present in the "name" and "locName" values.

                +
              • +
              • +

                The "language", "gameversion" and "build" values from the "Fileheader" event +are all now stored in state[] fields. See PLUGINS.md for +updated documentation.

                +
              • +
              • +

                We have a new Contributing.md policy of adding +comments in a defined format when we add or change code such that there's a +'hack', 'magic' or 'workaround' in play. You might find some of this +enlightening going forwards.

                +
              • +
              +

              Release 5.0.2

              This release is primarily aimed at getting the UI "Suit: ..." line working properly.

              @@ -589,10 +666,10 @@ about this: ]]>
              From 08e0cbf18d4a267a4e3793ab94bbcdaff01c6266 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 27 May 2021 18:08:33 +0100 Subject: [PATCH 020/261] Release 5.0.4: appcast --- edmarketconnector.xml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index c25d373cb..1b47862e8 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.0.3 + Release 5.0.4 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,17 @@ currently used version in a given branch.

            +

            Release 5.0.4

            +

            This is a minor bugfix release, ensuring that Odyssey Suit names (and loadout) +will actually display if you're in your ship on login and never leave it.

            +

            NB: This still requires a Frontier CAPI data pull, either automatically +because you're docked if you have that option set, or by pressing the +'Update' button. We can't display data when we don't have it from either +CAPI or Journal sources. You'll also see '<Unknown>' between the time we +see the Journal LoadGame event during login and when there's either a +Journal suit-related event, or a CAPI data pull completes.

            + +

            Release 5.0.3

            • @@ -666,11 +677,11 @@ about this: ]]> From 4318227eabb5e4c6597b2301b237da6e27938eba Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 4 Jun 2021 19:30:53 +0100 Subject: [PATCH 021/261] Release 5.1.0: appcast --- edmarketconnector.xml | 54 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 1b47862e8..06899402f 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.0.4 + Release 5.1.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,52 @@ currently used version in a given branch.

            +

            Release 5.1.0

            +
              +
            • +

              Updates to how this application utilises the Inara.cz API.

              +
                +
              1. The current state of your ShipLockerMaterials (MicroResources for Odyssey +Suit and handheld Weapons upgrading and engineering) will now be sent. +Note that we can't reliably track this on the fly, so it will only +update when we see a full ShipLockerMaterials Journal event, such as +at login or when you disembark from any vehicle.
              2. +
              3. Odyssey Suits and their Loadouts will now be sent.
              4. +
              5. When you land on a body surface, be that in your own ship, in a Taxi, +or in a Dropship. Depending on the exact scenario a Station might be +sent along with this.
              6. +
              +
            • +
            • +

              You can now both edit the 'normal' and 'beta' coriolis.io URLs, and +choose which of them are used. 'Auto' means allowing the application to +use the normal one when you're running the live game, or the beta version +if running a beta version of the game.

              +
            • +
            • +

              Suit names will now be displayed correctly when we have pulled the data +from the Frontier CAPI, rather than Journal entries.

              +
            • +
            • +

              Many translations updated once more, especially for new strings. Thanks +as always to those contributing!

              +
            • +
            +

            +Bug Fixes

            + +

            +Plugin Developers

            +

            There are some new members of the state dictionary passed to +journal_entry(); Taxi, Dropship, Body and BodyType. See +PLUGINS.md for the details.

            +

            Release 5.0.4

            This is a minor bugfix release, ensuring that Odyssey Suit names (and loadout) will actually display if you're in your ship on login and never leave it.

            @@ -677,11 +723,11 @@ about this: ]]>
            From d9f5456e82bc7ba7b7029c5d84dbc2261338ac62 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 10 Jun 2021 20:26:48 +0100 Subject: [PATCH 022/261] Release 5.1.1: appcast --- edmarketconnector.xml | 88 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 06899402f..6aef81811 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.1.0 + Release 5.1.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,86 @@ currently used version in a given branch.

          +

          Release 5.1.1

          +

          The big change in this is adjustments to be in line with Journal changes in +Elite Dangerous Odyssey 4.0.0.400, released 2021-06-10, with respect to the +Odyssey materials Inventory.

          +

          This update is mandatory if you want EDMarketConnector to update Inara.cz +with your Odyssey inventory.

          +
            +
          • +

            ShipLockerMaterials is dead, long live ShipLocker. Along with other +changes to how backpack inventory is handled we should now actually be +able to fully track all Odyssey on-foot materials and consumables without +errors.

            +
          • +
          • +

            Inara plugin adjusted to send the new ShipLocker inventory to Inara.cz. +This is still only your ship inventory of Odyssey materials, not +anything currently in your backpack whilst on foot. +See this issue +for some quotes from Artie (Inara.cz developer) about not including +backpack contents in the Inara inventory.

            +
          • +
          • +

            Errors related to sending data to EDDN are now more specific to aid in +diagnoising issues.

            +
          • +
          • +

            Quietened some log output if we encounter connection errors trying to +utilise the Frontier CAPI service.

            +
          • +
          +

          +Translations

          +

          We believe that nothing should be worse in this version compared to 5.1.1, +although a small tweak or two might have leaked through.

          +

          We'll be fully addressing translations in a near-future release after we've +conclude the necessary code level work for the new system. Nothing should +change for those of you helping on OneSky, other than at most the +'comments' on each translation. They should be more useful!

          +

          Pending that work we've specifically chosen not to update any +translations in this release, so they'll be the same as released in 5.1.0.

          +

          +Bug Fixes

          +
            +
          • +

            Handle where the Backpack.json file for a Backpack event is a zero length +file. Closes #1138.

            +
          • +
          • +

            Fixed case of 'Selection' in 'Override Beta/Normal Selection' text on +Settings > Configuration. This allows translations to work.

            +
          • +
          +

          +Plugin Developers

          +
            +
          • +

            We've updated Contributing.md including:

            +
              +
            1. Re-ordered the sections to be in a more logcial and helpful order.
            2. +
            3. Added a section about choosing an appropriate log level for messages.
            4. +
            5. fstrings now mandatory, other than some use of .format() with respect to +translated strings.
            6. +
            +
          • +
          • +

            docs/Translations.md updated about a forthcoming +change to how we can programmatically check that all translation strings +have a proper comment in 'L10n/en.template' to aid translators.

            +
          • +
          • +

            state passed to journal_entry() now has ShipLockerJSON which contains +the json.load()-ed data from the new 'ShipLocker.json' file. We do +attempt to always load from this file, even when the ShipLocker Journal +event itself contains all of the data (which it does on startup, embark and +disembark), so it should always be populated when plugins see any event +related to Odyssey inventory.

            +
          • +
          + +

          Release 5.1.0

          • @@ -723,11 +803,11 @@ about this: ]]> From 224d833606ebbefe757581f2d4066e10bb0697a0 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 30 Jul 2021 13:16:28 +0100 Subject: [PATCH 023/261] Release 5.1.2: appcast --- edmarketconnector.xml | 58 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 6aef81811..21fd6e6b9 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,14 +36,14 @@ - Release 5.1.1 + Release 5.1.2 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }
            • -

              We now test against, and package with, Python 3.9.5.

              +

              We now test against, and package with, Python 3.9.6.

              As a consequence of this we no longer support Windows 7.
              This is due to Python 3.9.x itself not supporting Windows 7. @@ -58,6 +58,54 @@ currently used version in a given branch.

            +

            Release 5.1.2

            +
              +
            • +

              A Journal event change in EDO Update 6 will have caused some translated +suit names to not be properly mapped to their sane versions. This change +has now been addressed and suit names should always come out as intended in +the EDMarketConnector.exe UI.

              +
            • +
            • +

              There is a new command-line argument to cause all Frontier Authorisation to +be forgotten: EDMarketConnector.exe --forget-frontier-auth.

              +
            • +
            • +

              Situations where Frontier CAPI data doesn't agree on the location we have +tracked from Journal events will now log more useful information.

              +
            • +
            +

            +Bug Fixes

            +
              +
            • The code should now be robust against the case of any Journal event name +changing.
            • +
            +

            +Plugin Developers

            +
              +
            • +

              We now store GameLanguage, GameVersion and GameBuild in the state +passed to journal_entry() from the LoadGame event.

              +
            • +
            • +

              Various suit data, i.e. class and mods, is now stored from relevant +Journal events, rather than only being available from CAPI data. In +general we now consider the Journal to be the canonical source of suit +data, with CAPI only as a backup.

              +
            • +
            • +

              Backpack contents should now track correctly if using the 'Resupply' option +available on the ship boarding menu.

              +
            • +
            • +

              We now cache the main application version when first determined, so +that subsequent references to config.appversion() won't cause extra log +spam (which was possible when, e.g. having a git command but using non-git +source).

              +
            • +
            +

            Release 5.1.1

            The big change in this is adjustments to be in line with Journal changes in Elite Dangerous Odyssey 4.0.0.400, released 2021-06-10, with respect to the @@ -803,11 +851,11 @@ about this: ]]> From 44052355b2b49d21e7433a07b23816ffe11cd97f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 8 Aug 2021 15:52:41 +0100 Subject: [PATCH 024/261] Release 5.1.3: appcast --- edmarketconnector.xml | 63 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 21fd6e6b9..bb2e21886 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.1.2 + Release 5.1.3 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,61 @@ currently used version in a given branch.

          +

          Release 5.1.3

          +
            +
          • +

            Attempt to flush any pending EDSM API data when a Journal Shutdown or +Fileheader event is seen. After this, the data is dropped. This ensures +that, if the user next logs in to a different commander, the data isn't then +sent to the wrong EDSM account.

            +
          • +
          • +

            Ensure a previous Journal file is fully read/drained before starting +processing of a new one. In particular, this ensures properly seeing the end +of a continued Journal file when opening the continuation file.

            +
          • +
          • +

            New config options, in a new Privacy tab, to hide the current Private +Group, or captain of a ship you're multi-crewing on. These usually appear +on the Commander line of the main UI, appended after your commander name, +with a / between.

            +
          • +
          • +

            EDO dockable settlement names with + characters appended will no longer +cause 'server lagging' reports.

            +
          • +
          • +

            Don't force DEBUG level logging to the +plain log file +if --trace isn't used to force TRACE level logging. This means logging +to the plain log file will once more respect the user-set Log Level, as in +the Configuration tab of Settings.

            +

            As its name implies, the debug log file +will always contain at least DEBUG level logging, or TRACE if forced.

            +
          • +
          +

          +(Plugin) Developers

          +
            +
          • +

            New EDMarketConnector option --trace-on ... to control if certain TRACE +level logging is used or not. This helps keep the noise down whilst being +able to have users activate choice bits of logging to help track down bugs.

            +

            See Contributing.md for +details.

            +
          • +
          • +

            Loading of ShipLocker.json content is now tried up to 5 times, 10ms apart, +if there is a file loading, or JSON decoding, failure. This should +hopefully result in the data being loaded correctly if a race condition with +the game client actually writing to and closing the file is encountered.

            +
          • +
          • +

            config.get_bool('some_str', default=SomeDefault) will now actually honour +that specified default.

            +
          • +
          +

          Release 5.1.2

          • @@ -851,11 +906,11 @@ about this: ]]> From f833afdb8b6f12a72bee9822f5e89cfd9dd80883 Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 18 Oct 2021 15:41:51 +0200 Subject: [PATCH 025/261] Create version 2 killswitches file Has no defined killswitches --- killswitches_v2.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 killswitches_v2.json diff --git a/killswitches_v2.json b/killswitches_v2.json new file mode 100644 index 000000000..2b2cdff8f --- /dev/null +++ b/killswitches_v2.json @@ -0,0 +1,5 @@ +{ + "version": 2, + "last_updated": "18 October 2021", + "kill_switches": [] +} From ce08a845002345dd8c5c34be90e1205649d79f74 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 25 Oct 2021 13:20:26 +0100 Subject: [PATCH 026/261] Release 5.2.0: appcast --- edmarketconnector.xml | 129 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index bb2e21886..6045e3696 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,14 +36,14 @@ - Release 5.1.3 + Release 5.2.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }
            • -

              We now test against, and package with, Python 3.9.6.

              +

              We now test against, and package with, Python 3.9.7.

              As a consequence of this we no longer support Windows 7.
              This is due to Python 3.9.x itself not supporting Windows 7. @@ -58,6 +58,125 @@ currently used version in a given branch.

            +

            Release 5.2.0

            +
              +
            • +

              The 'Update' button is disabled if CQC/Arena is detected.

              +
            • +
            • +

              Frontier CAPI queries now run in their own thread. There should be no +change in functionality for users. This affects both EDMarketConnector +(GUI) and EDMC (command-line).

              +
            • +
            • +

              File > Status will now use cached CAPI data, rather than causing a fresh +query. Currently if data has not yet been cached nothing will happen when +trying to use this.

              +
            • +
            • +

              Trying to use File > Status when the current commander is unknown, or +there is has been no CAPI data retrieval yet, will now result in the 'bad' +sound being played and an appropriate status line message.

              +
            • +
            • +

              File > Save Raw Data also now uses the cached CAPI data, rather than +causing a fresh query. This will write an empty JSON {} if no data is +yet available.

              +
            • +
            • +

              New docs/Licenses/ directory containing all relevant +third-party licenses for the software this application uses.

              +
            • +
            • +

              Settings > Output > File Location 'Browse' button will now always be +available, even if no output options are active.

              +
            • +
            • +

              The 'no git installed' logging when running from source is now at INFO +level, not ERROR. This will look less scary.

              +
            • +
            • +

              EDMarketConnetor command-line arguments have been re-ordered into +logical groups for --help output.

              +
            • +
            • +

              Support added for several new EDDN schemas relating to specific Journal +events. The live EDDN server has been updated to support these.

              +

              Schema support added for:

              +
                +
              • codexentry/1
              • +
              • fssdiscoveryscan/1
              • +
              • navbeaconscan/1
              • +
              • navroute/1
              • +
              • scanbarycentre/1
              • +
              +
            • +
            • +

              If a message to EDDN gets an 'unknown schema' response it will NOT be +saved in the replaylog for later retries, instead being discarded.

              +
            • +
            +

            +Bug Fixes

            +
              +
            • +

              Pressing the 'Update' button when in space (not docked, not on a body +surface) will no longer cause a spurious "Docked but unknown station: EDO +Settlement?" message.

              +
            • +
            • +

              A bug preventing --force-localserver-auth from working has been fixed.

              +
            • +
            • +

              horizons and odyssey flags should now always be set properly on all +EDDN messages. The horizons flag was missing from some.

              +
            • +
            +

            +Developers

            +
              +
            • +

              Now built using Python 3.9.7.

              +
            • +
            • +

              New journal_entry_cqc() function for plugins to receive journal events +specifically and only when the player is in CQC/Arena. This allows +for tracking things that happen in CQC/Arena without polluting +journal_entry(). See PLUGINS.md for details.

              +
            • +
            • +

              Command-line argument --trace-all to force all possible --trace-on to be +active.

              +
            • +
            • +

              Contributing.md has been updated for how to properly use trace_on().

              +
            • +
            • +

              EDMC.(py,exe) now also makes use of --trace-on.

              +
            • +
            • +

              EDMarketConnector now has --capi-pretend-down to act as if the CAPI +server is down.

              +
            • +
            • +

              Killswitches now have support for removing key/values entirely, or forcing +the value. See docs/Killswitches.md for details.

              +
            • +
            • +

              state['Odyssey'] added, set from LoadGame journal event.

              +
            • +
            • +

              You can now test against a different EDDN server using --eddn-url +command-line argument. This needs to be the full 'upload' URL, i.e. for +the live instance this is https://eddn.edcd.io:4430/upload/.

              +
            • +
            • +

              New command-line argument --eddn-tracking-ui to track the EDDN plugin's +idea of the current BodyName and BodyID, from both the Journal and +Status.json.

              +
            • +
            +

            Release 5.1.3

            • @@ -906,11 +1025,11 @@ about this: ]]> From 51fa8c94fa8f817354d8a7fa665dfcdb4e9e14d5 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 7 Nov 2021 12:11:57 +0000 Subject: [PATCH 027/261] Release 5.2.1: appcast --- edmarketconnector.xml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 6045e3696..1356b520e 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.2.0 + Release 5.2.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,15 @@ currently used version in a given branch.

            +

            Release 5.2.1

            +

            This release primarily addresses the issue of the program asking for +Frontier authorization much too often.

            +
              +
            • Actually utilise the Frontier Refresh Token when the CAPI response is +"Unauthorized". The re-factoring of this code to make CAPI queries +threaded inadvertently prevented this.
            • +
            +

            Release 5.2.0

            • @@ -1025,11 +1034,11 @@ about this: ]]> From c910acf66bfcc39ad77eab028baab298b21cfc48 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 13 Nov 2021 11:24:33 +0000 Subject: [PATCH 028/261] Release 5.2.2: appcast --- edmarketconnector.xml | 58 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 1356b520e..cf907fb89 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,14 +36,14 @@ - Release 5.2.1 + Release 5.2.2 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }
              • -

                We now test against, and package with, Python 3.9.7.

                +

                We now test against, and package with, Python 3.9.8.

                As a consequence of this we no longer support Windows 7.
                This is due to Python 3.9.x itself not supporting Windows 7. @@ -58,6 +58,54 @@ currently used version in a given branch.

              + + +

              Release 5.2.2

              +

              This release adds one new feature and addresses some bugs. We've also +updated to using Python 3.9.8.

              +
                +
              • +

                Windows now has "minimize to system tray" support.

                +
                  +
                • The system tray icon will always be present.
                • +
                • There is a new option on the Settings > Appearance tab - +Minimize to system tray.
                • +
                • When this new option is active, minimizing the application will also +hide the taskbar icon.
                • +
                • When the new option is not active, the application will minimize to the +taskbar as normal.
                • +
                +
              • +
              +

              +Bug Fixex

              +
                +
              • +

                If a CAPI query failed in such a way that no requests.Response object +was made available we attempted to blindly dump the non-existent object.
                +We now check that it actually exists, and log the specifics of the exception.

                +
              • +
              • +

                A user experienced the game writing a NavRoute.json file without a +Route array, which caused the application to attempt sending a badly formed +navroute message to EDDN. That message was then remembered and constantly +retried.

                +
                  +
                • +

                  We now sanity check the NavRoute.json contents to be sure there is a +Route array, even if it is empty. If it's not present no attempt +to send the EDDN message will be made.

                  +

                  If this scenario occurs the user will see a status line message No 'Route' array in NavRoute.json contents.

                  +
                • +
                • +

                  For any EDDN message that receives a 400 status back we will drop it +from the replay log.

                  +
                • +
                +
              • +
              +
              +

              Release 5.2.1

              This release primarily addresses the issue of the program asking for Frontier authorization much too often.

              @@ -1034,11 +1082,11 @@ about this: ]]>
              From 9da93e6a2c8d9350c5cb29504826f6c6e927ee85 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 22 Nov 2021 11:29:51 +0000 Subject: [PATCH 029/261] Release 5.2.3: appcast --- edmarketconnector.xml | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index cf907fb89..87e339de0 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.2.2 + Release 5.2.3 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,38 @@ currently used version in a given branch.

            +

            Release 5.2.3

            +

            This release fixes one bug and fixes some example code.

            +
              +
            • +

              Odyssey changed the order of some Journal events. This caused our logic +for tracking the following to break, and thus not report them ever to Inara:

              +
                +
              • Ship Combat, Trade and Exploration ranks.
              • +
              • On-foot Combat and Exobiologist ranks.
              • +
              • Engineer unlocks and progress.
              • +
              • Reputations with Major Factions (Superpowers).
              • +
              +

              This is now fixed and the current state of all of these will be correctly +reported to Inara if you have API access for it configured.

              +
            • +
            +

            +Developers

            +
              +
            • +

              Now built using Python 3.9.9.

              +
            • +
            • +

              Updated PLUGINS.md +to state that we don't actually include all of Python's standard library.

              +
            • +
            • +

              The click_counter +example plugin code has been corrected to both actually work fully, and pass +our linting.

              +
            • +

            Release 5.2.2

            @@ -1082,11 +1114,11 @@ about this: ]]>
            From fa17e5159161cc6b7a2e516334947bf315930a7c Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 16 Dec 2021 14:24:41 +0000 Subject: [PATCH 030/261] Release 5.2.4: appcast --- edmarketconnector.xml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 87e339de0..75728c63a 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.2.3 + Release 5.2.4 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,22 @@ currently used version in a given branch.

          +

          Release 5.2.4

          +

          This is a very minor update that simply imports the latest versions of +data files so that some niche functionality works properly.

          +
            +
          • +

            Update commodity.csv and rare_commodity.csv from the latest +EDCD/FDevIDs versions. This addresses +an issue with export of market data in Trade Dangerous format containing +OnionHeadC rather than the correct name, Onionhead Gamma Strain, that +Trade Dangerous is expecting.

            +

            This will only have affected Trade Dangerous users who use EDMarketConnector +as a source of market data.

            +
          • +
          +
          +

          Release 5.2.3

          This release fixes one bug and fixes some example code.

            @@ -1114,11 +1130,11 @@ about this: ]]> From 748043b22446b66922c19c8162da1622ea484f86 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 15 Feb 2022 18:47:31 +0000 Subject: [PATCH 031/261] Release 5.3.0: appcast --- edmarketconnector.xml | 176 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 170 insertions(+), 6 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 75728c63a..bc6bd1f2b 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,17 +36,17 @@ - Release 5.2.4 + Release 5.3.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }
            • -

              We now test against, and package with, Python 3.9.8.

              +

              We now test against, and package with, Python 3.10.2.

              As a consequence of this we no longer support Windows 7.
              This is due to -Python 3.9.x itself not supporting Windows 7. +Python 3.10.x itself not supporting Windows 7. The application (both EDMarketConnector.exe and EDMC.exe) will crash on startup due to a missing DLL.

              This should have no other impact on users or plugin developers, other @@ -58,6 +58,170 @@ currently used version in a given branch.

            +

            Release 5.3.0

            +

            As has sadly become routine now, please read +our statement about malware false positives +affecting our installers and/or the files they contain. We are as confident +as we can be, without detailed auditing of python.org's releases and all +the py2exe source and releases, that there is no malware in the files we make +available.

            +

            This release is primarily aimed at fixing some more egregious bugs, +shortcomings and annoyances with the application. It also adds support for +two additional +EDDN +schemas.

            +
              +
            • +

              We now test and build using Python 3.10.2. We do not yet make use of any +features specific to Python 3.10 (or 3.9). Let us restate that we +absolutely reserve the right to commence doing so.

              +
            • +
            • +

              We now set a custom User-Agent header in all web requests, i.e. to EDDN, +EDSM and the like. This is of the form:

              +

              EDCD-EDMarketConnector-<version>

              +
            • +
            • +

              "File" -> "Status" will now show the new Odyssey ranks, both the new +categories and the new 'prestige' ranks, e.g. 'Elite I'.

              +

              NB: Due to an oversight there are currently no translations for these.

              +

              Closes #1369.

              +
            • +
            • +

              Running EDMarketConnector.exe --reset-ui will now also reset any changes to +the application "UI Scale" or geometry (position and size).

              +

              Closes #1155.

              +
            • +
            • +

              We now use UTC-based timestamps in the application's log files. Prior to +this change it was the "local time", but without any indication of the +applied timezone. Each line's timestamp has UTC as a suffix now. We +are assuming that your local clock is correct and the timezone is set +correctly, such that Python's time.gmtime() yields UTC times.

              +

              This should make it easier to correlate application logfiles with in-game +time and/or third-party service timestamps.

              +
            • +
            • +

              The process used to build the Windows installers should now always pick up +all the necessary files automatically. Prior to this we used a manual +process to update the installer configuration which was prone to both user +error and neglecting to update it as necessary.

              +
            • +
            • +

              If the application fails to load valid data from the NavRoute.json file +when processing a Journal NavRoute event, it will attempt to retry this +operation a number of times as it processes subsequent Journal events.

              +

              This should hopefully work around a race condition where the game might +not have yet updated NavRoute.json at all, or has truncated it to empty, +when we first attempt this.

              +

              We will also now NOT attempt to load NavRoute.json during the startup +'Journal catch-up' mode, which only sets internal state.

              +

              Closes #1348.

              +
            • +
            • +

              Inara: Use the <journal log>->Statistics->Bank_Account->Current_Wealth +value when sending a setCommanderCredits message to Inara to set +commanderAssets.

              +

              In addition, a setCommanderCredits message at game login will now only +ever be sent at game login. Yes, you will NEED to relog to send an +updated balance. This is the only way in which to sanely keep the +'Total Assets' value on Inara from bouncing around.

              +

              Refer to Inara:API:docs:setCommanderCredits.

              +

              Closes #1401.

              +
            • +
            • +

              Inara: Send a setCommanderRankPilot message when the player logs in to the +game on-foot. Previously you would HAVE to be in a ship at login time +for this to be sent.

              +

              Thus, you can now relog on-foot in order to update Inara with any Rank up +or progress since the session started.

              +

              Closes #1378.

              +
            • +
            • +

              Inara: Fix for always sending a Rank Progress of 0%.

              +

              Closes #1378.

              +
            • +
            • +

              Inara: You should once more see updates for any materials used in +Engineering. The bug was in our more general Journal event processing +code pertaining to EngineerCraft events, such that the state passed to +the Inara plugin hadn't been updated.

              +

              Such updates should happen 'immediately', but take into account that there +can be a delay of up to 35 seconds for any data sent to Inara, due to how +we avoid breaking the "2 messages a minute" limit on the Inara API.

              +

              Closes #1395.

              +
            • +
            • +

              EDDN: Implement new approachsettlement/1 +schema.

              +
            • +
            • +

              EDDN: Implement new fssallbodiesfound/1 +schema.

              +
            • +
            • +

              EDDN: We now compress all outgoing messages. This might help get some +particularly large navroute messages go through.

              +

              If any message is now rejected as 'too large' we will drop it, and thus +not retry it later. The application logs will reflect this.

              +

              NB: The EDDN Gateway was updated to allow messages up to 1 MiB in size +anyway. The old limit was 100 KiB.

              +

              Closes #1390.

              +
            • +
            • +

              EDDN: In an attempt to diagnose some errors observed on the EDDN Gateway +with respect to messages sent from this application some additional checks +and logging have been added.

              +

              NB: After some thorough investigation it was concluded that these EDDN +errors were likely the result of long-delayed messages due to use of +the "Delay sending until docked" option.

              +

              There should be no functional changes for users. But if you see any of +the following in this application's log files PLEASE OPEN +AN ISSUE ON GITHUB +with all the requested information, so that we can correct the relevant +code:

              +
                +
              • No system name in entry, and system_name was not set either! entry: ...
              • +
              • BodyName was present but not a string! ...
              • +
              • post-processing entry contains entry ...
              • +
              • this.body_id was not set properly: ...
              • +
              • system is falsey, can't add StarSystem
              • +
              • this.coordinates is falsey, can't add StarPos
              • +
              • this.systemaddress is falsey, can't add SystemAddress
              • +
              • this.status_body_name was not set properly: ...
              • +
              +

              You might also see any of the following in the application status text +(bottom of the window):

              +
                +
              • passed-in system_name is empty, can't add System
              • +
              • CodexEntry had empty string, PLEASE ALERT THE EDMC DEVELOPERS
              • +
              • system is falsey, can't add StarSystem
              • +
              • this.coordinates is falsey, can't add StarPos
              • +
              • this.systemaddress is falsey, can't add SystemAddress
              • +
              +

              Ref: #1403 +#1393.

              +
            • +
            +

            +Translations

            +
              +
            • +

              Use a different workaround for OneSky (translations website) using "zh-Hans" +for Chinese (Simplified), whereas Windows will call this "zh-CN". This is +in-code and documented with a comment, as opposed to some 'magic' in the +Windows Installer configuration that had no such documentation. It's less +fragile than relying on that, or developers using a script/documented +process to rename the file.

              +
            • +
            • +

              As noted above we forgot to upload to +OneSky +after adding the Odyssey new ranks/categories. This has now been done, +and some new phrases await translation.

              +
            • +
            +

            Release 5.2.4

            This is a very minor update that simply imports the latest versions of data files so that some niche functionality works properly.

            @@ -1130,11 +1294,11 @@ about this: ]]>
            From 998816656fc522e334e24218e6d2b39b2d9dc0db Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 21 Feb 2022 12:49:16 +0000 Subject: [PATCH 032/261] Release 5.3.1: appcast --- edmarketconnector.xml | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index bc6bd1f2b..cc1018714 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.3.0 + Release 5.3.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,31 @@ currently used version in a given branch.

          +

          Release 5.3.1

          +

          This release addresses some issues with newer EDDN code which could cause erroneous alerts to the player, or sending of bad messages.

          • EDDN: Cope with ApproachSettlement on login occurring before Location, +such that we don't yet know the name of the star system the player is in.

            Closes #1484

            +
          • EDDN: Cope with ApproachSettlement missing planetary coordinates on login +at/near a settlement in Horizons.

            Closes #1476

            +
          • EDDN: Change the CodexEntry "empty string" checks to only apply to those +values where the schema enforces "must be at least one character".

            This prevents the big 'CodexEntry had empty string, PLEASE ALERT THE EDMC DEVELOPERS' message from triggering on, e.g. NearestDestination being +empty, which the schema allows.

            Closes #1481

            +

          Plugin Developers

          +
          • +

            If you use a sub-class for a widget the core code will no longer break if +your code raises an exception. e.g. a plugin was failing due to Python +3.10 using collections.abc instead of collections, and the plugin's +custom widget had a configure() method which was called by the core +theme code on startup or theme change. This then caused the whole +application UI to never show up on startup.

            +

            This also applies if you set up a button such that enter/leave on it, i.e. +mouse in/out, causes the theme.py code for that to trigger.

            +

            So, now in such cases the main UI should actually show up, although your +plugin's UI might look weird due to theming not being properly applied.

            +

            The plugin exception WILL be logged, at ERROR level.

            +
          • +
          + +

          Release 5.3.0

          As has sadly become routine now, please read our statement about malware false positives @@ -1294,11 +1319,11 @@ about this: ]]> From e82bf4ca5e5ad295f45e0cef89e8cd82365352f2 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 15 Mar 2022 16:42:11 +0000 Subject: [PATCH 033/261] Release 5.3.3: appcast NB: 5.3.2 got reverted in this branch, so both its changelog and that for 5.3.3 are in this commit now. --- edmarketconnector.xml | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index cc1018714..83497deab 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.3.1 + Release 5.3.3 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -58,6 +58,23 @@ currently used version in a given branch.

        +

        Release 5.3.3

        +

        Unfortunately 5.3.2 failed to fully address the issues caused by the different +Journal filenames when using the Odyssey Update 11 client. It's fine if you +run EDMarketConnector first and then the game, as the code path that detects +a new file always does just that.

        +

        But the code for EDMarketConnector startup to find the current newest Journal +file relied on sorting the filenames and that would mean the new-style names +would always sort as 'oldest'.

        +

        This release fixes that code to properly use the file modification timestamp +to determine the newest file on startup.

        + + +

        Release 5.3.2

        +

        This release contains one change to cope with how Frontier decided to name +the Journal files differently in the Update 11 Odyssey client.

        + +

        Release 5.3.1

        This release addresses some issues with newer EDDN code which could cause erroneous alerts to the player, or sending of bad messages.

        • EDDN: Cope with ApproachSettlement on login occurring before Location, such that we don't yet know the name of the star system the player is in.

          Closes #1484

          @@ -1319,11 +1336,11 @@ about this: ]]> From 0cb49f9c0df22583012d636cad198b7212858477 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 22 Mar 2022 07:45:56 +0000 Subject: [PATCH 034/261] Release 5.3.4: appcast --- edmarketconnector.xml | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 83497deab..7e6e00957 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,28 +36,23 @@ - Release 5.3.3 + Release 5.3.4 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +

          We now test against, and package with, Python 3.10.3.

          +

          As a consequence of this we no longer support Windows 7.
          + +

          Release 5.3.4

          +

          Whilst EDMarketConnector.exe was fixed for the Odyssey Update 11 difference in Journal file names, EDMC.exe was not. If you're wondering, that's the command-line utility that, for instance, Trade Computer Extensions uses to obtain data.

            -
          • -

            We now test against, and package with, Python 3.10.2.

            -

            As a consequence of this we no longer support Windows 7.
            -This is due to -Python 3.10.x itself not supporting Windows 7. -The application (both EDMarketConnector.exe and EDMC.exe) will crash on -startup due to a missing DLL.

            -

            This should have no other impact on users or plugin developers, other -than the latter now being free to use features that were introduced since the -Python 3.7 series.

            -

            Developers can check the contents of the .python-version file -in the source (it's not distributed with the Windows installer) for the -currently used version in a given branch.

            -
          • +
          • Use the new common function for finding latest journal file in EDMC.py.
          • +
          • Quietens some NavRoute related logging for the benefit of EDMC.py. This is +now at DEBUG level, rather than INFO.
          +

          Release 5.3.3

          Unfortunately 5.3.2 failed to fully address the issues caused by the different Journal filenames when using the Odyssey Update 11 client. It's fine if you @@ -1336,11 +1331,11 @@ about this: ]]> From cfa78955fbc5c9a43ca8ba8bb1250ab53b1687b9 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 8 Jun 2022 14:18:49 +0100 Subject: [PATCH 035/261] Release 5.4.0: appcast --- edmarketconnector.xml | 47 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 7e6e00957..379297af2 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,14 +36,51 @@ - Release 5.3.4 + Release 5.4.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

          We now test against, and package with, Python 3.10.3.

          +

          We now test against, and package with, Python 3.10.4.

          As a consequence of this we no longer support Windows 7.
          +

          Release 5.4.0

          +
            +
          • We now test against, and package with, Python 3.10.4.
          • +
          • New EDDN schema fssbodysignals is now supported.
          • +
          • Odyssey Update 12 will add BodyID to CodexEntry journal events, so don't +overwrite this with an augmentation if it is already present. We've also +added the same for BodyName in case Frontier ever add that.
          • +
          • +Translations updated. +Thanks again to all the contributors.
          • +
          +

          +Bug Fixes

          +
            +
          • Cross-check the MarketID in CAPI data, not only the station name, to ensure +the data is for the correct station. Closes #1572.
          • +
          • Location cross-check paranoia added to several EDDN message types to ensure +no bad data is sent.
          • +
          • Ensure we don't send bad BodyID/Name for an orbital station if the player +uses a taxi. +Closes #1522.
          • +
          +

          +Developers

          +
            +
          • Odyssey Update 12 adds a new Journal event, and file, FCMaterials.json, +detailing the available trades at a Fleet Carrier's bar tender. Support has +been added for this. Plugin developers are sent an FCMaterials event +with the full contents of the file.
          • +
          +

          +EDMC.exe

          +

          This now uses specific exit codes in all cases, rather than a generic +EXIT_SYS_ERR (6) for some cases. See the appropriate line in EDMC.py for +details.

          + +

          Release 5.3.4

          Whilst EDMarketConnector.exe was fixed for the Odyssey Update 11 difference in Journal file names, EDMC.exe was not. If you're wondering, that's the command-line utility that, for instance, Trade Computer Extensions uses to obtain data.

            @@ -1331,11 +1368,11 @@ about this: ]]> From c9d97946456d13e76aed77a098a1dd701956c781 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 18 Jun 2022 14:47:19 +0100 Subject: [PATCH 036/261] Release 5.4.1: appcast --- edmarketconnector.xml | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 379297af2..6acd1aea9 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,14 +36,41 @@ - Release 5.4.0 + Release 5.4.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

            We now test against, and package with, Python 3.10.4.

            +

            We now test against, and package with, Python 3.10.5.

            As a consequence of this we no longer support Windows 7.
            +

            Release 5.4.1

            +
              +
            • +

              If for any reason EDMarketConnector.exe fails to shutdown and exit when +asked to by the upgrade process this should no longer result in a spontaneous +system reboot. Closes #1492.

              +

              A manual reboot will still be required to complete the EDMarketConnector +upgrade process and we make no guarantees about the stability of the +application until this is done.

              +
            • +
            • +

              The new EDDN fsssignaldiscovered/1 schema has been implemented.

              +
            • +
            • +

              EDSM trace level logging will no longer log API credentials unless explicitly +asked to, separately from other EDSM API trace logging.

              +
            • +
            +

            +Bug Fixes

            +
              +
            • EDDN: Ensure we always remove all _Localised suffix keys in data. This +was missed in some recent new schemas and turned out to be an issue for at +least approachsettlement/1.
            • +
            +
            +

            Release 5.4.0

            • We now test against, and package with, Python 3.10.4.
            • @@ -1368,11 +1395,11 @@ about this: ]]> From 76e841069362fe0dad70bae155408eb677c46212 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 26 Sep 2022 11:04:18 +0100 Subject: [PATCH 037/261] Release 5.5.0: appcast file --- edmarketconnector.xml | 59 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 6acd1aea9..3073e2ff4 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,14 +36,63 @@ - Release 5.4.1 + Release 5.5.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

              We now test against, and package with, Python 3.10.5.

              +

              We now test against, and package with, Python 3.10.7.

              As a consequence of this we no longer support Windows 7.
              +

              Release 5.5.0

              +
                +
              • Virus Total scan results for this release.
              • +
              • We now test against, and package with, Python 3.10.7.
              • +
              • EDDN: Support added for the FCMaterials schemas to aid third-party sites in +offering searches for where to buy and sell Odyssey Micro Resources, +including on Fleet Carriers with the bar tender facility.
              • +
              +

              Bug Fixes

              +
                +
              • EDDN: Abort fsssignaldiscovered sending of message if no signals passed +the checks.
              • +
              • EDDN: Add Horizons check for location on fsssignaldiscovered messages.
              • +
              • Don't alert the user if the first attempted load of NavRoute.json contains +no route.
              • +
              • Inara: Don't set marketID for ApproachSettlement unless it's actually +present in the event.
              • +
              +

              Plugin Developers

              +
                +
              • +

                We now build using the new, setuptools mediated py2exe freeze() method, +so we're in the clear for when distutils is removed in Python 3.12.

                +

                This shouldn't have any adverse effects on plugins, i.e. all of the same +Python modules are still packaged as before.

                +
              • +
              • +

                Support has been added for the NavRouteClear event. We do send this +through to plugins, so that they know the player has cleared the route, +but we keep the previously plotted route details in state['NavRoute'].

                +
              • +
              • +

                The documentation of the return type of journal_entry() has been corrected +to Optional[str].

                +
              • +
              • +

                FDevIDs files (commodity.csv rare_commodity.csv) updated to latest +versions.

                +
              • +
              +

              Developers

              +
                +
              • We now build using the new, setuptools mediated py2exe freeze() method, +so we're in the clear for when distutils is removed in Python 3.12.
              • +
              • The old setup.py file, along with associated py2exe.cmd have been removed +in favour of the new Build-exe-and-msi.py file. Documentation updated.
              • +
              +
              +

              Release 5.4.1

              • @@ -1395,11 +1444,11 @@ about this: ]]> From bcd3f89b2fa8d9a20149fbd0ed83e066ce7419b0 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 28 Nov 2022 16:38:34 +0000 Subject: [PATCH 038/261] Release 5.6.0: appcast --- coriolis-data | 2 +- edmarketconnector.xml | 141 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 6 deletions(-) diff --git a/coriolis-data b/coriolis-data index 5ac8a2474..566d768a7 160000 --- a/coriolis-data +++ b/coriolis-data @@ -1 +1 @@ -Subproject commit 5ac8a2474e931bbf7cae351f7c987bc40af81012 +Subproject commit 566d768a711362c423748963c5f56d3bea68e6a5 diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 3073e2ff4..94f239316 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,14 +36,145 @@ - Release 5.5.0 + Release 5.6.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

                We now test against, and package with, Python 3.10.7.

                +

                We now test against, and package with, Python 3.10.8.

                As a consequence of this we no longer support Windows 7.
                +

                Release 5.6.0

                +

                Tha major reason for this release is to address the Live versus Legacy galaxy +split coming in Update 14 of the game. +See the section "Update 14 and the Galaxy Split" below for how this might +impact you.

                +

                Changes

                +
                  +
                • +

                  We now test against, and package with, Python 3.10.8.

                  +
                • +
                • +

                  The code for sending data to EDDN has been reworked. This changes the +'replay log' from utilising an internal array, backed by a flat file +(replay.jsonl), to an sqlite3 database.

                  +

                  As a result:

                  +
                    +
                  1. Any messages stored in the old replay.jsonl are converted at startup, +if that file is present, and then the file removed.
                  2. +
                  3. All new messages are stored in this new sqlite3 queue before any attempt +is made to send them. An immediate attempt is then made to send any +message not affected by "Delay sending until docked".
                  4. +
                  5. Sending of queued messages will be attempted every 5 minutes, unless +"Delay sending until docked" is active and the Cmdr is not docked in +their own ship. This is in case a message failed to send due to an issue +communicating with the EDDN Gateway.
                  6. +
                  7. When you dock in your own ship an immediate attempt to send all queued +messages will be initiated.
                  8. +
                  9. When processing queued messages the same 0.4-second inter-message delay +as with the old code has been implemented. This serves to not suddenly +flood the EDDN Gateway. If any message fails to send for Gateway reasons, +i.e. not a bad message, then this processing is abandoned to wait for +the next invocation.
                  10. +
                  +

                  The 5-minute timer in point 3 differs from the old code, where almost any +new message sending attempt could initiate processing of the queue. At +application startup this delay is only 10 seconds.

                  +

                  Currently, the feedback of "Sending data to EDDN..." in the UI status line +has been removed.

                  +

                  If you do not have "Delay sending until docked" active, then the only +messages that will be at all delayed will be where there was a communication +problem with the EDDN Gateway, or it otherwise indicated a problem other +than 'your message is bad'.

                  +
                • +
                • +

                  As a result of this EDDN rework this application now sends appropriate +gameversion and gamebuild strings in EDDN message headers. +The rework was necessary in order to enable this, in case of any queued +or delayed messages which did not contain this information in the legacy +replay.jsonl format.

                  +
                • +
                • +

                  For EDSM there is a very unlikely set of circumstances that could, in theory +lead to some events not being sent. This is so as to safeguard against +sending a batch with a gameversion/build claimed that does not match for +all of the events in that batch.

                  +

                  It would take a combination of "communications with EDSM are slow", more +events (the ones that would be lost), a game client crash, and starting +a new game client before the 'more events' are sent.

                  +
                • +
                +

                Update 14 and the Galaxy Split

                +

                Due to the galaxy split announced by Frontier +there are some changes to the major third-party websites and tools.

                +
                  +
                • +

                  Inara has chosen +to only accept Live galaxy data on its API.

                  +

                  This application will not even process Journal data for Inara after +2022-11-29T09:00:00+00:00 unless the gameversion indicates a Live client. +This explicitly checks that the game's version is semantically equal to or +greater than '4.0.0'.

                  +

                  If a Live client is not detected, then there is an INFO level logging +message "Inara only accepts Live galaxy data", which is also set as the main +UI status line. This message will repeat, at most, every 5 minutes.

                  +

                  If you continue to play in the Legacy galaxy only then you probably want to +just disable the Inara plugin with the checkbox on Settings > Inara.

                  +
                • +
                • +

                  All batches of events sent to EDSM will be tagged with a gameversion, in +a similar manner to the EDDN header.

                  +

                  Ref: EDSM api-journal-v1

                  +
                • +
                • +

                  All EDDN messages will now have appropriate gameversion and gamebuild +fields in the header as per +EDDN/docs/Developers.md.

                  +

                  As a result of this you can expect third-party sites to choose to filter data +based on that.

                  +

                  Look for announcements by individual sites/tools as to what they have chosen +to do.

                  +
                • +
                +

                Known Bugs

                +

                In testing if it had been broken at all due to 5.5.0 -> 5.6.0 changes it has +come to light that EDMC.EXE -n, to send data to EDDN, was already broken in +5.5.0.

                +

                In addition, there is now some extra 'INFO' logging output which will be +produced by any invocation of EDMC.EXE. This might break third-party use of +it, e.g. Trade Computer Extension Mk.II. +This will be fixed as soon as the dust settles from Update 14, with emphasis +being on ensuring the GUI EDMarketConnector.exe functions properly.

                +

                Notes for EDDN Listeners

                +
                  +
                • +

                  Where EDMC sourced data from the Journal files it will set gameversion +and gamebuild as per their values in Fileheader or LoadGame, whichever +was more recent (there are some events that occur between these).

                  +
                • +
                • +

                  If any message was already delayed such that it did not +have the EDDN header recorded, then the gameversion and gamebuild will +be empty strings. In order to indicate this the softwareName will have + (legacy replay) appended to it, e.g. E:D Market Connector Connector [Windows] (legacy replay). In general this indicates that the message was +queued up using a version of EDMC prior to this one. If you're only +interested in Live galaxy data then you might want to ignore such messages.

                  +
                • +
                • +

                  Where EDMC sourced data from a CAPI endpoint, the resulting EDDN message +will have a gameversion of CAPI-<endpoint> set, e.g. CAPI-market. +At this time it is not 100% certain which galaxy this data will be for, so +all listeners are advised to ignore/queue such data until this is clarified.

                  +

                  gamebuild will be an empty string for all CAPI-sourced data.

                  +
                • +
                +

                Plugin Developers

                +
                  +
                • There is a new flag in state passed to plugins, IsDocked. See PLUGINS.md +for details.
                • +
                +
                +

                Release 5.5.0

                • Virus Total scan results for this release.
                • @@ -1444,11 +1575,11 @@ about this: ]]> From ae4fbe8f00698c7e411564ca95cb70571aebf7d1 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 2 Dec 2022 12:52:47 +0000 Subject: [PATCH 039/261] Release 5.6.1: appcast --- edmarketconnector.xml | 92 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 94f239316..4089fabc0 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.6.0 + Release 5.6.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -44,6 +44,92 @@

                  We now test against, and package with, Python 3.10.8.

                  As a consequence of this we no longer support Windows 7.
                  +

                  Release 5.6.1

                  +

                  This release addresses some minor bugs and annoyances with v5.6.0, especially +for Legacy galaxy players.

                  +

                  In general, at this early stage of the galaxy split, we prefer to continue to +warn Legacy users who have 'send data' options active for sites that only +accept Live data. In the future this might be reviewed and such warnings +removed such that the functionality fails silently. This might be of use +to users who actively play in both galaxies.

                  +
                    +
                  • +

                    CAPI queries will now only be attempted for Live galaxy players This is +a stop-gap whilst the functionality is implemented for Legacy galaxy players. +Doing so prevents using Live galaxy data whilst playing Legacy galaxy, which +would be increasingly wrong and misleading.

                    +
                      +
                    1. 'Automatic update on docking' will do nothing for Legacy players.
                    2. +
                    3. Pressing the 'Update' button whilst playing Legacy will result in a status +line message "CAPI for Legacy not yet supported", and otherwise achieve +nothing. The only function of this button is to query CAPI data and +pass it to plugins, which does not include Inara and EDSM.
                    4. +
                    5. A Legacy player trying to use "File" > "Status" will get the message +"Status: No CAPI data yet" due to depending on CAPI data.
                    6. +
                    +

                    It is hoped to implement CAPI data retrieval and use for Legacy players soon, +although this will likely entail extending the plugins API to include a new +function specifically for this. Thus only updated plugins would support +this.

                    +
                  • +
                  • +

                    EDDN: Where data has been sourced from the CAPI this application now sends +a header->gameversion in the format "CAPI-(Live|Legacy)-<endpoint" as per +the updated documentation.

                    +
                      +
                    1. +

                      As this version only queries CAPI for Live players that will only be +"CAPI-Live-<endpoint>" for the time being.

                      +
                    2. +
                    3. +

                      If, somehow, the CAPI host queried matches neither the +current Live host, the Legacy host, nor the past beta host, you will see +"CAPI-UNKNOWN-<endpoint>".

                      +
                    4. +
                    5. +

                      As that statement implies, this application will also signal 'Live' if +pts-companion.orerve.net has been used, due to detecting an alpha or beta +version of the game. However, in that case the /test schemas will be used.

                      +
                    6. +
                    +

                    Closes #1734.

                    +
                  • +
                  • +

                    Inara: Only warn about Legacy data if sending is enabled in Settings > Inara.

                    +

                    Closes #1730.

                    +
                  • +
                  • +

                    Inara: Handling of some events has had a sanity check added so that the +Inara API doesn't complain about empty strings being sent. In these cases +the event will simply not be sent.

                    +

                    Closes #1732.

                    +
                  • +
                  • +

                    EDSM: EDSM has decided to accept only Live data on its API. Thus, this +application will only attempt to send data for Live galaxy players.

                    +

                    If a Legacy galaxy player has the Settings > EDSM > "Send flight log and +Cmdr status to EDSM" option active then they will receive an error about +this at most once every 5 minutes. Disabling that option will prevent the +warning.

                    +
                  • +
                  +

                  Plugin Developers

                  +
                    +
                  • PLUGINS.md has been updated to make it clear that the only use of imports +from the config module are for setting/getting/removing a plugin's own +configuration, or detecting application shutdown in progress.
                  • +
                  • PLUGINS.md has also been updated to add a note about how the data passed +to a plugin cmdr_data() is, strictly speaking, an instance of CAPIData, +which is an extension of UserDict. It has some extra properties on it, +but these are for internal use only and no plugin should rely on them.
                  • +
                  • As noted above, implementing CAPI data for Legacy players will likely entail +an additional function in the API provided to plugins. See +#1728 for discussion +about this.
                  • +
                  +
                  + +

                  Release 5.6.0

                  Tha major reason for this release is to address the Live versus Legacy galaxy split coming in Update 14 of the game. @@ -1575,10 +1661,10 @@ about this: ]]> From fdd4e8f331aa4ab0a757a2bf6fc084f1495fbe67 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 16 Dec 2022 11:24:29 +0000 Subject: [PATCH 040/261] Release 5.7.0: appcast --- edmarketconnector.xml | 129 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 4089fabc0..b9a74e852 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,14 +36,133 @@ - Release 5.6.1 + Release 5.7.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

                  We now test against, and package with, Python 3.10.8.

                  +

                  We now test against, and package with, Python 3.11.1.

                  As a consequence of this we no longer support Windows 7.
                  +

                  Release 5.7.0

                  +

                  This release re-enables CAPI queries for Legacy players. As a result, the +'Update' button functionality is now restored for Legacy players, along with +"Automatically update on docking" functionality.

                  +
                    +
                  • +

                    We now test against, and package with, Python 3.11.1, 32-bit.

                    +
                  • +
                  • +

                    This release is functionally identical to 5.7.0-rc1, as no problems were +reported with that.

                    +
                  • +
                  • +

                    As noted above, Legacy players now have CAPI functionality once more. +Plugin developers check below for how you can determine the source galaxy +of such data.

                    +
                  • +
                  • +

                    Due to a bug it turned out that a workaround for "old browsers don't support +very long URLs" had been inactive since late 2019. As no-one has noticed +or complained we've now removed the defunct code in favour of the simple +webbrowser.open(<url>).

                    +

                    Testing showed that all of Firefox, Chrome and Chrome-based Edge worked with +very long URLs without issues.

                    +
                  • +
                  • +

                    EDMC.exe -n had been broken for a while, it now functions once more.

                    +
                  • +
                  • +

                    Some output related to detecting and parsing gameversion from Journals +has been moved from INFO to DEBUG. This returns the output of any EDMC.exe +command to the former, quieter, version.

                    +
                  • +
                  +

                  Bugs

                  +
                    +
                  • +

                    A corner case of "game not running" and "user presses 'Update' button" would +result in an empty uploaderID string being sent to EDDN. Such messages are +still accepted by the EDDN Gateway, and the Relay then obfuscates this field +anyway. So, at worse, this would make it look like the same uploader was in +lots of different places. This has been fixed.

                    +
                  • +
                  • +

                    The message about converting legacy replay.jsonl was being emitted even +when there was no file to convert. This has been fixed.

                    +
                  • +
                  +

                  Plugin Developers

                  +
                    +
                  • +

                    An erroneous statement about "all of Python stdlib" in PLUGINS.md has been +corrected. We don't/can't easily include all of this. Ask if any part of it +you require is missing.

                    +
                  • +
                  • +

                    In order to not pass Legacy data to plugins without them being aware of it +there is now a new function cmdr_data_legacy(), which mirrors the +functionality of cmdr_data(), but for Legacy data only. See PLUGINS.md +for more details.

                    +
                  • +
                  • +

                    The data passed to cmdr_data() and cmdr_data_legacy() is now correctly +typed as CAPIData. This is a sub-class of UserDict, so you can continue +to use it as such. However, it also has one extra property, source_host, +which can be used to determine if the data was from the Live or Legacy +CAPI endpoint host. See PLUGINS.md for more details.

                    +
                  • +
                  • +

                    If any plugin had been attempting to make use of config.get_int('theme'), +then be aware that we've finally moved from hard-coded values to actual +defined constants. Example use would be as in:

                    +
                    from config import config
                    +from theme import theme
                    +
                    +active_theme = config.get_int('theme')
                    +if active_theme == theme.THEME_DARK:
                    +    ...
                    +elif active_theme == theme.THEME_TRANSPARENT:
                    +    ...
                    +elif active_theme == theme.THEME_DEFAULT:
                    +    ...
                    +else:
                    +    ...
                    +

                    But remember that all tkinter widgets in plugins will inherit the main UI +current theme colours anyway.

                    +
                  • +
                  • +

                    The contents of NavRoute.json will now be loaded during 'catch-up' when +EDMarketConnector is (re-)started. The synthetic StartUp (note the +capitalisation) event that is emitted after the catch-up ends will have +state['NavRoute'] containing this data.

                    +

                    However, the Fileheader event from detecting a subsequent new Journal file +will blank this data again. Thus, if you're interested in "last plotted +route" on startup you should react to the StartUp event. Also, note that +the contents will indicate a NavRouteClear if that was the last such +event.

                    +

                    PLUGINS.md has been updated to reflect this.

                    +
                  • +
                  • +

                    If you've ever been in the habit of running our develop branch, please +don't. Whilst we try to ensure that any code merged into this branch doesn't +contain bugs, it hasn't at that point undergone more thorough testing. +Please use the stable branch unless otherwise directed.

                    +
                  • +
                  • +

                    Some small updates have been made in edmc_data as a part of reviewing the +latest update to coriolis-data. +We make no guarantee about keeping these parts of edmc_data up to date. +Any plugins attempting to use that data should look at alternatives, such +as FDevIDs/outfitting.csv.

                    +

                    A future update might remove those maps, or at least fully deprecate their +use by plugins. Please contact us now if you actually make use of this +data.

                    +
                  • +
                  +
                  + +

                  Release 5.6.1

                  This release addresses some minor bugs and annoyances with v5.6.0, especially for Legacy galaxy players.

                  @@ -1661,11 +1780,11 @@ about this: ]]>
                  From 17f305efc9343092986499b7e765aa7d1b3a6aa3 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 20 Jan 2023 12:27:22 +0000 Subject: [PATCH 041/261] Release 5.8.0: appcast update --- edmarketconnector.xml | 239 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 235 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index b9a74e852..c0238dbed 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.7.0 + Release 5.8.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -44,6 +44,237 @@

                  We now test against, and package with, Python 3.11.1.

                  As a consequence of this we no longer support Windows 7.
                  +

                  Release 5.8.0

                  +

                  This release is essentially the same as 5.8.0-rc3 with only the version and +this changelog updated.

                  +

                  It brings a new feature related to Fleetcarrier data, some convenience for +Linux users, some fixes, and otherwise some internal changes that should not +adversely affect either users or third-party plugins. For the latter, read +below for some new/changed things that could benefit you.

                  +
                    +
                  • +

                    This release, and all future ones, now create two additional archive files +in the GitHub release:

                    +
                      +
                    1. EDMarketConnector-release-<version>.zip
                    2. +
                    3. EDMarketConnector-release-<version>.tar.gz
                    4. +
                    +

                    The advantage of these over the GitHub auto-generated ones is that they +have been hand-crafted to contain all the necessary files, and only +those files.

                    +

                    If you use the application from source, and not via a git clone, then we +highly recommend you use one of these archives, not the GitHub +auto-generated ones.

                    +

                    Anyone installing on Windows should continue to use the +EDMarketConnector_win_<version>.msi files as before.

                    +
                  • +
                  • +

                    New Feature - You can now have the application query the /fleetcarrier +CAPI endpoint for data about your Fleet Carrier. The data will then be +passed to interested plugins.

                    +

                    Note that there are some caveats:

                    +
                      +
                    1. +

                      This feature defaults to Off. The option is on the Configuration tab +of Settings as "Enable Fleetcarrier CAPI Queries". It is advised to only +enable this if you know you utilise plugins that make use of the data.

                      +
                    2. +
                    3. +

                      These queries are only triggered by CarrierBuy and CarrierStats +Journal events, i.e. upon buying a Fleetcarrier or opening the Carrier +Management UI in-game. NB: There is a 15 minute cooldown between +queries.

                      +
                    4. +
                    5. +

                      If you have Fleetcarrier cargo which got into the cargo hold through a lot +of individual transactions, or if you have a lot of separate buy/sell +orders then these queries can take a long time to complete.

                      +

                      If this happens with your game account then all other CAPI queries will +be blocked until the /fleetcarrier query completes. 'Other CAPI +queries' means those usually triggered upon docking to gather station +market, shipyard and outfitting data. To ameliorate the effects of this +there is currently a timeout of 60 seconds on /fleetcarrier queries, +and will not occur more often than every 15 minutes.

                      +

                      We plan to address this by moving the /fleetcarrier queries into their +own separate thread in the future.

                      +
                    6. +
                    +
                  • +
                  • +

                    The code for choosing the 'Output' folder is now simply the tkinter +function for such a dialogue, rather than a special case on Windows. In +the past the former had issues with Unicode characters, but in testing no +such issue was observed (on a supported OS).

                    +
                  • +
                  • +

                    There are two new items on the "Help" menu:

                    +
                      +
                    1. Troubleshooting -> Wiki:Troubleshooting +
                    2. +
                    3. Report A Bug -> Issues - New Bug Report +
                    4. +
                    +
                  • +
                  • +

                    Translations have been updated. Thanks again to our volunteer translators!

                    +
                  • +
                  • +

                    If we ever activate any functionality killswitches, the popup denoting which +are active has been made more readable.

                    +
                  • +
                  • +

                    There's a new section in Contributing.md - "Python Environment". This +should aid any new developers in getting things set up.

                    +
                  • +
                  +

                  Linux Users

                  +

                  We now ship an io.edcd.EDMarketConnector.desktop file. To make use of this +you should run scripts/linux-setup.sh once. This will:

                  +
                    +
                  1. +

                    Check that you have $HOME/bin in your PATH. If not, it will abort.

                    +
                  2. +
                  3. +

                    Create a shell script edmarketconnector in $HOME/bin to launch the +application.

                    +

                    NB: This relies on the filesystem location you placed the source in not +changing. So if you move the source you will need to re-run the script.

                    +
                  4. +
                  5. +

                    Copy the .desktop and .icon files into appropriate locations. The .desktop +file utilises the shell script created in step 2, and thus relies on it +existing and on it being in a directory that is in your PATH.

                    +
                  6. +
                  +

                  Once this has been completed any XDG-compliant desktops should have an entry +for "E:D Market Connector" in their "Games" menu.

                  +

                  Fixes

                  +
                    +
                  • +

                    The tracking of a Cmdr's location that was being performed by the core EDDN +plugin has been moved into the Journal monitoring code. This results in +the tracking being correct upon application (re)start, reflecting the state +from the latest Journal file, rather than only picking up with any +subsequent new Journal events.

                    +

                    This change should remove instances of "Wrong System! Missed Jump ?" and +similar sanity-check "errors" when continuing to play after a user restarts +the application whilst the game is running.

                    +

                    Plugin developers, see below for how this change can positively affect you.

                    +
                  • +
                  • +

                    The name of the files written by "File" > "Save Raw Data" now have a . +between the system and station names.

                    +
                  • +
                  • +

                    Use of CAPI data in EDMC.exe when invoked with either -s or -n +arguments hadn't been updated for prior changes, causing such invocations to +fail. This has been fixed.

                    +
                  • +
                  +

                  Plugin Developers

                  +
                    +
                  • +

                    Each plugin is now handed its own sub-frame as the parent parameter passed +to plugin_app() instead of the actual main UI frame. These new Frames +are placed in the position that plugin UI would have gone into. This should +have no side effects on well-behaved plugins.

                    +

                    However, if you have code that attempts to do things like parent.children() +or the like in your plugin_app() implementation, this might have stopped +working. You shouldn't be trying to do anything with any of the UI outside +your plugin anyway, but if you definitely have a need then look things up +using .nametowidget(). There are examples in the core plugins (which DO +have good reason, due to maintaining main UI label values).

                    +

                    All of the plugins listed on our Wiki were given perfunctory testing and no +issues from this change were observed.

                    +

                    This is a necessary first step to some pending plugin/UI work:

                    + +
                  • +
                  • +

                    New - capi_fleetcarrier() function to receive the data from a CAPI +/fleetcarrier query. See PLUGINS.md for details.

                    +
                  • +
                  • +

                    It was found that the ShutDown event (note the capitalisation, this is +distinct from the actual Journal Shutdown event) synthesized for plugins +when it is detected that the game has exited was never actually being +delivered. Instead this was erroneously replaced with a synthesized StartUp +event. This has been fixed.

                    +
                  • +
                  • +

                    As the location tracking has been moved out of the core EDDN plugin, and into +monitor.py all of it is now available as members of the state dictionary +which is passed to journal_entry().

                    +

                    This both means that no plugin should need to perform such location state +tracking itself and they can take advantage of it being fully up to date +when a user restarts the application with the game running.

                    +

                    A reminder: When performing 'catch up' on the newest Journal file found at +startup, the application does not pass any events to the +journal_entry() method in plugins. This is to avoid spamming with +data/state that has possibly already been handled, and in the case of the +Cmdr moving around will end up not being relevant by the time the end of the +file is reached. This limitation was also why the core EDDN plugin couldn't +properly initiate its location tracking state in this scenario.

                    +

                    See PLUGINS.md for details of the new state members. Pay particular +attention to the footnote that details the caveats around Body tracking.

                    +

                    Careful testing has been done for only the following. So, if you make use +of any of the other new state values and spot a bug, please report it:

                    +
                      +
                    1. SystemName
                    2. +
                    3. SystemAddress
                    4. +
                    5. Body (Name)
                    6. +
                    7. BodyID
                    8. +
                    9. BodyType
                    10. +
                    11. StationName
                    12. +
                    13. StationType
                    14. +
                    15. (Station) MarketID
                    16. +
                    +
                  • +
                  • +

                    There is an additional property request_cmdr on CAPIData objects, which +records the name of the Cmdr the request was made for.

                    +
                  • +
                  • +

                    FDevIDs files are their latest versions at time of this version's build.

                    +
                  • +
                  • +

                    examples\plugintest - dropped the "pre-5.0.0 config" code, as it's long +since irrelevant.

                    +
                  • +
                  +

                  Developers

                  +
                    +
                  • +

                    If you utilise a git clone of the source code, you should also ensure the +sub-modules are initialised and synchronised. +wiki:Running from source +has been updated to include the necessary commands.

                    +
                  • +
                  • +

                    The coriolis-data git sub-module now uses an HTTPS, not "git" URL, so won't +require authentication for a simple git pull.

                    +
                  • +
                  • +

                    If you have a dump directory in CWD when running EDMarketConnector.py under +a debugger you will get files in that location when CAPI queries complete. +This will now include files with names of the form +FleetCarrier.<callsign>.<timstamp>.json for /fleetcarrier data.

                    +
                  • +
                  • +

                    All the main UI tk widgets are now properly named. This might make things +easier if debugging UI widgets as you'll no longer see a bunch of !label1, +!frame1 and the like.

                    +

                    Each plugin's separator is named as per the scheme plugin_hr_<X>, and when +a plugin has UI its new container Frame is named plugin_X. Both of these +start with 1, not 0.

                    +
                  • +
                  +
                  + +

                  Release 5.7.0

                  This release re-enables CAPI queries for Legacy players. As a result, the 'Update' button functionality is now restored for Legacy players, along with @@ -1780,11 +2011,11 @@ about this: ]]> From 2a78a970f93bed0ef10e3478db296802ab2ce844 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 22 Jan 2023 13:21:04 +0000 Subject: [PATCH 042/261] Release 5.8.1: appcast --- edmarketconnector.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index c0238dbed..fa824558b 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.8.0 + Release 5.8.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -44,6 +44,10 @@

                  We now test against, and package with, Python 3.11.1.

                  As a consequence of this we no longer support Windows 7.
                  +

                  Release 5.8.1

                  +

                  This fixes a bug where the Cmdr/APIKey sections on Settings > EDSM would never be shown.

                  +
                  +

                  Release 5.8.0

                  This release is essentially the same as 5.8.0-rc3 with only the version and this changelog updated.

                  From cf1a103248658fd348974b7d1acccc1744a6d282 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 22 Jan 2023 13:27:27 +0000 Subject: [PATCH 043/261] Release 5.8.1: Change the enclosure as well --- edmarketconnector.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index fa824558b..a7046e497 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -2015,11 +2015,11 @@ about this: ]]>
                  From 9228c5a47843b21db8e97726e8b95bc30ccadef3 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 26 Jul 2023 17:08:07 -0400 Subject: [PATCH 044/261] Release 5.9.0 Change the enclosure --- edmarketconnector.xml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index a7046e497..f6562f977 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.8.1 + Release 5.9.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -44,6 +44,19 @@

                  We now test against, and package with, Python 3.11.1.

                  As a consequence of this we no longer support Windows 7.
                  +

                  Release 5.9.0

                  +

                  This release is essentially the same as 5.9.0-rc1 with only a typo, the version and +this changelog updated.

                  +

                  This release contains the removal of the EDDB module, as well as a few under-the-hood +updates.

                  +
                    +
                  • Removes the EDDB plugin due to EDDB shutting down.
                  • +
                  • Unsets EDDB as the default handler for certain URL preferences.
                  • +
                  • Updates the FDevIDs to latest versions.
                  • +
                  • Removes EDDB references from help string documentations.
                  • +
                  • Updated a number of dependencies to their latest working versions
                  • +

                  +

                  Release 5.8.1

                  This fixes a bug where the Cmdr/APIKey sections on Settings > EDSM would never be shown.


                  @@ -2015,11 +2028,11 @@ about this: ]]>
                  From e9875d8df3f2b4a04073f0069ce48d531274e7ac Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 2 Aug 2023 15:58:41 -0400 Subject: [PATCH 045/261] Update edmarketconnector.xml --- edmarketconnector.xml | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index f6562f977..efe5b27bb 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.9.0 + Release 5.9.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -44,6 +44,21 @@

                  We now test against, and package with, Python 3.11.1.

                  As a consequence of this we no longer support Windows 7.
                  +

                  Release 5.9.1

                  +

                  This release updates the build system in use for EDMC to a more feature-rich installer, as well +as updating the commodity information to be up-to-date for Update 16.

                  +

                  NOTE: This version hands over the installer to an EXE file for Windows instead of an MSI. +This does not change any functionality or plugin capability of EDMC. You may need to +manually close EDMC during the update process if updating from version 5.9.0 or earlier.

                  +
                    +
                  • Removed the old WiX Build System
                  • +
                  • Handed over the Build system to Inno Setup
                  • +
                  • Broke apart the Build and Installer scripts for ease of development
                  • +
                  • Updated FDevIDs to latest version
                  • +
                  • Updated coriolis-data to latest version
                  • +
                  • Updated some internal documentation
                  • +

                  +

                  Release 5.9.0

                  This release is essentially the same as 5.9.0-rc1 with only a typo, the version and this changelog updated.

                  @@ -2028,12 +2043,11 @@ about this: ]]>
                  From 8dea3e3c45667ed1487e61dfc22f89b50b94b1ce Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 3 Aug 2023 19:16:19 -0400 Subject: [PATCH 046/261] Release 5.9.2 Update the XML --- ChangeLog.md | 2 +- edmarketconnector.xml | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 351ab33dd..04f9a6db8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -41,7 +41,7 @@ Windows registry to allow for protocol handling. All users are **strongly** enco - Fixes a critical bug with the installer on new installs not creating registry keys (#2046) - Re-enables automatic submodule updates (#1443) - Help -> About Version String can now be copied to clipboard (#1936) -- EDSM Task Manager Printout now is less useless (#2045) +- EDMC Task Manager Printout now is less useless (#2045) - Deprecated load_module() is now retired (#1462) - API Keys are masked in Settings (#2047) - Installer will now refuse to install on Win7 and Earlier (#1122) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index efe5b27bb..c5b1fa918 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.9.1 + Release 5.9.2 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -44,6 +44,20 @@

                  We now test against, and package with, Python 3.11.1.

                  As a consequence of this we no longer support Windows 7.
                  +

                  Release 5.9.2

                  +

                  This release fixes a critical issue on clean installs which would not update +the Windows registry to allow for protocol handling.

                  +

                  All users are strongly encouraged to update.

                  +
                    +
                  • Fixes a critical bug with the installer on new installs not creating registry keys (#2046)
                  • +
                  • Re-enables automatic submodule updates (#1443)
                  • +
                  • Help -> About Version String can now be copied to clipboard (#1936)
                  • +
                  • EDMC Task Manager Printout now is less useless (#2045)
                  • +
                  • Deprecated load_module() is now retired (#1462)
                  • +
                  • API Keys are masked in Settings (#2047)
                  • +
                  • Installer will now refuse to install on Win7 and Earlier (#1122)
                  • +

                  +

                  Release 5.9.1

                  This release updates the build system in use for EDMC to a more feature-rich installer, as well as updating the commodity information to be up-to-date for Update 16.

                  @@ -2043,10 +2057,10 @@ about this: ]]>
                  From 826ad27e68f89508f6aa5c0bad75821dc0b640a6 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 3 Aug 2023 22:47:13 -0400 Subject: [PATCH 047/261] Publish Reversion of Bad Change to XML --- ChangeLog.md | 2 +- edmarketconnector.xml | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0896a5828..05f7d9f0b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -35,7 +35,7 @@ produce the Windows executables and installer. --- Release 5.9.3 === -This release is identical to 5.9.3, except reverts a bad change. +This release is identical to 5.9.2, except reverts a bad change. - REVERTS Deprecated load_module() is now retired (#1462) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index c5b1fa918..27182b2a6 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -36,7 +36,7 @@ - Release 5.9.2 + Release 5.9.3 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } @@ -44,6 +44,12 @@

                  We now test against, and package with, Python 3.11.1.

                  As a consequence of this we no longer support Windows 7.
                  +

                  Release 5.9.3

                  +

                  This release is identical to 5.9.2, except reverts a bad change.

                  +
                    +
                  • REVERTS Deprecated load_module() is now retired (#1462)
                  • +

                  +

                  Release 5.9.2

                  This release fixes a critical issue on clean installs which would not update the Windows registry to allow for protocol handling.

                  @@ -2057,10 +2063,10 @@ about this: ]]>
                  From e62e4f35d0cd936e71d0e32f8138dbce80fdc657 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 8 Aug 2023 15:14:04 -0400 Subject: [PATCH 048/261] Release/5.9.4 XML --- edmarketconnector.xml | 77 ++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 27182b2a6..0b6398350 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -1,17 +1,13 @@ - - - - E:D Market Connector - https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml - Most recent changes with links to updates. - - - - - Release 3.44 - - h2 { font-size: 105%; } + + + + E:D Market Connector + https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml + Most recent changes with links to updates. + + + Release 3.44 + h2 { font-size: 105%; }

                  Release 3.44

                  CHANGE OF MAINTAINER

                  Due to a lack of time to give the project the attention it needs Marginal has handed over ownership of the EDMarketConnector GitHub repository to the EDCD (Elite Dangerous Community Developers) organisation.

                  @@ -21,29 +17,25 @@
                  • Version increased to 3.4.4.0 / 3.44.
                  • URL the application checks for updates changed to point to github,
                  • -
                  - ]]> -
                  - -
                  - - - - - Release 5.9.3 - - body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } +
                ]]>
                + +
                + + + Release 5.9.4 + body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }

                We now test against, and package with, Python 3.11.1.

                As a consequence of this we no longer support Windows 7.
                +

                Release 5.9.4

                +

                This release fixes a widely-reported bug that resulted in the cAPI Authentication flow being disrupted for a subset of users. Thank you to all the CMDRs who reported this to us and provided logs to us so that we could get the issue isolated. +

                +
                  +
                • Fixes a missing registry issue that could cause the EDMC:// protocol to fail. (#2061, #2059, #2058, #2057)
                • +
                • Renames the default start menu shortcut to be more clear. (#2062)
                • +

                +

                Release 5.9.3

                This release is identical to 5.9.2, except reverts a bad change.

                - - - ]]> -
                - -
                - - +
              ]]>
              + +
              + From 6560b5f0e0fbb55426b783382c3d50b1f71bfb9a Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 22 Aug 2023 17:13:28 -0400 Subject: [PATCH 049/261] 5.9.5 XML --- edmarketconnector.xml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 0b6398350..f98f82773 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,15 +22,22 @@ - Release 5.9.4 + Release 5.9.5 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }

              We now test against, and package with, Python 3.11.1.

              As a consequence of this we no longer support Windows 7.
              +

              Release 5.9.5

              +

              This release fixes an uncommon problem with the uninstaller logic if upgrading from a version prior to 5.9.0 to improve consistancy across versions.

              +Note to plugin developers: modules.p and ships.p will be deprecated in the next version, and slated for removal in the next major release! Please look for that change coming soon. +
                +
              • Updates Module pickle files to latest values.
              • +
              • Fixes a problem with the uninstaller logic caused by prior versions having fluctuating GUIDs.
              • +

              +

              Release 5.9.4

              -

              This release fixes a widely-reported bug that resulted in the cAPI Authentication flow being disrupted for a subset of users. Thank you to all the CMDRs who reported this to us and provided logs to us so that we could get the issue isolated. -

              +

              This release fixes a widely-reported bug that resulted in the cAPI Authentication flow being disrupted for a subset of users. Thank you to all the CMDRs who reported this to us and provided logs to us so that we could get the issue isolated.

              ]]>
              - +
              From 9083e70301b435dd02fcf832fea30ecdbce784cc Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 20 Dec 2023 22:57:57 -0500 Subject: [PATCH 050/261] Update edmarketconnector.xml --- edmarketconnector.xml | 50 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index f98f82773..f1e072853 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,12 +22,60 @@ - Release 5.9.5 + Release 5.10.0 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }

              We now test against, and package with, Python 3.11.1.

              As a consequence of this we no longer support Windows 7.
              +

              Release 5.10.0

              +

              This release contains a number of under-the-hood changes to EDMC designed to improve performance, code maintainability, and stability of the EDMC application, while providing new features and quality-of-life fixes.

              +Note to plugin developers: modules.p and ships.p are deprecated, and slated for removal in the next major release! Please look for that change coming soon. + +

              Changes and Enhancements

              +
                +
              • Added new modules.json and ships.json files to improve security and readability
              • +
              • Added a core Spanch URL provider plugin
              • +
              • Added a new auth response page for successful FDEV authentication
              • +
              • Added a new Open Log Folder option to the Help menu
              • +
              • Added a new --start_min command flag to force the application to start minimized
              • +
              • Added a new pop-up if plugins fail to load or are not supported
              • +
              • Updated commodities and module files to the latest versions
              • +
              • Updated core EDMC and core Plugin menus to a standardized layout
              • +
              • Updates the Inara URL formats to the new endpoints
              • +
              +

              Bug Fixes

              +
                +
              • Fixed an issue where indentation of text strings in certain settings windows under various languages +would be unevenly indented
              • +
              • Fixed an issue where the Plugins Folder label in the Plugins settings window would cut off the +selection box for the plugin storage location
              • +
              +

              Code Clean Up

              +
                +
              • Added future annotation imports to help with code compatibility
              • +
              • Added a few conditional checks on input processing
              • +
              • Simplified some RegEx expressions, complex functions, logic flows, and Import statements
              • +
              • Simplified the WinSparkle GitHub Build Action
              • +
              • Began to change single-character variables to more descriptive names
              • +
              • Moved a number of global variables into their requisite classes
              • +
              • Updated a number of dependencies to the latest versions
              • +
              • Updated GitHub Actions to the latest versions
              • +
              • Updated a number of resource-allocating functions to use more efficient closing logic
              • +
              • Updated some calls to arrays to be more efficient
              • +
              • Removed a number of old-style typing hints in favor of PEP 585 style hints
              • +
              • Removed a number of redundant if - return - else or raise - else statements for code readability
              • +
              • Removed some default parameter assignments
              • +
              • Removed some obsolete calls to Object
              • +
              +

              Plugin Developers

              +
                +
              • modules.p and ships.p have been deprecated, and will be removed in 6.0. +If you are using these files, please update to use the new modules.json and ships.json files instead.
              • +
              • A new method of standardizing the paddings used in settings panels has been applied to the core settings panels. +We strongly encourage you to follow these style hints! A proper guide will be added to the wiki.
              • +

              +

              Release 5.9.5

              This release fixes an uncommon problem with the uninstaller logic if upgrading from a version prior to 5.9.0 to improve consistancy across versions.

              Note to plugin developers: modules.p and ships.p will be deprecated in the next version, and slated for removal in the next major release! Please look for that change coming soon. From bd3d96c4ea7272fe3991956911de9585af3c7301 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 20 Dec 2023 23:00:42 -0500 Subject: [PATCH 051/261] Update edmarketconnector.xml --- edmarketconnector.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index f1e072853..600aa551d 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -2105,7 +2105,7 @@ about this: PTS CAPI saying Commander is Docked after jumping to new system.

            ]]>
            - +
            From 4f31724c6e29217132d87acb9ef2c441370bdefe Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 21 Dec 2023 02:19:38 -0500 Subject: [PATCH 052/261] Fix Spelling Mistake --- ChangeLog.md | 2 +- edmarketconnector.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index df37c83a5..ee4f7c562 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -43,7 +43,7 @@ for removal in the next major release! Please look for that change coming soon. **Changes and Enhancements** * Added new `modules.json` and `ships.json` files to improve security and readability -* Added a core Spanch URL provider plugin +* Added a core Spansh URL provider plugin * Added a new auth response page for successful FDEV authentication * Added a new Open Log Folder option to the Help menu * Added a new `--start_min` command flag to force the application to start minimized diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 600aa551d..f5ba4e2e1 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -35,7 +35,7 @@

            Changes and Enhancements

            • Added new modules.json and ships.json files to improve security and readability
            • -
            • Added a core Spanch URL provider plugin
            • +
            • Added a core Spansh URL provider plugin
            • Added a new auth response page for successful FDEV authentication
            • Added a new Open Log Folder option to the Help menu
            • Added a new --start_min command flag to force the application to start minimized
            • From 5882cad69f4ea8fcd400152497bd820d80e217b3 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 31 Dec 2023 20:39:48 -0500 Subject: [PATCH 053/261] [2127] Remove VisualManifest --- EDMarketConnector.VisualElementsManifest.xml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 EDMarketConnector.VisualElementsManifest.xml diff --git a/EDMarketConnector.VisualElementsManifest.xml b/EDMarketConnector.VisualElementsManifest.xml deleted file mode 100644 index f0088658f..000000000 --- a/EDMarketConnector.VisualElementsManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - From e8cf33bdc9ff78ee2ca0fcbeb689bad546f9e836 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 31 Dec 2023 20:42:04 -0500 Subject: [PATCH 054/261] [2127] Remove Obsolete icns file --- EDMarketConnector.icns | Bin 116913 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 EDMarketConnector.icns diff --git a/EDMarketConnector.icns b/EDMarketConnector.icns deleted file mode 100644 index bdc874234fe58976ab81463d688a44a287153b5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116913 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�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 fO}Eb6 Date: Sun, 31 Dec 2023 20:49:11 -0500 Subject: [PATCH 055/261] [2127] Move Icon File This is basically the GIMP version of a photoshop file. Move this to resources for order sake. Not bundled in code. --- .../EDMarketConnector.xcf | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename EDMarketConnector.xcf => resources/EDMarketConnector.xcf (100%) diff --git a/EDMarketConnector.xcf b/resources/EDMarketConnector.xcf similarity index 100% rename from EDMarketConnector.xcf rename to resources/EDMarketConnector.xcf From a8b18e8ca4977d1f91bdf60955d64c4c63a6eb93 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 1 Jan 2024 09:33:18 -0500 Subject: [PATCH 056/261] [2127] Remove XML from Build --- build.py | 1 - 1 file changed, 1 deletion(-) diff --git a/build.py b/build.py index e1d37f49f..ad5a96432 100644 --- a/build.py +++ b/build.py @@ -80,7 +80,6 @@ def generate_data_files( "resources/modules.json", "resources/ships.json", "ships.p", # TODO: Remove in 6.0 - f"{app_name}.VisualElementsManifest.xml", f"{app_name}.ico", "EDMarketConnector - TRACE.bat", "EDMarketConnector - localserver-auth.bat", From 1d571fe288a08896f8840a6ec9ad266204f2920e Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 3 Jan 2024 20:04:33 -0500 Subject: [PATCH 057/261] Update edmarketconnector.xml --- edmarketconnector.xml | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index f5ba4e2e1..b3e5f1976 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,12 +22,45 @@ - Release 5.10.0 + Release 5.10.1 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

              We now test against, and package with, Python 3.11.1.

              +

              We now test against, and package with, Python 3.11.7.

              As a consequence of this we no longer support Windows 7.
              +

              Release 5.10.1

              +

              This release contains a number of bugfixes, minor performance enhancements, workflow and dependency updates, and a function deprecation.

              +Note to plugin developers: modules.p and ships.p are deprecated, and slated for removal in the next major release! Please look for that change coming soon. +Note to plugin developers: The openurl() function in ttkHyperlinkLabel has been deprecated, and slated for removal in the next major release! Please migrate to webbrowser.open(). + +

              Changes and Enhancements

              +
                +
              • Deprecated `openurl()`. Please migrate to `webbrowser.open()`
              • +
              • Updated a number of list comparisons to use more efficient tuple comparisons
              • +
              • Updated a few type hints
              • +
              • Updated a few binary comparitors to be more efficient
              • +
              • Moved `resources.json` and `modules.json` back to the top level for all users
              • +
              • Updated several dependencies
              • +
              • Updated Python version to 3.11.7
              • +
              +

              Bug Fixes

              +
                +
              • Fixed an issue where resources files could be in different locations for different users.
                  +
                • These files are now in the same location (top level) for all users on all distributions.
                • +
                +
              • +
              • Fixed an issue where CMDRs without the Git application installed would crash on start if running from Source.
                  +
                • Thanks to the Flatpak team for pointing this one out!
                • +
                +
              • +
              • Fixed a bug where CMDRs running from source would have their git hash version displayed as UNKNOWN.
                  +
                • We're now more failure tolerant and use the bundled .gitversion if no true git hash is provided.
                • +
                +
              • +
              • Fixed a bug where starting two copies of EDMC with a valid install would not generate a duplicate warning.
              • +
              +
              +

              Release 5.10.0

              This release contains a number of under-the-hood changes to EDMC designed to improve performance, code maintainability, and stability of the EDMC application, while providing new features and quality-of-life fixes.

              Note to plugin developers: modules.p and ships.p are deprecated, and slated for removal in the next major release! Please look for that change coming soon. @@ -2105,7 +2138,7 @@ about this: PTS CAPI saying Commander is Docked after jumping to new system.

            ]]> - + From 7cac00b2e81d619988286c3d745c076d27dce7c1 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 3 Jan 2024 23:55:12 -0500 Subject: [PATCH 058/261] [635] Remove Ordered Dict --- companion.py | 4 +- coriolis-update-files.py | 5 +- edmc_data.py | 16 ++-- l10n.py | 7 +- monitor.py | 12 +-- outfitting.py | 4 +- plugins/eddn.py | 102 +++++++++++---------- plugins/inara.py | 189 ++++++++++++++++++++------------------- 8 files changed, 172 insertions(+), 167 deletions(-) diff --git a/companion.py b/companion.py index 217370d51..4a8277691 100644 --- a/companion.py +++ b/companion.py @@ -28,7 +28,7 @@ import webbrowser from email.utils import parsedate from queue import Queue -from typing import TYPE_CHECKING, Any, Mapping, OrderedDict, TypeVar +from typing import TYPE_CHECKING, Any, Mapping, TypeVar import requests import config as conf_module import killswitch @@ -1328,7 +1328,7 @@ def index_possibly_sparse_list(data: Mapping[str, V] | list[V], key: int) -> V: if isinstance(data, list): return data[key] - if isinstance(data, (dict, OrderedDict)): + if isinstance(data, (dict, dict)): return data[str(key)] raise ValueError(f'Unexpected data type {type(data)}') diff --git a/coriolis-update-files.py b/coriolis-update-files.py index 9c1d7ecc7..f31a2e326 100755 --- a/coriolis-update-files.py +++ b/coriolis-update-files.py @@ -16,7 +16,6 @@ import json import subprocess import sys -from collections import OrderedDict import outfitting from edmc_data import coriolis_ship_map, ship_name_map @@ -56,7 +55,7 @@ def add(modules, name, attributes) -> None: for i, bulkhead in enumerate(bulkheads): modules['_'.join([reverse_ship_map[name], 'armour', bulkhead])] = {'mass': m['bulkheads'][i]['mass']} - ships = OrderedDict([(k, ships[k]) for k in sorted(ships)]) # sort for easier diffing + ships = {k: ships[k] for k in sorted(ships)} with open("ships.json", "w") as ships_file: json.dump(ships, ships_file, indent=4) @@ -91,6 +90,6 @@ def add(modules, name, attributes) -> None: add(modules, 'hpt_multicannon_fixed_small_advanced', {'mass': 2}) add(modules, 'hpt_multicannon_fixed_medium_advanced', {'mass': 4}) - modules = OrderedDict([(k, modules[k]) for k in sorted(modules)]) # sort for easier diffing + modules = {k: modules[k] for k in sorted(modules)} with open("modules.json", "w") as modules_file: json.dump(modules, modules_file, indent=4) diff --git a/edmc_data.py b/edmc_data.py index c628ae8d9..7be760967 100644 --- a/edmc_data.py +++ b/edmc_data.py @@ -4,7 +4,6 @@ For easy reference any variable should be prefixed with the name of the file it was either in originally, or where the primary code utilising it is. """ -from collections import OrderedDict # Map numeric 'demand/supply brackets' to the names as shown in-game. commodity_bracketmap = { @@ -57,13 +56,14 @@ # Map API module names to in-game names -outfitting_armour_map = OrderedDict([ - ('grade1', 'Lightweight Alloy'), - ('grade2', 'Reinforced Alloy'), - ('grade3', 'Military Grade Composite'), - ('mirrored', 'Mirrored Surface Composite'), - ('reactive', 'Reactive Surface Composite'), -]) +outfitting_armour_map = { + 'grade1': 'Lightweight Alloy', + 'grade2': 'Reinforced Alloy', + 'grade3': 'Military Grade Composite', + 'mirrored': 'Mirrored Surface Composite', + 'reactive': 'Reactive Surface Composite', +} + outfitting_weapon_map = { 'advancedtorppylon': 'Torpedo Pylon', diff --git a/l10n.py b/l10n.py index a34ea0dc0..a2b5185ae 100755 --- a/l10n.py +++ b/l10n.py @@ -16,7 +16,6 @@ import re import sys import warnings -from collections import OrderedDict from contextlib import suppress from os import pardir, listdir, sep, makedirs from os.path import basename, dirname, isdir, isfile, join, abspath, exists @@ -192,10 +191,10 @@ def available(self) -> set[str]: def available_names(self) -> dict[str | None, str]: """Available language names by code.""" - names: dict[str | None, str] = OrderedDict([ + names: dict[str | None, str] = { # LANG: The system default language choice in Settings > Appearance - (None, _('Default')), # Appearance theme and language setting - ]) + None: _('Default'), # Appearance theme and language setting + } names.update(sorted( [(lang, self.contents(lang).get(LANGUAGE_ID, lang)) for lang in self.available()] + [(_Translations.FALLBACK, _Translations.FALLBACK_NAME)], diff --git a/monitor.py b/monitor.py index 17a6fa165..a397dbf9d 100644 --- a/monitor.py +++ b/monitor.py @@ -14,7 +14,7 @@ import sys import threading from calendar import timegm -from collections import OrderedDict, defaultdict +from collections import defaultdict from os import SEEK_END, SEEK_SET, listdir from os.path import basename, expanduser, getctime, isdir, join from time import gmtime, localtime, mktime, sleep, strftime, strptime, time @@ -567,7 +567,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C try: # Preserve property order because why not? - entry: MutableMapping[str, Any] = json.loads(line, object_pairs_hook=OrderedDict) + entry: MutableMapping[str, Any] = json.loads(line) assert 'timestamp' in entry, "Timestamp does not exist in the entry" self.__navroute_retry() @@ -1042,7 +1042,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C rank[k] = (rank[k][0], min(v, 100)) elif event_type in ('reputation', 'statistics'): - payload = OrderedDict(entry) + payload = dict(entry) payload.pop('event') payload.pop('timestamp') # NB: We need the original casing for these keys @@ -1073,7 +1073,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # From 3.3 full Cargo event (after the first one) is written to a separate file if 'Inventory' not in entry: with open(join(self.currentdir, 'Cargo.json'), 'rb') as h: # type: ignore - entry = json.load(h, object_pairs_hook=OrderedDict) # Preserve property order because why not? + entry = json.load(h) # Preserve property order because why not? self.state['CargoJSON'] = entry clean = self.coalesce_cargo(entry['Inventory']) @@ -1108,7 +1108,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C attempts += 1 try: with open(shiplocker_filename, 'rb') as h: - entry = json.load(h, object_pairs_hook=OrderedDict) + entry = json.load(h) self.state['ShipLockerJSON'] = entry break @@ -2189,7 +2189,7 @@ def ship(self, timestamped=True) -> MutableMapping[str, Any] | None: 'PowerDistributor', 'Radar', 'FuelTank' ) - d: MutableMapping[str, Any] = OrderedDict() + d: MutableMapping[str, Any] = {} if timestamped: d['timestamp'] = strftime('%Y-%m-%dT%H:%M:%SZ', gmtime()) diff --git a/outfitting.py b/outfitting.py index 2bb47c6d0..7a343a8e4 100644 --- a/outfitting.py +++ b/outfitting.py @@ -8,8 +8,6 @@ from __future__ import annotations import json -from collections import OrderedDict -from typing import OrderedDict as OrderedDictT from config import config from edmc_data import ( outfitting_armour_map as armour_map, @@ -36,7 +34,7 @@ logger = get_main_logger() # Module mass, FSD data etc -moduledata: OrderedDictT = OrderedDict() +moduledata: dict = {} def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR001 diff --git a/plugins/eddn.py b/plugins/eddn.py index 687b39b7f..b488d8e90 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -29,7 +29,6 @@ import sqlite3 import sys import tkinter as tk -from collections import OrderedDict from platform import system from textwrap import dedent from threading import Lock @@ -40,7 +39,6 @@ Mapping, MutableMapping, ) -from typing import OrderedDict as OrderedDictT import requests import companion import edmc_data @@ -98,13 +96,13 @@ def __init__(self): # Avoid duplicates self.marketId: str | None = None - self.commodities: list[OrderedDictT[str, Any]] | None = None + self.commodities: list[dict[str, Any]] | None = None self.outfitting: tuple[bool, list[str]] | None = None self.shipyard: tuple[bool, list[Mapping[str, Any]]] | None = None self.fcmaterials_marketid: int = 0 - self.fcmaterials: list[OrderedDictT[str, Any]] | None = None + self.fcmaterials: list[dict[str, Any]] | None = None self.fcmaterials_capi_marketid: int = 0 - self.fcmaterials_capi: list[OrderedDictT[str, Any]] | None = None + self.fcmaterials_capi: list[dict[str, Any]] | None = None # For the tkinter parent window, so we can call update_idletasks() self.parent: tk.Tk @@ -651,21 +649,21 @@ def export_commodities(self, data: CAPIData, is_beta: bool) -> None: # noqa: CC modules, ships ) - commodities: list[OrderedDictT[str, Any]] = [] + commodities: list[dict[str, Any]] = [] for commodity in data['lastStarport'].get('commodities') or []: # Check 'marketable' and 'not prohibited' if (category_map.get(commodity['categoryname'], True) and not commodity.get('legality')): - commodities.append(OrderedDict([ - ('name', commodity['name'].lower()), - ('meanPrice', int(commodity['meanPrice'])), - ('buyPrice', int(commodity['buyPrice'])), - ('stock', int(commodity['stock'])), - ('stockBracket', commodity['stockBracket']), - ('sellPrice', int(commodity['sellPrice'])), - ('demand', int(commodity['demand'])), - ('demandBracket', commodity['demandBracket']), - ])) + commodities.append({ + 'name': commodity['name'].lower(), + 'meanPrice': int(commodity['meanPrice']), + 'buyPrice': int(commodity['buyPrice']), + 'stock': int(commodity['stock']), + 'stockBracket': commodity['stockBracket'], + 'sellPrice': int(commodity['sellPrice']), + 'demand': int(commodity['demand']), + 'demandBracket': commodity['demandBracket'], + }) if commodity['statusFlags']: commodities[-1]['statusFlags'] = commodity['statusFlags'] @@ -679,15 +677,15 @@ def export_commodities(self, data: CAPIData, is_beta: bool) -> None: # noqa: CC # none and that really does need to be recorded over EDDN so that # tools can update in a timely manner. if this.commodities != commodities: - message: OrderedDictT[str, Any] = OrderedDict([ - ('timestamp', data['timestamp']), - ('systemName', data['lastSystem']['name']), - ('stationName', data['lastStarport']['name']), - ('marketId', data['lastStarport']['id']), - ('commodities', commodities), - ('horizons', horizons), - ('odyssey', this.odyssey), - ]) + message: dict[str, Any] = { + 'timestamp': data['timestamp'], + 'systemName': data['lastSystem']['name'], + 'stationName': data['lastStarport']['name'], + 'marketId': data['lastStarport']['id'], + 'commodities': commodities, + 'horizons': horizons, + 'odyssey': this.odyssey, + } if 'economies' in data['lastStarport']: message['economies'] = sorted( @@ -802,7 +800,7 @@ def export_outfitting(self, data: CAPIData, is_beta: bool) -> None: if outfitting and this.outfitting != (horizons, outfitting): self.send_message(data['commander']['name'], { '$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}', - 'message': OrderedDict([ + 'message': { ('timestamp', data['timestamp']), ('systemName', data['lastSystem']['name']), ('stationName', data['lastStarport']['name']), @@ -810,7 +808,7 @@ def export_outfitting(self, data: CAPIData, is_beta: bool) -> None: ('horizons', horizons), ('modules', outfitting), ('odyssey', this.odyssey), - ]), + }, 'header': self.standard_header( game_version=self.capi_gameversion_from_host_endpoint( data.source_host, companion.Session.FRONTIER_CAPI_PATH_SHIPYARD @@ -864,7 +862,7 @@ def export_shipyard(self, data: CAPIData, is_beta: bool) -> None: if shipyard and this.shipyard != (horizons, shipyard): self.send_message(data['commander']['name'], { '$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}', - 'message': OrderedDict([ + 'message': { ('timestamp', data['timestamp']), ('systemName', data['lastSystem']['name']), ('stationName', data['lastStarport']['name']), @@ -872,7 +870,7 @@ def export_shipyard(self, data: CAPIData, is_beta: bool) -> None: ('horizons', horizons), ('ships', shipyard), ('odyssey', this.odyssey), - ]), + }, 'header': self.standard_header( game_version=self.capi_gameversion_from_host_endpoint( data.source_host, companion.Session.FRONTIER_CAPI_PATH_SHIPYARD @@ -898,16 +896,22 @@ def export_journal_commodities(self, cmdr: str, is_beta: bool, entry: Mapping[st :param entry: the journal entry containing the commodities data """ items: list[Mapping[str, Any]] = entry.get('Items') or [] - commodities: list[OrderedDictT[str, Any]] = sorted((OrderedDict([ - ('name', self.canonicalise(commodity['Name'])), - ('meanPrice', commodity['MeanPrice']), - ('buyPrice', commodity['BuyPrice']), - ('stock', commodity['Stock']), - ('stockBracket', commodity['StockBracket']), - ('sellPrice', commodity['SellPrice']), - ('demand', commodity['Demand']), - ('demandBracket', commodity['DemandBracket']), - ]) for commodity in items), key=lambda c: c['name']) + commodities: list[dict[str, Any]] = sorted( + ( + { + 'name': self.canonicalise(commodity['Name']), + 'meanPrice': commodity['MeanPrice'], + 'buyPrice': commodity['BuyPrice'], + 'stock': commodity['Stock'], + 'stockBracket': commodity['StockBracket'], + 'sellPrice': commodity['SellPrice'], + 'demand': commodity['Demand'], + 'demandBracket': commodity['DemandBracket'], + } + for commodity in items + ), + key=lambda c: c['name'] + ) # This used to have a check `commodities and ` at the start so as to # not send an empty commodities list, as the EDDN Schema doesn't allow @@ -918,7 +922,7 @@ def export_journal_commodities(self, cmdr: str, is_beta: bool, entry: Mapping[st if this.commodities != commodities: self.send_message(cmdr, { '$schemaRef': f'https://eddn.edcd.io/schemas/commodity/3{"/test" if is_beta else ""}', - 'message': OrderedDict([ + 'message': { ('timestamp', entry['timestamp']), ('systemName', entry['StarSystem']), ('stationName', entry['StationName']), @@ -926,7 +930,7 @@ def export_journal_commodities(self, cmdr: str, is_beta: bool, entry: Mapping[st ('commodities', commodities), ('horizons', this.horizons), ('odyssey', this.odyssey), - ]), + }, }) this.commodities = commodities @@ -957,7 +961,7 @@ def export_journal_outfitting(self, cmdr: str, is_beta: bool, entry: Mapping[str if outfitting and this.outfitting != (horizons, outfitting): self.send_message(cmdr, { '$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}', - 'message': OrderedDict([ + 'message': { ('timestamp', entry['timestamp']), ('systemName', entry['StarSystem']), ('stationName', entry['StationName']), @@ -965,7 +969,7 @@ def export_journal_outfitting(self, cmdr: str, is_beta: bool, entry: Mapping[str ('horizons', horizons), ('modules', outfitting), ('odyssey', entry['odyssey']) - ]), + }, }) this.outfitting = (horizons, outfitting) @@ -991,7 +995,7 @@ def export_journal_shipyard(self, cmdr: str, is_beta: bool, entry: Mapping[str, if shipyard and this.shipyard != (horizons, shipyard): self.send_message(cmdr, { '$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}', - 'message': OrderedDict([ + 'message': { ('timestamp', entry['timestamp']), ('systemName', entry['StarSystem']), ('stationName', entry['StationName']), @@ -999,7 +1003,7 @@ def export_journal_shipyard(self, cmdr: str, is_beta: bool, entry: Mapping[str, ('horizons', horizons), ('ships', shipyard), ('odyssey', entry['odyssey']) - ]), + }, }) # this.shipyard = (horizons, shipyard) @@ -2182,14 +2186,14 @@ def plugin_stop() -> None: logger.debug('Done.') -def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: +def filter_localised(d: Mapping[str, Any]) -> dict[str, Any]: """ Recursively remove any dict keys with names ending `_Localised` from a dict. :param d: dict to filter keys of. :return: The filtered dict. """ - filtered: OrderedDictT[str, Any] = OrderedDict() + filtered: dict[str, Any] = {} for k, v in d.items(): if k.endswith('_Localised'): pass @@ -2206,14 +2210,14 @@ def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: return filtered -def capi_filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: +def capi_filter_localised(d: Mapping[str, Any]) -> dict[str, Any]: """ Recursively remove any dict keys for known CAPI 'localised' names. :param d: dict to filter keys of. :return: The filtered dict. """ - filtered: OrderedDictT[str, Any] = OrderedDict() + filtered: dict[str, Any] = {} for k, v in d.items(): if EDDN.CAPI_LOCALISATION_RE.search(k): pass diff --git a/plugins/inara.py b/plugins/inara.py index d523bb2e1..121ae2276 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -24,14 +24,13 @@ import threading import time import tkinter as tk -from collections import OrderedDict, defaultdict, deque +from collections import defaultdict, deque from dataclasses import dataclass from datetime import datetime, timedelta, timezone from operator import itemgetter from threading import Lock, Thread from tkinter import ttk from typing import TYPE_CHECKING, Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union -from typing import OrderedDict as OrderedDictT import requests import edmc_data import killswitch @@ -102,12 +101,12 @@ def __init__(self): self.newsession: bool = True # starting a new session - wait for Cargo event self.undocked: bool = False # just undocked self.suppress_docked = False # Skip initial Docked event if started docked - self.cargo: list[OrderedDictT[str, Any]] | None = None - self.materials: list[OrderedDictT[str, Any]] | None = None + self.cargo: list[dict[str, Any]] | None = None + self.materials: list[dict[str, Any]] | None = None self.last_credits: int = 0 # Send credit update soon after Startup / new game - self.storedmodules: list[OrderedDictT[str, Any]] | None = None - self.loadout: OrderedDictT[str, Any] | None = None - self.fleet: list[OrderedDictT[str, Any]] | None = None + self.storedmodules: list[dict[str, Any]] | None = None + self.loadout: dict[str, Any] | None = None + self.fleet: list[dict[str, Any]] | None = None self.shipswap: bool = False # just swapped ship self.on_foot = False @@ -721,13 +720,13 @@ def journal_entry( # noqa: C901, CCR001 this.suppress_docked = True # Send cargo and materials if changed - cargo = [OrderedDict({'itemName': k, 'itemCount': state['Cargo'][k]}) for k in sorted(state['Cargo'])] + cargo = [{'itemName': k, 'itemCount': state['Cargo'][k]} for k in sorted(state['Cargo'])] if this.cargo != cargo: new_add_event('setCommanderInventoryCargo', entry['timestamp'], cargo) this.cargo = cargo materials = [ - OrderedDict([('itemName', k), ('itemCount', state[category][k])]) + {'itemName': k, 'itemCount': state[category][k]} for category in ('Raw', 'Manufactured', 'Encoded') for k in sorted(state[category]) ] @@ -823,24 +822,30 @@ def journal_entry( # noqa: C901, CCR001 # Fleet if event_name == 'StoredShips': - fleet: list[OrderedDictT[str, Any]] = sorted( - [OrderedDict({ - 'shipType': x['ShipType'], - 'shipGameID': x['ShipID'], - 'shipName': x.get('Name'), - 'isHot': x['Hot'], - 'starsystemName': entry['StarSystem'], - 'stationName': entry['StationName'], - 'marketID': entry['MarketID'], - }) for x in entry['ShipsHere']] + - [OrderedDict({ - 'shipType': x['ShipType'], - 'shipGameID': x['ShipID'], - 'shipName': x.get('Name'), - 'isHot': x['Hot'], - 'starsystemName': x.get('StarSystem'), # Not present for ships in transit - 'marketID': x.get('ShipMarketID'), # " - }) for x in entry['ShipsRemote']], + fleet = sorted( + [ + { + 'shipType': x['ShipType'], + 'shipGameID': x['ShipID'], + 'shipName': x.get('Name'), + 'isHot': x['Hot'], + 'starsystemName': entry['StarSystem'], + 'stationName': entry['StationName'], + 'marketID': entry['MarketID'], + } + for x in entry['ShipsHere'] + ] + + [ + { + 'shipType': x['ShipType'], + 'shipGameID': x['ShipID'], + 'shipName': x.get('Name'), + 'isHot': x['Hot'], + 'starsystemName': x.get('StarSystem'), # Not present for ships in transit + 'marketID': x.get('ShipMarketID'), # " + } + for x in entry['ShipsRemote'] + ], key=itemgetter('shipGameID') ) @@ -851,7 +856,6 @@ def journal_entry( # noqa: C901, CCR001 # this.events = [x for x in this.events if x['eventName'] != 'setCommanderShip'] # Remove any unsent for ship in this.fleet: new_add_event('setCommanderShip', entry['timestamp'], ship) - # Loadout if event_name == 'Loadout' and not this.newsession: loadout = make_loadout(state) @@ -870,14 +874,14 @@ def journal_entry( # noqa: C901, CCR001 # Stored modules if event_name == 'StoredModules': items = {mod['StorageSlot']: mod for mod in entry['Items']} # Impose an order - modules: list[OrderedDictT[str, Any]] = [] + modules: list[dict[str, Any]] = [] for slot in sorted(items): item = items[slot] - module: OrderedDictT[str, Any] = OrderedDict([ - ('itemName', item['Name']), - ('itemValue', item['BuyPrice']), - ('isHot', item['Hot']), - ]) + module: dict[str, Any] = { + 'itemName': item['Name'], + 'itemValue': item['BuyPrice'], + 'isHot': item['Hot'], + } # Location can be absent if in transit if 'StarSystem' in item: @@ -887,7 +891,7 @@ def journal_entry( # noqa: C901, CCR001 module['marketID'] = item['MarketID'] if 'EngineerModifications' in item: - module['engineering'] = OrderedDict([('blueprintName', item['EngineerModifications'])]) + module['engineering'] = {'blueprintName': item['EngineerModifications']} if 'Level' in item: module['engineering']['blueprintLevel'] = item['Level'] @@ -907,15 +911,15 @@ def journal_entry( # noqa: C901, CCR001 # Missions if event_name == 'MissionAccepted': - data: OrderedDictT[str, Any] = OrderedDict([ - ('missionName', entry['Name']), - ('missionGameID', entry['MissionID']), - ('influenceGain', entry['Influence']), - ('reputationGain', entry['Reputation']), - ('starsystemNameOrigin', system), - ('stationNameOrigin', station), - ('minorfactionNameOrigin', entry['Faction']), - ]) + data: dict[str, Any] = { + 'missionName': entry['Name'], + 'missionGameID': entry['MissionID'], + 'influenceGain': entry['Influence'], + 'reputationGain': entry['Reputation'], + 'starsystemNameOrigin': system, + 'stationNameOrigin': station, + 'minorfactionNameOrigin': entry['Faction'], + } # optional mission-specific properties for (iprop, prop) in ( @@ -946,7 +950,7 @@ def journal_entry( # noqa: C901, CCR001 for x in entry.get('PermitsAwarded', []): new_add_event('addCommanderPermit', entry['timestamp'], {'starsystemName': x}) - data = OrderedDict([('missionGameID', entry['MissionID'])]) + data = {'missionGameID': entry['MissionID']} if 'Donation' in entry: data['donationCredits'] = entry['Donation'] @@ -966,7 +970,7 @@ def journal_entry( # noqa: C901, CCR001 factioneffects = [] for faction in entry.get('FactionEffects', []): - effect: OrderedDictT[str, Any] = OrderedDict([('minorfactionName', faction['Faction'])]) + effect: dict[str, Any] = {'minorfactionName': faction['Faction']} for influence in faction.get('Influence', []): if 'Influence' in influence: highest_gain = influence['Influence'] @@ -990,7 +994,7 @@ def journal_entry( # noqa: C901, CCR001 # Combat if event_name == 'Died': - data = OrderedDict([('starsystemName', system)]) + data = {'starsystemName': system} if 'Killers' in entry: data['wingOpponentNames'] = [x['Name'] for x in entry['Killers']] @@ -1000,10 +1004,11 @@ def journal_entry( # noqa: C901, CCR001 new_add_event('addCommanderCombatDeath', entry['timestamp'], data) elif event_name == 'Interdicted': - data = OrderedDict([('starsystemName', system), - ('isPlayer', entry['IsPlayer']), - ('isSubmit', entry['Submitted']), - ]) + data = { + 'starsystemName': system, + 'isPlayer': entry['IsPlayer'], + 'isSubmit': entry['Submitted'] + } if 'Interdictor' in entry: data['opponentName'] = entry['Interdictor'] @@ -1022,11 +1027,11 @@ def journal_entry( # noqa: C901, CCR001 new_add_event('addCommanderCombatInterdicted', entry['timestamp'], data) elif event_name == 'Interdiction': - data = OrderedDict([ - ('starsystemName', system), - ('isPlayer', entry['IsPlayer']), - ('isSuccess', entry['Success']), - ]) + data = { + 'starsystemName': system, + 'isPlayer': entry['IsPlayer'], + 'isSuccess': entry['Success'], + } if 'Interdicted' in entry: data['opponentName'] = entry['Interdicted'] @@ -1256,16 +1261,16 @@ def journal_entry( # noqa: C901, CCR001 # )) for goal in entry['CurrentGoals']: - data = OrderedDict([ - ('communitygoalGameID', goal['CGID']), - ('communitygoalName', goal['Title']), - ('starsystemName', goal['SystemName']), - ('stationName', goal['MarketName']), - ('goalExpiry', goal['Expiry']), - ('isCompleted', goal['IsComplete']), - ('contributorsNum', goal['NumContributors']), - ('contributionsTotal', goal['CurrentTotal']), - ]) + data = { + 'communitygoalGameID': goal['CGID'], + 'communitygoalName': goal['Title'], + 'starsystemName': goal['SystemName'], + 'stationName': goal['MarketName'], + 'goalExpiry': goal['Expiry'], + 'isCompleted': goal['IsComplete'], + 'contributorsNum': goal['NumContributors'], + 'contributionsTotal': goal['CurrentTotal'], + } if 'TierReached' in goal: data['tierReached'] = int(goal['TierReached'].split()[-1]) @@ -1279,11 +1284,11 @@ def journal_entry( # noqa: C901, CCR001 new_add_event('setCommunityGoal', entry['timestamp'], data) - data = OrderedDict([ - ('communitygoalGameID', goal['CGID']), - ('contribution', goal['PlayerContribution']), - ('percentileBand', goal['PlayerPercentileBand']), - ]) + data = { + 'communitygoalGameID': goal['CGID'], + 'contribution': goal['PlayerContribution'], + 'percentileBand': goal['PlayerPercentileBand'], + } if 'Bonus' in goal: data['percentileBandReward'] = goal['Bonus'] @@ -1380,7 +1385,7 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001, reanalyze me later pass -def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR001 +def make_loadout(state: dict[str, Any]) -> dict[str, Any]: # noqa: CCR001 """ Construct an inara loadout from an event. @@ -1389,13 +1394,13 @@ def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR0 """ modules = [] for m in state['Modules'].values(): - module: OrderedDictT[str, Any] = OrderedDict([ - ('slotName', m['Slot']), - ('itemName', m['Item']), - ('itemHealth', m['Health']), - ('isOn', m['On']), - ('itemPriority', m['Priority']), - ]) + module: dict[str, Any] = { + 'slotName': m['Slot'], + 'itemName': m['Item'], + 'itemHealth': m['Health'], + 'isOn': m['On'], + 'itemPriority': m['Priority'], + } if 'AmmoInClip' in m: module['itemAmmoClip'] = m['AmmoInClip'] @@ -1410,20 +1415,20 @@ def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR0 module['isHot'] = m['Hot'] if 'Engineering' in m: - engineering: OrderedDictT[str, Any] = OrderedDict([ - ('blueprintName', m['Engineering']['BlueprintName']), - ('blueprintLevel', m['Engineering']['Level']), - ('blueprintQuality', m['Engineering']['Quality']), - ]) + engineering: dict[str, Any] = { + 'blueprintName': m['Engineering']['BlueprintName'], + 'blueprintLevel': m['Engineering']['Level'], + 'blueprintQuality': m['Engineering']['Quality'], + } if 'ExperimentalEffect' in m['Engineering']: engineering['experimentalEffect'] = m['Engineering']['ExperimentalEffect'] engineering['modifiers'] = [] for mod in m['Engineering']['Modifiers']: - modifier: OrderedDictT[str, Any] = OrderedDict([ - ('name', mod['Label']), - ]) + modifier: dict[str, Any] = { + 'name': mod['Label'], + } if 'OriginalValue' in mod: modifier['value'] = mod['Value'] @@ -1439,11 +1444,11 @@ def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR0 modules.append(module) - return OrderedDict([ - ('shipType', state['ShipType']), - ('shipGameID', state['ShipID']), - ('shipLoadout', modules), - ]) + return { + 'shipType': state['ShipType'], + 'shipGameID': state['ShipID'], + 'shipLoadout': modules, + } def new_add_event( From 6b137355b04ff8bd794f564425c674fd594997bf Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 4 Jan 2024 13:47:24 -0500 Subject: [PATCH 059/261] [2127] Move Manifests to Resources Since these are only used by the builder, we can simply update the location of them. I'm not convinced they're all still in use but not convinced enough to remove them. --- build.py | 5 +++-- EDMC.manifest => resources/EDMC.manifest | 0 .../EDMarketConnector-dpiaware+gdiscaling.manifest | 0 .../EDMarketConnector-dpiaware.manifest | 0 .../EDMarketConnector-gdiscaling.manifest | 0 .../EDMarketConnector.manifest | 0 6 files changed, 3 insertions(+), 2 deletions(-) rename EDMC.manifest => resources/EDMC.manifest (100%) rename EDMarketConnector-dpiaware+gdiscaling.manifest => resources/EDMarketConnector-dpiaware+gdiscaling.manifest (100%) rename EDMarketConnector-dpiaware.manifest => resources/EDMarketConnector-dpiaware.manifest (100%) rename EDMarketConnector-gdiscaling.manifest => resources/EDMarketConnector-gdiscaling.manifest (100%) rename EDMarketConnector.manifest => resources/EDMarketConnector.manifest (100%) diff --git a/build.py b/build.py index ad5a96432..0cdf38487 100644 --- a/build.py +++ b/build.py @@ -164,15 +164,16 @@ def build() -> None: "script": "EDMarketConnector.py", "icon_resources": [(0, f"{appname}.ico")], "other_resources": [ - (24, 1, pathlib.Path(f"{appname}.manifest").read_text(encoding="UTF8")) + (24, 1, pathlib.Path(f"resources/{appname}.manifest").read_text(encoding="UTF8")) ], } console_config: dict = { "dest_base": appcmdname, "script": "EDMC.py", + "icon_resources": [(0, f"{appname}.ico")], "other_resources": [ - (24, 1, pathlib.Path(f"{appcmdname}.manifest").read_text(encoding="UTF8")) + (24, 1, pathlib.Path(f"resources/{appcmdname}.manifest").read_text(encoding="UTF8")) ], } diff --git a/EDMC.manifest b/resources/EDMC.manifest similarity index 100% rename from EDMC.manifest rename to resources/EDMC.manifest diff --git a/EDMarketConnector-dpiaware+gdiscaling.manifest b/resources/EDMarketConnector-dpiaware+gdiscaling.manifest similarity index 100% rename from EDMarketConnector-dpiaware+gdiscaling.manifest rename to resources/EDMarketConnector-dpiaware+gdiscaling.manifest diff --git a/EDMarketConnector-dpiaware.manifest b/resources/EDMarketConnector-dpiaware.manifest similarity index 100% rename from EDMarketConnector-dpiaware.manifest rename to resources/EDMarketConnector-dpiaware.manifest diff --git a/EDMarketConnector-gdiscaling.manifest b/resources/EDMarketConnector-gdiscaling.manifest similarity index 100% rename from EDMarketConnector-gdiscaling.manifest rename to resources/EDMarketConnector-gdiscaling.manifest diff --git a/EDMarketConnector.manifest b/resources/EDMarketConnector.manifest similarity index 100% rename from EDMarketConnector.manifest rename to resources/EDMarketConnector.manifest From 4df38d6a73f796dd25d5234aac4c6b0e02e53825 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 4 Jan 2024 14:12:47 -0500 Subject: [PATCH 060/261] [635] Remove Outdated Comment All Dicts Keep Order. Co-authored-by: Phoebe <40956085+C1701D@users.noreply.github.com> --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index a397dbf9d..8364f95bf 100644 --- a/monitor.py +++ b/monitor.py @@ -1073,7 +1073,7 @@ def parse_entry(self, line: bytes) -> MutableMapping[str, Any]: # noqa: C901, C # From 3.3 full Cargo event (after the first one) is written to a separate file if 'Inventory' not in entry: with open(join(self.currentdir, 'Cargo.json'), 'rb') as h: # type: ignore - entry = json.load(h) # Preserve property order because why not? + entry = json.load(h) self.state['CargoJSON'] = entry clean = self.coalesce_cargo(entry['Inventory']) From 41b879716f0a27bda98f3901bbe62244dde83f03 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 4 Jan 2024 13:02:39 -0500 Subject: [PATCH 061/261] [2140] Translate Newver Str --- EDMC.py | 5 ++++- L10n/en.template | 2 ++ update.py | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/EDMC.py b/EDMC.py index 072b04927..651536e95 100755 --- a/EDMC.py +++ b/EDMC.py @@ -26,6 +26,7 @@ if TYPE_CHECKING: from logging import TRACE # type: ignore # noqa: F401 # needed to make mypy happy + def _(x): return x edmclogger.set_channels_loglevel(logging.INFO) @@ -162,7 +163,9 @@ def main(): # noqa: C901, CCR001 updater = Updater() newversion: EDMCVersion | None = updater.check_appcast() if newversion: - print(f'{appversion()} ({newversion.title!r} is available)') + # LANG: Updater Available Text + newverstr = _("{NEWVER} is available").format(NEWVER=newversion.title) + print(f'{appversion()} ({newverstr})') else: print(appversion()) return diff --git a/L10n/en.template b/L10n/en.template index 3aed0952f..c41681ddd 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -789,3 +789,5 @@ /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Ships"; +/* update.py: Updater Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} is available"; \ No newline at end of file diff --git a/update.py b/update.py index f88daa38c..f2aa1420b 100644 --- a/update.py +++ b/update.py @@ -21,6 +21,7 @@ if TYPE_CHECKING: import tkinter as tk + def _(x): return x logger = get_main_logger() @@ -225,7 +226,8 @@ def worker(self) -> None: if newversion and self.root: status = self.root.nametowidget(f'.{appname.lower()}.status') - status['text'] = newversion.title + ' is available' + # LANG: Updater Available Text + status['text'] = _("{NEWVER} is available").format(NEWVER=newversion.title) self.root.update_idletasks() else: From 881c94a619be3089d187bf8569ca7739199448f4 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 4 Jan 2024 14:07:18 -0500 Subject: [PATCH 062/261] [Lang] Update Lang Comment --- EDMC.py | 2 +- L10n/en.template | 2 +- update.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EDMC.py b/EDMC.py index 651536e95..81c4ddc8c 100755 --- a/EDMC.py +++ b/EDMC.py @@ -163,7 +163,7 @@ def main(): # noqa: C901, CCR001 updater = Updater() newversion: EDMCVersion | None = updater.check_appcast() if newversion: - # LANG: Updater Available Text + # LANG: Update Available Text newverstr = _("{NEWVER} is available").format(NEWVER=newversion.title) print(f'{appversion()} ({newverstr})') else: diff --git a/L10n/en.template b/L10n/en.template index c41681ddd..de607eb4f 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -789,5 +789,5 @@ /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Ships"; -/* update.py: Updater Available Text; In files: update.py:229; */ +/* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} is available"; \ No newline at end of file diff --git a/update.py b/update.py index f2aa1420b..5e780a37f 100644 --- a/update.py +++ b/update.py @@ -226,7 +226,7 @@ def worker(self) -> None: if newversion and self.root: status = self.root.nametowidget(f'.{appname.lower()}.status') - # LANG: Updater Available Text + # LANG: Update Available Text status['text'] = _("{NEWVER} is available").format(NEWVER=newversion.title) self.root.update_idletasks() From e531db8d488c3cda33bec22c26343e95b1109aae Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 4 Jan 2024 15:30:02 -0500 Subject: [PATCH 063/261] [Flake8] Ignore Type Hint Missing It's not missing. They're there. --- EDMC.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EDMC.py b/EDMC.py index 81c4ddc8c..a237f4ead 100755 --- a/EDMC.py +++ b/EDMC.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# flake8: noqa TAE001 """ EDMC.py - Command-line interface. Requires prior setup through the GUI. @@ -164,7 +165,7 @@ def main(): # noqa: C901, CCR001 newversion: EDMCVersion | None = updater.check_appcast() if newversion: # LANG: Update Available Text - newverstr = _("{NEWVER} is available").format(NEWVER=newversion.title) + newverstr: str = _("{NEWVER} is available").format(NEWVER=newversion.title) print(f'{appversion()} ({newverstr})') else: print(appversion()) From 5ee7688c6a8aba0bde472ca881d53eb4a5c0ce4a Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 4 Jan 2024 16:19:51 -0500 Subject: [PATCH 064/261] [Minor] Adds icon to EDMC.exe EDMC didn't have an icon, and everyone deserves to have their own special thing. Thanks to CMDR DrebinOmega for the design! --- build.py | 6 ++++-- config/__init__.py | 2 +- resources/EDMC_Installer_Config_template.txt | 2 +- resources/edmc.ico | Bin 0 -> 25184 bytes resources/io.edcd.EDMarketConnector.cmd.psd | Bin 0 -> 511770 bytes 5 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 resources/edmc.ico create mode 100644 resources/io.edcd.EDMarketConnector.cmd.psd diff --git a/build.py b/build.py index 596b64fb1..1bc967651 100644 --- a/build.py +++ b/build.py @@ -81,6 +81,7 @@ def generate_data_files( "ships.json", "ships.p", # TODO: Remove in 6.0 f"{app_name}.ico", + f"resources/{appcmdname}.ico", "EDMarketConnector - TRACE.bat", "EDMarketConnector - localserver-auth.bat", "EDMarketConnector - reset-ui.bat", @@ -171,7 +172,7 @@ def build() -> None: console_config: dict = { "dest_base": appcmdname, "script": "EDMC.py", - "icon_resources": [(0, f"{appname}.ico")], + "icon_resources": [(0, f"resources/{appcmdname}.ico")], "other_resources": [ (24, 1, pathlib.Path(f"resources/{appcmdname}.manifest").read_text(encoding="UTF8")) ], @@ -185,7 +186,8 @@ def build() -> None: data_files=data_files, options=options, ) - except FileNotFoundError: + except FileNotFoundError as err: + print(err) sys.exit( "Build Failed due to Missing Files! Have you set up your submodules? \n" "https://github.com/EDCD/EDMarketConnector/wiki/Running-from-source" diff --git a/config/__init__.py b/config/__init__.py index 7e5025a64..5dfb0d249 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -57,7 +57,7 @@ _static_appversion = '5.10.1' _cached_version: semantic_version.Version | None = None -copyright = '© 2015-2019 Jonathan Harris, 2020-2023 EDCD' +copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' update_interval = 8*60*60 # 8 Hours diff --git a/resources/EDMC_Installer_Config_template.txt b/resources/EDMC_Installer_Config_template.txt index b72ed5072..de40630c1 100644 --- a/resources/EDMC_Installer_Config_template.txt +++ b/resources/EDMC_Installer_Config_template.txt @@ -16,7 +16,7 @@ AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#SuppURL} AppUpdatesURL={#SuppURL} -AppCopyright=Copyright (C) 2015-2019 Jonathan Harris, 2020-2023 EDCD +AppCopyright=Copyright (C) 2015-2019 Jonathan Harris, 2020-2024 EDCD AllowUNCPath=no AllowNetworkDrive=no DefaultDirName={autopf}\{#MyAppName} diff --git a/resources/edmc.ico b/resources/edmc.ico new file mode 100644 index 0000000000000000000000000000000000000000..8f3f9ff4d0632d87e0abd781f4ea1d04fa9a83d4 GIT binary patch literal 25184 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 literal 0 HcmV?d00001 From f318b3256b8ccd8b8c1267b770409b38f68dfa7e Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 26 Dec 2023 23:02:39 -0500 Subject: [PATCH 065/261] [662] Apply Rate Limit --- plugins/edsm.py | 134 +++++++++++++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 48 deletions(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index 754ffb615..46d05f4c1 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -28,7 +28,7 @@ from threading import Thread from time import sleep from tkinter import ttk -from typing import TYPE_CHECKING, Any, Literal, Mapping, MutableMapping, cast +from typing import TYPE_CHECKING, Any, Literal, Mapping, MutableMapping, cast, Sequence import requests import killswitch import monitor @@ -722,6 +722,87 @@ def get_discarded_events_list() -> None: logger.warning('Exception while trying to set this.discarded_events:', exc_info=e) +def process_discarded_events() -> None: + """Process discarded events until the discarded events list is retrieved or the shutdown signal is received.""" + while not this.discarded_events: + if this.shutting_down: + logger.debug(f'returning from discarded_events loop due to {this.shutting_down=}') + return + get_discarded_events_list() + if this.discarded_events: + break + sleep(DISCARDED_EVENTS_SLEEP) + + logger.debug('Got "events to discard" list, commencing queue consumption...') + + +def send_to_edsm( # noqa: CCR001 + data: dict[str, Sequence[object]], pending: list[Mapping[str, Any]], closing: bool +) -> list[Mapping[str, Any]]: + """Send data to the EDSM API endpoint and handle the API response.""" + response = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT) + logger.trace_if('plugin.edsm.api', f'API response content: {response.content!r}') + + # Check for rate limit headers + rate_limit_remaining = response.headers.get('X-Rate-Limit-Remaining') + rate_limit_reset = response.headers.get('X-Rate-Limit-Reset') + + # Convert headers to integers if they exist + try: + remaining = int(rate_limit_remaining) if rate_limit_remaining else None + reset = int(rate_limit_reset) if rate_limit_reset else None + except ValueError: + remaining = reset = None + + if remaining is not None and reset is not None: + # Respect rate limits if they exist + if remaining == 0: + # Calculate sleep time until the rate limit reset time + reset_time = datetime.utcfromtimestamp(reset) + current_time = datetime.utcnow() + + sleep_time = (reset_time - current_time).total_seconds() + + if sleep_time > 0: + sleep(sleep_time) + + response.raise_for_status() + reply = response.json() + msg_num = reply['msgnum'] + msg = reply['msg'] + # 1xx = OK + # 2xx = fatal error + # 3&4xx not generated at top-level + # 5xx = error but events saved for later processing + + if msg_num // 100 == 2: + logger.warning(f'EDSM\t{msg_num} {msg}\t{json.dumps(pending, separators=(",", ": "))}') + # LANG: EDSM Plugin - Error message from EDSM API + plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg)) + else: + if msg_num // 100 == 1: + logger.trace_if('plugin.edsm.api', 'Overall OK') + pass + elif msg_num // 100 == 5: + logger.trace_if('plugin.edsm.api', 'Event(s) not currently processed, but saved for later') + pass + else: + logger.warning(f'EDSM API call status not 1XX, 2XX or 5XX: {msg.num}') + + for e, r in zip(pending, reply['events']): + if not closing and e['event'] in ('StartUp', 'Location', 'FSDJump', 'CarrierJump'): + # Update main window's system status + this.lastlookup = r + # calls update_status in main thread + if not config.shutting_down and this.system_link is not None: + this.system_link.event_generate('<>', when="tail") + if r['msgnum'] // 100 != 1: + logger.warning(f'EDSM event with not-1xx status:\n{r["msgnum"]}\n' + f'{r["msg"]}\n{json.dumps(e, separators=(",", ": "))}') + pending = [] + return pending + + def worker() -> None: # noqa: CCR001 C901 """ Handle uploading events to EDSM API. @@ -738,17 +819,9 @@ def worker() -> None: # noqa: CCR001 C901 last_game_version = "" last_game_build = "" - while not this.discarded_events: - if this.shutting_down: - logger.debug(f'returning from discarded_events loop due to {this.shutting_down=}') - return - get_discarded_events_list() - if this.discarded_events: - break - - sleep(DISCARDED_EVENTS_SLEEP) + # Process the Discard Queue + process_discarded_events() - logger.debug('Got "events to discard" list, commencing queue consumption...') while True: if this.shutting_down: logger.debug(f'{this.shutting_down=}, so setting closing = True') @@ -861,43 +934,8 @@ def worker() -> None: # noqa: CCR001 C901 'journal.locations', f'Overall POST data (elided) is:\n{json.dumps(data_elided, indent=2)}' ) - response = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT) - logger.trace_if('plugin.edsm.api', f'API response content: {response.content!r}') - response.raise_for_status() - - reply = response.json() - msg_num = reply['msgnum'] - msg = reply['msg'] - # 1xx = OK - # 2xx = fatal error - # 3&4xx not generated at top-level - # 5xx = error but events saved for later processing - - if msg_num // 100 == 2: - logger.warning(f'EDSM\t{msg_num} {msg}\t{json.dumps(pending, separators=(",", ": "))}') - # LANG: EDSM Plugin - Error message from EDSM API - plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg)) - else: - if msg_num // 100 == 1: - logger.trace_if('plugin.edsm.api', 'Overall OK') - pass - elif msg_num // 100 == 5: - logger.trace_if('plugin.edsm.api', 'Event(s) not currently processed, but saved for later') - pass - else: - logger.warning(f'EDSM API call status not 1XX, 2XX or 5XX: {msg.num}') - - for e, r in zip(pending, reply['events']): - if not closing and e['event'] in ('StartUp', 'Location', 'FSDJump', 'CarrierJump'): - # Update main window's system status - this.lastlookup = r - # calls update_status in main thread - if not config.shutting_down and this.system_link is not None: - this.system_link.event_generate('<>', when="tail") - if r['msgnum'] // 100 != 1: - logger.warning(f'EDSM event with not-1xx status:\n{r["msgnum"]}\n' - f'{r["msg"]}\n{json.dumps(e, separators = (",", ": "))}') - pending = [] + pending = send_to_edsm(data, pending, closing) + break # No exception, so assume success except Exception as e: From 48311ae70c5f82381dfe62181d14dd6cc3d9bd5c Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 4 Jan 2024 19:31:59 -0500 Subject: [PATCH 066/261] [Minor] Bump Version String for internal --- config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index 5dfb0d249..7d4aa43f2 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.10.1' +_static_appversion = '5.10.2-alpha0' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 29c4bd402804899974fdb57b739c4b5d57caf39d Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 5 Jan 2024 15:35:25 -0500 Subject: [PATCH 067/261] [Minor] Update Type Hint --- EDMC.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EDMC.py b/EDMC.py index a237f4ead..39d989824 100755 --- a/EDMC.py +++ b/EDMC.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# flake8: noqa TAE001 """ EDMC.py - Command-line interface. Requires prior setup through the GUI. @@ -27,7 +26,7 @@ if TYPE_CHECKING: from logging import TRACE # type: ignore # noqa: F401 # needed to make mypy happy - def _(x): return x + def _(x: str): return x edmclogger.set_channels_loglevel(logging.INFO) From 0b90ca770885515097a62ecc4fdb9d6c3bd24bd0 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 5 Jan 2024 15:39:26 -0500 Subject: [PATCH 068/261] [Minor] More Type Hints --- companion.py | 2 +- update.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index 4a8277691..d00307482 100644 --- a/companion.py +++ b/companion.py @@ -41,7 +41,7 @@ logger = get_main_logger() if TYPE_CHECKING: - def _(x): return x + def _(x: str): return x UserDict = collections.UserDict[str, Any] # indicate to our type checkers what this generic class holds normally else: diff --git a/update.py b/update.py index 5e780a37f..024aeb098 100644 --- a/update.py +++ b/update.py @@ -21,7 +21,7 @@ if TYPE_CHECKING: import tkinter as tk - def _(x): return x + def _(x: str): return x logger = get_main_logger() From 840dd54faf39b7b6557e9cac5b2f028609760097 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 30 Jan 2024 17:38:30 -0500 Subject: [PATCH 069/261] [#2146] Cleanup On CtrlC --- EDMarketConnector.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 690300e65..3bd440158 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -2365,6 +2365,9 @@ def check_fdev_ids(): # Check for FDEV IDs root.after(3, check_fdev_ids) # Start the main event loop - root.mainloop() - + try: + root.mainloop() + except KeyboardInterrupt: + logger.info("Ctrl+C Detected, Attempting Clean Shutdown") + app.onexit() logger.info('Exiting') From f46c2e1dc08c58c73afc526fb91144d244014cce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:19:45 +0000 Subject: [PATCH 070/261] build(deps-dev): bump types-requests Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.20231231 to 2.31.0.20240125. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f8d701ff0..5f340861f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -21,7 +21,7 @@ flake8-use-fstring==1.4 mypy==1.8.0 pep8-naming==0.13.3 safety==2.3.5 -types-requests==2.31.0.20231231 +types-requests==2.31.0.20240125 types-pkg-resources==0.1.3 # Code formatting tools From 4b04ff98f0cc4e28c35b8107046e77e615404245 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:19:56 +0000 Subject: [PATCH 071/261] build(deps-dev): bump flake8-noqa from 1.3.2 to 1.4.0 Bumps [flake8-noqa](https://github.com/plinss/flake8-noqa) from 1.3.2 to 1.4.0. - [Commits](https://github.com/plinss/flake8-noqa/compare/v1.3.2...v1.4.0) --- updated-dependencies: - dependency-name: flake8-noqa dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f8d701ff0..72f98a612 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,7 +14,7 @@ flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.14.0 flake8-docstrings==1.7.0 flake8-json==23.7.0 -flake8-noqa==1.3.2 +flake8-noqa==1.4.0 flake8-polyfill==1.0.2 flake8-use-fstring==1.4 From 9263c4bc9b31ebc3e4719c1e1b348771b83b22b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:20:22 +0000 Subject: [PATCH 072/261] build(deps-dev): bump coverage[toml] from 7.3.4 to 7.4.1 Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 7.3.4 to 7.4.1. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.3.4...7.4.1) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f8d701ff0..006e71424 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -40,7 +40,7 @@ py2exe==0.13.0.1; sys_platform == 'win32' # Testing pytest==7.4.3 pytest-cov==4.1.0 # Pytest code coverage support -coverage[toml]==7.3.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==7.4.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 # For manipulating folder permissions and the like. pywin32==306; sys_platform == 'win32' From bbd911cfd9abf9848a8babd73bceda985b26394e Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 7 Feb 2024 19:12:58 -0500 Subject: [PATCH 073/261] [#2146] Cleanup On CtrlC Already Running --- EDMarketConnector.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 3bd440158..523f62366 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -353,7 +353,13 @@ def already_running_popup(): button = ttk.Button(frame, text='OK', command=lambda: sys.exit(0)) button.grid(row=2, column=0, sticky=tk.S) - root.mainloop() + try: + root.mainloop() + except KeyboardInterrupt: + logger.info("Ctrl+C Detected, Attempting Clean Shutdown") + sys.exit() + logger.info('Exiting') + journal_lock = JournalLock() locked = journal_lock.obtain_lock() From f4776e83178865b1a558130204d3d2b8cc2fb41f Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 7 Feb 2024 19:15:18 -0500 Subject: [PATCH 074/261] [Eyeroll] --- EDMarketConnector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 523f62366..8a0cb3f56 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -360,7 +360,6 @@ def already_running_popup(): sys.exit() logger.info('Exiting') - journal_lock = JournalLock() locked = journal_lock.obtain_lock() From 4fcdacbc504dbbdeb709a928b93072f4a12266e6 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 7 Feb 2024 19:29:56 -0500 Subject: [PATCH 075/261] [Lang] Update Serbian Translations --- L10n/sr-Latn.strings | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings index 5f2c01c52..83da541a3 100644 --- a/L10n/sr-Latn.strings +++ b/L10n/sr-Latn.strings @@ -213,12 +213,24 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više aktiviranih dodataka (plugins) nemaju podršku za Python 3.x. Pogledajte listu u '{PLUGINS}' tabu u '{FILE}' > '{SETTINGS}'. Proverite da li postoji nadograđena verzija ili obavesite autora da treba da promeni kod za Python 3.x.\n\nMožete deaktivirati dodatak (plugin) dodavanjem '{DISABLED}' na kraju imena njegovog foldera."; +/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ +"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID fajlovi nisu pronađeni! Neke funkcionalnosti vezane za artikle će možda biti deaktivirane.\n\nDa li želite da otvorite Wiki stranu koja objašnjava kako da podesite pod-module?"; + +/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ +"FDevIDs: Missing Commodity Files" = "FDevIDs: Nedostaju fajlovi za robu"; + /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Dodaci (plugins)"; /* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Dodaci (plugins) bez Python 3.x podrške"; +/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */ +"EDMC: Broken Plugins" = "EDMC: Pokvareni Pluginovi"; + +/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ +"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više vaših aktivnih pluginova se nije učitao. Molimo pogledajte listu na '{PLUGINS}' tabu u okviru '{FILE}' > '{SETTINGS}'. Obo vi moglo da se desi zbog pogrešne strukture foldera. load.py fajl bi trebao da se nalazi unutar plugins/PLUGIN_NAME/load.py.\n\nPojedinačni plugin možete deaktivirati promenom imena njegovog foldera dodavanjem '{DISABLED}' na kraju."; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Žurnal direktorijum je već zaključan"; @@ -471,6 +483,9 @@ /* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */ "Information on migrating plugins" = "Informacije o migraciji dodataka (plugins)"; +/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */ +"Broken Plugins" = "Pokvareni pluginovi"; + /* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */ "Disabled Plugins" = "Deaktivirani dodaci (plugins)"; From 1a70fc942977c5c1e2f4d0f38e8afb88e0dd67dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 00:31:50 +0000 Subject: [PATCH 076/261] build(deps-dev): bump flake8 from 6.1.0 to 7.0.0 Bumps [flake8](https://github.com/pycqa/flake8) from 6.1.0 to 7.0.0. - [Commits](https://github.com/pycqa/flake8/compare/6.1.0...7.0.0) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ff3523c01..2e9e563e1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ wheel setuptools==69.0.3 # Static analysis tools -flake8==6.1.0 +flake8==7.0.0 flake8-annotations-coverage==0.0.6 flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.14.0 From e5172e04865ef9047044fde28a6579809abec391 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 8 Feb 2024 13:10:16 -0500 Subject: [PATCH 077/261] [#2077] Ensure Valid Providers on Boot Ensure we have valid providers set, and throw a warning dialog if we don't. --- EDMarketConnector.py | 47 ++++++++++++++++++++++++++++++++++++++++++++ L10n/en.template | 9 +++++++++ 2 files changed, 56 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 8a0cb3f56..4846ea00b 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -807,6 +807,9 @@ def open_window(systray: 'SysTrayIcon') -> None: self.w.bind_all('<>', self.auth) # cAPI auth self.w.bind_all('<>', self.onexit) # Updater + # Check for Valid Providers + validate_providers() + # Start a protocol handler to handle cAPI registration. Requires main loop to be running. self.w.after_idle(lambda: protocol.protocolhandler.start(self.w)) @@ -2115,6 +2118,50 @@ def show_killswitch_poppup(root=None): ok_button.grid(columnspan=2, sticky=tk.EW) +def validate_providers(): + """Check if Config has an invalid provider set, and reset to default if we do.""" + reset_providers = {} + station_provider: str = config.get_str("station_provider") + if station_provider not in plug.provides('station_url'): + logger.error("Station Provider Not Valid. Setting to Default.") + config.set('station_provider', 'EDSM') + reset_providers["Station"] = (station_provider, "EDSM") + + shipyard_provider: str = config.get_str("shipyard_provider") + if shipyard_provider not in plug.provides('shipyard_url'): + logger.error("Shipyard Provider Not Valid. Setting to Default.") + config.set('shipyard_provider', 'EDSY') + reset_providers["Shipyard"] = (shipyard_provider, "EDSY") + + system_provider: str = config.get_str("system_provider") + if system_provider not in plug.provides('system_url'): + logger.error("System Provider Not Valid. Setting to Default.") + config.set('system_provider', 'EDSM') + reset_providers["System"] = (system_provider, "EDSM") + + if not reset_providers: + return + + # LANG: Popup-text about Reset Providers + popup_text = _(r'One or more of your URL Providers were invalid, and have been reset:\r\n\r\n') + for provider in reset_providers: + # LANG: Text About What Provider Was Reset + popup_text += _(r'{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n') + popup_text = popup_text.format( + PROVIDER=provider, + OLDPROV=reset_providers[provider][0], + NEWPROV=reset_providers[provider][1] + ) + # And now we do need these to be actual \r\n + popup_text = popup_text.replace('\\n', '\n') + popup_text = popup_text.replace('\\r', '\r') + + tk.messagebox.showinfo( + # LANG: Popup window title for Reset Providers + _('EDMC: Default Providers Reset'), + popup_text + ) + # Run the app if __name__ == "__main__": # noqa: C901 logger.info(f'Startup v{appversion()} : Running on Python v{sys.version}') diff --git a/L10n/en.template b/L10n/en.template index de607eb4f..5c07ae8f3 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -231,6 +231,15 @@ /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name."; +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "One or more of your URL Providers were invalid, and have been reset:\r\n\r\n"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: Default Providers Reset"; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Journal directory already locked"; From c45a476feff4ba5a7ba55aeb3c9a6cfc4112433f Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 8 Feb 2024 13:12:21 -0500 Subject: [PATCH 078/261] >:( --- EDMarketConnector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4846ea00b..8fcab4ba0 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -2162,6 +2162,7 @@ def validate_providers(): popup_text ) + # Run the app if __name__ == "__main__": # noqa: C901 logger.info(f'Startup v{appversion()} : Running on Python v{sys.version}') From c7a3fd44e7ffc88626ae56ba340af12e5661d71a Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 9 Feb 2024 11:35:15 -0500 Subject: [PATCH 079/261] [Nit] Properly Display Lang Comment Representation --- scripts/find_localised_strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/find_localised_strings.py b/scripts/find_localised_strings.py index 1d1da826b..ef861bb71 100644 --- a/scripts/find_localised_strings.py +++ b/scripts/find_localised_strings.py @@ -291,7 +291,7 @@ def generate_lang_template(data: dict[pathlib.Path, list[ast.Call]]) -> str: print(f'NEW! {file}:{c.lineno}: {arg!r}') for old in set(template) ^ seen: - print(f'No longer used: {old}') + print(f'No longer used: {old!r}') elif args.json: to_print_data = [ From 4a4010ad7b1664f23b3f45659b53b6daae554948 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 9 Feb 2024 13:32:59 -0500 Subject: [PATCH 080/261] [#2157] Add Station and Docking Information When Available --- plugins/eddn.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index b488d8e90..55ab729be 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -920,18 +920,24 @@ def export_journal_commodities(self, cmdr: str, is_beta: bool, entry: Mapping[st # none and that really does need to be recorded over EDDN so that # tools can update in a timely manner. if this.commodities != commodities: - self.send_message(cmdr, { + message: dict[str, Any] = { # Yes, this is a broad type hint. '$schemaRef': f'https://eddn.edcd.io/schemas/commodity/3{"/test" if is_beta else ""}', 'message': { - ('timestamp', entry['timestamp']), - ('systemName', entry['StarSystem']), - ('stationName', entry['StationName']), - ('marketId', entry['MarketID']), - ('commodities', commodities), - ('horizons', this.horizons), - ('odyssey', this.odyssey), - }, - }) + 'timestamp': entry['timestamp'], + 'systemName': entry['StarSystem'], + 'stationName': entry['StationName'], + 'marketId': entry['MarketID'], + 'commodities': commodities, + 'horizons': this.horizons, + 'odyssey': this.odyssey, + 'stationType': entry['StationType'], + } + } + + if entry.get('CarrierDockingAccess'): + message['message']['carrierDockingAccess'] = entry['CarrierDockingAccess'] + + self.send_message(cmdr, message) this.commodities = commodities From f37fb0d634550f0504b2f640f3579df55bd872d4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 11 Feb 2024 12:22:22 +0000 Subject: [PATCH 081/261] updating submodules --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index 069b09b1a..8fe254aa7 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 069b09b1afbd2001248ad93b1481d6f6a309c6f5 +Subproject commit 8fe254aa7c56ba7980576dd952b0c50205b03daa From 4843f3b957ab0e3a97b7a7af297fc3907e2dc05a Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 12 Feb 2024 12:24:45 +0000 Subject: [PATCH 082/261] updating submodules --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index 8fe254aa7..510c05d69 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 8fe254aa7c56ba7980576dd952b0c50205b03daa +Subproject commit 510c05d69977bcbc21712b64780d11282da30282 From cb437dd9a4e2024c5471c90abab4811268de898c Mon Sep 17 00:00:00 2001 From: Phoebe Date: Fri, 16 Feb 2024 23:11:46 +0100 Subject: [PATCH 083/261] [Minor} Update Copyright year in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d2973955..600df0751 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,6 @@ Please see the [Acknowledgements](https://github.com/EDCD/EDMarketConnector/wiki License ------- -Copyright © 2015-2019 Jonathan Harris, 2020-2021 EDCD +Copyright © 2015-2019 Jonathan Harris, 2020-2024 EDCD Licensed under the [GNU Public License (GPL)](http://www.gnu.org/licenses/gpl-2.0.html) version 2 or later. From dbdc8e47f2ef2391714b8ba6ff16193514f2a797 Mon Sep 17 00:00:00 2001 From: Phoebe Date: Mon, 19 Feb 2024 23:18:39 +0100 Subject: [PATCH 084/261] #1181 Keep Coriolis URL Selection fix/1181/coriolis-overrides-change-when-switching-languages --- plugins/coriolis.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/plugins/coriolis.py b/plugins/coriolis.py index 9de635d08..1c34e2173 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -45,6 +45,9 @@ def __init__(self): self.normal_url = '' self.beta_url = '' self.override_mode = '' + self.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto + self.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal + self.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta self.normal_textvar = tk.StringVar() self.beta_textvar = tk.StringVar() @@ -87,6 +90,11 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr PADY = 1 # noqa: N806 BOXY = 2 # noqa: N806 # box spacing + # Save the old text values for the override mode, so we can update them if the language is changed + coriolis_config.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto + coriolis_config.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal + coriolis_config.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta + conf_frame = nb.Frame(parent) conf_frame.columnconfigure(index=1, weight=1) cur_row = 0 @@ -157,6 +165,24 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: _('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto }.get(coriolis_config.override_mode, coriolis_config.override_mode) + # Check if the language was changed and the override_mode was valid before the change + if coriolis_config.override_mode not in ('beta', 'normal', 'auto'): + coriolis_config.override_mode = { + coriolis_config.override_text_old_normal: 'normal', + coriolis_config.override_text_old_beta: 'beta', + coriolis_config.override_text_old_auto: 'auto', + }.get(coriolis_config.override_mode, coriolis_config.override_mode) + # Language was seemingly changed, so we need to update the textvars + if coriolis_config.override_mode in ('beta', 'normal', 'auto'): + coriolis_config.override_textvar.set( + value={ + 'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection + 'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection + 'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection + }.get(coriolis_config.override_mode) + ) + + # If the override mode is still invalid, default to auto if coriolis_config.override_mode not in ('beta', 'normal', 'auto'): logger.warning(f'Unexpected value {coriolis_config.override_mode=!r}. Defaulting to "auto"') coriolis_config.override_mode = 'auto' From 51fea68909486235f2582bc07a48b957ded44982 Mon Sep 17 00:00:00 2001 From: Phoebe Date: Mon, 19 Feb 2024 23:23:56 +0100 Subject: [PATCH 085/261] [Minor] Flake8 and mypy --- plugins/coriolis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/coriolis.py b/plugins/coriolis.py index 1c34e2173..70de067f2 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -94,7 +94,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr coriolis_config.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto coriolis_config.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal coriolis_config.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta - + conf_frame = nb.Frame(parent) conf_frame.columnconfigure(index=1, weight=1) cur_row = 0 @@ -179,9 +179,9 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: 'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection 'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection 'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection - }.get(coriolis_config.override_mode) + }.get(coriolis_config.override_mode, _('Auto')) # LANG: 'Auto' label for Coriolis site override selection ) - + # If the override mode is still invalid, default to auto if coriolis_config.override_mode not in ('beta', 'normal', 'auto'): logger.warning(f'Unexpected value {coriolis_config.override_mode=!r}. Defaulting to "auto"') From e322dedba7e9b7f8bccf3f17087cb54d51d8a37a Mon Sep 17 00:00:00 2001 From: Phoebe Date: Mon, 19 Feb 2024 23:30:16 +0100 Subject: [PATCH 086/261] Flake8 fix the second --- plugins/coriolis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/coriolis.py b/plugins/coriolis.py index 70de067f2..3d5659e36 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -179,7 +179,8 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: 'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection 'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection 'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection - }.get(coriolis_config.override_mode, _('Auto')) # LANG: 'Auto' label for Coriolis site override selection + # LANG: 'Auto' label for Coriolis site override selection + }.get(coriolis_config.override_mode, _('Auto')) ) # If the override mode is still invalid, default to auto From 57a1f1cdd9aeb679d1d1b0972d5611ea014e3b0b Mon Sep 17 00:00:00 2001 From: Phoebe Date: Mon, 19 Feb 2024 23:42:39 +0100 Subject: [PATCH 087/261] Flake8 the third --- plugins/coriolis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/coriolis.py b/plugins/coriolis.py index 3d5659e36..c142c686e 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -179,7 +179,7 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: 'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection 'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection 'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection - # LANG: 'Auto' label for Coriolis site override selection + # LANG: 'Auto' label for Coriolis site override selection }.get(coriolis_config.override_mode, _('Auto')) ) From 41b789c5d2c8b4a1ef7b455d20beb017f6d60f03 Mon Sep 17 00:00:00 2001 From: Phoebe Date: Sun, 25 Feb 2024 12:27:07 +0100 Subject: [PATCH 088/261] Update en.template --- L10n/en.template | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/L10n/en.template b/L10n/en.template index 5c07ae8f3..8793873a1 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -255,13 +255,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Default"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Auto"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normal"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ From af3b9d250a5aae2a346d8c617622c61c68a9aa64 Mon Sep 17 00:00:00 2001 From: Phoebe Date: Sun, 25 Feb 2024 15:20:14 +0100 Subject: [PATCH 089/261] Check for and use ``isThargoid`` flag - Check and use ``isThargoid`` flag for Interdiction events. - Also updates the Paranoia check to also work in case of missing and not just empty parameter. --- plugins/inara.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 121ae2276..601bb98e5 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1019,8 +1019,11 @@ def journal_entry( # noqa: C901, CCR001 elif 'Power' in entry: data['opponentName'] = entry['Power'] + elif 'IsThargoid' in entry and entry['IsThargoid']: + data['opponentName'] = 'Thargoid' + # Paranoia in case of e.g. Thargoid activity not having complete data - if data['opponentName'] == "": + if 'opponentName' not in data or data['opponentName'] == "": logger.warning('Dropping addCommanderCombatInterdicted message because opponentName came out as ""') else: @@ -1042,8 +1045,13 @@ def journal_entry( # noqa: C901, CCR001 elif 'Power' in entry: data['opponentName'] = entry['Power'] + # Shouldn't be needed here as Interdiction events can't target Thargoids (yet) + # but done just in case of future changes or so + elif 'IsThargoid' in entry and entry['IsThargoid']: + data['opponentName'] = 'Thargoid' + # Paranoia in case of e.g. Thargoid activity not having complete data - if data['opponentName'] == "": + if 'opponentName' not in data or data['opponentName'] == "": logger.warning('Dropping addCommanderCombatInterdiction message because opponentName came out as ""') else: From a45527e104932a28be7955f6f8d153428a83ce9b Mon Sep 17 00:00:00 2001 From: Phoebe Date: Sun, 25 Feb 2024 15:32:44 +0100 Subject: [PATCH 090/261] Check for ``EscapeInterdiction`` events as well Also rewrites the ``EscapeInterdiction`` path to match the other Interdiction paths. --- plugins/inara.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 601bb98e5..8528f07b7 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1058,23 +1058,23 @@ def journal_entry( # noqa: C901, CCR001 new_add_event('addCommanderCombatInterdiction', entry['timestamp'], data) elif event_name == 'EscapeInterdiction': + data = { + 'starsystemName': system, + 'isPlayer': entry['IsPlayer'], + } + + if 'Interdictor' in entry: + data['opponentName'] = entry['Interdictor'] + + elif 'isThargoid' in entry and entry['isThargoid']: + data['opponentName'] = 'Thargoid' + # Paranoia in case of e.g. Thargoid activity not having complete data - if entry.get('Interdictor') is None or entry['Interdictor'] == "": - logger.warning( - 'Dropping addCommanderCombatInterdictionEscape message' - 'because opponentName came out as ""' - ) + if 'opponentName' not in data or data['opponentName'] == "": + logger.warning('Dropping addCommanderCombatInterdiction message because opponentName came out as ""') else: - new_add_event( - 'addCommanderCombatInterdictionEscape', - entry['timestamp'], - { - 'starsystemName': system, - 'opponentName': entry['Interdictor'], - 'isPlayer': entry['IsPlayer'], - } - ) + new_add_event('addCommanderCombatInterdictionEscape', entry['timestamp'], data) elif event_name == 'PVPKill': new_add_event( From 68a2767c39294fa9683d6e6f838b760d8c7de5bd Mon Sep 17 00:00:00 2001 From: Phoebe Date: Sun, 25 Feb 2024 19:14:08 +0100 Subject: [PATCH 091/261] Match remaining parts of ``EscapeInterdiction`` --- plugins/inara.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/inara.py b/plugins/inara.py index 8528f07b7..0a18b9ae8 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1066,6 +1066,12 @@ def journal_entry( # noqa: C901, CCR001 if 'Interdictor' in entry: data['opponentName'] = entry['Interdictor'] + elif 'Faction' in entry: + data['opponentName'] = entry['Faction'] + + elif 'Power' in entry: + data['opponentName'] = entry['Power'] + elif 'isThargoid' in entry and entry['isThargoid']: data['opponentName'] = 'Thargoid' From 249f89f0fef2b9e32e5592d8a5a528a81c09ce8c Mon Sep 17 00:00:00 2001 From: Phoebe Date: Sun, 25 Feb 2024 19:46:49 +0100 Subject: [PATCH 092/261] Add checks to ``Died`` event --- plugins/inara.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/inara.py b/plugins/inara.py index 0a18b9ae8..ae416f84e 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1001,7 +1001,17 @@ def journal_entry( # noqa: C901, CCR001 elif 'KillerName' in entry: data['opponentName'] = entry['KillerName'] - new_add_event('addCommanderCombatDeath', entry['timestamp'], data) + elif 'KillerShip' in entry: + data['opponentName'] = entry['KillerShip'] + + # Paranoia in case of e.g. Thargoid activity not having complete data + opponent_name_issue = 'opponentName' not in data or data['opponentName'] == "" + wing_opponent_names_issue = 'wingOpponentNames' not in data or data['wingOpponentNames'] == [] + if opponent_name_issue and wing_opponent_names_issue: + logger.warning('Dropping addCommanderCombatDeath message because opponentName and wingOpponentNames came out as ""') + + else: + new_add_event('addCommanderCombatDeath', entry['timestamp'], data) elif event_name == 'Interdicted': data = { From 99daff1295e4088a61051bfe9e7002aa9a35b0be Mon Sep 17 00:00:00 2001 From: Phoebe Date: Sun, 25 Feb 2024 20:45:59 +0100 Subject: [PATCH 093/261] Flake8 --- plugins/inara.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/inara.py b/plugins/inara.py index ae416f84e..0e0eb7bff 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1008,7 +1008,8 @@ def journal_entry( # noqa: C901, CCR001 opponent_name_issue = 'opponentName' not in data or data['opponentName'] == "" wing_opponent_names_issue = 'wingOpponentNames' not in data or data['wingOpponentNames'] == [] if opponent_name_issue and wing_opponent_names_issue: - logger.warning('Dropping addCommanderCombatDeath message because opponentName and wingOpponentNames came out as ""') + logger.warning('Dropping addCommanderCombatDeath message' + 'because opponentName and wingOpponentNames came out as ""') else: new_add_event('addCommanderCombatDeath', entry['timestamp'], data) From f19ed7b6425f6fb2ce5804756a269822798ad0fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:43:40 +0000 Subject: [PATCH 094/261] build(deps-dev): bump setuptools from 69.0.3 to 69.1.1 Bumps [setuptools](https://github.com/pypa/setuptools) from 69.0.3 to 69.1.1. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v69.0.3...v69.1.1) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2e9e563e1..976512215 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ wheel # We can't rely on just picking this up from either the base (not venv), # or venv-init-time version. Specify here so that dependabot will prod us # about new versions. -setuptools==69.0.3 +setuptools==69.1.1 # Static analysis tools flake8==7.0.0 From 2fc9e49092d80d21525c6f7fd415fca408a329fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:43:46 +0000 Subject: [PATCH 095/261] build(deps-dev): bump pytest from 7.4.3 to 8.0.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.3 to 8.0.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.3...8.0.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2e9e563e1..c8adb5916 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -38,7 +38,7 @@ grip==4.6.2 py2exe==0.13.0.1; sys_platform == 'win32' # Testing -pytest==7.4.3 +pytest==8.0.2 pytest-cov==4.1.0 # Pytest code coverage support coverage[toml]==7.4.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 From 15efc590cae246cc71c6455d971b991f6cbe1d40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:43:53 +0000 Subject: [PATCH 096/261] build(deps-dev): bump pre-commit from 3.6.0 to 3.6.2 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.6.0 to 3.6.2. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.6.0...v3.6.2) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2e9e563e1..839d60230 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,7 +28,7 @@ types-pkg-resources==0.1.3 autopep8==2.0.4 # Git pre-commit checking -pre-commit==3.6.0 +pre-commit==3.6.2 # HTML changelogs grip==4.6.2 From 110f9efd07f9b91ae5a0894af841a330855a1644 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 4 Mar 2024 18:37:00 -0500 Subject: [PATCH 097/261] [#635] Properly Convert Message Message should have been updated for the new format. It got missed here. --- plugins/eddn.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 55ab729be..522ce6cb9 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -801,15 +801,15 @@ def export_outfitting(self, data: CAPIData, is_beta: bool) -> None: self.send_message(data['commander']['name'], { '$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}', 'message': { - ('timestamp', data['timestamp']), - ('systemName', data['lastSystem']['name']), - ('stationName', data['lastStarport']['name']), - ('marketId', data['lastStarport']['id']), - ('horizons', horizons), - ('modules', outfitting), - ('odyssey', this.odyssey), + 'timestamp': data['timestamp'], + 'systemName': data['lastSystem']['name'], + 'stationName': data['lastStarport']['name'], + 'marketId': data['lastStarport']['id'], + 'horizons': horizons, + 'modules': outfitting, + 'odyssey': this.odyssey, }, - 'header': self.standard_header( + 'header': self.standard_header( game_version=self.capi_gameversion_from_host_endpoint( data.source_host, companion.Session.FRONTIER_CAPI_PATH_SHIPYARD ), @@ -863,13 +863,13 @@ def export_shipyard(self, data: CAPIData, is_beta: bool) -> None: self.send_message(data['commander']['name'], { '$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}', 'message': { - ('timestamp', data['timestamp']), - ('systemName', data['lastSystem']['name']), - ('stationName', data['lastStarport']['name']), - ('marketId', data['lastStarport']['id']), - ('horizons', horizons), - ('ships', shipyard), - ('odyssey', this.odyssey), + 'timestamp': data['timestamp'], + 'systemName': data['lastSystem']['name'], + 'stationName': data['lastStarport']['name'], + 'marketId': data['lastStarport']['id'], + 'horizons': horizons, + 'ships': shipyard, + 'odyssey': this.odyssey, }, 'header': self.standard_header( game_version=self.capi_gameversion_from_host_endpoint( From 8d43d00cfe0c652dfc3843d83ff1e71908011433 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 13 Mar 2024 12:25:01 +0000 Subject: [PATCH 098/261] updating submodules --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index 510c05d69..7e1e4ae53 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 510c05d69977bcbc21712b64780d11282da30282 +Subproject commit 7e1e4ae533b5c2c1011dead192a516006bfa3f74 From dd9d6e94cfd87080dd2127a4cac201b3cdbc5973 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 15 Mar 2024 12:24:10 +0000 Subject: [PATCH 099/261] updating submodules --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index 7e1e4ae53..7205c7933 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 7e1e4ae533b5c2c1011dead192a516006bfa3f74 +Subproject commit 7205c79331f42c1a28b757b27467f79ff106716b From 9020b7c0c8cb43d010d462412a5e25e8cff17d9a Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 15 Mar 2024 15:33:12 -0400 Subject: [PATCH 100/261] Update edmarketconnector.xml --- edmarketconnector.xml | 55 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index b3e5f1976..a0cac14da 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,11 +22,58 @@ - Release 5.10.1 + Release 5.10.2 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

            We now test against, and package with, Python 3.11.7.

            -

            As a consequence of this we no longer support Windows 7.
            +

            As a result, we do not support Windows 7, 8, or 8.1.
            + +

            Release 5.10.2

            +

            This release contains updated dependencies, some bug fixes, a few minor enhancements to some supporting files, +and some resorted resources as well as a new image for some of the built EXEs.

            + +We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit
            https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC +

            Changes and Enhancements

            +
              +
            • Added additional logging to the Python build string in the case of missing files
            • +
            • Added a new icon to EDMC's Command-Line EXE
            • +
            • Added additional logging to the build system
            • +
            • Updated several dependencies
            • +
            • Updated FDEV IDs
            • +
            • Updated relevant copyright dates
            • +
            • Updated automatic build script to support code signing workflow
            • +
            • Updated translations to the latest versions
            • +
            • Moved a few unused files to the resources folder. These files have no references in the code
            • +
            +

            Bug Fixes

            +
              +
            • Fixed a bug that could cause EDMC to handle SIGINT signals improperly
            • +
            • Fixed a bug that could result in URL providers to be set to invalid values
            • +
            • Fixed a bug that could result in Coriolis URL providers to revert back to "Auto" on language translations
            • +
            • Fixed a bug where Inara didn't understand being blown up by a Thargoid, and blew itself up instead
            • +
            • Fixed a printing issue for the localization system for unused strings
            • +
            +

            Removed Files

            +
              +
            • Removed two unused manifest and MacOS icon files which are no longer in use.
            • +
            +

            Known Issues

            + +

            Plugin Developers

            +
              +
            • modules.p and ships.p are deprecated, and slated +for removal in the next major release! Please look for that change coming soon.
            • +
            • Note to plugin developers: The openurl() function in ttkHyperlinkLabel has been deprecated, +and slated for removal in the next major release! Please migrate to webbrowser.open().
            • +
            + +

            Release 5.10.1

            This release contains a number of bugfixes, minor performance enhancements, workflow and dependency updates, and a function deprecation.

            @@ -2138,7 +2185,7 @@ about this: PTS CAPI saying Commander is Docked after jumping to new system.

          ]]>
          - +
          From 5b85e323743e57ca1f1cd056d007f3ce4a7e48ad Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 16 Mar 2024 13:21:43 -0400 Subject: [PATCH 101/261] [901] Add Status Update for Incomplete Logins --- EDMarketConnector.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 8fcab4ba0..e6a4d784a 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -809,6 +809,8 @@ def open_window(systray: 'SysTrayIcon') -> None: # Check for Valid Providers validate_providers() + if monitor.cmdr is None: + self.status['text'] = _("Awaiting Full CMDR Login") # Start a protocol handler to handle cAPI registration. Requires main loop to be running. self.w.after_idle(lambda: protocol.protocolhandler.start(self.w)) From 6da7597e796cadcfb702c5689fdce833873a661c Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 23 Mar 2024 10:27:01 -0400 Subject: [PATCH 102/261] [Lang] Add Language Check --- EDMarketConnector.py | 2 +- L10n/en.template | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index e6a4d784a..6debf4e98 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -810,7 +810,7 @@ def open_window(systray: 'SysTrayIcon') -> None: # Check for Valid Providers validate_providers() if monitor.cmdr is None: - self.status['text'] = _("Awaiting Full CMDR Login") + self.status['text'] = _("Awaiting Full CMDR Login") # LANG: Await Full CMDR Login to Game # Start a protocol handler to handle cAPI registration. Requires main loop to be running. self.w.after_idle(lambda: protocol.protocolhandler.start(self.w)) diff --git a/L10n/en.template b/L10n/en.template index 8793873a1..f5acb3772 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -240,6 +240,9 @@ /* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ "EDMC: Default Providers Reset" = "EDMC: Default Providers Reset"; +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Awaiting Full CMDR Login"; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Journal directory already locked"; From b25249ad8f4c846e235838aa79e78f3eacc8a4d0 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 23 Mar 2024 11:29:24 -0400 Subject: [PATCH 103/261] [2176] Fix Outfitting Split Fixes a bug introduced in e268c24 --- outfitting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/outfitting.py b/outfitting.py index 7a343a8e4..a1b6b9db6 100644 --- a/outfitting.py +++ b/outfitting.py @@ -67,7 +67,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 # Armour - e.g. Federation_Dropship_Armour_Grade2 if name[-2] == 'armour': # Armour is ship-specific, and ship names can have underscores - ship_name, armour_grade = module["name"].lower().rsplit("_", 2)[0:2] + ship_name, armour, armour_grade = module["name"].lower().rsplit("_", 2)[0:3] if ship_name not in ship_map: raise AssertionError(f"Unknown ship: {ship_name}") new['category'] = 'standard' From bf6d753a7152b760740d621d99f6be3f3793ad71 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 23 Mar 2024 11:39:29 -0400 Subject: [PATCH 104/261] [Lang] Update French --- L10n/fr.strings | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/L10n/fr.strings b/L10n/fr.strings index 93e097770..5d53fd24a 100644 --- a/L10n/fr.strings +++ b/L10n/fr.strings @@ -1,21 +1,59 @@ +/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ +"Send flight log and CMDR status to EDSM" = "Envoyer les données de vol et le status de CMDR à EDSM"; + +/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */ +"Open Log Folder" = "Ouvrir le dossier de log"; + +/* inara.py:Text Inara Show API key; In files: inara.py:305; */ +"Show API Key" = "Afficher la clé API"; /* Language name */ "!Language" = "Français"; +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */ +"Error: Frontier CAPI didn't respond" = "L'API Compagnon de Frontier ne répond pas"; + /* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */ "Error: Frontier server is lagging" = "Erreur : Le serveur Frontier ne répond pas"; +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:261; */ +"Docked but unknown station: EDO Settlement?" = "Amarré, mais station inconnue : colonie EDO ?"; + /* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */ "Error: Invalid Credentials" = "Erreur : Identifiants invalides"; /* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */ "Error: Wrong Cmdr" = "Erreur : Cmdr incorrect"; +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */ +"Error" = "Erreur"; + +/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:475; companion.py:480; */ +"Error: Couldn't check token customer_id" = "Erreur : Impossible de vérifier le jeton customer_id"; + +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */ +"Error: customer_id doesn't match!" = "Erreur : customer_id ne correspond pas !"; + +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ +"Error: unable to get token" = "Erreur : impossible d'obtenir le jeton"; + +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ +"Frontier CAPI down for maintenance" = "L'API Compagnon de Frontier est en panne pour maintenance"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */ +"Frontier CAPI query failure" = "Échec de la requête à l'API Compagnon Frontier"; + /* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */ "Update" = "Mettre à jour"; /* EDMarketConnector.py: Appearance - Label for checkbox to select if application always on top; prefs.py: Appearance - Label for checkbox to select if application always on top; In files: EDMarketConnector.py:710; prefs.py:875; */ "Always on top" = "Toujours visible"; +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */ +"Unknown" = "Inconnue"; + +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */ +"Error: Check E:D journal file location" = "Erreur : Vérifier l'emplacement du fichier de journal Elite : Dangerous"; + /* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */ "Cmdr" = "Cmd"; @@ -64,6 +102,12 @@ /* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */ "Documentation" = "Documentation"; +/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */ +"Troubleshooting" = "Résolution de problème"; + +/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */ +"Report A Bug" = "Signaler un bug"; + /* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */ "Privacy Policy" = "Politique de Confidentialité"; From b8fedc84e4b9b7fd12dd6ec85fac5b12805357d8 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 23 Mar 2024 12:01:54 -0400 Subject: [PATCH 105/261] [5.10.3] Update Changelog and Version String --- ChangeLog.md | 19 +++++++++++++++++++ config/__init__.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 480d80a05..6ee5749f0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,25 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- +Release 5.10.3 +=== +This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations. + +We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC + +**Changes and Enhancements** +* Updated French Translations + +**Bug Fixes** +* Fixed a bug that crashed the outfitting system when encountering armor. (Thanks TCE team for identifying this one!) + +**Plugin Developers** +* modules.p and ships.p are deprecated, and slated +for removal in the next major release! Please look for that change coming soon. +* Note to plugin developers: The `openurl()` function in ttkHyperlinkLabel has been deprecated, +and slated for removal in the next major release! Please migrate to `webbrowser.open()`. + Release 5.10.2 === This release contains updated dependencies, some bug fixes, a few minor enhancements to some supporting files, diff --git a/config/__init__.py b/config/__init__.py index 0d16693cb..3acb2edd3 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.10.2' +_static_appversion = '5.10.3' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From a53cf58c4b2452d2e1558f5737b2eabbd27271a8 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 23 Mar 2024 12:19:19 -0400 Subject: [PATCH 106/261] Update edmarketconnector.xml --- edmarketconnector.xml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index a0cac14da..82dd8b5d0 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,11 +22,33 @@ - Release 5.10.2 + Release 5.10.3 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }

          We now test against, and package with, Python 3.11.7.

          As a result, we do not support Windows 7, 8, or 8.1.
          +

          Release 5.10.3

          +

          This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations.

          +We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC +

          Changes and Enhancements

          +
            +
          • Updated French Translations
          • +
          +

          Bug Fixes

          +
            +
          • Fixed a bug that crashed the outfitting system when encountering armor. (Thanks TCE team for identifying this one!)
          • +
          +

          Plugin Developers

          +
            +
          • modules.p and ships.p are deprecated, and slated +for removal in the next major release! Please look for that change coming soon.
          • +
          • Note to plugin developers: The openurl() function in ttkHyperlinkLabel has been deprecated, +and slated for removal in the next major release! Please migrate to webbrowser.open().
          • +
          + +
          +

          Release 5.10.2

          This release contains updated dependencies, some bug fixes, a few minor enhancements to some supporting files, and some resorted resources as well as a new image for some of the built EXEs.

          @@ -2185,7 +2207,7 @@ about this: PTS CAPI saying Commander is Docked after jumping to new system.

        ]]>
        - +
        From 14a38688e164ef1b2d639f85bccbe9995588d32c Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 23 Mar 2024 14:42:18 -0400 Subject: [PATCH 107/261] [2155] Add DockingDenied and DockingGranted Schemas I love the schema system. It's so simple! --- plugins/eddn.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 522ce6cb9..8ac1c41fe 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1890,6 +1890,74 @@ def export_journal_fsssignaldiscovered( return None + def export_journal_dockingdenied( + self, cmdr: str, is_beta: bool, entry: Mapping[str, Any] + ) -> str | None: + """ + Send a DockingDenied to EDDN on the correct schema. + + :param cmdr: the commander under which this upload is made + :param is_beta: whether or not we are in beta mode + :param entry: the journal entry to send + + Example: + { + "timestamp":"2022-06-10T10:09:41Z", + "event":"DockingDenied", + "Reason":"RestrictedAccess", + "MarketID":3706117376, + "StationName":"V7G-T1G", + "StationType":"FleetCarrier" + } + """ + ####################################################################### + # Elisions + ####################################################################### + # In case Frontier ever add any + entry = filter_localised(entry) + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/dockingdenied/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.send_message(cmdr, msg) + return None + + def export_journal_dockinggranted( + self, cmdr: str, is_beta: bool, entry: Mapping[str, Any] + ) -> str | None: + """ + Send a DockingDenied to EDDN on the correct schema. + + :param cmdr: the commander under which this upload is made + :param is_beta: whether or not we are in beta mode + :param entry: the journal entry to send + + Example: + { + "timestamp":"2023-10-01T14:56:34Z", + "event":"DockingGranted", + "LandingPad":41, + "MarketID":3227312896, + "StationName":"Evans Horizons", + "StationType":"Coriolis" + } + """ + ####################################################################### + # Elisions + ####################################################################### + # In case Frontier ever add any + entry = filter_localised(entry) + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/dockinggranted/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.send_message(cmdr, msg) + return None + def canonicalise(self, item: str) -> str: """ Canonicalise the given commodity name. @@ -2337,6 +2405,12 @@ def journal_entry( # noqa: C901, CCR001 if event_name == 'fcmaterials': return this.eddn.export_journal_fcmaterials(cmdr, is_beta, entry) + if event_name == "dockingdenied": + return this.eddn.export_journal_dockingdenied(cmdr, is_beta, entry) + + if event_name == "dockinggranted": + return this.eddn.export_journal_dockinggranted(cmdr, is_beta, entry) + if event_name == 'approachsettlement': # An `ApproachSettlement` can appear *before* `Location` if you # logged at one. We won't have necessary augmentation data From 813cf92521045cbe121c5215e4e2d56859738ce5 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 23 Mar 2024 16:54:13 -0400 Subject: [PATCH 108/261] [1133] Add ContextMenu Globally --- monitor.py | 2 ++ myNotebook.py | 54 +++++++++++++++++++++++++++++++++++++++++--- requirements-dev.txt | 14 ++++++------ 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/monitor.py b/monitor.py index 8364f95bf..e7f99543d 100644 --- a/monitor.py +++ b/monitor.py @@ -63,6 +63,8 @@ def _(x: str) -> str: GetWindowText = ctypes.windll.user32.GetWindowTextW GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int] GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW + GetWindowTextLength.argtypes = [ctypes.wintypes.HWND] + GetWindowTextLength.restype = ctypes.c_int GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd diff --git a/myNotebook.py b/myNotebook.py index 6fa357746..02b1eb45e 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -77,7 +77,7 @@ class Label(tk.Label): """Custom tk.Label class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that + # This format chosen over `sys.platform in (...)` as mypy and friends don't understand that if sys.platform in ('darwin', 'win32'): kw['foreground'] = kw.pop('foreground', PAGEFG) kw['background'] = kw.pop('background', PAGEBG) @@ -87,7 +87,55 @@ def __init__(self, master: ttk.Frame | None = None, **kw): tk.Label.__init__(self, master, **kw) # Just use tk.Label on all platforms -class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): # type: ignore +class EntryMenu(ttk.Entry): + """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands.""" + + def __init__(self, *args: ttk.Frame | None, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self.menu = tk.Menu(self, tearoff=False) + self.menu.add_command(label="Copy", command=self.copy) + self.menu.add_command(label="Cut", command=self.cut) + self.menu.add_separator() + self.menu.add_command(label="Paste", command=self.paste) + self.menu.add_separator() + self.menu.add_command(label="Select All", command=self.select_all) + + self.bind("", self.display_popup) + + def display_popup(self, event: tk.Event) -> None: + """Display the menu popup.""" + self.menu.post(event.x_root, event.y_root) + + def select_all(self) -> None: + """Select all the text within the Entry.""" + self.selection_range(0, tk.END) + self.focus_set() + + def copy(self) -> None: + """Copy the selected Entry text.""" + if self.selection_present(): + self.clipboard_clear() + self.clipboard_append(self.selection_get()) + + def cut(self) -> None: + """Cut the selected Entry text.""" + if self.selection_present(): + self.copy() + self.delete(tk.SEL_FIRST, tk.SEL_LAST) + + def paste(self) -> None: + """Paste the selected Entry text.""" + if self.selection_present(): + self.delete(tk.SEL_FIRST, tk.SEL_LAST) + try: + text = self.clipboard_get() + self.insert(tk.INSERT, text) + except tk.TclError: + pass # No text in clipboard or clipboard is not text + + +class Entry(sys.platform == 'darwin' and tk.Entry or EntryMenu or ttk.Entry): # type: ignore """Custom t(t)k.Entry class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): @@ -95,7 +143,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) tk.Entry.__init__(self, master, **kw) else: - ttk.Entry.__init__(self, master, **kw) + EntryMenu.__init__(self, master, **kw) class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): # type: ignore diff --git a/requirements-dev.txt b/requirements-dev.txt index 73ab5465e..41c3a9281 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ wheel # We can't rely on just picking this up from either the base (not venv), # or venv-init-time version. Specify here so that dependabot will prod us # about new versions. -setuptools==69.1.1 +setuptools==69.2.0 # Static analysis tools flake8==7.0.0 @@ -18,14 +18,14 @@ flake8-noqa==1.4.0 flake8-polyfill==1.0.2 flake8-use-fstring==1.4 -mypy==1.8.0 +mypy==1.9.0 pep8-naming==0.13.3 -safety==2.3.5 -types-requests==2.31.0.20240125 +safety==3.0.1 +types-requests==2.31.0.20240311 types-pkg-resources==0.1.3 # Code formatting tools -autopep8==2.0.4 +autopep8==2.1.0 # Git pre-commit checking pre-commit==3.6.2 @@ -38,9 +38,9 @@ grip==4.6.2 py2exe==0.13.0.1; sys_platform == 'win32' # Testing -pytest==8.0.2 +pytest==8.1.1 pytest-cov==4.1.0 # Pytest code coverage support -coverage[toml]==7.4.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==7.4.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 # For manipulating folder permissions and the like. pywin32==306; sys_platform == 'win32' From ccda74c8f103cff0d63d3d605a0de076f53a77d9 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 23 Mar 2024 17:05:14 -0400 Subject: [PATCH 109/261] [Minor] Remove Crappy Type Hint --- myNotebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/myNotebook.py b/myNotebook.py index 02b1eb45e..63bb7dc89 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -90,7 +90,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class EntryMenu(ttk.Entry): """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands.""" - def __init__(self, *args: ttk.Frame | None, **kwargs) -> None: + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.menu = tk.Menu(self, tearoff=False) From 791a0c80c2c0b202d27a636c36302229af88c328 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 18:36:03 -0400 Subject: [PATCH 110/261] [Minor] Remove Deprecated Files and Functions --- build.py | 2 -- modules.p | Bin 44802 -> 0 bytes ships.p | Bin 920 -> 0 bytes ttkHyperlinkLabel.py | 41 ----------------------------------------- 4 files changed, 43 deletions(-) delete mode 100644 modules.p delete mode 100644 ships.p diff --git a/build.py b/build.py index 1bc967651..904c4c5b8 100644 --- a/build.py +++ b/build.py @@ -76,10 +76,8 @@ def generate_data_files( "ChangeLog.md", "snd_good.wav", "snd_bad.wav", - "modules.p", # TODO: Remove in 6.0 "modules.json", "ships.json", - "ships.p", # TODO: Remove in 6.0 f"{app_name}.ico", f"resources/{appcmdname}.ico", "EDMarketConnector - TRACE.bat", diff --git a/modules.p b/modules.p deleted file mode 100644 index c48f76c4894fdb061e0a1967f388afe819e679c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44802 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 None: """Copy the current text to the clipboard.""" self.clipboard_clear() self.clipboard_append(self['text']) - - -def openurl(url: str) -> None: - r""" - Open the given URL in appropriate browser. - - 2022-12-06: - Firefox itself will gladly attempt to use very long URLs in its URL - input. Up to 16384 was attempted, but the Apache instance this was - tested against only allowed up to 8207 total URL length to pass, that - being 8190 octets of REQUEST_URI (path + GET params). - - Testing from Windows 10 Home 21H2 cmd.exe with: - - "\firefox.exe" -osint -url "" - - only allowed 8115 octest of REQUEST_URI to pass through. - - Microsoft Edge yielded 8092 octets. Google Chrome yielded 8093 octets. - - However, this is actually the limit of how long a CMD.EXE command-line - can be. The URL was being cut off *there*. - - The 8207 octet URL makes it through `webbrowser.open()` to: - - Firefox 107.0.1 - Microsoft Edge 108.0.1462.42 - Google Chrome 108.0.5359.95 - - This was also tested as working *with* the old winreg/subprocess code, - so it wasn't even suffering from the same limit as CMD.EXE. - - Conclusion: No reason to not just use `webbrowser.open()`, as prior - to e280d6c2833c25867b8139490e68ddf056477917 there was a bug, introduced - in 5989acd0d3263e54429ff99769ff73a20476d863, which meant the code always - ended up using `webbrowser.open()` *anyway*. - :param url: URL to open. - """ - warnings.warn("This function is deprecated. " - "Please use `webbrowser.open() instead.", DeprecationWarning, stacklevel=2) - webbrowser.open(url) From 57b6ecd88ea9bd989518ddc676db13753ba74973 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 19:16:45 -0400 Subject: [PATCH 111/261] [2186] Remove Config and Hotkey --- config/__init__.py | 3 - config/darwin.py | 191 ------------------------------- hotkey/__init__.py | 4 - hotkey/darwin.py | 276 --------------------------------------------- 4 files changed, 474 deletions(-) delete mode 100644 config/darwin.py delete mode 100644 hotkey/darwin.py diff --git a/config/__init__.py b/config/__init__.py index 3acb2edd3..992710a0f 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -468,9 +468,6 @@ def get_config(*args, **kwargs) -> AbstractConfig: :param kwargs: Args to be passed through to implementation. :return: Instance of the implementation. """ - if sys.platform == "darwin": # pragma: sys-platform-darwin - from .darwin import MacConfig - return MacConfig(*args, **kwargs) if sys.platform == "win32": # pragma: sys-platform-win32 from .windows import WinConfig diff --git a/config/darwin.py b/config/darwin.py deleted file mode 100644 index 9c15ec32d..000000000 --- a/config/darwin.py +++ /dev/null @@ -1,191 +0,0 @@ -""" -darwin.py - Darwin/macOS implementation of AbstractConfig. - -Copyright (c) EDCD, All Rights Reserved -Licensed under the GNU General Public License. -See LICENSE file. -""" -from __future__ import annotations - -import pathlib -import sys -from typing import Any -from Foundation import ( # type: ignore - NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults, - NSUserDomainMask -) -from config import AbstractConfig, appname, logger - -assert sys.platform == 'darwin' - - -class MacConfig(AbstractConfig): - """MacConfig is the implementation of AbstractConfig for Darwin based OSes.""" - - def __init__(self) -> None: - super().__init__() - support_path = pathlib.Path( - NSSearchPathForDirectoriesInDomains( - NSApplicationSupportDirectory, NSUserDomainMask, True - )[0] - ) - - self.app_dir_path = support_path / appname - self.app_dir_path.mkdir(exist_ok=True) - - self.plugin_dir_path = self.app_dir_path / 'plugins' - self.plugin_dir_path.mkdir(exist_ok=True) - - # Bundle IDs identify a singled app though out a system - - if getattr(sys, 'frozen', False): - exe_dir = pathlib.Path(sys.executable).parent - self.internal_plugin_dir_path = exe_dir.parent / 'Library' / 'plugins' - self.respath_path = exe_dir.parent / 'Resources' - self.identifier = NSBundle.mainBundle().bundleIdentifier() - - else: - file_dir = pathlib.Path(__file__).parent.parent - self.internal_plugin_dir_path = file_dir / 'plugins' - self.respath_path = file_dir - - self.identifier = f'uk.org.marginal.{appname.lower()}' - NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier - - self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous' - self._defaults: Any = NSUserDefaults.standardUserDefaults() - self._settings: dict[str, int | str | list] = dict( - self._defaults.persistentDomainForName_(self.identifier) or {} - ) # make writeable - - if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists(): - self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]) - - def __raw_get(self, key: str) -> None | list | str | int: - """ - Retrieve the raw data for the given key. - - :param str: str - The key data is being requested for. - :return: The requested data. - """ - res = self._settings.get(key) - # On MacOS Catalina, with python.org python 3.9.2 any 'list' - # has type __NSCFArray so a simple `isinstance(res, list)` is - # False. So, check it's not-None, and not the other types. - # - # If we can find where to import the definition of NSCFArray - # then we could possibly test against that. - if res is not None and not isinstance(res, str) and not isinstance(res, int): - return list(res) - - return res - - def get_str(self, key: str, *, default: str = None) -> str: - """ - Return the string referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_str`. - """ - res = self.__raw_get(key) - if res is None: - return default # Yes it could be None, but we're _assuming_ that people gave us a default - - if not isinstance(res, str): - raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}') - - return res - - def get_list(self, key: str, *, default: list = None) -> list: - """ - Return the list referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_list`. - """ - res = self.__raw_get(key) - if res is None: - return default # Yes it could be None, but we're _assuming_ that people gave us a default - - if not isinstance(res, list): - raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') - - return res - - def get_int(self, key: str, *, default: int = 0) -> int: - """ - Return the int referred to by key if it exists in the config. - - Implements :meth:`AbstractConfig.get_int`. - """ - res = self.__raw_get(key) - if res is None: - return default - - if not isinstance(res, (str, int)): - raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') - - try: - return int(res) - - except ValueError as e: - logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}') - return default # Yes it could be None, but we're _assuming_ that people gave us a default - - def get_bool(self, key: str, *, default: bool = None) -> bool: - """ - Return the bool referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_bool`. - """ - res = self.__raw_get(key) - if res is None: - return default # Yes it could be None, but we're _assuming_ that people gave us a default - - if not isinstance(res, bool): - raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') - - return res - - def set(self, key: str, val: int | str | list[str] | bool) -> None: - """ - Set the given key's data to the given value. - - Implements :meth:`AbstractConfig.set`. - """ - if self._settings is None: - raise ValueError('attempt to use a closed _settings') - - if not isinstance(val, (bool, str, int, list)): - raise ValueError(f'Unexpected type for value {type(val)=}') - - self._settings[key] = val - - def delete(self, key: str, *, suppress=False) -> None: - """ - Delete the given key from the config. - - Implements :meth:`AbstractConfig.delete`. - """ - try: - del self._settings[key] - - except Exception: - if suppress: - pass - - def save(self) -> None: - """ - Save the current configuration. - - Implements :meth:`AbstractConfig.save`. - """ - self._defaults.setPersistentDomain_forName_(self._settings, self.identifier) - self._defaults.synchronize() - - def close(self) -> None: - """ - Close this config and release any associated resources. - - Implements :meth:`AbstractConfig.close`. - """ - self.save() - self._defaults = None diff --git a/hotkey/__init__.py b/hotkey/__init__.py index e75158075..313415f9f 100644 --- a/hotkey/__init__.py +++ b/hotkey/__init__.py @@ -76,10 +76,6 @@ def get_hotkeymgr() -> AbstractHotkeyMgr: :return: Appropriate class instance. :raises ValueError: If unsupported platform. """ - if sys.platform == 'darwin': - from hotkey.darwin import MacHotkeyMgr - return MacHotkeyMgr() - if sys.platform == 'win32': from hotkey.windows import WindowsHotkeyMgr return WindowsHotkeyMgr() diff --git a/hotkey/darwin.py b/hotkey/darwin.py deleted file mode 100644 index 6afd0239c..000000000 --- a/hotkey/darwin.py +++ /dev/null @@ -1,276 +0,0 @@ -"""darwin/macOS implementation of hotkey.AbstractHotkeyMgr.""" -from __future__ import annotations - -import pathlib -import sys -import tkinter as tk -from typing import Callable -assert sys.platform == 'darwin' - -import objc -from AppKit import ( - NSAlternateKeyMask, NSApplication, NSBeep, NSClearLineFunctionKey, NSCommandKeyMask, NSControlKeyMask, - NSDeleteFunctionKey, NSDeviceIndependentModifierFlagsMask, NSEvent, NSF1FunctionKey, NSF35FunctionKey, - NSFlagsChanged, NSKeyDown, NSKeyDownMask, NSKeyUp, NSNumericPadKeyMask, NSShiftKeyMask, NSSound, NSWorkspace -) - -from config import config -from EDMCLogging import get_main_logger -from hotkey import AbstractHotkeyMgr - -logger = get_main_logger() - - -class MacHotkeyMgr(AbstractHotkeyMgr): - """Hot key management.""" - - POLL = 250 - # https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/#//apple_ref/doc/constant_group/Function_Key_Unicodes - DISPLAY = { - 0x03: u'⌅', 0x09: u'⇥', 0xd: u'↩', 0x19: u'⇤', 0x1b: u'esc', 0x20: u'⏘', 0x7f: u'⌫', - 0xf700: u'↑', 0xf701: u'↓', 0xf702: u'←', 0xf703: u'→', - 0xf727: u'Ins', - 0xf728: u'⌦', 0xf729: u'↖', 0xf72a: u'Fn', 0xf72b: u'↘', - 0xf72c: u'⇞', 0xf72d: u'⇟', 0xf72e: u'PrtScr', 0xf72f: u'ScrollLock', - 0xf730: u'Pause', 0xf731: u'SysReq', 0xf732: u'Break', 0xf733: u'Reset', - 0xf739: u'⌧', - } - (ACQUIRE_INACTIVE, ACQUIRE_ACTIVE, ACQUIRE_NEW) = range(3) - - def __init__(self): - self.MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask \ - | NSNumericPadKeyMask - self.root: tk.Tk - - self.keycode = 0 - self.modifiers = 0 - self.activated = False - self.observer = None - - self.acquire_key = 0 - self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE - - self.tkProcessKeyEvent_old: Callable - - self.snd_good = NSSound.alloc().initWithContentsOfFile_byReference_( - pathlib.Path(config.respath_path) / 'snd_good.wav', False - ) - self.snd_bad = NSSound.alloc().initWithContentsOfFile_byReference_( - pathlib.Path(config.respath_path) / 'snd_bad.wav', False - ) - - def register(self, root: tk.Tk, keycode: int, modifiers: int) -> None: - """ - Register current hotkey for monitoring. - - :param root: parent window. - :param keycode: Key to monitor. - :param modifiers: Any modifiers to take into account. - """ - self.root = root - self.keycode = keycode - self.modifiers = modifiers - self.activated = False - - if keycode: - if not self.observer: - self.root.after_idle(self._observe) - self.root.after(MacHotkeyMgr.POLL, self._poll) - - # Monkey-patch tk (tkMacOSXKeyEvent.c) - if not callable(self.tkProcessKeyEvent_old): - sel = b'tkProcessKeyEvent:' - cls = NSApplication.sharedApplication().class__() # type: ignore - self.tkProcessKeyEvent_old = NSApplication.sharedApplication().methodForSelector_(sel) # type: ignore - newmethod = objc.selector( # type: ignore - self.tkProcessKeyEvent, - selector=self.tkProcessKeyEvent_old.selector, - signature=self.tkProcessKeyEvent_old.signature - ) - objc.classAddMethod(cls, sel, newmethod) # type: ignore - - def tkProcessKeyEvent(self, cls, the_event): # noqa: N802 - """ - Monkey-patch tk (tkMacOSXKeyEvent.c). - - - workaround crash on OSX 10.9 & 10.10 on seeing a composing character - - notice when modifier key state changes - - keep a copy of NSEvent.charactersIgnoringModifiers, which is what we need for the hotkey - - (Would like to use a decorator but need to ensure the application is created before this is installed) - :param cls: ??? - :param the_event: tk event - :return: ??? - """ - if self.acquire_state: - if the_event.type() == NSFlagsChanged: - self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask - self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW - # suppress the event by not chaining the old function - return the_event - - if the_event.type() in (NSKeyDown, NSKeyUp): - c = the_event.charactersIgnoringModifiers() - self.acquire_key = (c and ord(c[0]) or 0) | \ - (the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask) - self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW - # suppress the event by not chaining the old function - return the_event - - # replace empty characters with charactersIgnoringModifiers to avoid crash - elif the_event.type() in (NSKeyDown, NSKeyUp) and not the_event.characters(): - the_event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_( # noqa: E501 - # noqa: E501 - the_event.type(), - the_event.locationInWindow(), - the_event.modifierFlags(), - the_event.timestamp(), - the_event.windowNumber(), - the_event.context(), - the_event.charactersIgnoringModifiers(), - the_event.charactersIgnoringModifiers(), - the_event.isARepeat(), - the_event.keyCode() - ) - return self.tkProcessKeyEvent_old(cls, the_event) - - def _observe(self): - # Must be called after root.mainloop() so that the app's message loop has been created - self.observer = NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, self._handler) - - def _poll(self): - if config.shutting_down: - return - - # No way of signalling to Tkinter from within the callback handler block that doesn't - # cause Python to crash, so poll. - if self.activated: - self.activated = False - self.root.event_generate('<>', when="tail") - - if self.keycode or self.modifiers: - self.root.after(MacHotkeyMgr.POLL, self._poll) - - def unregister(self) -> None: - """Remove hotkey registration.""" - self.keycode = 0 - self.modifiers = 0 - - @objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_) - def _handler(self, event) -> None: - # use event.charactersIgnoringModifiers to handle composing characters like Alt-e - if ( - (event.modifierFlags() & self.MODIFIERMASK) == self.modifiers - and ord(event.charactersIgnoringModifiers()[0]) == self.keycode - ): - if config.get_int('hotkey_always'): - self.activated = True - - else: # Only trigger if game client is front process - front = NSWorkspace.sharedWorkspace().frontmostApplication() - if front and front.bundleIdentifier() == 'uk.co.frontier.EliteDangerous': - self.activated = True - - def acquire_start(self) -> None: - """Start acquiring hotkey state via polling.""" - self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE - self.root.after_idle(self._acquire_poll) - - def acquire_stop(self) -> None: - """Stop acquiring hotkey state.""" - self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE - - def _acquire_poll(self) -> None: - """Perform a poll of current hotkey state.""" - if config.shutting_down: - return - - # No way of signalling to Tkinter from within the monkey-patched event handler that doesn't - # cause Python to crash, so poll. - if self.acquire_state: - if self.acquire_state == MacHotkeyMgr.ACQUIRE_NEW: - # Abuse tkEvent's keycode field to hold our acquired key & modifier - self.root.event_generate('', keycode=self.acquire_key) - self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE - self.root.after(50, self._acquire_poll) - - def fromevent(self, event) -> bool | tuple | None: - """ - Return configuration (keycode, modifiers) or None=clear or False=retain previous. - - :param event: tk event ? - :return: False to retain previous, None to not use, else (keycode, modifiers) - """ - (keycode, modifiers) = (event.keycode & 0xffff, event.keycode & 0xffff0000) # Set by _acquire_poll() - if ( - keycode - and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask)) - ): - if keycode == 0x1b: # Esc = retain previous - self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE - return False - - # BkSp, Del, Clear = clear hotkey - if keycode in (0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)): - self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE - return None - - # don't allow keys needed for typing in System Map - if keycode in (0x13, 0x20, 0x2d) or 0x61 <= keycode <= 0x7a: - NSBeep() - self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE - return None - - return keycode, modifiers - - def display(self, keycode, modifiers) -> str: - """ - Return displayable form of given hotkey + modifiers. - - :param keycode: - :param modifiers: - :return: string form - """ - text = '' - if modifiers & NSControlKeyMask: - text += u'⌃' - - if modifiers & NSAlternateKeyMask: - text += u'⌥' - - if modifiers & NSShiftKeyMask: - text += u'⇧' - - if modifiers & NSCommandKeyMask: - text += u'⌘' - - if (modifiers & NSNumericPadKeyMask) and keycode <= 0x7f: - text += u'№' - - if not keycode: - pass - - elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey): - text += f'F{keycode + 1 - ord(NSF1FunctionKey)}' - - elif keycode in MacHotkeyMgr.DISPLAY: # specials - text += MacHotkeyMgr.DISPLAY[keycode] - - elif keycode < 0x20: # control keys - text += chr(keycode + 0x40) - - elif keycode < 0xf700: # key char - text += chr(keycode).upper() - - else: - text += u'⁈' - - return text - - def play_good(self): - """Play the 'good' sound.""" - self.snd_good.play() - - def play_bad(self): - """Play the 'bad' sound.""" - self.snd_bad.play() From 93d26e07e057d1b70f3144898ecf34e35c4cdae2 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 19:19:15 -0400 Subject: [PATCH 112/261] [2186] Remove Monitor, L10n References --- l10n.py | 50 +++++--------------------------------------------- monitor.py | 23 ++--------------------- 2 files changed, 7 insertions(+), 66 deletions(-) diff --git a/l10n.py b/l10n.py index a2b5185ae..183d902a5 100755 --- a/l10n.py +++ b/l10n.py @@ -17,8 +17,8 @@ import sys import warnings from contextlib import suppress -from os import pardir, listdir, sep, makedirs -from os.path import basename, dirname, isdir, isfile, join, abspath, exists +from os import listdir, sep, makedirs +from os.path import basename, dirname, isdir, join, abspath, exists from typing import TYPE_CHECKING, Iterable, TextIO, cast from config import config from EDMCLogging import get_main_logger @@ -39,12 +39,7 @@ def _(x: str) -> str: return x LANGUAGE_ID = '!Language' LOCALISATION_DIR = 'L10n' -if sys.platform == 'darwin': - from Foundation import ( # type: ignore # exists on Darwin - NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle - ) - -elif sys.platform == 'win32': +if sys.platform == 'win32': import ctypes from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR if TYPE_CHECKING: @@ -178,14 +173,8 @@ def translate(self, x: str, context: str | None = None) -> str: def available(self) -> set[str]: """Return a list of available language codes.""" path = self.respath() - if getattr(sys, 'frozen', False) and sys.platform == 'darwin': - available = { - x[:-len('.lproj')] for x in listdir(path) - if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings')) - } - else: - available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')} + available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')} return available @@ -206,9 +195,6 @@ def available_names(self) -> dict[str | None, str]: def respath(self) -> str: """Path to localisation files.""" if getattr(sys, 'frozen', False): - if sys.platform == 'darwin': - return abspath(join(dirname(sys.executable), pardir, 'Resources')) - return abspath(join(dirname(sys.executable), LOCALISATION_DIR)) if __file__: @@ -234,10 +220,6 @@ def file(self, lang: str, plugin_path: str | None = None) -> TextIO | None: except OSError: logger.exception(f'could not open {file_path}') - elif getattr(sys, 'frozen', False) and sys.platform == 'darwin': - res_path = join(self.respath(), f'{lang}.lproj', 'Localizable.strings') - return open(res_path, encoding='utf-16') - res_path = join(self.respath(), f'{lang}.strings') return open(res_path, encoding='utf-8') @@ -245,15 +227,6 @@ def file(self, lang: str, plugin_path: str | None = None) -> TextIO | None: class _Locale: """Locale holds a few utility methods to convert data to and from localized versions.""" - def __init__(self) -> None: - if sys.platform == 'darwin': - self.int_formatter = NSNumberFormatter.alloc().init() - self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle) - self.float_formatter = NSNumberFormatter.alloc().init() - self.float_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle) - self.float_formatter.setMinimumFractionDigits_(5) - self.float_formatter.setMaximumFractionDigits_(5) - def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802 warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.')) return self.string_from_number(number, decimals) # type: ignore @@ -279,14 +252,6 @@ def string_from_number(self, number: float | int, decimals: int = 5) -> str: if decimals == 0 and not isinstance(number, numbers.Integral): number = int(round(number)) - if sys.platform == 'darwin': - if not decimals and isinstance(number, numbers.Integral): - return self.int_formatter.stringFromNumber_(number) - - self.float_formatter.setMinimumFractionDigits_(decimals) - self.float_formatter.setMaximumFractionDigits_(decimals) - return self.float_formatter.stringFromNumber_(number) - if not decimals and isinstance(number, numbers.Integral): return locale.format_string('%d', number, True) return locale.format_string('%.*f', (decimals, number), True) @@ -299,9 +264,6 @@ def number_from_string(self, string: str) -> int | float | None: :param string: The string to convert :return: None if the string cannot be parsed, otherwise an int or float dependant on input data. """ - if sys.platform == 'darwin': - return self.float_formatter.numberFromString_(string) - with suppress(ValueError): return locale.atoi(string) @@ -332,10 +294,8 @@ def preferred_languages(self) -> Iterable[str]: :return: The preferred language list """ languages: Iterable[str] - if sys.platform == 'darwin': - languages = NSLocale.preferredLanguages() - elif sys.platform != 'win32': + if sys.platform != 'win32': # POSIX lang = locale.getlocale()[0] languages = [lang.replace('_', '-')] if lang else [] diff --git a/monitor.py b/monitor.py index 8364f95bf..d549e5330 100644 --- a/monitor.py +++ b/monitor.py @@ -38,16 +38,7 @@ def _(x: str) -> str: return x -if sys.platform == 'darwin': - from fcntl import fcntl - - from AppKit import NSWorkspace - from watchdog.events import FileSystemEventHandler - from watchdog.observers import Observer - from watchdog.observers.api import BaseObserver - F_GLOBAL_NOCACHE = 55 - -elif sys.platform == 'win32': +if sys.platform == 'win32': import ctypes from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR @@ -382,8 +373,6 @@ def worker(self) -> None: # noqa: C901, CCR001 logfile = self.logfile if logfile: loghandle: BinaryIO = open(logfile, 'rb', 0) # unbuffered - if sys.platform == 'darwin': - fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB self.catching_up = True for line in loghandle: @@ -483,9 +472,6 @@ def worker(self) -> None: # noqa: C901, CCR001 if logfile: loghandle = open(logfile, 'rb', 0) # unbuffered - if sys.platform == 'darwin': - fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB - log_pos = 0 sleep(self._POLL) @@ -2144,12 +2130,7 @@ def game_running(self) -> bool: # noqa: CCR001 :return: bool - True if the game is running. """ - if sys.platform == 'darwin': - for app in NSWorkspace.sharedWorkspace().runningApplications(): - if app.bundleIdentifier() == 'uk.co.frontier.EliteDangerous': - return True - - elif sys.platform == 'win32': + if sys.platform == 'win32': def WindowTitle(h): # noqa: N802 if h: length = GetWindowTextLength(h) + 1 From 27093d88620ed760600b0e1df672160e25dffc7c Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 19:26:25 -0400 Subject: [PATCH 113/261] [2186] Main, Dashboard, Prefs --- EDMarketConnector.py | 354 +++++++++++++++++-------------------------- dashboard.py | 2 +- myNotebook.py | 2 +- prefs.py | 177 ++++------------------ 4 files changed, 167 insertions(+), 368 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 6debf4e98..967488e0f 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -496,21 +496,20 @@ def open_window(systray: 'SysTrayIcon') -> None: plug.load_plugins(master) - if sys.platform != 'darwin': - if sys.platform == 'win32': - self.w.wm_iconbitmap(default='EDMarketConnector.ico') + if sys.platform == 'win32': + self.w.wm_iconbitmap(default='EDMarketConnector.ico') - else: - self.w.tk.call('wm', 'iconphoto', self.w, '-default', - tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png'))) + else: + self.w.tk.call('wm', 'iconphoto', self.w, '-default', + tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png'))) - # TODO: Export to files and merge from them in future ? - self.theme_icon = tk.PhotoImage( - data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501 - self.theme_minimize = tk.BitmapImage( - data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 - self.theme_close = tk.BitmapImage( - data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 + # TODO: Export to files and merge from them in future ? + self.theme_icon = tk.PhotoImage( + data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501 + self.theme_minimize = tk.BitmapImage( + data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 + self.theme_close = tk.BitmapImage( + data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 frame = tk.Frame(self.w, name=appname.lower()) frame.grid(sticky=tk.NSEW) @@ -599,7 +598,7 @@ def open_window(systray: 'SysTrayIcon') -> None: self.theme_button = tk.Label( frame, name='themed_update_button', - width=32 if sys.platform == 'darwin' else 28, + width=28, state=tk.DISABLED ) @@ -633,148 +632,104 @@ def open_window(systray: 'SysTrayIcon') -> None: self.updater = update.Updater(tkroot=self.w) self.updater.check_for_updates() # Sparkle / WinSparkle does this automatically for packaged apps - if sys.platform == 'darwin': - # Can't handle (de)iconify if topmost is set, so suppress iconify button - # http://wiki.tcl.tk/13428 and p15 of - # https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf - root.call('tk::unsupported::MacWindowStyle', 'style', root, 'document', 'closeBox resizable') - - # https://www.tcl.tk/man/tcl/TkCmd/menu.htm - self.system_menu = tk.Menu(self.menubar, name='apple') - self.system_menu.add_command(command=lambda: self.w.call('tk::mac::standardAboutPanel')) - self.system_menu.add_command(command=lambda: self.updater.check_for_updates()) + self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) + self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status)) + self.file_menu.add_command(command=self.save_raw) + self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs)) + self.file_menu.add_separator() + self.file_menu.add_command(command=self.onexit) + self.menubar.add_cascade(menu=self.file_menu) + self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) + self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy) + self.menubar.add_cascade(menu=self.edit_menu) + self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) # type: ignore + self.help_menu.add_command(command=self.help_general) # Documentation + self.help_menu.add_command(command=self.help_troubleshooting) # Troubleshooting + self.help_menu.add_command(command=self.help_report_a_bug) # Report A Bug + self.help_menu.add_command(command=self.help_privacy) # Privacy Policy + self.help_menu.add_command(command=self.help_releases) # Release Notes + self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates... + # About E:D Market Connector + self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w)) + self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder + + self.menubar.add_cascade(menu=self.help_menu) + if sys.platform == 'win32': + # Must be added after at least one "real" menu entry + self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop'))) + self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) + self.system_menu.add_separator() + # LANG: Appearance - Label for checkbox to select if application always on top + self.system_menu.add_checkbutton(label=_('Always on top'), + variable=self.always_ontop, + command=self.ontop_changed) # Appearance setting self.menubar.add_cascade(menu=self.system_menu) - self.file_menu = tk.Menu(self.menubar, name='file') - self.file_menu.add_command(command=self.save_raw) - self.menubar.add_cascade(menu=self.file_menu) - self.edit_menu = tk.Menu(self.menubar, name='edit') - self.edit_menu.add_command(accelerator='Command-c', state=tk.DISABLED, command=self.copy) - self.menubar.add_cascade(menu=self.edit_menu) - self.w.bind('', self.copy) - self.view_menu = tk.Menu(self.menubar, name='view') - self.view_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status)) - self.menubar.add_cascade(menu=self.view_menu) - window_menu = tk.Menu(self.menubar, name='window') - self.menubar.add_cascade(menu=window_menu) - self.help_menu = tk.Menu(self.menubar, name='help') - self.w.createcommand("::tk::mac::ShowHelp", self.help_general) - self.help_menu.add_command(command=self.help_troubleshooting) - self.help_menu.add_command(command=self.help_report_a_bug) - self.help_menu.add_command(command=self.help_privacy) - self.help_menu.add_command(command=self.help_releases) - self.menubar.add_cascade(menu=self.help_menu) - self.w['menu'] = self.menubar - # https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm - self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0') - self.w.createcommand('tkAboutDialog', lambda: self.w.call('tk::mac::standardAboutPanel')) - self.w.createcommand("::tk::mac::Quit", self.onexit) - self.w.createcommand("::tk::mac::ShowPreferences", lambda: prefs.PreferencesDialog(self.w, self.postprefs)) - self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify) # click on app in dock = restore - self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw) # close button shouldn't quit app - self.w.resizable(tk.FALSE, tk.FALSE) # Can't be only resizable on one axis - else: - self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) - self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status)) - self.file_menu.add_command(command=self.save_raw) - self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs)) - self.file_menu.add_separator() - self.file_menu.add_command(command=self.onexit) - self.menubar.add_cascade(menu=self.file_menu) - self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) - self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy) - self.menubar.add_cascade(menu=self.edit_menu) - self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) # type: ignore - self.help_menu.add_command(command=self.help_general) # Documentation - self.help_menu.add_command(command=self.help_troubleshooting) # Troubleshooting - self.help_menu.add_command(command=self.help_report_a_bug) # Report A Bug - self.help_menu.add_command(command=self.help_privacy) # Privacy Policy - self.help_menu.add_command(command=self.help_releases) # Release Notes - self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates... - # About E:D Market Connector - self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w)) - self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder - - self.menubar.add_cascade(menu=self.help_menu) - if sys.platform == 'win32': - # Must be added after at least one "real" menu entry - self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop'))) - self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) - self.system_menu.add_separator() - # LANG: Appearance - Label for checkbox to select if application always on top - self.system_menu.add_checkbutton(label=_('Always on top'), - variable=self.always_ontop, - command=self.ontop_changed) # Appearance setting - self.menubar.add_cascade(menu=self.system_menu) - self.w.bind('', self.copy) - - # Bind to the Default theme minimise button - self.w.bind("", self.default_iconify) - - self.w.protocol("WM_DELETE_WINDOW", self.onexit) - theme.register(self.menubar) # menus and children aren't automatically registered - theme.register(self.file_menu) - theme.register(self.edit_menu) - theme.register(self.help_menu) - - # Alternate title bar and menu for dark theme - self.theme_menubar = tk.Frame(frame, name="alternate_menubar") - self.theme_menubar.columnconfigure(2, weight=1) - theme_titlebar = tk.Label( - self.theme_menubar, - name="alternate_titlebar", - text=applongname, - image=self.theme_icon, cursor='fleur', - anchor=tk.W, compound=tk.LEFT - ) - theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW) - self.drag_offset: tuple[int | None, int | None] = (None, None) - theme_titlebar.bind('', self.drag_start) - theme_titlebar.bind('', self.drag_continue) - theme_titlebar.bind('', self.drag_end) - theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize) - theme_minimize.grid(row=0, column=3, padx=2) - theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize) - theme_close = tk.Label(self.theme_menubar, image=self.theme_close) - theme_close.grid(row=0, column=4, padx=2) - theme.button_bind(theme_close, self.onexit, image=self.theme_close) - self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W) - self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W) - theme.button_bind(self.theme_file_menu, - lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), - e.widget.winfo_rooty() - + e.widget.winfo_height())) - self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W) - self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W) - theme.button_bind(self.theme_edit_menu, - lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), - e.widget.winfo_rooty() - + e.widget.winfo_height())) - self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W) - self.theme_help_menu.grid(row=1, column=2, sticky=tk.W) - theme.button_bind(self.theme_help_menu, - lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), - e.widget.winfo_rooty() - + e.widget.winfo_height())) - tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW) - theme.register(self.theme_minimize) # images aren't automatically registered - theme.register(self.theme_close) - self.blank_menubar = tk.Frame(frame, name="blank_menubar") - tk.Label(self.blank_menubar).grid() - tk.Label(self.blank_menubar).grid() - tk.Frame(self.blank_menubar, height=2).grid() - theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), - {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW}) - self.w.resizable(tk.TRUE, tk.FALSE) + self.w.bind('', self.copy) + + # Bind to the Default theme minimise button + self.w.bind("", self.default_iconify) + + self.w.protocol("WM_DELETE_WINDOW", self.onexit) + theme.register(self.menubar) # menus and children aren't automatically registered + theme.register(self.file_menu) + theme.register(self.edit_menu) + theme.register(self.help_menu) + + # Alternate title bar and menu for dark theme + self.theme_menubar = tk.Frame(frame, name="alternate_menubar") + self.theme_menubar.columnconfigure(2, weight=1) + theme_titlebar = tk.Label( + self.theme_menubar, + name="alternate_titlebar", + text=applongname, + image=self.theme_icon, cursor='fleur', + anchor=tk.W, compound=tk.LEFT + ) + theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW) + self.drag_offset: tuple[int | None, int | None] = (None, None) + theme_titlebar.bind('', self.drag_start) + theme_titlebar.bind('', self.drag_continue) + theme_titlebar.bind('', self.drag_end) + theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize) + theme_minimize.grid(row=0, column=3, padx=2) + theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize) + theme_close = tk.Label(self.theme_menubar, image=self.theme_close) + theme_close.grid(row=0, column=4, padx=2) + theme.button_bind(theme_close, self.onexit, image=self.theme_close) + self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W) + self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W) + theme.button_bind(self.theme_file_menu, + lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(), + e.widget.winfo_rooty() + + e.widget.winfo_height())) + self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W) + self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W) + theme.button_bind(self.theme_edit_menu, + lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(), + e.widget.winfo_rooty() + + e.widget.winfo_height())) + self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W) + self.theme_help_menu.grid(row=1, column=2, sticky=tk.W) + theme.button_bind(self.theme_help_menu, + lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(), + e.widget.winfo_rooty() + + e.widget.winfo_height())) + tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW) + theme.register(self.theme_minimize) # images aren't automatically registered + theme.register(self.theme_close) + self.blank_menubar = tk.Frame(frame, name="blank_menubar") + tk.Label(self.blank_menubar).grid() + tk.Label(self.blank_menubar).grid() + tk.Frame(self.blank_menubar, height=2).grid() + theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar), + {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW}) + self.w.resizable(tk.TRUE, tk.FALSE) # update geometry if config.get_str('geometry'): match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry')) if match: - if sys.platform == 'darwin': - # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - if int(match.group(2)) >= 0: - self.w.geometry(config.get_str('geometry')) - elif sys.platform == 'win32': + if sys.platform == 'win32': # Check that the titlebar will be at least partly on screen import ctypes from ctypes.wintypes import POINT @@ -910,49 +865,28 @@ def set_labels(self): self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window - if sys.platform == 'darwin': - self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title on OSX - self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title on OSX - self.menubar.entryconfigure(3, label=_('View')) # LANG: 'View' menu title on OSX - self.menubar.entryconfigure(4, label=_('Window')) # LANG: 'Window' menu title on OSX - self.menubar.entryconfigure(5, label=_('Help')) # LANG: Help' menu title on OSX - self.system_menu.entryconfigure( - 0, - label=_("About {APP}").format(APP=applongname) # LANG: App menu entry on OSX - ) - self.system_menu.entryconfigure(1, label=_("Check for Updates...")) # LANG: Help > Check for Updates... - self.file_menu.entryconfigure(0, label=_('Save Raw Data...')) # LANG: File > Save Raw Data... - self.view_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status - self.help_menu.entryconfigure(1, label=_('Documentation')) # LANG: Help > Documentation - self.help_menu.entryconfigure(2, label=_('Troubleshooting')) # LANG: Help > Troubleshooting - self.help_menu.entryconfigure(3, label=_('Report A Bug')) # LANG: Help > Report A Bug - self.help_menu.entryconfigure(4, label=_('Privacy Policy')) # LANG: Help > Privacy Policy - self.help_menu.entryconfigure(5, label=_('Release Notes')) # LANG: Help > Release Notes - self.help_menu.entryconfigure(6, label=_('Open Log Folder')) # LANG: Help > Open Log Folder - - else: - self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title - self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title - self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title - self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title - self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title - self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title - - # File menu - self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status - self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data... - self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings - self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit - - # Help menu - self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation - self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting - self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug - self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy - self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes - self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates... - self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App - self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder + self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title + self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title + self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title + self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title + self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title + self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title + + # File menu + self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status + self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data... + self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings + self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit + + # Help menu + self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation + self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting + self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug + self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy + self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes + self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates... + self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App + self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder # Edit menu self.edit_menu.entryconfigure(0, label=_('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste' @@ -975,13 +909,8 @@ def login(self): self.button['state'] = self.theme_button['state'] = tk.DISABLED - if sys.platform == 'darwin': - self.view_menu.entryconfigure(0, state=tk.DISABLED) # Status - self.file_menu.entryconfigure(0, state=tk.DISABLED) # Save Raw Data - - else: - self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status - self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data + self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status + self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data self.w.update_idletasks() try: @@ -989,13 +918,8 @@ def login(self): # LANG: Successfully authenticated with the Frontier website self.status['text'] = _('Authentication successful') - if sys.platform == 'darwin': - self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status - self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data - - else: - self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status - self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data + self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status + self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data except (companion.CredentialsError, companion.ServerError, companion.ServerLagging) as e: self.status['text'] = str(e) @@ -1666,13 +1590,8 @@ def auth(self, event=None) -> None: companion.session.auth_callback() # LANG: Successfully authenticated with the Frontier website self.status['text'] = _('Authentication successful') - if sys.platform == 'darwin': - self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status - self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data - - else: - self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status - self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data + self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status + self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data except companion.ServerError as e: self.status['text'] = str(e) @@ -1831,7 +1750,7 @@ def __init__(self, parent: tk.Tk) -> None: # position over parent # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - if sys.platform != 'darwin' or parent.winfo_rooty() > 0: + if parent.winfo_rooty() > 0: self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}') # remove decoration @@ -1916,9 +1835,6 @@ def save_raw(self) -> None: """ default_extension: str = '' - if sys.platform == 'darwin': - default_extension = '.json' - timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime()) f = tkinter.filedialog.asksaveasfilename( parent=self.w, @@ -1954,7 +1870,7 @@ def onexit(self, event=None) -> None: config.set_shutdown() # Signal we're in shutdown now. # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - if sys.platform != 'darwin' or self.w.winfo_rooty() > 0: + if self.w.winfo_rooty() > 0: x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267' config.set('geometry', f'+{x}+{y}') diff --git a/dashboard.py b/dashboard.py index 1f95a9436..3948c92bf 100644 --- a/dashboard.py +++ b/dashboard.py @@ -20,7 +20,7 @@ logger = get_main_logger() -if sys.platform in ('darwin', 'win32'): +if sys.platform == 'win32': from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer else: diff --git a/myNotebook.py b/myNotebook.py index 6fa357746..dbfc92505 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -78,7 +78,7 @@ class Label(tk.Label): def __init__(self, master: ttk.Frame | None = None, **kw): # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that - if sys.platform in ('darwin', 'win32'): + if sys.platform == 'win32': kw['foreground'] = kw.pop('foreground', PAGEFG) kw['background'] = kw.pop('background', PAGEBG) else: diff --git a/prefs.py b/prefs.py index 6c2722b67..862bda279 100644 --- a/prefs.py +++ b/prefs.py @@ -18,7 +18,7 @@ import myNotebook as nb # noqa: N813 import plug -from config import applongname, appversion_nobuild, config +from config import appversion_nobuild, config from EDMCLogging import edmclogger, get_main_logger from constants import appname from hotkey import hotkeymgr @@ -49,9 +49,6 @@ def help_open_log_folder() -> None: if sys.platform.startswith('win'): # On Windows, use the "start" command to open the folder system(f'start "" "{logfile_loc}"') - elif sys.platform.startswith('darwin'): - # On macOS, use the "open" command to open the folder - system(f'open "{logfile_loc}"') elif sys.platform.startswith('linux'): # On Linux, use the "xdg-open" command to open the folder system(f'xdg-open "{logfile_loc}"') @@ -172,32 +169,7 @@ def __exit__( return None -if sys.platform == 'darwin': - import objc # type: ignore - from Foundation import NSFileManager # type: ignore - try: - from ApplicationServices import ( # type: ignore - AXIsProcessTrusted, AXIsProcessTrustedWithOptions, kAXTrustedCheckOptionPrompt - ) - - except ImportError: - HIServices = objc.loadBundle( - 'HIServices', - globals(), - '/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework' - ) - - objc.loadBundleFunctions( - HIServices, - globals(), - [('AXIsProcessTrusted', 'B'), ('AXIsProcessTrustedWithOptions', 'B@')] - ) - - objc.loadBundleVariables(HIServices, globals(), [('kAXTrustedCheckOptionPrompt', '@^{__CFString=}')]) - - was_accessible_at_launch = AXIsProcessTrusted() # type: ignore - -elif sys.platform == 'win32': +if sys.platform == 'win32': import ctypes import winreg from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT @@ -251,19 +223,14 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): self.parent = parent self.callback = callback - if sys.platform == 'darwin': - # LANG: File > Preferences menu entry for macOS - self.title(_('Preferences')) - - else: - # LANG: File > Settings (macOS) - self.title(_('Settings')) + # LANG: File > Settings (macOS) + self.title(_('Settings')) if parent.winfo_viewable(): self.transient(parent) # position over parent - if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 + if parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 # TODO this is fixed supposedly. self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}') @@ -271,10 +238,6 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): if sys.platform == 'win32': self.attributes('-toolwindow', tk.TRUE) - elif sys.platform == 'darwin': - # http://wiki.tcl.tk/13428 - parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') - self.resizable(tk.FALSE, tk.FALSE) self.cmdr: str | bool | None = False # Note if Cmdr changes in the Journal @@ -302,19 +265,15 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): self.__setup_appearance_tab(notebook) self.__setup_plugin_tab(notebook) - if sys.platform == 'darwin': - self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes - - else: - buttonframe = ttk.Frame(frame) - buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW) - buttonframe.columnconfigure(0, weight=1) - ttk.Label(buttonframe).grid(row=0, column=0) # spacer - # LANG: 'OK' button on Settings/Preferences window - button = ttk.Button(buttonframe, text=_('OK'), command=self.apply) - button.grid(row=0, column=1, sticky=tk.E) - button.bind("", lambda event: self.apply()) - self.protocol("WM_DELETE_WINDOW", self._destroy) + buttonframe = ttk.Frame(frame) + buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW) + buttonframe.columnconfigure(0, weight=1) + ttk.Label(buttonframe).grid(row=0, column=0) # spacer + # LANG: 'OK' button on Settings/Preferences window + button = ttk.Button(buttonframe, text=_('OK'), command=self.apply) + button.grid(row=0, column=1, sticky=tk.E) + button.bind("", lambda event: self.apply()) + self.protocol("WM_DELETE_WINDOW", self._destroy) # FIXME: Why are these being called when *creating* the Settings window? # Selectively disable buttons depending on output settings @@ -405,11 +364,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: self.outdir_entry = nb.Entry(output_frame, takefocus=False) self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - if sys.platform == 'darwin': - text = (_('Change...')) # LANG: macOS Preferences - files location selection button - - else: - text = (_('Browse...')) # LANG: NOT-macOS Settings - files location selection button + text = (_('Browse...')) # LANG: NOT-macOS Settings - files location selection button self.outbutton = nb.Button( output_frame, @@ -455,11 +410,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - if sys.platform == 'darwin': - text = (_('Change...')) # LANG: macOS Preferences - files location selection button - - else: - text = (_('Browse...')) # LANG: NOT-macOS Setting - files location selection button + text = (_('Browse...')) # LANG: NOT-macOS Setting - files location selection button with row as cur_row: self.logbutton = nb.Button( @@ -499,7 +450,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 variable=self.capi_fleetcarrier ).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) - if sys.platform in ('darwin', 'win32'): + if sys.platform == 'win32': ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid( columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) @@ -511,49 +462,21 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 with row as cur_row: nb.Label( config_frame, - text=_('Keyboard shortcut') if # LANG: Hotkey/Shortcut settings prompt on OSX - sys.platform == 'darwin' else - _('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows + text=_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) - if sys.platform == 'darwin' and not was_accessible_at_launch: - if AXIsProcessTrusted(): - # Shortcut settings prompt on OSX - nb.Label( - config_frame, - # LANG: macOS Preferences > Configuration - restart the app message - text=_('Re-start {APP} to use shortcuts').format(APP=applongname), - foreground='firebrick' - ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) - - else: - # Shortcut settings prompt on OSX - nb.Label( - config_frame, - # LANG: macOS - Configuration - need to grant the app permission for keyboard shortcuts - text=_('{APP} needs permission to use shortcuts').format(APP=applongname), - foreground='firebrick' - ).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) - - # LANG: Shortcut settings button on OSX - nb.Button(config_frame, text=_('Open System Preferences'), command=self.enableshortcuts).grid( - padx=self.PADX, pady=self.BOXY, sticky=tk.E, row=cur_row - ) - - else: - self.hotkey_text = nb.Entry(config_frame, width=( - 20 if sys.platform == 'darwin' else 30), justify=tk.CENTER) - self.hotkey_text.insert( - 0, - # No hotkey/shortcut currently defined - # TODO: display Only shows up on darwin or windows - # LANG: No hotkey/shortcut set - hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None') - ) + self.hotkey_text = nb.Entry(config_frame, width=30, justify=tk.CENTER) + self.hotkey_text.insert( + 0, + # No hotkey/shortcut currently defined + # TODO: display Only shows up on windows + # LANG: No hotkey/shortcut set + hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None') + ) - self.hotkey_text.bind('', self.hotkeystart) - self.hotkey_text.bind('', self.hotkeyend) - self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row) + self.hotkey_text.bind('', self.hotkeystart) + self.hotkey_text.bind('', self.hotkeyend) + self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row) # Hotkey/Shortcut setting self.hotkey_only_btn = nb.Checkbutton( @@ -1070,14 +993,6 @@ def cmdrchanged(self, event=None): def tabchanged(self, event: tk.Event) -> None: """Handle preferences active tab changing.""" self.outvarchanged() - if sys.platform == 'darwin': - # Hack to recompute size so that buttons show up under Mojave - notebook = event.widget - frame = self.nametowidget(notebook.winfo_parent()) - temp = nb.Label(frame) - temp.grid() - temp.update_idletasks() - temp.destroy() def outvarchanged(self, event: Optional[tk.Event] = None) -> None: """Handle Output tab variable changes.""" @@ -1139,16 +1054,6 @@ def displaypath(self, pathvar: tk.StringVar, entryfield: tk.Entry) -> None: entryfield.insert(0, '\\'.join(display)) # None if path doesn't exist - elif sys.platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()): - if pathvar.get().startswith(config.home): - display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[ - len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)): - ] - - else: - display = NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()) - - entryfield.insert(0, '/'.join(display)) else: if pathvar.get().startswith(config.home): entryfield.insert(0, '~' + pathvar.get()[len(config.home):]) @@ -1288,7 +1193,7 @@ def apply(self) -> None: config.set('capi_fleetcarrier', self.capi_fleetcarrier.get()) - if sys.platform in ('darwin', 'win32'): + if sys.platform == 'win32': config.set('hotkey_code', self.hotkey_code) config.set('hotkey_mods', self.hotkey_mods) config.set('hotkey_always', int(not self.hotkey_only.get())) @@ -1333,25 +1238,3 @@ def _destroy(self) -> None: self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0) self.destroy() - - if sys.platform == 'darwin': - def enableshortcuts(self) -> None: - """Set up macOS preferences shortcut.""" - self.apply() - # popup System Preferences dialog - try: - # http://stackoverflow.com/questions/6652598/cocoa-button-opens-a-system-preference-page/6658201 - from ScriptingBridge import SBApplication # type: ignore - sysprefs = 'com.apple.systempreferences' - prefs = SBApplication.applicationWithBundleIdentifier_(sysprefs) - pane = [x for x in prefs.panes() if x.id() == 'com.apple.preference.security'][0] - prefs.setCurrentPane_(pane) - anchor = [x for x in pane.anchors() if x.name() == 'Privacy_Accessibility'][0] - anchor.reveal() - prefs.activate() - - except Exception: - AXIsProcessTrustedWithOptions({kAXTrustedCheckOptionPrompt: True}) - - if not config.shutting_down: - self.parent.event_generate('<>', when="tail") From 57cd75e75ebb1a61eebac1f4b3e17b0356d6b57e Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 19:39:51 -0400 Subject: [PATCH 114/261] [2186] Additional Files --- journal_lock.py | 4 -- plugins/eddn.py | 4 +- protocol.py | 63 +------------------------ stats.py | 13 +---- td.py | 2 +- tests/config/_old_config.py | 94 +------------------------------------ theme.py | 21 +-------- update.py | 24 ---------- 8 files changed, 9 insertions(+), 216 deletions(-) diff --git a/journal_lock.py b/journal_lock.py index 4d04992f8..3a4dad52d 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -218,10 +218,6 @@ def __init__(self, parent: tk.Tk, callback: Callable) -> None: if sys.platform == 'win32': self.attributes('-toolwindow', tk.TRUE) - elif sys.platform == 'darwin': - # http://wiki.tcl.tk/13428 - parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') - self.resizable(tk.FALSE, tk.FALSE) frame = ttk.Frame(self) diff --git a/plugins/eddn.py b/plugins/eddn.py index 522ce6cb9..b71337257 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -282,7 +282,7 @@ def add_message(self, cmdr: str, msg: MutableMapping[str, Any]) -> int: msg['header'] = { # We have to lie and say it's *this* version, but denote that # it might not actually be this version. - 'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]' + 'softwareName': f'{applongname} [{system()}]' ' (legacy replay)', 'softwareVersion': str(appversion_nobuild()), 'uploaderID': cmdr, @@ -1074,7 +1074,7 @@ def standard_header( gb = this.game_build return { - 'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]', + 'softwareName': f'{applongname} [{system()}]', 'softwareVersion': str(appversion_nobuild()), 'uploaderID': this.cmdr_name, 'gameversion': gv, diff --git a/protocol.py b/protocol.py index f3b956d28..0f1c157f0 100644 --- a/protocol.py +++ b/protocol.py @@ -58,66 +58,7 @@ def event(self, url: str) -> None: self.master.event_generate('<>', when="tail") -if sys.platform == 'darwin' and getattr(sys, 'frozen', False): # noqa: C901 # its guarding ALL macos stuff. - import struct - - import objc # type: ignore - from AppKit import NSAppleEventManager, NSObject # type: ignore - - kInternetEventClass = kAEGetURL = struct.unpack('>l', b'GURL')[0] # noqa: N816 # API names - keyDirectObject = struct.unpack('>l', b'----')[0] # noqa: N816 # API names - - class DarwinProtocolHandler(GenericProtocolHandler): - """ - MacOS protocol handler implementation. - - Uses macOS event stuff. - """ - - POLL = 100 # ms - - def start(self, master: 'tkinter.Tk') -> None: - """Start Protocol Handler.""" - GenericProtocolHandler.start(self, master) - self.lasturl: str | None = None - self.eventhandler = EventHandler.alloc().init() - - def poll(self) -> None: - """Poll event until URL is updated.""" - # No way of signalling to Tkinter from within the callback handler block that doesn't cause Python to crash, - # so poll. TODO: Resolved? - if self.lasturl and self.lasturl.startswith(self.redirect): - self.event(self.lasturl) - self.lasturl = None - - class EventHandler(NSObject): - """Handle NSAppleEventManager IPC stuff.""" - - def init(self) -> None: - """ - Init method for handler. - - (I'd assume this is related to the subclassing of NSObject for why its not __init__) - """ - self = objc.super(EventHandler, self).init() - NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID_( - self, - 'handleEvent:withReplyEvent:', - kInternetEventClass, - kAEGetURL - ) - return self - - def handleEvent_withReplyEvent_(self, event, replyEvent) -> None: # noqa: N802 N803 # Required to override - """Actual event handling from NSAppleEventManager.""" - protocolhandler.lasturl = parse.unquote( - event.paramDescriptorForKeyword_(keyDirectObject).stringValue() - ).strip() - - protocolhandler.master.after(DarwinProtocolHandler.POLL, protocolhandler.poll) - - -elif (config.auth_force_edmc_protocol +if (config.auth_force_edmc_protocol or ( sys.platform == 'win32' and getattr(sys, 'frozen', False) @@ -480,8 +421,6 @@ def get_handler_impl() -> Type[GenericProtocolHandler]: :return: An instantiatable GenericProtocolHandler """ - if sys.platform == 'darwin' and getattr(sys, 'frozen', False): - return DarwinProtocolHandler # pyright: reportUnboundVariable=false if ( (sys.platform == 'win32' and config.auth_force_edmc_protocol) diff --git a/stats.py b/stats.py index 4351e2974..c377e5d3a 100644 --- a/stats.py +++ b/stats.py @@ -374,7 +374,7 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: self.transient(parent) # position over parent - if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 + if parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}") # remove decoration @@ -382,10 +382,6 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: if sys.platform == 'win32': self.attributes('-toolwindow', tk.TRUE) - elif sys.platform == 'darwin': - # http://wiki.tcl.tk/13428 - parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') - frame = ttk.Frame(self) frame.grid(sticky=tk.NSEW) @@ -423,13 +419,6 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: ttk.Frame(page).grid(pady=5) # bottom spacer notebook.add(page, text=_('Ships')) # LANG: Status dialog title - if sys.platform != 'darwin': - buttonframe = ttk.Frame(frame) - buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW) # type: ignore # the tuple is supported - buttonframe.columnconfigure(0, weight=1) - ttk.Label(buttonframe).grid(row=0, column=0) # spacer - ttk.Button(buttonframe, text='OK', command=self.destroy).grid(row=0, column=1, sticky=tk.E) - # wait for window to appear on screen before calling grab_set self.wait_visibility() self.grab_set() diff --git a/td.py b/td.py index 6a588f579..d2b5dbdd5 100644 --- a/td.py +++ b/td.py @@ -32,7 +32,7 @@ def export(data: CAPIData) -> None: with open(data_path / data_filename, 'wb') as h: # Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data h.write('#! trade.py import -\n'.encode('utf-8')) - this_platform = "Mac OS" if sys.platform == 'darwin' else system() + this_platform = system() cmdr_name = data['commander']['name'].strip() h.write( f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8') diff --git a/tests/config/_old_config.py b/tests/config/_old_config.py index 690c75eb8..35ae19e32 100644 --- a/tests/config/_old_config.py +++ b/tests/config/_old_config.py @@ -13,13 +13,7 @@ logger = get_main_logger() -if sys.platform == 'darwin': - from Foundation import ( # type: ignore - NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, - NSUserDefaults, NSUserDomainMask - ) - -elif sys.platform == 'win32': +if sys.platform == 'win32': import ctypes import uuid from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR @@ -115,91 +109,7 @@ class OldConfig: OUT_EDDN_DELAY = 4096 OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV - if sys.platform == 'darwin': # noqa: C901 # It's gating *all* the functions - - def __init__(self): - self.app_dir = join( - NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], appname - ) - if not isdir(self.app_dir): - mkdir(self.app_dir) - - self.plugin_dir = join(self.app_dir, 'plugins') - if not isdir(self.plugin_dir): - mkdir(self.plugin_dir) - - if getattr(sys, 'frozen', False): - self.internal_plugin_dir = normpath(join(dirname(sys.executable), pardir, 'Library', 'plugins')) - self.respath = normpath(join(dirname(sys.executable), pardir, 'Resources')) - self.identifier = NSBundle.mainBundle().bundleIdentifier() - - else: - self.internal_plugin_dir = join(dirname(__file__), 'plugins') - self.respath = dirname(__file__) - # Don't use Python's settings if interactive - self.identifier = f'uk.org.marginal.{appname.lower()}' - NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier - - self.default_journal_dir: str | None = join( - NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], - 'Frontier Developments', - 'Elite Dangerous' - ) - self.home = expanduser('~') - - self.defaults = NSUserDefaults.standardUserDefaults() - self.settings = dict(self.defaults.persistentDomainForName_(self.identifier) or {}) # make writeable - - # Check out_dir exists - if not self.get('outdir') or not isdir(str(self.get('outdir'))): - self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]) - - def get(self, key: str, default: None | list | str = None) -> None | list | str: - """Look up a string configuration value.""" - val = self.settings.get(key) - if val is None: - return default - - if isinstance(val, str): - return str(val) - - if isinstance(val, list): - return list(val) # make writeable - - return default - - def getint(self, key: str, default: int = 0) -> int: - """Look up an integer configuration value.""" - try: - return int(self.settings.get(key, default)) # should already be int, but check by casting - - except ValueError as e: - logger.error(f"Failed to int({key=})", exc_info=e) - return default - - except Exception as e: - logger.debug('The exception type is ...', exc_info=e) - return default - - def set(self, key: str, val: int | str | list) -> None: - """Set value on the specified configuration key.""" - self.settings[key] = val - - def delete(self, key: str) -> None: - """Delete the specified configuration key.""" - self.settings.pop(key, None) - - def save(self) -> None: - """Save current configuration to disk.""" - self.defaults.setPersistentDomain_forName_(self.settings, self.identifier) - self.defaults.synchronize() - - def close(self) -> None: - """Close the configuration.""" - self.save() - self.defaults = None - - elif sys.platform == 'win32': + if sys.platform == 'win32': def __init__(self): self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore diff --git a/theme.py b/theme.py index bfb76c55d..b128bc910 100644 --- a/theme.py +++ b/theme.py @@ -268,8 +268,7 @@ def _colors(self, root: tk.Tk, theme: int) -> None: # (Mostly) system colors style = ttk.Style() self.current = { - 'background': (sys.platform == 'darwin' and 'systemMovableModalBackground' or - style.lookup('TLabel', 'background')), + 'background': (style.lookup('TLabel', 'background')), 'foreground': style.lookup('TLabel', 'foreground'), 'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or style.lookup('TLabel', 'background', ['active'])), @@ -366,8 +365,6 @@ def _update_widget(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: C if 'bg' not in attribs: widget['background'] = self.current['background'] widget['activebackground'] = self.current['activebackground'] - if sys.platform == 'darwin' and isinstance(widget, tk.Button): - widget['highlightbackground'] = self.current['background'] if 'font' not in attribs: widget['font'] = self.current['font'] @@ -426,21 +423,7 @@ def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 return # Don't need to mess with the window manager self.active = theme - if sys.platform == 'darwin': - from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask - root.update_idletasks() # need main window to be created - if theme == self.THEME_DEFAULT: - appearance = NSAppearance.appearanceNamed_('NSAppearanceNameAqua') - - else: # Dark (Transparent only on win32) - appearance = NSAppearance.appearanceNamed_('NSAppearanceNameDarkAqua') - - for window in NSApplication.sharedApplication().windows(): - window.setStyleMask_(window.styleMask() & ~( - NSMiniaturizableWindowMask | NSResizableWindowMask)) # disable zoom - window.setAppearance_(appearance) - - elif sys.platform == 'win32': + if sys.platform == 'win32': GWL_STYLE = -16 # noqa: N806 # ctypes WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes # tk8.5.9/win/tkWinWm.c:342 diff --git a/update.py b/update.py index 024aeb098..e0b8f97bf 100644 --- a/update.py +++ b/update.py @@ -115,21 +115,6 @@ def __init__(self, tkroot: tk.Tk | None = None, provider: str = 'internal'): return - if sys.platform == 'darwin': - import objc - - try: - objc.loadBundle( - 'Sparkle', globals(), join(dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework') - ) - # loadBundle presumably supplies `SUUpdater` - self.updater = SUUpdater.sharedUpdater() # noqa: F821 - - except Exception: - # can't load framework - not frozen or not included in app bundle? - print_exc() - self.updater = None - def set_automatic_updates_check(self, onoroff: bool) -> None: """ Set (Win)Sparkle to perform automatic update checks, or not. @@ -142,9 +127,6 @@ def set_automatic_updates_check(self, onoroff: bool) -> None: if sys.platform == 'win32' and self.updater: self.updater.win_sparkle_set_automatic_check_for_updates(onoroff) - if sys.platform == 'darwin' and self.updater: - self.updater.SUEnableAutomaticChecks(onoroff) - def check_for_updates(self) -> None: """Trigger the requisite method to check for an update.""" if self.use_internal(): @@ -155,9 +137,6 @@ def check_for_updates(self) -> None: elif sys.platform == 'win32' and self.updater: self.updater.win_sparkle_check_update_with_ui() - elif sys.platform == 'darwin' and self.updater: - self.updater.checkForUpdates_(None) - def check_appcast(self) -> EDMCVersion | None: """ Manually (no Sparkle or WinSparkle) check the update_feed appcast file. @@ -184,9 +163,6 @@ def check_appcast(self) -> EDMCVersion | None: return None - if sys.platform == 'darwin': - sparkle_platform = 'macos' - else: # For *these* purposes anything else is the same as 'windows', as # non-win32 would be running from source. From 05eaf059389a25d16c778ea6861e0836a71184f3 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 19:41:01 -0400 Subject: [PATCH 115/261] [Minor] Remove Unused Import For Now --- ttkHyperlinkLabel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 0ca87fc7f..3b65d6b9f 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -22,7 +22,6 @@ import sys import tkinter as tk -import warnings import webbrowser from tkinter import font as tk_font from tkinter import ttk From 08818785d051b81a7ad88c6975b0be0df88a07c7 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 20:03:23 -0400 Subject: [PATCH 116/261] [2186] HyperLinkLabel first pass --- ttkHyperlinkLabel.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 3b65d6b9f..de9065105 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -32,7 +32,7 @@ def _(x: str) -> str: return x # FIXME: Split this into multi-file module to separate the platforms -class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # type: ignore +class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore """Clickable label for HTTP links.""" def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None: @@ -50,22 +50,14 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non self.foreground = kw.get('foreground', 'blue') self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup( 'TLabel', 'foreground', ('disabled',))) # ttk.Label doesn't support disabledforeground option - - if sys.platform == 'darwin': - # Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult - kw['background'] = kw.pop('background', 'systemDialogBackgroundActive') - kw['anchor'] = kw.pop('anchor', tk.W) # like ttk.Label - tk.Label.__init__(self, master, **kw) - - else: - ttk.Label.__init__(self, master, **kw) + ttk.Label.__init__(self, master, **kw) self.bind('', self._click) self.menu = tk.Menu(tearoff=tk.FALSE) # LANG: Label for 'Copy' as in 'Copy and Paste' self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste - self.bind(sys.platform == 'darwin' and '' or '', self._contextmenu) + self.bind('', self._contextmenu) self.bind('', self._enter) self.bind('', self._leave) @@ -106,10 +98,9 @@ def configure( # noqa: CCR001 if state == tk.DISABLED: kw['cursor'] = 'arrow' # System default elif self.url and (kw['text'] if 'text' in kw else self['text']): - kw['cursor'] = 'pointinghand' if sys.platform == 'darwin' else 'hand2' + kw['cursor'] = 'hand2' else: - kw['cursor'] = 'notallowed' if sys.platform == 'darwin' else ( - 'no' if sys.platform == 'win32' else 'circle') + kw['cursor'] = ('no' if sys.platform == 'win32' else 'circle') return super().configure(cnf, **kw) @@ -139,7 +130,7 @@ def _click(self, event: tk.Event) -> None: def _contextmenu(self, event: tk.Event) -> None: if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy): - self.menu.post(sys.platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root) + self.menu.post(event.x_root, event.y_root) def copy(self) -> None: """Copy the current text to the clipboard.""" From f7b39f8dafedf453f6f80e278f88d8e12ccced60 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 20:10:55 -0400 Subject: [PATCH 117/261] [2186] MyNB First Pass --- myNotebook.py | 66 +++++++-------------------------------------------- 1 file changed, 9 insertions(+), 57 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index dbfc92505..efae591c8 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -16,13 +16,7 @@ import tkinter as tk from tkinter import ttk -# Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult -if sys.platform == 'darwin': - from platform import mac_ver - PAGEFG = 'systemButtonText' - PAGEBG = 'systemButtonActiveDarkShadow' - -elif sys.platform == 'win32': +if sys.platform == 'win32': PAGEFG = 'SystemWindowText' PAGEBG = 'SystemWindow' # typically white @@ -35,14 +29,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): ttk.Notebook.__init__(self, master, **kw) style = ttk.Style() - if sys.platform == 'darwin': - if list(map(int, mac_ver()[0].split('.'))) >= [10, 10]: - # Hack for tab appearance with 8.5 on Yosemite & El Capitan. For proper fix see - # https://github.com/tcltk/tk/commit/55c4dfca9353bbd69bbcec5d63bf1c8dfb461e25 - style.configure('TNotebook.Tab', padding=(12, 10, 12, 2)) - style.map('TNotebook.Tab', foreground=[('selected', '!background', 'systemWhite')]) - self.grid(sticky=tk.NSEW) # Already padded apropriately - elif sys.platform == 'win32': + if sys.platform == 'win32': style.configure('nb.TFrame', background=PAGEBG) style.configure('nb.TButton', background=PAGEBG) style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG) @@ -60,11 +47,7 @@ class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame): # type: ignore """Custom t(t)k.Frame class to fix some display issues.""" def __init__(self, master: ttk.Notebook | None = None, **kw): - if sys.platform == 'darwin': - kw['background'] = kw.pop('background', PAGEBG) - tk.Frame.__init__(self, master, **kw) - tk.Frame(self).grid(pady=5) - elif sys.platform == 'win32': + if sys.platform == 'win32': ttk.Frame.__init__(self, master, style='nb.TFrame', **kw) ttk.Frame(self).grid(pady=5) # top spacer else: @@ -91,21 +74,14 @@ class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): # type: ignore """Custom t(t)k.Entry class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - if sys.platform == 'darwin': - kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) - tk.Entry.__init__(self, master, **kw) - else: - ttk.Entry.__init__(self, master, **kw) + ttk.Entry.__init__(self, master, **kw) class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): # type: ignore """Custom t(t)k.Button class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - if sys.platform == 'darwin': - kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) - tk.Button.__init__(self, master, **kw) - elif sys.platform == 'win32': + if sys.platform == 'win32': ttk.Button.__init__(self, master, style='nb.TButton', **kw) else: ttk.Button.__init__(self, master, **kw) @@ -115,29 +91,14 @@ class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button): # type """Custom t(t)k.ColoredButton class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - if sys.platform == 'darwin': - # Can't set Button background on OSX, so use a Label instead - kw['relief'] = kw.pop('relief', tk.RAISED) - self._command = kw.pop('command', None) - tk.Label.__init__(self, master, **kw) - self.bind('', self._press) - else: - tk.Button.__init__(self, master, **kw) - - if sys.platform == 'darwin': - def _press(self, event): - self._command() + tk.Button.__init__(self, master, **kw) class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): # type: ignore """Custom t(t)k.Checkbutton class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - if sys.platform == 'darwin': - kw['foreground'] = kw.pop('foreground', PAGEFG) - kw['background'] = kw.pop('background', PAGEBG) - tk.Checkbutton.__init__(self, master, **kw) - elif sys.platform == 'win32': + if sys.platform == 'win32': ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw) else: ttk.Checkbutton.__init__(self, master, **kw) @@ -147,11 +108,7 @@ class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton """Custom t(t)k.Radiobutton class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - if sys.platform == 'darwin': - kw['foreground'] = kw.pop('foreground', PAGEFG) - kw['background'] = kw.pop('background', PAGEBG) - tk.Radiobutton.__init__(self, master, **kw) - elif sys.platform == 'win32': + if sys.platform == 'win32': ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw) else: ttk.Radiobutton.__init__(self, master, **kw) @@ -161,12 +118,7 @@ class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu): """Custom t(t)k.OptionMenu class to fix some display issues.""" def __init__(self, master, variable, default=None, *values, **kw): - if sys.platform == 'darwin': - variable.set(default) - bg = kw.pop('background', PAGEBG) - tk.OptionMenu.__init__(self, master, variable, *values, **kw) - self['background'] = bg - elif sys.platform == 'win32': + if sys.platform == 'win32': # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw) self['menu'].configure(background=PAGEBG) From c1b8533cb41c2e33d63311dac120ff7b0ddd4c29 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 21:22:35 -0400 Subject: [PATCH 118/261] [2186] Simplify myNB Files --- docs/examples/click_counter/load.py | 4 +- myNotebook.py | 95 ++++++++++++----------------- plugins/coriolis.py | 8 +-- plugins/edsm.py | 8 +-- plugins/inara.py | 4 +- prefs.py | 22 +++---- 6 files changed, 62 insertions(+), 79 deletions(-) diff --git a/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py index ad90a084a..f7f238377 100644 --- a/docs/examples/click_counter/load.py +++ b/docs/examples/click_counter/load.py @@ -7,6 +7,8 @@ import logging import tkinter as tk +from tkinter import ttk + import myNotebook as nb # noqa: N813 from config import appname, config @@ -63,7 +65,7 @@ def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk # setup our config in a "Click Count: number" nb.Label(frame, text='Click Count').grid(row=current_row) - nb.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1) + ttk.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1) current_row += 1 # Always increment our row counter, makes for far easier tkinter design. return frame diff --git a/myNotebook.py b/myNotebook.py index efae591c8..528664ba3 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -1,12 +1,9 @@ """ Custom `ttk.Notebook` to fix various display issues. -Hacks to fix various display issues with notebooks and their child widgets on -OSX and Windows. +Hacks to fix various display issues with notebooks and their child widgets on Windows. - Windows: page background should be White, not SystemButtonFace -- OSX: page background should be a darker gray than systemWindowBody - selected tab foreground should be White when the window is active Entire file may be imported by plugins. """ @@ -26,24 +23,17 @@ class Notebook(ttk.Notebook): def __init__(self, master: ttk.Frame | None = None, **kw): - ttk.Notebook.__init__(self, master, **kw) + super().__init__(master, **kw) style = ttk.Style() + style.configure('nb.TFrame', background=PAGEBG) + style.configure('nb.TButton', background=PAGEBG) + style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG) + style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG) + style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG) + self.grid(padx=10, pady=10, sticky=tk.NSEW) - if sys.platform == 'win32': - style.configure('nb.TFrame', background=PAGEBG) - style.configure('nb.TButton', background=PAGEBG) - style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG) - style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG) - style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG) - self.grid(padx=10, pady=10, sticky=tk.NSEW) - else: - self.grid(padx=10, pady=10, sticky=tk.NSEW) - -# FIXME: The real fix for this 'dynamic type' would be to split this whole -# thing into being a module with per-platform files, as we've done with config -# That would also make the code cleaner. -class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame): # type: ignore +class Frame(tk.Frame or ttk.Frame): # type: ignore """Custom t(t)k.Frame class to fix some display issues.""" def __init__(self, master: ttk.Notebook | None = None, **kw): @@ -60,26 +50,25 @@ class Label(tk.Label): """Custom tk.Label class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that - if sys.platform == 'win32': - kw['foreground'] = kw.pop('foreground', PAGEFG) - kw['background'] = kw.pop('background', PAGEBG) - else: - kw['foreground'] = kw.pop('foreground', ttk.Style().lookup('TLabel', 'foreground')) - kw['background'] = kw.pop('background', ttk.Style().lookup('TLabel', 'background')) - tk.Label.__init__(self, master, **kw) # Just use tk.Label on all platforms + kw['foreground'] = kw.pop('foreground', PAGEFG if sys.platform == 'win32' + else ttk.Style().lookup('TLabel', 'foreground')) + kw['background'] = kw.pop('background', PAGEBG if sys.platform == 'win32' + else ttk.Style().lookup('TLabel', 'background')) + super().__init__(master, **kw) -class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): # type: ignore +class Entry(ttk.Entry): # type: ignore """Custom t(t)k.Entry class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later. def __init__(self, master: ttk.Frame | None = None, **kw): - ttk.Entry.__init__(self, master, **kw) + super().__init__(master, **kw) -class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): # type: ignore +class Button(tk.Button or ttk.Button): # type: ignore """Custom t(t)k.Button class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Button. Will remove in 5.12 or later. def __init__(self, master: ttk.Frame | None = None, **kw): if sys.platform == 'win32': ttk.Button.__init__(self, master, style='nb.TButton', **kw) @@ -87,47 +76,39 @@ def __init__(self, master: ttk.Frame | None = None, **kw): ttk.Button.__init__(self, master, **kw) -class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button): # type: ignore +class ColoredButton(tk.Label or tk.Button): # type: ignore """Custom t(t)k.ColoredButton class to fix some display issues.""" + # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later. def __init__(self, master: ttk.Frame | None = None, **kw): tk.Button.__init__(self, master, **kw) -class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): # type: ignore +class Checkbutton(ttk.Checkbutton): """Custom t(t)k.Checkbutton class to fix some display issues.""" - def __init__(self, master: ttk.Frame | None = None, **kw): - if sys.platform == 'win32': - ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw) - else: - ttk.Checkbutton.__init__(self, master, **kw) + def __init__(self, master=None, **kw): + style = 'nb.TCheckbutton' if sys.platform == 'win32' else None + super().__init__(master, style=style, **kw) # type: ignore -class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton): # type: ignore +class Radiobutton(ttk.Radiobutton): """Custom t(t)k.Radiobutton class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): - if sys.platform == 'win32': - ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw) - else: - ttk.Radiobutton.__init__(self, master, **kw) + style = 'nb.TRadiobutton' if sys.platform == 'win32' else None + super().__init__(master, style=style, **kw) # type: ignore -class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu): # type: ignore - """Custom t(t)k.OptionMenu class to fix some display issues.""" +class OptionMenu(ttk.OptionMenu): + """Custom ttk.OptionMenu class to fix some display issues.""" def __init__(self, master, variable, default=None, *values, **kw): - if sys.platform == 'win32': - # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style - ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw) - self['menu'].configure(background=PAGEBG) - # Workaround for https://bugs.python.org/issue25684 - for i in range(0, self['menu'].index('end')+1): - self['menu'].entryconfig(i, variable=variable) - else: - ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw) - self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background')) - # Workaround for https://bugs.python.org/issue25684 - for i in range(0, self['menu'].index('end')+1): - self['menu'].entryconfig(i, variable=variable) + style = 'nb.TMenubutton' if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background') + menu_background = PAGEBG if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background') + + super().__init__(master, variable, default, *values, style=style, **kw) + self['menu'].configure(background=menu_background) + + for i in range(self['menu'].index('end') + 1): + self['menu'].entryconfig(i, variable=variable) diff --git a/plugins/coriolis.py b/plugins/coriolis.py index c142c686e..20da16b19 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -106,12 +106,12 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) - nb.Entry(conf_frame, + ttk.Entry(conf_frame, textvariable=coriolis_config.normal_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - nb.Button(conf_frame, text=_("Reset"), + ttk.Button(conf_frame, text=_("Reset"), command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) @@ -119,11 +119,11 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr # LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) - nb.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid( + ttk.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - nb.Button(conf_frame, text=_('Reset'), + ttk.Button(conf_frame, text=_('Reset'), command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) diff --git a/plugins/edsm.py b/plugins/edsm.py index 46d05f4c1..146dd5ab6 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -113,10 +113,10 @@ def __init__(self): self.cmdr_text: nb.Label | None = None self.user_label: nb.Label | None = None - self.user: nb.Entry | None = None + self.user: ttk.Entry | None = None self.apikey_label: nb.Label | None = None - self.apikey: nb.Entry | None = None + self.apikey: ttk.Entry | None = None this = This() @@ -345,14 +345,14 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr # LANG: EDSM Commander name label in EDSM settings this.user_label = nb.Label(frame, text=_('Commander Name')) this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.user = nb.Entry(frame) + this.user = ttk.Entry(frame) this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 # LANG: EDSM API key label this.apikey_label = nb.Label(frame, text=_('API Key')) this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.apikey = nb.Entry(frame, show="*", width=50) + this.apikey = ttk.Entry(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 diff --git a/plugins/inara.py b/plugins/inara.py index 0e0eb7bff..d96e05ef5 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -125,7 +125,7 @@ def __init__(self): self.log: 'tk.IntVar' self.log_button: nb.Checkbutton self.label: HyperlinkLabel - self.apikey: nb.Entry + self.apikey: ttk.Entry self.apikey_label: tk.Label self.events: dict[Credentials, Deque[Event]] = defaultdict(deque) @@ -292,7 +292,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame: # LANG: Inara API key label this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.apikey = nb.Entry(frame, show="*", width=50) + this.apikey = ttk.Entry(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 diff --git a/prefs.py b/prefs.py index 862bda279..ef43c5de3 100644 --- a/prefs.py +++ b/prefs.py @@ -361,12 +361,12 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: # Type ignored due to incorrect type annotation. a 2 tuple does padding for each side self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) # type: ignore - self.outdir_entry = nb.Entry(output_frame, takefocus=False) + self.outdir_entry = ttk.Entry(output_frame, takefocus=False) self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) text = (_('Browse...')) # LANG: NOT-macOS Settings - files location selection button - self.outbutton = nb.Button( + self.outbutton = ttk.Button( output_frame, text=text, # Technically this is different from the label in Settings > Output, as *this* is used @@ -399,7 +399,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 logdir = default self.logdir.set(logdir) - self.logdir_entry = nb.Entry(config_frame, takefocus=False) + self.logdir_entry = ttk.Entry(config_frame, takefocus=False) # Location of the Journal files nb.Label( @@ -413,7 +413,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 text = (_('Browse...')) # LANG: NOT-macOS Setting - files location selection button with row as cur_row: - self.logbutton = nb.Button( + self.logbutton = ttk.Button( config_frame, text=text, # LANG: Settings > Configuration - Label for Journal files location @@ -423,7 +423,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 if config.default_journal_dir_path: # Appearance theme and language setting - nb.Button( + ttk.Button( config_frame, # LANG: Settings > Configuration - Label on 'reset journal files location to default' button text=_('Default'), @@ -465,7 +465,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 text=_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) - self.hotkey_text = nb.Entry(config_frame, width=30, justify=tk.CENTER) + self.hotkey_text = ttk.Entry(config_frame, width=30, justify=tk.CENTER) self.hotkey_text.insert( 0, # No hotkey/shortcut currently defined @@ -623,7 +623,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.loglevel_dropdown.configure(width=15) self.loglevel_dropdown.grid(column=1, pady=self.BOXY, sticky=tk.W, row=cur_row) - nb.Button( + ttk.Button( config_frame, # LANG: Label on button used to open a filesystem folder text=_('Open Log Folder'), # Button that opens a folder in Explorer/Finder @@ -726,7 +726,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: self.theme_label_0.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) # Main window - self.theme_button_0 = nb.ColoredButton( + self.theme_button_0 = tk.Button( appearance_frame, # LANG: Appearance - Example 'Normal' text text=_('Station'), @@ -739,7 +739,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: with row as cur_row: self.theme_label_1 = nb.Label(appearance_frame, text=self.theme_prompts[1]) self.theme_label_1.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) - self.theme_button_1 = nb.ColoredButton( + self.theme_button_1 = tk.Button( appearance_frame, text=' Hutton Orbital ', # Do not translate background='grey4', @@ -870,7 +870,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) - plugdirentry = nb.Entry(plugins_frame, justify=tk.LEFT) + plugdirentry = ttk.Entry(plugins_frame, justify=tk.LEFT) self.displaypath(plugdir, plugdirentry) plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) @@ -882,7 +882,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled') ).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row) - nb.Button( + ttk.Button( plugins_frame, # LANG: Label on button used to open a filesystem folder text=_('Open'), # Button that opens a folder in Explorer/Finder From ae74d949a892b919a275c2cda45e951b97c3082c Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 21:24:11 -0400 Subject: [PATCH 119/261] [Nit] Indentation, Yay! --- plugins/coriolis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/coriolis.py b/plugins/coriolis.py index 20da16b19..a97eb6c6f 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -107,12 +107,12 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) ttk.Entry(conf_frame, - textvariable=coriolis_config.normal_textvar).grid( + textvariable=coriolis_config.normal_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label ttk.Button(conf_frame, text=_("Reset"), - command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( + command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) cur_row += 1 @@ -124,7 +124,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr ) # LANG: Generic 'Reset' button label ttk.Button(conf_frame, text=_('Reset'), - command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( + command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) cur_row += 1 From cfb6f729ab5888238c552aa0bb0d568b69356fbb Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 21:27:22 -0400 Subject: [PATCH 120/261] [2186] Remove Unused Translations --- L10n/en.template | 24 ------------------------ config/__init__.py | 2 +- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/L10n/en.template b/L10n/en.template index f5acb3772..7ba67a618 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -78,12 +78,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Edit"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "View"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Window"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Help"; @@ -351,9 +345,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Error: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Preferences"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Please choose what data to save"; @@ -372,9 +363,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "File location"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Change..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Browse..."; @@ -390,21 +378,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Enable Fleetcarrier CAPI Queries"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Keyboard shortcut"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Hotkey"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Re-start {APP} to use shortcuts"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} needs permission to use shortcuts"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Open System Preferences"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Only when Elite: Dangerous is the active app"; diff --git a/config/__init__.py b/config/__init__.py index 992710a0f..c6c404b92 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.10.3' +_static_appversion = '5.11.0-alpha0' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 016fb96e065b45bd803587b31edb9619809f7198 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 22:01:49 -0400 Subject: [PATCH 121/261] [2186] General Cleanup --- prefs.py | 4 ++-- td.py | 1 - ttkHyperlinkLabel.py | 1 - update.py | 9 +++------ 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/prefs.py b/prefs.py index ef43c5de3..307e3c7ac 100644 --- a/prefs.py +++ b/prefs.py @@ -364,7 +364,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: self.outdir_entry = ttk.Entry(output_frame, takefocus=False) self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - text = (_('Browse...')) # LANG: NOT-macOS Settings - files location selection button + text = _('Browse...') # LANG: NOT-macOS Settings - files location selection button self.outbutton = ttk.Button( output_frame, @@ -410,7 +410,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - text = (_('Browse...')) # LANG: NOT-macOS Setting - files location selection button + text = _('Browse...') # LANG: NOT-macOS Setting - files location selection button with row as cur_row: self.logbutton = ttk.Button( diff --git a/td.py b/td.py index d2b5dbdd5..484e8d29c 100644 --- a/td.py +++ b/td.py @@ -1,7 +1,6 @@ """Export data for Trade Dangerous.""" import pathlib -import sys import time from collections import defaultdict from operator import itemgetter diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index de9065105..9bb3b9bff 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -31,7 +31,6 @@ def _(x: str) -> str: return x -# FIXME: Split this into multi-file module to separate the platforms class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore """Clickable label for HTTP links.""" diff --git a/update.py b/update.py index e0b8f97bf..346aff636 100644 --- a/update.py +++ b/update.py @@ -7,10 +7,8 @@ """ from __future__ import annotations -import os import sys import threading -from os.path import dirname, join from traceback import print_exc from typing import TYPE_CHECKING from xml.etree import ElementTree @@ -163,10 +161,9 @@ def check_appcast(self) -> EDMCVersion | None: return None - else: - # For *these* purposes anything else is the same as 'windows', as - # non-win32 would be running from source. - sparkle_platform = 'windows' + # For *these* purposes anything else is the same as 'windows', as + # non-win32 would be running from source. + sparkle_platform = 'windows' for item in feed.findall('channel/item'): # xml is a pain with types, hence these ignores From 1800f8f0b18cb8e13a7cc032d4eac61f7f67cbb1 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 22:17:21 -0400 Subject: [PATCH 122/261] [2186] Remove Some Comments --- config/__init__.py | 1 - dashboard.py | 4 ++-- monitor.py | 2 +- prefs.py | 2 +- theme.py | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index c6c404b92..99e8fdc63 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -7,7 +7,6 @@ Windows uses the Registry to store values in a flat manner. Linux uses a file, but for commonality it's still a flat data structure. -macOS uses a 'defaults' object. """ from __future__ import annotations diff --git a/dashboard.py b/dashboard.py index 3948c92bf..4776319c5 100644 --- a/dashboard.py +++ b/dashboard.py @@ -26,7 +26,7 @@ else: # Linux's inotify doesn't work over CIFS or NFS, so poll class FileSystemEventHandler: # type: ignore - """Dummy class to represent a file system event handler on platforms other than macOS and Windows.""" + """Dummy class to represent a file system event handler on platforms other than Windows.""" class Dashboard(FileSystemEventHandler): @@ -160,7 +160,7 @@ def poll(self, first_time: bool = False) -> None: def on_modified(self, event) -> None: """ - Watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows. + Watchdog callback - FileModifiedEvent on Windows. :param event: Watchdog event. """ diff --git a/monitor.py b/monitor.py index d549e5330..5acbcbc4d 100644 --- a/monitor.py +++ b/monitor.py @@ -439,7 +439,7 @@ def worker(self) -> None: # noqa: C901, CCR001 new_journal_file = None if logfile: - loghandle.seek(0, SEEK_END) # required to make macOS notice log change over SMB + loghandle.seek(0, SEEK_END) # required to make macOS notice log change over SMB. # TODO: Do we need this? loghandle.seek(log_pos, SEEK_SET) # reset EOF flag # TODO: log_pos reported as possibly unbound for line in loghandle: # Paranoia check to see if we're shutting down diff --git a/prefs.py b/prefs.py index 307e3c7ac..5b045b1a6 100644 --- a/prefs.py +++ b/prefs.py @@ -285,7 +285,7 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): # wait for window to appear on screen before calling grab_set self.parent.update_idletasks() - self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on OSX & Linux + self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on Linux self.wait_visibility() self.grab_set() diff --git a/theme.py b/theme.py index b128bc910..bbe62ef5f 100644 --- a/theme.py +++ b/theme.py @@ -150,7 +150,7 @@ def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, # the widget has explicit fg or bg attributes. assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget if not self.defaults: - # Can't initialise this til window is created # Windows, MacOS + # Can't initialise this til window is created # Windows self.defaults = { 'fg': tk.Label()['foreground'], # SystemButtonText, systemButtonText 'bg': tk.Label()['background'], # SystemButtonFace, White From 56a4ae25e0e598666bd0c8c6f8fa72daacdd90a2 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 27 Mar 2024 22:21:26 -0400 Subject: [PATCH 123/261] [Nit] Clarify Comments --- monitor.py | 2 +- myNotebook.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monitor.py b/monitor.py index 5acbcbc4d..ba634c51f 100644 --- a/monitor.py +++ b/monitor.py @@ -439,7 +439,7 @@ def worker(self) -> None: # noqa: C901, CCR001 new_journal_file = None if logfile: - loghandle.seek(0, SEEK_END) # required to make macOS notice log change over SMB. # TODO: Do we need this? + loghandle.seek(0, SEEK_END) # required for macOS to notice log change over SMB. TODO: Do we need this? loghandle.seek(log_pos, SEEK_SET) # reset EOF flag # TODO: log_pos reported as possibly unbound for line in loghandle: # Paranoia check to see if we're shutting down diff --git a/myNotebook.py b/myNotebook.py index 528664ba3..646245505 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -57,7 +57,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) -class Entry(ttk.Entry): # type: ignore +class Entry(ttk.Entry): """Custom t(t)k.Entry class to fix some display issues.""" # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later. From b5a4ee6ed2f60c68cec307e9bcd946d6440fc0b7 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 28 Mar 2024 10:49:01 -0400 Subject: [PATCH 124/261] [Nit] Cleanup some Flake8 --- config/__init__.py | 1 - plugins/eddn.py | 1 - protocol.py | 36 ++++++++++++++++++------------------ tests/config/_old_config.py | 6 +++--- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index 99e8fdc63..d85956a78 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -467,7 +467,6 @@ def get_config(*args, **kwargs) -> AbstractConfig: :param kwargs: Args to be passed through to implementation. :return: Instance of the implementation. """ - if sys.platform == "win32": # pragma: sys-platform-win32 from .windows import WinConfig return WinConfig(*args, **kwargs) diff --git a/plugins/eddn.py b/plugins/eddn.py index b71337257..6345f5793 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -27,7 +27,6 @@ import pathlib import re import sqlite3 -import sys import tkinter as tk from platform import system from textwrap import dedent diff --git a/protocol.py b/protocol.py index 0f1c157f0..4c9e41ca5 100644 --- a/protocol.py +++ b/protocol.py @@ -26,6 +26,7 @@ if sys.platform == 'win32': from ctypes import windll # type: ignore + try: if windll.ntdll.wine_get_version: is_wine = True @@ -58,13 +59,13 @@ def event(self, url: str) -> None: self.master.event_generate('<>', when="tail") -if (config.auth_force_edmc_protocol - or ( - sys.platform == 'win32' - and getattr(sys, 'frozen', False) - and not is_wine - and not config.auth_force_localserver - )): +if (config.auth_force_edmc_protocol # noqa: C901 + or ( + sys.platform == 'win32' + and getattr(sys, 'frozen', False) + and not is_wine + and not config.auth_force_localserver + )): # This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces assert sys.platform == 'win32' # spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL @@ -186,11 +187,11 @@ def WndProc(hwnd: HWND, message: UINT, wParam: WPARAM, lParam: LPARAM) -> c_long # which we can read out as shown below, and then compare. target_is_valid = lparam_low == 0 or ( - GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname + GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname ) topic_is_valid = lparam_high == 0 or ( - GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system' + GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system' ) if target_is_valid and topic_is_valid: @@ -251,15 +252,15 @@ def worker(self) -> None: # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw hwnd = CreateWindowExW( - 0, # dwExStyle + 0, # dwExStyle wndclass.lpszClassName, # lpClassName - "DDE Server", # lpWindowName - 0, # dwStyle + "DDE Server", # lpWindowName + 0, # dwStyle CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, # X, Y, nWidth, nHeight self.master.winfo_id(), # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts - None, # hMenu - wndclass.hInstance, # hInstance - None # lpParam + None, # hMenu + wndclass.hInstance, # hInstance + None # lpParam ) msg = MSG() @@ -421,10 +422,9 @@ def get_handler_impl() -> Type[GenericProtocolHandler]: :return: An instantiatable GenericProtocolHandler """ - if ( - (sys.platform == 'win32' and config.auth_force_edmc_protocol) - or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver) + (sys.platform == 'win32' and config.auth_force_edmc_protocol) + or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver) ): return WindowsProtocolHandler diff --git a/tests/config/_old_config.py b/tests/config/_old_config.py index 35ae19e32..2b5244b67 100644 --- a/tests/config/_old_config.py +++ b/tests/config/_old_config.py @@ -5,8 +5,8 @@ import sys import warnings from configparser import NoOptionError -from os import getenv, makedirs, mkdir, pardir -from os.path import dirname, expanduser, isdir, join, normpath +from os import getenv, makedirs, mkdir +from os.path import dirname, expanduser, isdir, join from typing import TYPE_CHECKING from config import applongname, appname, update_interval from EDMCLogging import get_main_logger @@ -109,7 +109,7 @@ class OldConfig: OUT_EDDN_DELAY = 4096 OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV - if sys.platform == 'win32': + if sys.platform == 'win32': # noqa: C901 def __init__(self): self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore From 572f724a71a851b717f1a9938fd21e4fd24682c2 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 28 Mar 2024 12:59:22 -0400 Subject: [PATCH 125/261] [1471] Add PIL to Improve Clipboard --- L10n/en.template | 5 ++++- build.py | 1 - myNotebook.py | 22 ++++++++++++++++++---- requirements.txt | 1 + 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/L10n/en.template b/L10n/en.template index f5acb3772..bf8d022e4 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -802,4 +802,7 @@ "Ships" = "Ships"; /* update.py: Update Available Text; In files: update.py:229; */ -"{NEWVER} is available" = "{NEWVER} is available"; \ No newline at end of file +"{NEWVER} is available" = "{NEWVER} is available"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Cannot paste non-text content."; diff --git a/build.py b/build.py index 1bc967651..2d4ef7902 100644 --- a/build.py +++ b/build.py @@ -133,7 +133,6 @@ def build() -> None: "distutils", "_markerlib", "optparse", - "PIL", "simplejson", "unittest", "doctest", diff --git a/myNotebook.py b/myNotebook.py index 63bb7dc89..398b21360 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -14,7 +14,12 @@ import sys import tkinter as tk -from tkinter import ttk +from tkinter import ttk, messagebox +from typing import TYPE_CHECKING +from PIL import ImageGrab + +if TYPE_CHECKING: + def _(x: str) -> str: return x # Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult if sys.platform == 'darwin': @@ -126,13 +131,22 @@ def cut(self) -> None: def paste(self) -> None: """Paste the selected Entry text.""" - if self.selection_present(): - self.delete(tk.SEL_FIRST, tk.SEL_LAST) try: + # Attempt to grab an image from the clipboard (apprently also works for files) + img = ImageGrab.grabclipboard() + if img: + # Hijack existing translation, yes it doesn't exactly match here. + # LANG: Generic error prefix - following text is from Frontier auth service; + messagebox.showwarning(_('Error'), + _('Cannot paste non-text content.')) # LANG: Can't Paste Images or Files in Text + return text = self.clipboard_get() + if self.selection_present() and text: + self.delete(tk.SEL_FIRST, tk.SEL_LAST) self.insert(tk.INSERT, text) except tk.TclError: - pass # No text in clipboard or clipboard is not text + # No text in clipboard or clipboard is not text + pass class Entry(sys.platform == 'darwin' and tk.Entry or EntryMenu or ttk.Entry): # type: ignore diff --git a/requirements.txt b/requirements.txt index 398041a13..e0e6138f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ certifi==2023.11.17 requests==2.31.0 +pillow==10.2.0 # requests depends on this now ? charset-normalizer==2.1.1 From 0d9607b4f89ee2d351542fb2d69b156e4bce9b3c Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 30 Mar 2024 16:02:54 -0400 Subject: [PATCH 126/261] [Minor] Add Missing Future Annotation --- config/linux.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/linux.py b/config/linux.py index 73100800f..51a406262 100644 --- a/config/linux.py +++ b/config/linux.py @@ -5,6 +5,8 @@ Licensed under the GNU General Public License. See LICENSE file. """ +from __future__ import annotations + import os import pathlib import sys From a863cebc661e01869863deeb4ccd201d133fd4bf Mon Sep 17 00:00:00 2001 From: aussig Date: Mon, 1 Apr 2024 07:48:15 +0100 Subject: [PATCH 127/261] [2188] Add optional lang parameter to l10n.Translations.translate() to allow language to be overridden. --- PLUGINS.md | 103 +++++++++++++++++++++++++++++------------------------ l10n.py | 39 +++++++++++++++----- 2 files changed, 86 insertions(+), 56 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 3048f345f..5800efb0d 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -31,7 +31,7 @@ then you'll need to be using an appropriate version of Python. The current version is listed in the [Environment section of Releasing.md](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md#environment). If you're developing your plugin simply against an install of EDMarketConnector -then you'll be relying on the bundled version of Python (it's baked +then you'll be relying on the bundled version of Python (it's baked into the .exe via the py2exe build process). Please be sure to read the [Avoiding potential pitfalls](#avoiding-potential-pitfalls) @@ -40,21 +40,21 @@ EDMarketConnector code including whole application crashes. ## Being aware of core application changes -It is highly advisable to ensure you are aware of all EDMarketConnector -releases, including the pre-releases. The -beta and -rc changelogs will +It is highly advisable to ensure you are aware of all EDMarketConnector +releases, including the pre-releases. The -beta and -rc changelogs will contain valuable information about any forthcoming changes that affect plugins. The easiest way is: 1. Login to [GitHub](https://github.com). 2. Navigate to [EDMarketConnector](https://github.com/EDCD/EDMarketConnector). - 3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on - us). It's currently (2021-05-13) the left-most button of 3 near the + 3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on + us). It's currently (2021-05-13) the left-most button of 3 near the top-right of the page. 4. Click 'Custom'. 5. Ensure 'Releases' is selected. 6. Click 'Apply'. -And, of course, either ensure you check your GitHub messages regularly, or +And, of course, either ensure you check your GitHub messages regularly, or have it set up to email you such notifications. You should also keep an eye on [our GitHub Discussions](https://github.com/EDCD/EDMarketConnector/discussions) @@ -113,13 +113,13 @@ from the original files unless specified as allowed in this section. Use `monitor.game_running()` as follows in case a plugin needs to know if we think the game is running. *NB: This is a function, and should be called as such. Using the bare word `game_running` will always be `True`.* - + ``` from monitor import monitor ... if monitor.game_running(): ... -``` +``` Use `monitor.is_live_galaxy()` to determine if the player is playing in the Live galaxy. Note the implementation details of this. At time of writing it @@ -135,7 +135,7 @@ append a string to call out your plugin if you wish). `from ttkHyperlinkLabel import HyperlinkLabel` and `import myNotebook as nb` - For creating UI elements. -In addition to the above we also explicitly package the following python +In addition to the above we also explicitly package the following python modules for plugin use: - shutil @@ -252,7 +252,7 @@ include variables, and even the returns of functions, in the output. ## Checking core EDMC version -If you have code that needs to act differently under different versions of +If you have code that needs to act differently under different versions of this application then you can check utilise `config.appversion`. Prior to version 5.0.0 this was a simple string. From 5.0.0 onwards it is, @@ -313,7 +313,7 @@ Mac, and `$TMP/EDMarketConnector.log` on Linux. ## Avoiding potential pitfalls -There are a number of things that your code should either do or avoiding +There are a number of things that your code should either do or avoiding doing so as to play nicely with the core EDMarketConnector code and not risk causing application crashes or hangs. @@ -324,12 +324,12 @@ See the section on [packaging extra modules](#your-plugin-directory-name-must-be ### Use a thread for long-running code -By default, your plugin code will be running in the main thread. So, if you -perform some operation that takes significant time (more than a second) you -will be blocking both the core code from continuing *and* any other plugins +By default, your plugin code will be running in the main thread. So, if you +perform some operation that takes significant time (more than a second) you +will be blocking both the core code from continuing *and* any other plugins from running their main-thread code. -This includes any connections to remote services, such as a website or +This includes any connections to remote services, such as a website or remote database. So please place such code within its own thread. See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py) @@ -338,20 +338,20 @@ with a queue to send data, and telling the sub-thread to stop during shutdown. ### All tkinter calls in main thread -The only tkinter calls that should ever be made from a sub-thread are +The only tkinter calls that should ever be made from a sub-thread are `event_generate()` calls to send data back to the main thread. -Any attempt to manipulate tkinter UI elements directly from a sub-thread +Any attempt to manipulate tkinter UI elements directly from a sub-thread will most likely crash the whole program. See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py) -code for an example of using `event_generate()` to cause the plugin main -thread code to update a UI element. Start from the `plugin_app()` +code for an example of using `event_generate()` to cause the plugin main +thread code to update a UI element. Start from the `plugin_app()` implementation. ### Do not call tkinter `event_generate` during shutdown. -However, you must **not** make *any* tkinter `event_generate()` call whilst +However, you must **not** make *any* tkinter `event_generate()` call whilst the application is shutting down. The application shutdown sequence is itself triggered from the `<>` event @@ -359,8 +359,8 @@ handler, and generating another event from any code in, or called from, there causes the application to hang somewhere in the tk libraries. You can detect if the application is shutting down with the boolean -`config.shutting_down`. Note that although this is technically a function -its implementation is of a property on `config.AbstractConfig` and thus you +`config.shutting_down`. Note that although this is technically a function +its implementation is of a property on `config.AbstractConfig` and thus you should treat it as a variable. **Do NOT use**: @@ -372,7 +372,7 @@ should treat it as a variable. # During shutdown ``` -as this will cause the 'During shutdown' branch to *always* be taken, as in +as this will cause the 'During shutdown' branch to *always* be taken, as in this context you're testing if the function exists, and that is always True. So instead use: @@ -417,8 +417,8 @@ your plugin's settings in a platform-independent way. Previously this was done with a single set and two get methods, the new methods provide better type safety. -If you want to maintain compatibility with pre-5.0.0 versions of this -application (please encourage plugin users to update!) then you'll need to +If you want to maintain compatibility with pre-5.0.0 versions of this +application (please encourage plugin users to update!) then you'll need to include this code in at least once in your plugin (no harm in putting it in all modules/files): @@ -699,8 +699,8 @@ cause `state['NavRoute'] = None`, but if you open the galaxy map in-game and cause an automatic re-plot of last route, then a new `NavRoute` event will also be generated and passed to plugins. -[2] - Some data from the CAPI is sometimes returned as a `list` (when all -members are present) and other times as an integer-keyed `dict` (when at +[2] - Some data from the CAPI is sometimes returned as a `list` (when all +members are present) and other times as an integer-keyed `dict` (when at least one member is missing, so the indices are not contiguous). We choose to always convert to the integer-keyed `dict` form so that code utilising the data is simpler. @@ -751,7 +751,7 @@ Journal `ModuleInfo` event. `OnFoot` is an indication as to if the player is on-foot, rather than in a vehicle. -`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your +`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your Odyssey MicroResources in your Ship Locker. `BacKPack` contains `dict`s for the same when you're on-foot. @@ -760,10 +760,10 @@ relating to suits and their loadouts. New in version 5.0.1: -`Odyssey` boolean based on the presence of such a flag in the `LoadGame` +`Odyssey` boolean based on the presence of such a flag in the `LoadGame` event. Defaults to `False`, i.e. if no such key in the event. -The previously undocumented `Horizons` boolean is similarly from `LoadGame`, +The previously undocumented `Horizons` boolean is similarly from `LoadGame`, but blindly retrieves the value rather than having a strict default. There'd be an exception if it wasn't there, and the value would be `None`. Note that this is **NOT** the same as the return from @@ -821,7 +821,7 @@ if that's what was in the file. New in version 5.8.0: -`StarPos`, `SystemAddress`, `SystemName` and `SystemPopulation` have been +`StarPos`, `SystemAddress`, `SystemName` and `SystemPopulation` have been added to the `state` dictionary. Best efforts data pertaining to the star system the player is in. @@ -853,8 +853,8 @@ react to either in your plugin code then either compare in a case insensitive manner or check for both. The difference in case allows you to differentiate between the two scenarios. -**NB: Any of these events are passing to `journal_entry_cqc` rather than to -`journal_entry` if player has loaded into Arena (CQC).** +**NB: Any of these events are passing to `journal_entry_cqc` rather than to +`journal_entry` if player has loaded into Arena (CQC).** This event is not sent when EDMarketConnector is running on a different machine so you should not *rely* on receiving this event. @@ -871,15 +871,15 @@ Examples of this are: 1. Every `NavRoute` event contains the full `Route` array as loaded from `NavRoute.json`. - + *NB: There is no indication available when a player cancels a route.* The game itself does not provide any such, not in a Journal event, not in a `Status.json` flag. - + The Journal documentation v28 is incorrect about the event and file being `Route(.json)` the word is `NavRoute`. Also the format of the data is, e.g. - + ```json { "timestamp":"2021-03-10T11:31:37Z", "event":"NavRoute", @@ -893,9 +893,9 @@ Examples of this are: ``` 1. Every `ModuleInfo` event contains the full data as loaded from the - `ModulesInfo.json` file. Note that we use the singular form here to + `ModulesInfo.json` file. Note that we use the singular form here to stay consistent with the Journal event name. - + --- ### Journal entry in CQC @@ -955,7 +955,7 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]): sys.stderr.write("Hardpoints {}\n".format(is_deployed and "deployed" or "stowed")) ``` -`dashboard_entry()` is called with the latest data from the `Status.json` +`dashboard_entry()` is called with the latest data from the `Status.json` file when an update to that file is detected. This will be when something on the player's cockpit display changes - @@ -1208,6 +1208,15 @@ Wrap each string that needs translating with the `_()` function, e.g.: somewidget["text"] = _("Happy!") ``` +If you wish to override EDMCs current language when translating, +`l10n.Translations.translate()` takes an optional `lang` parameter which can +be passed a language identifier. For example to override all translations +to German: + +```python +_ = functools.partial(l10n.Translations.translate, context=__file__, lang="de") +``` + If you display localized strings in EDMarketConnector's main window you should refresh them in your `prefs_changed` function in case the user has changed their preferred language. @@ -1262,11 +1271,11 @@ Any modules the core application code uses will naturally be packaged, and we explicitly include a small number of additional modules for the use of plugins. -Whilst we would like to make all of the `stdlib` of Python available it is -not automatically packaged into our releases by py2exe. We hope to address -this in the 5.3 release series. In the meantime, if there's anything -missing that you'd like to use, please ask. Yes, this very much means you -need to test your plugins against a Windows installation of the application +Whilst we would like to make all of the `stdlib` of Python available it is +not automatically packaged into our releases by py2exe. We hope to address +this in the 5.3 release series. In the meantime, if there's anything +missing that you'd like to use, please ask. Yes, this very much means you +need to test your plugins against a Windows installation of the application to be sure it will work. See @@ -1416,7 +1425,7 @@ versions of EDMarketConnector: [2to3](https://docs.python.org/3/library/2to3.html) tool can automate much of this work. -We advise *against* making any attempt to have a plugin's code work under -both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based -versions of this application, and you shouldn't support use of them with +We advise *against* making any attempt to have a plugin's code work under +both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based +versions of this application, and you shouldn't support use of them with your plugin. diff --git a/l10n.py b/l10n.py index a2b5185ae..66d66cad4 100755 --- a/l10n.py +++ b/l10n.py @@ -17,9 +17,10 @@ import sys import warnings from contextlib import suppress -from os import pardir, listdir, sep, makedirs -from os.path import basename, dirname, isdir, isfile, join, abspath, exists +from os import listdir, makedirs, pardir, sep +from os.path import abspath, basename, dirname, exists, isdir, isfile, join from typing import TYPE_CHECKING, Iterable, TextIO, cast + from config import config from EDMCLogging import get_main_logger @@ -154,21 +155,41 @@ def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: return translations - def translate(self, x: str, context: str | None = None) -> str: + def translate(self, x: str, context: str | None = None, lang: str | None = None) -> str: # noqa: CCR001 """ - Translate the given string to the current lang. + Translate the given string to the current lang or an overriden lang. :param x: The string to translate - :param context: Whether or not to search the given directory for translation files, defaults to None + :param context: Contains the full path to the file being localised, from which the plugin name is parsed and + used to locate the plugin translation files, defaults to None + :param lang: Contains a language code to override the EDMC language for this translation, defaults to None :return: The translated string """ + plugin_name: str | None = None + plugin_path: str | None = None + if context: # TODO: There is probably a better way to go about this now. - context = context[len(config.plugin_dir)+1:].split(sep)[0] - if self.translations[None] and context not in self.translations: - logger.debug(f'No translations for {context!r}') + plugin_name = context[len(config.plugin_dir)+1:].split(sep)[0] + plugin_path = join(config.plugin_dir_path, plugin_name, LOCALISATION_DIR) + + if lang: + contents: dict[str, str] = self.contents(lang=lang, plugin_path=plugin_path) + + if not contents or type(contents) is not dict: + logger.debug(f'Failure loading translations for overridden language {lang!r}') + return self.translate(x) + elif x not in contents.keys(): + logger.debug(f'Missing translation: {x!r} for overridden language {lang!r}') + return self.translate(x) + else: + return contents.get(x) or self.translate(x) + + if plugin_name: + if self.translations[None] and plugin_name not in self.translations: + logger.debug(f'No translations for {plugin_name!r}') - return self.translations.get(context, {}).get(x) or self.translate(x) + return self.translations.get(plugin_name, {}).get(x) or self.translate(x) if self.translations[None] and x not in self.translations[None]: logger.debug(f'Missing translation: {x!r}') From ecf0ceb612f474e1f54bf2c28fa603708db960d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:08:13 +0000 Subject: [PATCH 128/261] build(deps-dev): bump mypy from 1.8.0 to 1.9.0 Bumps [mypy](https://github.com/python/mypy) from 1.8.0 to 1.9.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.8.0...1.9.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 73ab5465e..dd59b4f5a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-noqa==1.4.0 flake8-polyfill==1.0.2 flake8-use-fstring==1.4 -mypy==1.8.0 +mypy==1.9.0 pep8-naming==0.13.3 safety==2.3.5 types-requests==2.31.0.20240125 From 2ea942aec06e2a420ab801751e61db2a4d21dc65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:08:16 +0000 Subject: [PATCH 129/261] build(deps-dev): bump autopep8 from 2.0.4 to 2.1.0 Bumps [autopep8](https://github.com/hhatto/autopep8) from 2.0.4 to 2.1.0. - [Release notes](https://github.com/hhatto/autopep8/releases) - [Commits](https://github.com/hhatto/autopep8/compare/v2.0.4...v2.1.0) --- updated-dependencies: - dependency-name: autopep8 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 73ab5465e..83e68822c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,7 +25,7 @@ types-requests==2.31.0.20240125 types-pkg-resources==0.1.3 # Code formatting tools -autopep8==2.0.4 +autopep8==2.1.0 # Git pre-commit checking pre-commit==3.6.2 From 854dd4072e221d066add0a136a3435e30d0dc0ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:08:19 +0000 Subject: [PATCH 130/261] build(deps): bump certifi from 2023.11.17 to 2024.2.2 Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.11.17 to 2024.2.2. - [Commits](https://github.com/certifi/python-certifi/compare/2023.11.17...2024.02.02) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 398041a13..75b7fc019 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -certifi==2023.11.17 +certifi==2024.2.2 requests==2.31.0 # requests depends on this now ? charset-normalizer==2.1.1 From 25d52eacf6dcf603bbdb36e9443023756342c4fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:29:12 +0000 Subject: [PATCH 131/261] build(deps): bump softprops/action-gh-release from 1 to 2 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/windows-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 50962bbd7..7bc601367 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -155,7 +155,7 @@ jobs: run: sha256sum EDMarketConnector_Installer_*.exe EDMarketConnector-release-*.{zip,tar.gz} > ./hashes.sum - name: Create Draft Release - uses: "softprops/action-gh-release@v1" + uses: "softprops/action-gh-release@v2" with: token: "${{secrets.GITHUB_TOKEN}}" draft: true From d8d8540ceeea7b2c9beb57843df4ce3be136e7e2 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 5 Apr 2024 17:16:25 -0400 Subject: [PATCH 132/261] [2186] Resolve Some Merge Issues From EntryMenu --- myNotebook.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 646245505..7379aa5a5 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -57,12 +57,69 @@ def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) -class Entry(ttk.Entry): +class EntryMenu(ttk.Entry): + """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands.""" + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self.menu = tk.Menu(self, tearoff=False) + self.menu.add_command(label="Copy", command=self.copy) + self.menu.add_command(label="Cut", command=self.cut) + self.menu.add_separator() + self.menu.add_command(label="Paste", command=self.paste) + self.menu.add_separator() + self.menu.add_command(label="Select All", command=self.select_all) + + self.bind("", self.display_popup) + + def display_popup(self, event: tk.Event) -> None: + """Display the menu popup.""" + self.menu.post(event.x_root, event.y_root) + + def select_all(self) -> None: + """Select all the text within the Entry.""" + self.selection_range(0, tk.END) + self.focus_set() + + def copy(self) -> None: + """Copy the selected Entry text.""" + if self.selection_present(): + self.clipboard_clear() + self.clipboard_append(self.selection_get()) + + def cut(self) -> None: + """Cut the selected Entry text.""" + if self.selection_present(): + self.copy() + self.delete(tk.SEL_FIRST, tk.SEL_LAST) + + def paste(self) -> None: + """Paste the selected Entry text.""" + try: + # Attempt to grab an image from the clipboard (apprently also works for files) + img = ImageGrab.grabclipboard() + if img: + # Hijack existing translation, yes it doesn't exactly match here. + # LANG: Generic error prefix - following text is from Frontier auth service; + messagebox.showwarning(_('Error'), + _('Cannot paste non-text content.')) # LANG: Can't Paste Images or Files in Text + return + text = self.clipboard_get() + if self.selection_present() and text: + self.delete(tk.SEL_FIRST, tk.SEL_LAST) + self.insert(tk.INSERT, text) + except tk.TclError: + # No text in clipboard or clipboard is not text + pass + + +class Entry(ttk.Entry or EntryMenu): """Custom t(t)k.Entry class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later. + # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later. def __init__(self, master: ttk.Frame | None = None, **kw): - super().__init__(master, **kw) + EntryMenu.__init__(self, master, **kw) class Button(tk.Button or ttk.Button): # type: ignore From d00226f9e3f8dbbf211851433bb501a984334658 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 5 Apr 2024 17:20:05 -0400 Subject: [PATCH 133/261] [2186] Additional Documentations --- .flake8 | 1 - .mypy.ini | 1 - Contributing.md | 2 +- pyproject.toml | 3 --- requirements.txt | 3 --- 5 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.flake8 b/.flake8 index bdb25ca44..179e00ece 100644 --- a/.flake8 +++ b/.flake8 @@ -7,7 +7,6 @@ exclude = FDevIDs/ venv/ .venv/ - hotkey/darwin.py # FIXME: Check under macOS VM at some point # Show exactly where in a line the error happened #show-source = True diff --git a/.mypy.ini b/.mypy.ini index 75f4ccdb6..9ac2f2273 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -6,5 +6,4 @@ scripts_are_modules = True ; ` = ` ; i.e. no typing info. check_untyped_defs = True -; platform = darwin explicit_package_bases = True diff --git a/Contributing.md b/Contributing.md index 5f7bc158b..f021f1a32 100644 --- a/Contributing.md +++ b/Contributing.md @@ -679,7 +679,7 @@ the following does not work: ```py from sys import platform -if platform == 'darwin': +if platform == 'win32': ... ``` diff --git a/pyproject.toml b/pyproject.toml index e32ad617f..b98fbf4f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,3 @@ sys-platform-not-darwin = "sys_platform == 'darwin'" sys-platform-linux = "sys_platform != 'linux'" sys-platform-not-linux = "sys_platform == 'linux'" sys-platform-not-known = "sys_platform in ('darwin', 'linux', 'win32')" - -[tool.pyright] -# pythonPlatform = 'Darwin' diff --git a/requirements.txt b/requirements.txt index e9d4f58d5..c9239fdc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,3 @@ infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep # pyyaml==5.3.1 watchdog dep semantic-version==2.10.0 - -# Base requirement for MacOS -pyobjc; sys_platform == 'darwin' From fbdc44139077db712fa39b6d4ae60fc3013e1fc2 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 5 Apr 2024 17:28:08 -0400 Subject: [PATCH 134/261] [Minor] Return Visual Padding Just makes it nicer to read. --- protocol.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol.py b/protocol.py index 4c9e41ca5..b052ebae1 100644 --- a/protocol.py +++ b/protocol.py @@ -254,12 +254,12 @@ def worker(self) -> None: hwnd = CreateWindowExW( 0, # dwExStyle wndclass.lpszClassName, # lpClassName - "DDE Server", # lpWindowName - 0, # dwStyle + "DDE Server", # lpWindowName + 0, # dwStyle CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, # X, Y, nWidth, nHeight self.master.winfo_id(), # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts - None, # hMenu - wndclass.hInstance, # hInstance + None, # hMenu + wndclass.hInstance, # hInstance None # lpParam ) From 3a8227a8741ef2856472d300490ab2e30c2c7d57 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 5 Apr 2024 17:33:22 -0400 Subject: [PATCH 135/261] [2186] Correct Logic --- EDMarketConnector.py | 8 +++----- prefs.py | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 967488e0f..3ab97eb52 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1750,8 +1750,7 @@ def __init__(self, parent: tk.Tk) -> None: # position over parent # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - if parent.winfo_rooty() > 0: - self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}') + self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}') # remove decoration if sys.platform == 'win32': @@ -1870,9 +1869,8 @@ def onexit(self, event=None) -> None: config.set_shutdown() # Signal we're in shutdown now. # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - if self.w.winfo_rooty() > 0: - x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267' - config.set('geometry', f'+{x}+{y}') + x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267' + config.set('geometry', f'+{x}+{y}') # Let the user know we're shutting down. # LANG: The application is shutting down diff --git a/prefs.py b/prefs.py index 5b045b1a6..9e0459b64 100644 --- a/prefs.py +++ b/prefs.py @@ -230,9 +230,9 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): self.transient(parent) # position over parent - if parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - # TODO this is fixed supposedly. - self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}') + # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 + # TODO this is fixed supposedly. + self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}') # remove decoration if sys.platform == 'win32': From e0ef9b52c3cdc5aedc9982c736a7169bd3a5d2f8 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 5 Apr 2024 17:45:07 -0400 Subject: [PATCH 136/261] [2186] Additional Tweaks --- myNotebook.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 44488b8a2..e8f8779b4 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -30,15 +30,16 @@ def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) style = ttk.Style() - style.configure('nb.TFrame', background=PAGEBG) - style.configure('nb.TButton', background=PAGEBG) - style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG) - style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG) - style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG) + if sys.platform == 'win32': + style.configure('nb.TFrame', background=PAGEBG) + style.configure('nb.TButton', background=PAGEBG) + style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG) + style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG) + style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG) self.grid(padx=10, pady=10, sticky=tk.NSEW) -class Frame(tk.Frame or ttk.Frame): # type: ignore +class Frame(ttk.Frame): """Custom t(t)k.Frame class to fix some display issues.""" def __init__(self, master: ttk.Notebook | None = None, **kw): @@ -127,7 +128,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): EntryMenu.__init__(self, master, **kw) -class Button(tk.Button or ttk.Button): # type: ignore +class Button(ttk.Button): # type: ignore """Custom t(t)k.Button class to fix some display issues.""" # DEPRECATED: Migrate to ttk.Button. Will remove in 5.12 or later. @@ -138,7 +139,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): ttk.Button.__init__(self, master, **kw) -class ColoredButton(tk.Label or tk.Button): # type: ignore +class ColoredButton(tk.Button): # type: ignore """Custom t(t)k.ColoredButton class to fix some display issues.""" # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later. @@ -166,11 +167,14 @@ class OptionMenu(ttk.OptionMenu): """Custom ttk.OptionMenu class to fix some display issues.""" def __init__(self, master, variable, default=None, *values, **kw): - style = 'nb.TMenubutton' if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background') - menu_background = PAGEBG if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background') - - super().__init__(master, variable, default, *values, style=style, **kw) - self['menu'].configure(background=menu_background) + if sys.platform == 'win32': + # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style + ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw) + self['menu'].configure(background=PAGEBG) + else: + ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw) + self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background')) - for i in range(self['menu'].index('end') + 1): + # Workaround for https://bugs.python.org/issue25684 + for i in range(0, self['menu'].index('end') + 1): self['menu'].entryconfig(i, variable=variable) From 3c6ea3c9328a95e2a82fa276a2d1e3865c235f36 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 5 Apr 2024 17:50:30 -0400 Subject: [PATCH 137/261] [Minor] Comment Clarify --- update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.py b/update.py index 346aff636..991558d64 100644 --- a/update.py +++ b/update.py @@ -161,7 +161,7 @@ def check_appcast(self) -> EDMCVersion | None: return None - # For *these* purposes anything else is the same as 'windows', as + # For *these* purposes all systems are the same as 'windows', as # non-win32 would be running from source. sparkle_platform = 'windows' From c14bd826d08daee4509cf2bf2b184245d7f54695 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 6 Apr 2024 16:52:45 -0400 Subject: [PATCH 138/261] [Minor] Additional Visual Padding Fixes --- protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol.py b/protocol.py index b052ebae1..1fb885957 100644 --- a/protocol.py +++ b/protocol.py @@ -252,7 +252,7 @@ def worker(self) -> None: # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw hwnd = CreateWindowExW( - 0, # dwExStyle + 0, # dwExStyle wndclass.lpszClassName, # lpClassName "DDE Server", # lpWindowName 0, # dwStyle @@ -260,7 +260,7 @@ def worker(self) -> None: self.master.winfo_id(), # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts None, # hMenu wndclass.hInstance, # hInstance - None # lpParam + None # lpParam ) msg = MSG() From d9c7a791553f12489ed47f74f6275b533ca7ca22 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 6 Apr 2024 16:59:49 -0400 Subject: [PATCH 139/261] [Minor] Update Type Hintings No Content Changes, Shuts Up MyPy --- docs/examples/click_counter/load.py | 4 ++-- myNotebook.py | 2 +- plug.py | 2 +- plugins/coriolis.py | 2 +- plugins/eddn.py | 4 ++-- plugins/edsm.py | 2 +- plugins/inara.py | 2 +- prefs.py | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py index f7f238377..a3b8cac62 100644 --- a/docs/examples/click_counter/load.py +++ b/docs/examples/click_counter/load.py @@ -49,7 +49,7 @@ def on_unload(self) -> None: """ self.on_preferences_closed("", False) # Save our prefs - def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk.Frame | None: + def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None: """ setup_preferences is called by plugin_prefs below. @@ -128,7 +128,7 @@ def plugin_stop() -> None: return cc.on_unload() -def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk.Frame | None: +def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None: """ Handle preferences tab for the plugin. diff --git a/myNotebook.py b/myNotebook.py index e8f8779b4..0eeb96a48 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -120,7 +120,7 @@ def paste(self) -> None: pass -class Entry(ttk.Entry or EntryMenu): +class Entry(ttk.Entry or EntryMenu): # type: ignore """Custom t(t)k.Entry class to fix some display issues.""" # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later. diff --git a/plug.py b/plug.py index 15e7cfaac..dad08bc60 100644 --- a/plug.py +++ b/plug.py @@ -126,7 +126,7 @@ def get_app(self, parent: tk.Frame) -> tk.Frame | None: return None - def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame | None: + def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame | None: """ If the plugin provides a prefs frame, create and return it. diff --git a/plugins/coriolis.py b/plugins/coriolis.py index a97eb6c6f..cc7e965a6 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -84,7 +84,7 @@ def plugin_start3(path: str) -> str: return 'Coriolis' -def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame: +def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame: """Set up plugin preferences.""" PADX = 10 # noqa: N806 PADY = 1 # noqa: N806 diff --git a/plugins/eddn.py b/plugins/eddn.py index c5a978dbc..9504d1abc 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1898,7 +1898,7 @@ def export_journal_dockingdenied( :param cmdr: the commander under which this upload is made :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send - + ___ Example: { "timestamp":"2022-06-10T10:09:41Z", @@ -1932,7 +1932,7 @@ def export_journal_dockinggranted( :param cmdr: the commander under which this upload is made :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send - + ___ Example: { "timestamp":"2023-10-01T14:56:34Z", diff --git a/plugins/edsm.py b/plugins/edsm.py index 146dd5ab6..55aae7b82 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -279,7 +279,7 @@ def toggle_password_visibility(): this.apikey.config(show="*") # type: ignore -def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame: +def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame: """ Plugin preferences setup hook. diff --git a/plugins/inara.py b/plugins/inara.py index d96e05ef5..29bfaaf2b 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -244,7 +244,7 @@ def toggle_password_visibility(): this.apikey.config(show="*") -def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame: +def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: """Plugin Preferences UI hook.""" PADX = 10 # noqa: N806 BUTTONX = 12 # noqa: N806 # indent Checkbuttons and Radiobuttons diff --git a/prefs.py b/prefs.py index 9e0459b64..5e0f946bd 100644 --- a/prefs.py +++ b/prefs.py @@ -376,7 +376,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: ) self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get()) - nb.Frame(output_frame).grid(row=row.get()) # bottom spacer # TODO: does nothing? + nb.Frame(output_frame).grid(row=row.get()) # type: ignore # bottom spacer # TODO: does nothing? # LANG: Label for 'Output' Settings/Preferences tab root_notebook.add(output_frame, text=_('Output')) # Tab heading in settings From 4cbc86e650d04f696b58332dd27f1b502f312437 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 11 Apr 2024 12:25:11 +0000 Subject: [PATCH 140/261] updating submodules --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index 7205c7933..7cffab3d9 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 7205c79331f42c1a28b757b27467f79ff106716b +Subproject commit 7cffab3d913b788f981923687203399c22cf358f From dd5e3812a759c1d3186df277e249d0a9e6216e76 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 13 Apr 2024 14:44:44 -0400 Subject: [PATCH 141/261] [2186] Refine macOS to preserve ContextMenu --- docs/examples/click_counter/load.py | 3 +-- myNotebook.py | 23 +++++++++++------------ plugins/coriolis.py | 15 +++++++-------- plugins/edsm.py | 8 ++++---- plugins/inara.py | 4 ++-- prefs.py | 2 -- requirements.txt | 4 ++-- 7 files changed, 27 insertions(+), 32 deletions(-) diff --git a/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py index a3b8cac62..3620e1594 100644 --- a/docs/examples/click_counter/load.py +++ b/docs/examples/click_counter/load.py @@ -7,7 +7,6 @@ import logging import tkinter as tk -from tkinter import ttk import myNotebook as nb # noqa: N813 from config import appname, config @@ -65,7 +64,7 @@ def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb # setup our config in a "Click Count: number" nb.Label(frame, text='Click Count').grid(row=current_row) - ttk.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1) + nb.EntryMenu(frame, textvariable=self.click_count).grid(row=current_row, column=1) current_row += 1 # Always increment our row counter, makes for far easier tkinter design. return frame diff --git a/myNotebook.py b/myNotebook.py index 0eeb96a48..2442acbee 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -40,7 +40,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class Frame(ttk.Frame): - """Custom t(t)k.Frame class to fix some display issues.""" + """Custom ttk.Frame class to fix some display issues.""" def __init__(self, master: ttk.Notebook | None = None, **kw): if sys.platform == 'win32': @@ -120,18 +120,17 @@ def paste(self) -> None: pass -class Entry(ttk.Entry or EntryMenu): # type: ignore - """Custom t(t)k.Entry class to fix some display issues.""" +class Entry(EntryMenu or ttk.Entry): # type: ignore + """Custom ttk.Entry class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later. + # DEPRECATED: Migrate to EntryMenu. Will remove in 5.12 or later. def __init__(self, master: ttk.Frame | None = None, **kw): EntryMenu.__init__(self, master, **kw) -class Button(ttk.Button): # type: ignore - """Custom t(t)k.Button class to fix some display issues.""" +class Button(ttk.Button): + """Custom ttk.Button class to fix some display issues.""" - # DEPRECATED: Migrate to ttk.Button. Will remove in 5.12 or later. def __init__(self, master: ttk.Frame | None = None, **kw): if sys.platform == 'win32': ttk.Button.__init__(self, master, style='nb.TButton', **kw) @@ -139,8 +138,8 @@ def __init__(self, master: ttk.Frame | None = None, **kw): ttk.Button.__init__(self, master, **kw) -class ColoredButton(tk.Button): # type: ignore - """Custom t(t)k.ColoredButton class to fix some display issues.""" +class ColoredButton(tk.Button): + """Custom tk.Button class to fix some display issues.""" # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later. def __init__(self, master: ttk.Frame | None = None, **kw): @@ -148,15 +147,15 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class Checkbutton(ttk.Checkbutton): - """Custom t(t)k.Checkbutton class to fix some display issues.""" + """Custom ttk.Checkbutton class to fix some display issues.""" - def __init__(self, master=None, **kw): + def __init__(self, master: ttk.Frame | None = None, **kw): style = 'nb.TCheckbutton' if sys.platform == 'win32' else None super().__init__(master, style=style, **kw) # type: ignore class Radiobutton(ttk.Radiobutton): - """Custom t(t)k.Radiobutton class to fix some display issues.""" + """Custom ttk.Radiobutton class to fix some display issues.""" def __init__(self, master: ttk.Frame | None = None, **kw): style = 'nb.TRadiobutton' if sys.platform == 'win32' else None diff --git a/plugins/coriolis.py b/plugins/coriolis.py index cc7e965a6..07b4ac9f2 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -106,25 +106,24 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) - ttk.Entry(conf_frame, - textvariable=coriolis_config.normal_textvar).grid( + nb.EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - ttk.Button(conf_frame, text=_("Reset"), - command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( + nb.Button(conf_frame, text=_("Reset"), + command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) cur_row += 1 # LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) - ttk.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid( - sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY + nb.EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid( + sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - ttk.Button(conf_frame, text=_('Reset'), - command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( + nb.Button(conf_frame, text=_('Reset'), + command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) cur_row += 1 diff --git a/plugins/edsm.py b/plugins/edsm.py index 55aae7b82..b2a8035f5 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -113,10 +113,10 @@ def __init__(self): self.cmdr_text: nb.Label | None = None self.user_label: nb.Label | None = None - self.user: ttk.Entry | None = None + self.user: nb.EntryMenu | None = None self.apikey_label: nb.Label | None = None - self.apikey: ttk.Entry | None = None + self.apikey: nb.EntryMenu | None = None this = This() @@ -345,14 +345,14 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr # LANG: EDSM Commander name label in EDSM settings this.user_label = nb.Label(frame, text=_('Commander Name')) this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.user = ttk.Entry(frame) + this.user = nb.EntryMenu(frame) this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 # LANG: EDSM API key label this.apikey_label = nb.Label(frame, text=_('API Key')) this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.apikey = ttk.Entry(frame, show="*", width=50) + this.apikey = nb.EntryMenu(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 diff --git a/plugins/inara.py b/plugins/inara.py index 29bfaaf2b..810f7523d 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -125,7 +125,7 @@ def __init__(self): self.log: 'tk.IntVar' self.log_button: nb.Checkbutton self.label: HyperlinkLabel - self.apikey: ttk.Entry + self.apikey: nb.EntryMenu self.apikey_label: tk.Label self.events: dict[Credentials, Deque[Event]] = defaultdict(deque) @@ -292,7 +292,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: # LANG: Inara API key label this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) - this.apikey = ttk.Entry(frame, show="*", width=50) + this.apikey = nb.EntryMenu(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 diff --git a/prefs.py b/prefs.py index 5e0f946bd..285ef0d70 100644 --- a/prefs.py +++ b/prefs.py @@ -376,8 +376,6 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: ) self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get()) - nb.Frame(output_frame).grid(row=row.get()) # type: ignore # bottom spacer # TODO: does nothing? - # LANG: Label for 'Output' Settings/Preferences tab root_notebook.add(output_frame, text=_('Output')) # Tab heading in settings diff --git a/requirements.txt b/requirements.txt index c9239fdc2..75ea40411 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ certifi==2024.2.2 requests==2.31.0 -pillow==10.2.0 +pillow==10.3.0 # requests depends on this now ? -charset-normalizer==2.1.1 +charset-normalizer==3.3.2 watchdog==3.0.0 # Commented out because this doesn't package well with py2exe From 0410806152fbe324867e9bc1258b3211b409344a Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 14 Apr 2024 18:07:30 -0400 Subject: [PATCH 142/261] [2199] Add Broader Exception --- killswitch.py | 4 ++-- myNotebook.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/killswitch.py b/killswitch.py index 9cc407a61..89a55292f 100644 --- a/killswitch.py +++ b/killswitch.py @@ -361,8 +361,8 @@ def fetch_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> KillSwitchJSONFile | N logger.warning(f"Failed to get kill switches, data was invalid: {e}") return None - except (requests.exceptions.BaseHTTPError, requests.exceptions.ConnectionError) as e: # type: ignore - logger.warning(f"unable to connect to {target!r}: {e}") + except requests.exceptions.RequestException as requ_err: + logger.warning(f"unable to connect to {target!r}: {requ_err}") return None return data diff --git a/myNotebook.py b/myNotebook.py index 0eeb96a48..88b4aec5e 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -63,11 +63,11 @@ def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) -class EntryMenu(ttk.Entry): +class EntryMenu: """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands.""" def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + ttk.Entry.__init__(self, *args, **kwargs) self.menu = tk.Menu(self, tearoff=False) self.menu.add_command(label="Copy", command=self.copy) @@ -120,7 +120,7 @@ def paste(self) -> None: pass -class Entry(ttk.Entry or EntryMenu): # type: ignore +class Entry(ttk.Entry, EntryMenu): """Custom t(t)k.Entry class to fix some display issues.""" # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later. From fbeeded28046aac2985dc468b757ccf82711cf92 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 14 Apr 2024 20:17:08 -0400 Subject: [PATCH 143/261] [2202] Add a number of missing modules This will need to be basically speedran into live. --- edmc_data.py | 31 ++++++++++++++++++++++++------- outfitting.py | 17 ++++++++++++++++- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/edmc_data.py b/edmc_data.py index 7be760967..d76badc62 100644 --- a/edmc_data.py +++ b/edmc_data.py @@ -69,6 +69,8 @@ 'advancedtorppylon': 'Torpedo Pylon', 'atdumbfiremissile': 'AX Missile Rack', 'atmulticannon': 'AX Multi-Cannon', + ('atmulticannon', 'v2'): 'Enhanced AX Multi-Cannon', + ('atdumbfiremissile', 'v2'): 'Enhanced AX Missile Rack', 'basicmissilerack': 'Seeker Missile Rack', 'beamlaser': 'Beam Laser', ('beamlaser', 'heat'): 'Retributor Beam Laser', @@ -88,6 +90,8 @@ 'mining_abrblstr': 'Abrasion Blaster', 'mining_seismchrgwarhd': 'Seismic Charge Launcher', 'mining_subsurfdispmisle': 'Sub-Surface Displacement Missile', + 'human_extraction': 'Sub-Surface Extraction Missile', + 'atventdisruptorpylon': 'Guardian Nanite Torpedo Pylon', 'mininglaser': 'Mining Laser', ('mininglaser', 'advanced'): 'Mining Lance Beam Laser', 'multicannon': 'Multi-Cannon', @@ -266,6 +270,11 @@ 'hpt_slugshot_turret_medium': 'D', 'hpt_slugshot_turret_large': 'C', 'hpt_xenoscannermk2_basic_tiny': '?', + 'hpt_atmulticannon_gimbal_large': 'C', + 'hpt_atmulticannon_gimbal_medium': 'E', + 'hpt_human_extraction_fixed_medium': 'B', + 'hpt_atventdisruptorpylon_fixed_medium': 'I', + 'hpt_atventdisruptorpylon_fixed_large': 'I', } # Old standard weapon variants @@ -278,13 +287,14 @@ } outfitting_countermeasure_map = { - 'antiunknownshutdown': ('Shutdown Field Neutraliser', 'F'), - 'chafflauncher': ('Chaff Launcher', 'I'), - 'electroniccountermeasure': ('Electronic Countermeasure', 'F'), - 'heatsinklauncher': ('Heat Sink Launcher', 'I'), - 'plasmapointdefence': ('Point Defence', 'I'), - 'xenoscanner': ('Xeno Scanner', 'E'), - 'xenoscannermk2': ('Unknown Xeno Scanner Mk II', '?'), + 'antiunknownshutdown': ('Shutdown Field Neutraliser', 'F'), + ('antiunknownshutdown', 'v2'): ('Thargoid Pulse Neutraliser', 'E'), + 'chafflauncher': ('Chaff Launcher', 'I'), + 'electroniccountermeasure': ('Electronic Countermeasure', 'F'), + 'heatsinklauncher': ('Heat Sink Launcher', 'I'), + 'plasmapointdefence': ('Point Defence', 'I'), + 'xenoscanner': ('Xeno Scanner', 'E'), + 'xenoscannermk2': ('Unknown Xeno Scanner Mk II', '?'), } outfitting_utility_map = { @@ -347,6 +357,7 @@ 'guardianpowerdistributor': 'Guardian Hybrid Power Distributor', 'guardianpowerplant': 'Guardian Hybrid Power Plant', 'hyperdrive': 'Frame Shift Drive', + ('hyperdrive', 'overcharge'): 'Frame Shift Drive', 'lifesupport': 'Life Support', # 'planetapproachsuite': handled separately 'powerdistributor': 'Power Distributor', @@ -376,6 +387,11 @@ 'refinery': 'Refinery', 'recon': 'Recon Limpet Controller', 'repair': 'Repair Limpet Controller', + 'rescue': 'Rescue Limpet Controller', + 'mining': 'Mining Multi Limpet Controller', + 'xeno': 'Xeno Multi Limpet Controller', + 'operations': 'Operations Multi Limpet Controller', + 'universal': 'Universal Multi Limpet Controller', 'repairer': 'Auto Field-Maintenance Unit', 'resourcesiphon': 'Hatch Breaker Limpet Controller', 'shieldcellbank': 'Shield Cell Bank', @@ -383,6 +399,7 @@ ('shieldgenerator', 'fast'): 'Bi-Weave Shield Generator', ('shieldgenerator', 'strong'): 'Prismatic Shield Generator', 'unkvesselresearch': 'Research Limpet Controller', + 'expmodulestabiliser': 'Experimental Weapon Stabiliser', } # Dashboard Flags constants diff --git a/outfitting.py b/outfitting.py index a1b6b9db6..5632687c7 100644 --- a/outfitting.py +++ b/outfitting.py @@ -98,6 +98,12 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 elif not entitled and name[1] == 'planetapproachsuite': return None + # V2 Shutdown Field Neutralizer - Hpt_AntiUnknownShutdown_Tiny_V2 + elif name[0] == 'hpt' and name[1] in countermeasure_map and len(name) == 4 and name[3] == 'v2': + new['category'] = 'utility' + new['name'], new['rating'] = countermeasure_map[name[1]] + new['class'] = weaponclass_map[name[-2]] + # Countermeasures - e.g. Hpt_PlasmaPointDefence_Turret_Tiny elif name[0] == 'hpt' and name[1] in countermeasure_map: new['category'] = 'utility' @@ -179,12 +185,18 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 if name[1] == 'dronecontrol': # e.g. Int_DroneControl_Collection_Size1_Class1 name.pop(0) + elif name[1] == 'multidronecontrol': # e.g. Int_MultiDroneControl_Rescue_Size3_Class3 + name.pop(0) + elif name[-1] == 'free': # Starter Sidewinder or Freagle modules - just treat them like vanilla modules name.pop() if name[1] in standard_map: # e.g. Int_Engine_Size2_Class1, Int_ShieldGenerator_Size8_Class5_Strong new['category'] = 'standard' - new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]] + if name[2] == 'overcharge': + new['name'] = standard_map[(name[1], name[2])] + else: + new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]] elif name[1] in internal_map: # e.g. Int_CargoRack_Size8_Class1 new['category'] = 'internal' @@ -209,6 +221,9 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 elif len(name) < 4 and name[1] == 'guardianfsdbooster': # Hack! No class. (new['class'], new['rating']) = (str(name[2][4:]), 'H') + elif len(name) > 4 and name[1] == 'hyperdrive': # e.g. Int_Hyperdrive_Overcharge_Size6_Class3 + (new['class'], new['rating']) = (str(name[4][-1:]), 'C') + else: if len(name) < 3: raise AssertionError(f'{name}: length < 3]') From 5cc08f25175e6286f6012cc0b3c3311ec685f37d Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 14 Apr 2024 20:32:43 -0400 Subject: [PATCH 144/261] [Minor] Clarify Inheritence --- myNotebook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 88b4aec5e..dd6e34290 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -63,7 +63,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): super().__init__(master, **kw) -class EntryMenu: +class EntryMenu(ttk.Entry): """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands.""" def __init__(self, *args, **kwargs) -> None: @@ -120,7 +120,7 @@ def paste(self) -> None: pass -class Entry(ttk.Entry, EntryMenu): +class Entry(EntryMenu): """Custom t(t)k.Entry class to fix some display issues.""" # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later. From 674413b36f50deb76466fd0822c2b12ec325a3f9 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 15 Apr 2024 16:51:43 -0400 Subject: [PATCH 145/261] Update Version String and Changelog --- ChangeLog.md | 24 ++++++++++++++++++++++++ config/__init__.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 6ee5749f0..949b6bf8d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,30 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- +Release 5.10.4 +=== +This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also +adds Turkish translations to EDMC! + +We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC + +**Changes and Enhancements** +* Adds Turkish Translations to EDMC +* Adds DockingDenied and DockingGranted EDDN Schemas +* Updated FDevIDs Dependency +* Updated Translations +* Updated modules files to process several missing module types used for bug squishing or going fast +* Updated Python Dependencies + +**Bug Fixes** +* Fixed a bug on older Python versions which couldn't import updated type annotations + +**Plugin Developers** +* modules.p and ships.p are deprecated, and slated for removal in 5.11+! +* The `openurl()` function in ttkHyperlinkLabel has been deprecated, +and slated for removal in 5.11+! Please migrate to `webbrowser.open()`. + Release 5.10.3 === This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations. diff --git a/config/__init__.py b/config/__init__.py index d85956a78..27d9b4506 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -53,7 +53,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.0-alpha0' +_static_appversion = '5.10.4' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 44c40076917802bc05ea555a0fd5b9d45d6d4aea Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 15 Apr 2024 17:06:06 -0400 Subject: [PATCH 146/261] Update edmarketconnector.xml --- edmarketconnector.xml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 82dd8b5d0..cc7977943 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,11 +22,35 @@ - Release 5.10.3 + Release 5.10.4 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }

        We now test against, and package with, Python 3.11.7.

        As a result, we do not support Windows 7, 8, or 8.1.
        - +

        Release 5.10.4

        +

        This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also +adds Turkish translations to EDMC!

        +

        We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC

        +

        Changes and Enhancements

        +
          +
        • Adds Turkish Translations to EDMC
        • +
        • Adds DockingDenied and DockingGranted EDDN Schemas
        • +
        • Updated FDevIDs Dependency
        • +
        • Updated Translations
        • +
        • Updated modules files to process several missing module types used for bug squishing or going fast
        • +
        • Updated Python Dependencies
        • +
        +

        Bug Fixes

        +
          +
        • Fixed a bug on older Python versions which couldn't import updated type annotations
        • +
        +

        Plugin Developers

        +
          +
        • modules.p and ships.p are deprecated, and slated for removal in 5.11+!
        • +
        • The openurl() function in ttkHyperlinkLabel has been deprecated, +and slated for removal in 5.11+! Please migrate to webbrowser.open().
        • +
        +

        Release 5.10.3

        This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations.

        We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. @@ -2207,7 +2231,7 @@ about this: PTS CAPI saying Commander is Docked after jumping to new system.

      ]]>
      - +
      From 1f90ca94092012aa35078a5f0bcb06a4deabbfbf Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 15 Apr 2024 21:48:30 -0400 Subject: [PATCH 147/261] [Translations] Update Translation Files --- L10n/cs.strings | 6 +- L10n/de.strings | 20 +- L10n/es.strings | 6 +- L10n/it.strings | 20 +- L10n/ja.strings | 6 +- L10n/ko.strings | 6 +- L10n/pl.strings | 6 +- L10n/pt-BR.strings | 6 +- L10n/pt-PT.strings | 6 +- L10n/ru.strings | 35 +- L10n/sr-Latn-BA.strings | 6 +- L10n/sr-Latn.strings | 20 +- L10n/tr.strings | 805 ++++++++++++++++++++++++++++++++++++++++ L10n/zh-Hans.strings | 6 +- 14 files changed, 915 insertions(+), 39 deletions(-) create mode 100644 L10n/tr.strings diff --git a/L10n/cs.strings b/L10n/cs.strings index ba11dc39a..071b5e69d 100644 --- a/L10n/cs.strings +++ b/L10n/cs.strings @@ -178,13 +178,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Výchozí"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Auto"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normal"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ diff --git a/L10n/de.strings b/L10n/de.strings index 2b7c72c4c..be36b877a 100644 --- a/L10n/de.strings +++ b/L10n/de.strings @@ -231,6 +231,18 @@ /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Eins oder mehr deiner aktivierten Plugins konnte nicht geladen werden. Du kannst dir die Liste im '{PLUGINS}'-Tab unter '{FILE}' > '{SETTINGS}' ansehen. Dies könnte an einer falschen Ordnerstruktur liegen. Die Datei load.py sollte sich unter plugins/PLUGIN_NAME/load.py befinden.\n\nDu kannst ein Plugin deaktivieren, indem du dessen Ordner ein '{DISABLED}' am Ende des Namens anhängst."; +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Einer oder mehrere deiner URL-Anbieter waren ungültig und wurden zurückgesetzt:\n\n"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} war zuvor auf {OLDPROV} festgelegt und wurde zu {NEWPROV} zurückgesetzt.\n"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: Standardanbieter wurden zurückgesetzt"; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Warte auf vollständige CMDR Anmeldung"; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Journal-Ordner bereits gesperrt"; @@ -246,13 +258,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Standard"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Auto"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normal"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ @@ -768,3 +780,5 @@ /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Schiffe"; +/* update.py: Update Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} ist verfügbar"; diff --git a/L10n/es.strings b/L10n/es.strings index d2cb7b4b7..18874ccf8 100644 --- a/L10n/es.strings +++ b/L10n/es.strings @@ -184,13 +184,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Por defecto"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Auto"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normal"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ diff --git a/L10n/it.strings b/L10n/it.strings index e5f6ece6a..c1cd9b339 100644 --- a/L10n/it.strings +++ b/L10n/it.strings @@ -231,6 +231,18 @@ /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Impossibile caricare uno o più plugin abilitati. Consulta l'elenco nella scheda '{PLUGINS}' di '{FILE}' > '{SETTINGS}'. Ciò potrebbe essere causato da un'errata struttura delle cartelle. Il file load.py dovrebbe trovarsi in plugins/PLUGIN_NAME/load.py.\n\nPuoi disabilitare un plugin rinominando la sua cartella in modo che abbia '{DISABLED}' alla fine del nome."; +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Uno o più Provider URL non erano validi e sono stati ripristinati:\n"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} era impostato su {OLDPROV} ed è stato reimpostato su {NEWPROV}\n"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: Reset dei provider predefiniti"; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "In attesa del login completo al CMDR"; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "cartella Journal già bloccata"; @@ -246,13 +258,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Predefinito"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Auto"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normale"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ @@ -789,3 +801,5 @@ /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Navi"; +/* update.py: Update Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} è disponibile"; diff --git a/L10n/ja.strings b/L10n/ja.strings index 68a9fd34d..82d9f8df0 100644 --- a/L10n/ja.strings +++ b/L10n/ja.strings @@ -246,13 +246,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "デフォルト"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "自動"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "通常版"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "ベータ版"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ diff --git a/L10n/ko.strings b/L10n/ko.strings index 577695b04..e29ad74fc 100644 --- a/L10n/ko.strings +++ b/L10n/ko.strings @@ -202,13 +202,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "기본값"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "자동"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "일반"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "베타"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ diff --git a/L10n/pl.strings b/L10n/pl.strings index 72a667180..547406b18 100644 --- a/L10n/pl.strings +++ b/L10n/pl.strings @@ -234,13 +234,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Domyślne"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Auto"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normalny"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ diff --git a/L10n/pt-BR.strings b/L10n/pt-BR.strings index e719101a5..605e05596 100644 --- a/L10n/pt-BR.strings +++ b/L10n/pt-BR.strings @@ -234,13 +234,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Padrão"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Automático"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normal"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ diff --git a/L10n/pt-PT.strings b/L10n/pt-PT.strings index ea292b143..0fd5b585c 100644 --- a/L10n/pt-PT.strings +++ b/L10n/pt-PT.strings @@ -246,13 +246,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Predefinido"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Auto"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normal"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ diff --git a/L10n/ru.strings b/L10n/ru.strings index c3c0fae12..19449e21d 100644 --- a/L10n/ru.strings +++ b/L10n/ru.strings @@ -213,12 +213,36 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Один или несколько ваших подключённых плагинов ещё не имеют поддержки Python 3.x. Пожалуйста, ознакомьтесь со списком во вкладке '{PLUGINS}' '{FILE}' > '{SETTINGS}'. Вы должны проверить наличие обновлённой версии, в противном случае предупредите разработчика о необходимости обновления кода на Python 3.x.\n\nВы можете отключить плагин, переименовав его папку в '{DISABLED}'."; +/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ +"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID файлы не найдены! Некоторые функции, связанные с товарами, могут быть отключены.\n\nВы хотите открыть страницу Wiki о том, как настроить вспомогательные модули?"; + +/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ +"FDevIDs: Missing Commodity Files" = "FDevIDs: отсутствуют файлы товаров"; + /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Плагины"; /* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Без поддержки Python 3.x"; +/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */ +"EDMC: Broken Plugins" = "EDMC: Неработающие плагины"; + +/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ +"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Не удалось загрузить один или несколько включенных плагинов. Пожалуйста, посмотрите список на вкладке '{PLUGINS}' в разделе '{FILE}' > '{SETTINGS}'. Причиной может быть неправильная структура папок. Файл load.py должен находиться в папке plugins/PLUGIN_NAME/load.py.\n\nВы можете отключить плагин, переименовав его папку так, чтобы в конце названия стояло '{DISABLED}'."; + +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Один или несколько ваших URL-провайдеров оказались некорректными и были сброшены:"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} был установлен на {OLDPROV} и был сброшен на {NEWPROV}"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: сброс провайдеров по умолчанию"; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Ожидание полного входа в систему"; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Каталог журнала уже заблокирован"; @@ -234,13 +258,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "По умолчанию"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Автоматический"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Стандартный"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Бета"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ @@ -471,6 +495,9 @@ /* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */ "Information on migrating plugins" = "Информация о мигрирующих плагинах"; +/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */ +"Broken Plugins" = "Неработающие плагины"; + /* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */ "Disabled Plugins" = "Отключенные плагины"; @@ -774,3 +801,5 @@ /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Корабли"; +/* update.py: Update Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} доступен"; diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings index 2950f8b8c..f541416a0 100644 --- a/L10n/sr-Latn-BA.strings +++ b/L10n/sr-Latn-BA.strings @@ -246,13 +246,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Standardna"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Automatski"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normalni"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings index 83da541a3..e801d0676 100644 --- a/L10n/sr-Latn.strings +++ b/L10n/sr-Latn.strings @@ -231,6 +231,18 @@ /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više vaših aktivnih pluginova se nije učitao. Molimo pogledajte listu na '{PLUGINS}' tabu u okviru '{FILE}' > '{SETTINGS}'. Obo vi moglo da se desi zbog pogrešne strukture foldera. load.py fajl bi trebao da se nalazi unutar plugins/PLUGIN_NAME/load.py.\n\nPojedinačni plugin možete deaktivirati promenom imena njegovog foldera dodavanjem '{DISABLED}' na kraju."; +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Jedan ili više vaših URL Provajdera su pogrešni, pa su resetovani:\n"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} je bio postavljen na {OLDPROV} i sada je resetovan na {NEWPROV}\n"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: Podrazumevani Provajderi resetovani"; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Čekam da se CMDR potpuno uloguje"; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Žurnal direktorijum je već zaključan"; @@ -246,13 +258,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Podrazumevano"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "Auto"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "Normal"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "Beta"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ @@ -789,3 +801,5 @@ /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Brodovi"; +/* update.py: Update Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} je dostupna"; diff --git a/L10n/tr.strings b/L10n/tr.strings new file mode 100644 index 000000000..9d37dfa1d --- /dev/null +++ b/L10n/tr.strings @@ -0,0 +1,805 @@ +/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ +"Send flight log and CMDR status to EDSM" = "Uçuş günlüğünü ve CMDR durumunu EDSM'e gönder"; + +/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */ +"Open Log Folder" = "Günlük Klasörünü Aç"; + +/* inara.py:Text Inara Show API key; In files: inara.py:305; */ +"Show API Key" = "API Anahtarını Göster"; +/* Language name */ +"!Language" = "Türkçe"; + +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */ +"Error: Frontier CAPI didn't respond" = "Hata: Frontier CAPI yanıt vermedi"; + +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */ +"Error: Frontier server is lagging" = "Hata: Frontier sunucusu gecikmesi"; + +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:261; */ +"Docked but unknown station: EDO Settlement?" = "Kenetlendi ancak bilinmeyen istasyon: EDO yerleşimi?"; + +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */ +"Error: Invalid Credentials" = "Hata: Geçersiz Kimlik"; + +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */ +"Error: Wrong Cmdr" = "Hata: Yanlış CMDR"; + +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */ +"Error" = "Hata"; + +/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:475; companion.py:480; */ +"Error: Couldn't check token customer_id" = "Hata: token kontrol edilemedi customer_id"; + +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */ +"Error: customer_id doesn't match!" = "Hata: customer_id uyuşmuyor!"; + +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ +"Error: unable to get token" = "Hata: token erişilemedi"; + +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI bakım için kapalı durumda"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */ +"Frontier CAPI query failure" = "Frontier CAPI sorgulama hatası"; + +/* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */ +"Update" = "Güncelle"; + +/* EDMarketConnector.py: Appearance - Label for checkbox to select if application always on top; prefs.py: Appearance - Label for checkbox to select if application always on top; In files: EDMarketConnector.py:710; prefs.py:875; */ +"Always on top" = "Her zaman üstte"; + +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */ +"Unknown" = "Bilinmeyen"; + +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */ +"Error: Check E:D journal file location" = "Hata: E:D günlük dosyası konumunu kontrol edin"; + +/* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */ +"Cmdr" = "Cmdr"; + +/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:915; EDMarketConnector.py:1487; */ +"Role" = "Rol"; + +/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:915; EDMarketConnector.py:1497; EDMarketConnector.py:1520; stats.py:405; */ +"Ship" = "Gemi"; + +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:916; */ +"Suit" = "Süit"; + +/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:917; prefs.py:614; stats.py:407; */ +"System" = "Sistem"; + +/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:918; prefs.py:632; prefs.py:770; stats.py:408; */ +"Station" = "İstasyon"; + +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:921; EDMarketConnector.py:939; EDMarketConnector.py:942; EDMarketConnector.py:2264; */ +"File" = "Dosya"; + +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ +"Edit" = "Düzenle"; + +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ +"View" = "Görüntüle"; + +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ +"Window" = "Pencere"; + +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ +"Help" = "Yardım"; + +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:928; EDMarketConnector.py:959; EDMarketConnector.py:1804; */ +"About {APP}" = "Hakkında {APP}"; + +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ +"Check for Updates..." = "Güncellemeleri Denetle..."; + +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ +"Save Raw Data..." = "Ham Verileri Kaydet..."; + +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:932; EDMarketConnector.py:947; stats.py:402; */ +"Status" = "Durum"; + +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */ +"Documentation" = "Dokümantasyon"; + +/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */ +"Troubleshooting" = "Sorun giderme"; + +/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */ +"Report A Bug" = "Hata Bildir"; + +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */ +"Privacy Policy" = "Gizlilik Politikası"; + +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:937; EDMarketConnector.py:957; EDMarketConnector.py:1838; */ +"Release Notes" = "Sürüm notları"; + +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:949; EDMarketConnector.py:2265; prefs.py:241; */ +"Settings" = "Ayarlar"; + +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:950; */ +"Exit" = "Çıkış"; + +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ +"Copy" = "Kopyala"; + +/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ +"CAPI auth disabled by killswitch" = "CAPI kimlik doğrulaması killswitch tarafından devre dışı bırakıldı"; + +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */ +"Logging in..." = "Giriş yapılıyor..."; + +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:994; EDMarketConnector.py:1657; */ +"Authentication successful" = "Kimlik doğrulama başarılı"; + +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:1025; */ +"You're not docked at a station!" = "Bir istasyona kenetli değilsiniz!"; + +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:1033; */ +"Station doesn't have anything!" = "İstasyon içeriği mevcut değil."; + +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */ +"Station doesn't have a market!" = "İstasyon'da market bulunmuyor!"; + +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because Cmdr name is unknown; In files: EDMarketConnector.py:1077; EDMarketConnector.py:1164; */ +"CAPI query aborted: Cmdr name unknown" = "CAPI sorgusu iptal edildi: Cmdr adı bilinmiyor"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */ +"CAPI query aborted: Game mode unknown" = "CAPI sorgusu iptal edildi: Oyun modu bilinmiyor"; + +/* EDMarketConnector.py: CAPI queries aborted because GameVersion unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because GameVersion unknown; In files: EDMarketConnector.py:1089; EDMarketConnector.py:1170; */ +"CAPI query aborted: GameVersion unknown" = "CAPI sorgusu iptal edildi: GameVersion bilinmiyor"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */ +"CAPI query aborted: Current system unknown" = "CAPI sorgusu iptal edildi: Mevcut sistem bilinmiyor"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:1101; */ +"CAPI query aborted: In other-ship multi-crew" = "CAPI sorgusu iptal edildi: Başka gemi çoklu-mürettebatı"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */ +"CAPI query aborted: CQC (Arena) detected" = "CAPI sorgusu iptal edildi: CQC (Arena) tespit edildi"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */ +"Fetching data..." = "Veri işleniyor..."; + +/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ +"CAPI fleetcarrier disabled by killswitch" = "CAPI filo taşıyıcısı killswitch tarafından devre dışı bırakıldı"; + +/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No fleetcarrier data returned" = "CAPI: Filo taşıyıcı verisi gelmedi."; + +/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleetcarrier data incomplete" = "CAPI: Filo taşıyıcı verileri eksik"; + +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ +"CAPI: No commander data returned" = "CAPI: Cmdr verisi döndürülmedi"; + +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1246; stats.py:333; */ +"Who are you?!" = "Sen kimsin?!"; + +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1252; stats.py:341; */ +"Where are you?!" = "Neredesin?!"; + +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1259; stats.py:349; */ +"What are you flying?!" = "Ne uçuruyorsun?!"; + +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */ +"Frontier CAPI server error" = "Frontier CAPI sunucu hatası"; + +/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */ +"CAPI: Refreshing access token..." = "CAPI: Erişim token yenileniyor..."; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */ +"Last updated at %H:%M:%S" = "Son güncelleme %H:%M:%S"; + +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1462; */ +"Fighter" = "Avcı"; + +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1463; */ +"Gunner" = "Nişancı"; + +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1464; */ +"Helm" = "Dümen"; + +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1742; */ +"cooldown {SS}s" = "bekleme süresi {SS}s"; + +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1864; prefs.py:292; */ +"OK" = "Tamam"; + +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1936; */ +"Shutting down..." = "Kapanıyor..."; + +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ +"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası henüz Python 3.x desteğine sahip değil. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Güncellenmiş bir sürümün mevcut olup olmadığını kontrol edin, aksi takdirde geliştiriciyi Python 3.x kodunu güncellemesi gerektiği konusunda uyarmalısınız.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz."; + +/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ +"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID Dosyaları bulunamadı! Ürünlerle ilgili bazı işlevler devre dışı bırakılabilir.\n\nAlt modüllerin nasıl kurulacağına ilişkin Wiki sayfasını açmak ister misiniz?"; + +/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ +"FDevIDs: Missing Commodity Files" = "FDevIDs: Eksik Ürün Dosyaları"; + +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ +"Plugins" = "Eklentiler"; + +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */ +"EDMC: Plugins Without Python 3.x Support" = "EDMC: Python 3.x Desteği Olmayan Eklentiler"; + +/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */ +"EDMC: Broken Plugins" = "EDMC: Bozuk Eklentiler"; + +/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ +"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası yüklenemedi. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Sorunun sebebi yanlış klasör yapısı olabilir. Load.py dosyası, plugins/PLUGIN_NAME/load.py altında bulunmalıdır.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz."; + +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "URL Sağlayıcılarınızdan bir veya daha fazlası geçersizdi ve sıfırlandı:"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} , {OLDPROV}'a ayarlanmıştı ancak {NEWPROV}'a sıfırlandı"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: Varsayılan Sağlayıcıların Sıfırlanması"; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Tam CMDR Girişi Bekleniyor"; + +/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ +"Journal directory already locked" = "Günlük dizini zaten kilitli"; + +/* journal_lock.py: Text for when newly selected Journal directory is already locked; In files: journal_lock.py:225:226; */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Yeni Günlük Dizini konumu zaten kilitli.{CR}Çözüm için Tekrar deneyebilir veya Yoksay'ı seçebilirsiniz."; + +/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:230; */ +"Retry" = "Tekrar Dene"; + +/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:234; */ +"Ignore" = "Yoksay"; + +/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ +"Default" = "Varsayılan"; + +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ +"Auto" = "Otomatik"; + +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ +"Normal" = "Normal"; + +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ +"Beta" = "Beta"; + +/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ +"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" = "Coriolis.io gemi ekipman tasarımları ile kullanılacak URL'yi ayarlayın. Link'in '/import?data=' ile bitmesi GEREKTİĞİNİ unutmayın."; + +/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */ +"Normal URL" = "Normal URL"; + +/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */ +"Reset" = "Sıfırla"; + +/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */ +"Beta URL" = "Beta URL"; + +/* coriolis.py: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL; In files: coriolis.py:116; */ +"Override Beta/Normal Selection" = "Beta/Normal Seçimi Geçersiz Kıl"; + +/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: coriolis.py:156; */ +"Invalid Coriolis override mode!" = "Geçersiz Coriolis geçersiz kılma modu!"; + +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:458; eddn.py:2413; eddn.py:2451; eddn.py:2519; */ +"Error: Can't connect to EDDN" = "Hata: EDDN'e bağlanılamıyor"; + +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:576; */ +"EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Hatası: EDMC sürümü EDDN için çok eski. Lütfen güncelle."; + +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:582; */ +"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Hatası: Doğrulama Başarısız Oldu (EDMC sürümü eski olabilir?). Günlüğe bakın"; + +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:587; */ +"EDDN Error: Returned {STATUS} status code" = "EDDN Hatası: {STATUS} durum kodu ile döndü."; + +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:2041; */ +"Send station data to the Elite Dangerous Data Network" = "İstasyon verilerini Elite Dangerous Data Network'e gönderin"; + +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:2052; */ +"Send system and scan data to the Elite Dangerous Data Network" = "Sistem ve tarama verilerini Elite Dangerous Data Network'e gönderin"; + +/* eddn.py: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this; In files: eddn.py:2063; */ +"Delay sending until docked" = "Kenetlenmeye kadar gönderimi ertele"; + +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:2178; */ +"EDDN journal handler disabled. See Log." = "EDDN günlük düzenleyici devre dışı. Kayıt geçmişini kontrol edin."; + +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:2507; */ +"Sending data to EDDN..." = "Veriler EDDN'e gönderiliyor..."; + +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:319; */ +"Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map kimlik bilgileri"; + +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:341; */ +"Commander Name" = "Cmdr ismi"; + +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:350; inara.py:278; */ +"API Key" = "API Anahtarı"; + +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:394; prefs.py:527; prefs.py:1157; prefs.py:1190; stats.py:154; stats.py:173; stats.py:192; stats.py:209; */ +"None" = "Hiçbiri"; + +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:516; */ +"EDSM Handler disabled. See Log." = "EDSM düzenleyici devre dışı. Kayıt geçmişini kontrol edin."; + +/* edsm.py: EDSM - Only Live data; In files: edsm.py:632; */ +"EDSM only accepts Live galaxy data" = "EDSM sadece canlı galaxy verilerini kabul eder"; + +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:916; edsm.py:1048; */ +"Error: EDSM {MSG}" = "Hata: EDSM {MSG}"; + +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */ +"Error: Can't connect to EDSM" = "Hata: EDSM'e bağlanılamıyor"; + +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:257; */ +"Send flight log and Cmdr status to Inara" = "Uçuş günlüğünü ve Cmdr durumunu Inara'ya gönder"; + +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */ +"Inara credentials" = "Inara kimlik bilgileri"; + +/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; inara.py: Inara - Only Live data; In files: inara.py:384; inara.py:386; */ +"Inara only accepts Live galaxy data" = "Inara sadece canlı galaxy verilerini kabul eder"; + +/* inara.py: INARA support disabled via killswitch; In files: inara.py:395; */ +"Inara disabled. See Log." = "Inara devre dışı. Kayıt geçmişini kontrol edin."; + +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ +"Error: Inara {MSG}" = "Hata: Inara {MSG}"; + +/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ +"Preferences" = "Tercihler"; + +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ +"Please choose what data to save" = "Lütfen hangi verilerin kaydedileceğini seçin"; + +/* prefs.py: Settings > Output option; In files: prefs.py:341; */ +"Market data in CSV format file" = "CSV formatında Market verileri"; + +/* prefs.py: Settings > Output option; In files: prefs.py:350; */ +"Market data in Trade Dangerous format file" = "Trade Dangerous formatında Market verileri"; + +/* prefs.py: Settings > Output option; In files: prefs.py:360; */ +"Ship loadout" = "Gemi Ekipmanları"; + +/* prefs.py: Settings > Output option; In files: prefs.py:370; */ +"Automatically update on docking" = "Kenetlenme sırasında otomatik güncelle"; + +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ +"File location" = "Dosya konumu"; + +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ +"Change..." = "Değiştir..."; + +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ +"Browse..." = "Gezin..."; + +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:405; */ +"Output" = "Kayıt"; + +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ +"E:D journal file location" = "E:D günlük dosya konumu"; + +/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ +"CAPI Settings" = "CAPI ayarları"; + +/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ +"Enable Fleetcarrier CAPI Queries" = "Filo Taşıyıcı CAPI sorgulamalarını etkinleştir"; + +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ +"Keyboard shortcut" = "Klavye Kısayolu"; + +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ +"Hotkey" = "Kısayoltuşu"; + +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ +"Re-start {APP} to use shortcuts" = "Kısayolları kullanmak için {APP}'i yeniden başlat"; + +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ +"{APP} needs permission to use shortcuts" = "{APP}'nin kısayolları kullanabilmesi için izne ihtiyacı var"; + +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ +"Open System Preferences" = "Sistem Tercihlerini Aç"; + +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ +"Only when Elite: Dangerous is the active app" = "Sadece Elite: Dangerous aktif uygulama olduğunda"; + +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:549; */ +"Play sound" = "Ses çal"; + +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:564; */ +"Disable Automatic Application Updates Check when in-game" = "Otomatik Uygulama Güncellemelerini Oyun sırasında Devre Dışı Bırak"; + +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:577; */ +"Preferred websites" = "Tercih edilen web siteleri"; + +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:588; */ +"Shipyard" = "Tersane"; + +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:600; */ +"Use alternate URL method" = "Alternatif URL yöntemi kullan"; + +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:653; */ +"Log Level" = "Günlük Seviyesi"; + +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:681; */ +"Configuration" = "Yapılandırma"; + +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:690; */ +"Main UI privacy options" = "Ana Arayüz gizlilik seçenekleri"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:695; */ +"Hide private group name in UI" = "Özel grup ismini arayüzde gizle"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:699; */ +"Hide multi-crew captain name" = "Çoklu-mürettebat ismini gizle"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:703; */ +"Privacy" = "Gizlilik"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:716; */ +"Normal text" = "Normal metin"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:718; */ +"Highlighted text" = "Vurgulanan metin"; + +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:727; */ +"Language" = "Dil"; + +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:737; */ +"Theme" = "Tema"; + +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:749; */ +"Dark" = "Karanlık"; + +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:756; */ +"Transparent" = "Şeffaflık"; + +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:802; */ +"UI Scale Percentage" = "Arayüz ölçek yüzdesi"; + +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:823; */ +"100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 Varsayılan değerdir. Değişikliklerin geçerli olması için {CR}Yeniden Başlatma Gereklidir{CR}!"; + +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:833; */ +"Main window transparency" = "Ana pencere şeffaflığı"; + +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:853:856; */ +"100 means fully opaque.{CR}Window is updated in real time" = "100 tamamen opak anlamına gelir.{CR}Pencere gerçek zamanlı olarak güncellenir"; + +/* prefs.py: Appearance option for Windows "minimize to system tray"; In files: prefs.py:885; */ +"Minimize to system tray" = "Simge durumuna küçült"; + +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:893; */ +"Appearance" = "Görünüm"; + +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */ +"Plugins folder" = "Eklentiler klasörü"; + +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */ +"Open" = "Aç"; + +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */ +"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "İpucu: Bir eklentiyi, klasör adına{CR}'{EXT}' ekleyerek devre dışı bırakabilirsiniz"; + +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:934; */ +"Enabled Plugins" = "Etkin Eklentiler"; + +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:954; */ +"Plugins Without Python 3.x Support" = "Python 3.x Desteği Olmayan Eklentiler"; + +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */ +"Information on migrating plugins" = "Eklentilerin taşınmasıyla ilgili bilgiler"; + +/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */ +"Broken Plugins" = "Bozuk Eklentiler"; + +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */ +"Disabled Plugins" = "Devre Dışı Eklentiler"; + +/* stats.py: Cmdr stats; In files: stats.py:58; */ +"Balance" = "Bakiye"; + +/* stats.py: Cmdr stats; In files: stats.py:59; */ +"Loan" = "Borç"; + +/* stats.py: Top rank; In files: stats.py:63; */ +"Elite" = "Elite"; + +/* stats.py: Top rank +1; In files: stats.py:64; */ +"Elite I" = "Elite I"; + +/* stats.py: Top rank +2; In files: stats.py:65; */ +"Elite II" = "Elite II"; + +/* stats.py: Top rank +3; In files: stats.py:66; */ +"Elite III" = "Elite III"; + +/* stats.py: Top rank +4; In files: stats.py:67; */ +"Elite IV" = "Elite IV"; + +/* stats.py: Top rank +5; In files: stats.py:68; */ +"Elite V" = "Elite V"; + +/* stats.py: Ranking; In files: stats.py:74; */ +"Combat" = "Combat"; + +/* stats.py: Ranking; In files: stats.py:75; */ +"Trade" = "Trade"; + +/* stats.py: Ranking; In files: stats.py:76; */ +"Explorer" = "Explorer"; + +/* stats.py: Ranking; In files: stats.py:77; */ +"Mercenary" = "Mercenary"; + +/* stats.py: Ranking; In files: stats.py:78; */ +"Exobiologist" = "Exobiologist"; + +/* stats.py: Ranking; In files: stats.py:79; */ +"CQC" = "CQC"; + +/* stats.py: Ranking; In files: stats.py:80; */ +"Federation" = "Federation"; + +/* stats.py: Ranking; In files: stats.py:81; */ +"Empire" = "Empire"; + +/* stats.py: Ranking; In files: stats.py:82; */ +"Powerplay" = "Powerplay"; + +/* stats.py: Combat rank; In files: stats.py:91; */ +"Harmless" = "Harmless"; + +/* stats.py: Combat rank; In files: stats.py:92; */ +"Mostly Harmless" = "Mostly Harmless"; + +/* stats.py: Combat rank; In files: stats.py:93; */ +"Novice" = "Novice"; + +/* stats.py: Combat rank; In files: stats.py:94; */ +"Competent" = "Competent"; + +/* stats.py: Combat rank; In files: stats.py:95; */ +"Expert" = "Expert"; + +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:96; stats.py:176; */ +"Master" = "Master"; + +/* stats.py: Combat rank; In files: stats.py:97; */ +"Dangerous" = "Dangerous"; + +/* stats.py: Combat rank; In files: stats.py:98; */ +"Deadly" = "Deadly"; + +/* stats.py: Trade rank; In files: stats.py:101; */ +"Penniless" = "Penniless"; + +/* stats.py: Trade rank; In files: stats.py:102; */ +"Mostly Penniless" = "Mostly Penniless"; + +/* stats.py: Trade rank; In files: stats.py:103; */ +"Peddler" = "Peddler"; + +/* stats.py: Trade rank; In files: stats.py:104; */ +"Dealer" = "Dealer"; + +/* stats.py: Trade rank; In files: stats.py:105; */ +"Merchant" = "Merchant"; + +/* stats.py: Trade rank; In files: stats.py:106; */ +"Broker" = "Broker"; + +/* stats.py: Trade rank; In files: stats.py:107; */ +"Entrepreneur" = "Entrepreneur"; + +/* stats.py: Trade rank; In files: stats.py:108; */ +"Tycoon" = "Tycoon"; + +/* stats.py: Explorer rank; In files: stats.py:111; */ +"Aimless" = "Aimless"; + +/* stats.py: Explorer rank; In files: stats.py:112; */ +"Mostly Aimless" = "Mostly Aimless"; + +/* stats.py: Explorer rank; In files: stats.py:113; */ +"Scout" = "Scout"; + +/* stats.py: Explorer rank; In files: stats.py:114; */ +"Surveyor" = "Surveyor"; + +/* stats.py: Explorer rank; In files: stats.py:115; */ +"Trailblazer" = "Trailblazer"; + +/* stats.py: Explorer rank; In files: stats.py:116; */ +"Pathfinder" = "Pathfinder"; + +/* stats.py: Explorer rank; In files: stats.py:117; */ +"Ranger" = "Ranger"; + +/* stats.py: Explorer rank; In files: stats.py:118; */ +"Pioneer" = "Pioneer"; + +/* stats.py: Mercenary rank; In files: stats.py:122; */ +"Defenceless" = "Defenceless"; + +/* stats.py: Mercenary rank; In files: stats.py:123; */ +"Mostly Defenceless" = "Mostly Defenceless"; + +/* stats.py: Mercenary rank; In files: stats.py:124; */ +"Rookie" = "Rookie"; + +/* stats.py: Mercenary rank; In files: stats.py:125; */ +"Soldier" = "Soldier"; + +/* stats.py: Mercenary rank; In files: stats.py:126; stats.py:128; */ +"Gunslinger" = "Gunslinger"; + +/* stats.py: Mercenary rank; In files: stats.py:127; */ +"Warrior" = "Warrior"; + +/* stats.py: Mercenary rank; In files: stats.py:129; */ +"Deadeye" = "Deadeye"; + +/* stats.py: Exobiologist rank; In files: stats.py:132; */ +"Directionless" = "Directionless"; + +/* stats.py: Exobiologist rank; In files: stats.py:133; */ +"Mostly Directionless" = "Mostly Directionless"; + +/* stats.py: Exobiologist rank; In files: stats.py:134; */ +"Compiler" = "Compiler"; + +/* stats.py: Exobiologist rank; In files: stats.py:135; */ +"Collector" = "Collector"; + +/* stats.py: Exobiologist rank; In files: stats.py:136; */ +"Cataloguer" = "Cataloguer"; + +/* stats.py: Exobiologist rank; In files: stats.py:137; */ +"Taxonomist" = "Taxonomist"; + +/* stats.py: Exobiologist rank; In files: stats.py:138; */ +"Ecologist" = "Ecologist"; + +/* stats.py: Exobiologist rank; In files: stats.py:139; */ +"Geneticist" = "Geneticist"; + +/* stats.py: CQC rank; In files: stats.py:142; */ +"Helpless" = "Helpless"; + +/* stats.py: CQC rank; In files: stats.py:143; */ +"Mostly Helpless" = "Mostly Helpless"; + +/* stats.py: CQC rank; In files: stats.py:144; */ +"Amateur" = "Amateur"; + +/* stats.py: CQC rank; In files: stats.py:145; */ +"Semi Professional" = "Semi Professional"; + +/* stats.py: CQC rank; In files: stats.py:146; */ +"Professional" = "Professional"; + +/* stats.py: CQC rank; In files: stats.py:147; */ +"Champion" = "Champion"; + +/* stats.py: CQC rank; In files: stats.py:148; */ +"Hero" = "Hero"; + +/* stats.py: CQC rank; In files: stats.py:149; */ +"Gladiator" = "Gladiator"; + +/* stats.py: Federation rank; In files: stats.py:155; */ +"Recruit" = "Recruit"; + +/* stats.py: Federation rank; In files: stats.py:156; */ +"Cadet" = "Cadet"; + +/* stats.py: Federation rank; In files: stats.py:157; */ +"Midshipman" = "Midshipman"; + +/* stats.py: Federation rank; In files: stats.py:158; */ +"Petty Officer" = "Petty Officer"; + +/* stats.py: Federation rank; In files: stats.py:159; */ +"Chief Petty Officer" = "Chief Petty Officer"; + +/* stats.py: Federation rank; In files: stats.py:160; */ +"Warrant Officer" = "Warrant Officer"; + +/* stats.py: Federation rank; In files: stats.py:161; */ +"Ensign" = "Ensign"; + +/* stats.py: Federation rank; In files: stats.py:162; */ +"Lieutenant" = "Lieutenant"; + +/* stats.py: Federation rank; In files: stats.py:163; */ +"Lieutenant Commander" = "Lieutenant Commander"; + +/* stats.py: Federation rank; In files: stats.py:164; */ +"Post Commander" = "Post Commander"; + +/* stats.py: Federation rank; In files: stats.py:165; */ +"Post Captain" = "Post Captain"; + +/* stats.py: Federation rank; In files: stats.py:166; */ +"Rear Admiral" = "Rear Admiral"; + +/* stats.py: Federation rank; In files: stats.py:167; */ +"Vice Admiral" = "Vice Admiral"; + +/* stats.py: Federation rank; In files: stats.py:168; */ +"Admiral" = "Admiral"; + +/* stats.py: Empire rank; In files: stats.py:174; */ +"Outsider" = "Outsider"; + +/* stats.py: Empire rank; In files: stats.py:175; */ +"Serf" = "Serf"; + +/* stats.py: Empire rank; In files: stats.py:177; */ +"Squire" = "Squire"; + +/* stats.py: Empire rank; In files: stats.py:178; */ +"Knight" = "Knight"; + +/* stats.py: Empire rank; In files: stats.py:179; */ +"Lord" = "Lord"; + +/* stats.py: Empire rank; In files: stats.py:180; */ +"Baron" = "Baron"; + +/* stats.py: Empire rank; In files: stats.py:181; */ +"Viscount" = "Viscount"; + +/* stats.py: Empire rank; In files: stats.py:182; */ +"Count" = "Count"; + +/* stats.py: Empire rank; In files: stats.py:183; */ +"Earl" = "Earl"; + +/* stats.py: Empire rank; In files: stats.py:184; */ +"Marquis" = "Marquis"; + +/* stats.py: Empire rank; In files: stats.py:185; */ +"Duke" = "Duke"; + +/* stats.py: Empire rank; In files: stats.py:186; */ +"Prince" = "Prince"; + +/* stats.py: Empire rank; In files: stats.py:187; */ +"King" = "King"; + +/* stats.py: Power rank; In files: stats.py:193; */ +"Rating 1" = "Reyting 1"; + +/* stats.py: Power rank; In files: stats.py:194; */ +"Rating 2" = "Reyting 2"; + +/* stats.py: Power rank; In files: stats.py:195; */ +"Rating 3" = "Reyting 3"; + +/* stats.py: Power rank; In files: stats.py:196; */ +"Rating 4" = "Reyting 4"; + +/* stats.py: Power rank; In files: stats.py:197; */ +"Rating 5" = "Reyting 5"; + +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:315; */ +"Status: Don't yet know your Commander name" = "Durum: Cmdr adınızı henüz bilmiyorum"; + +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:323; */ +"Status: No CAPI data yet" = "Durum: Henüz CAPI verisi yok"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:409; */ +"Value" = "Değer"; + +/* stats.py: Status dialog title; In files: stats.py:418; */ +"Ships" = "Gemiler"; + +/* update.py: Update Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} sürüm mevcut"; diff --git a/L10n/zh-Hans.strings b/L10n/zh-Hans.strings index 2b9eb75dc..03e263dd7 100644 --- a/L10n/zh-Hans.strings +++ b/L10n/zh-Hans.strings @@ -226,13 +226,13 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "默认"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */ +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ "Auto" = "自动"; -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */ +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ "Normal" = "普通版"; -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */ +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ "Beta" = "测试版"; /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ From 164a92092f349a1889564f480db711e90f167b7a Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 17 Apr 2024 12:25:52 +0000 Subject: [PATCH 148/261] updating submodules --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index 7cffab3d9..9b3f40612 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 7cffab3d913b788f981923687203399c22cf358f +Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2 From 9cd69a19e4b7a0cfeb89045044aec8a85d4dccd5 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 17 Apr 2024 15:35:57 -0400 Subject: [PATCH 149/261] [1133] Refine Inheritence --- myNotebook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 2442acbee..070b28e12 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -67,7 +67,7 @@ class EntryMenu(ttk.Entry): """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands.""" def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + ttk.Entry.__init__(self, *args, **kwargs) self.menu = tk.Menu(self, tearoff=False) self.menu.add_command(label="Copy", command=self.copy) @@ -120,7 +120,7 @@ def paste(self) -> None: pass -class Entry(EntryMenu or ttk.Entry): # type: ignore +class Entry(EntryMenu): """Custom ttk.Entry class to fix some display issues.""" # DEPRECATED: Migrate to EntryMenu. Will remove in 5.12 or later. From 158456aba73fc9ee8a548ed2c35c6b9f4fd60b0d Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 12 Apr 2024 11:08:58 -0400 Subject: [PATCH 150/261] [2198] Remove Potentially Unused Dependencies Let's see what happens! --- FDevIDs | 2 +- config/__init__.py | 2 +- requirements.txt | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/FDevIDs b/FDevIDs index 9b3f40612..7cffab3d9 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2 +Subproject commit 7cffab3d913b788f981923687203399c22cf358f diff --git a/config/__init__.py b/config/__init__.py index 27d9b4506..c9a477c46 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -53,7 +53,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.10.4' +_static_appversion = '5.11.0-alpha1' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' diff --git a/requirements.txt b/requirements.txt index 75ea40411..8c4503621 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,5 @@ -certifi==2024.2.2 requests==2.31.0 pillow==10.3.0 -# requests depends on this now ? -charset-normalizer==3.3.2 - watchdog==3.0.0 -# Commented out because this doesn't package well with py2exe infi.systray==0.1.12; sys_platform == 'win32' -# argh==0.26.2 watchdog dep -# pyyaml==5.3.1 watchdog dep semantic-version==2.10.0 From 6b626fc0dabd758892a7975b0adb9ee6ad93790b Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 20 Apr 2024 17:45:14 -0400 Subject: [PATCH 151/261] Update FDevIDs --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index 7cffab3d9..9b3f40612 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 7cffab3d913b788f981923687203399c22cf358f +Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2 From 080d9f98f2f1edb64ae5d2c34025667222cb495f Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 22 Apr 2024 17:33:28 -0400 Subject: [PATCH 152/261] [1812] Handover Translations --- EDMC.py | 7 +- EDMarketConnector.py | 177 ++++++++++++++++---------------- companion.py | 29 +++--- journal_lock.py | 19 ++-- l10n.py | 47 +++++++-- monitor.py | 4 - plugins/coriolis.py | 65 ++++++------ plugins/eddn.py | 38 +++---- plugins/edsm.py | 33 +++--- plugins/inara.py | 25 ++--- prefs.py | 138 ++++++++++++------------- stats.py | 235 +++++++++++++++++++++---------------------- theme.py | 9 +- ttkHyperlinkLabel.py | 8 +- update.py | 4 +- 15 files changed, 415 insertions(+), 423 deletions(-) diff --git a/EDMC.py b/EDMC.py index 39d989824..64488778d 100755 --- a/EDMC.py +++ b/EDMC.py @@ -26,7 +26,6 @@ if TYPE_CHECKING: from logging import TRACE # type: ignore # noqa: F401 # needed to make mypy happy - def _(x: str): return x edmclogger.set_channels_loglevel(logging.INFO) @@ -35,7 +34,7 @@ def _(x: str): return x import commodity import companion import edshipyard -import l10n +from l10n import translations as tr import loadout import outfitting import shipyard @@ -66,7 +65,7 @@ def log_locale(prefix: str) -> None: ) -l10n.Translations.install_dummy() +tr.install_dummy() SERVER_RETRY = 5 # retry pause for Companion servers [s] EXIT_SUCCESS, EXIT_SERVER, EXIT_CREDENTIALS, EXIT_VERIFICATION, EXIT_LAGGING, EXIT_SYS_ERR, EXIT_ARGS, \ @@ -164,7 +163,7 @@ def main(): # noqa: C901, CCR001 newversion: EDMCVersion | None = updater.check_appcast() if newversion: # LANG: Update Available Text - newverstr: str = _("{NEWVER} is available").format(NEWVER=newversion.title) + newverstr: str = tr.tl("{NEWVER} is available").format(NEWVER=newversion.title) print(f'{appversion()} ({newverstr})') else: print(appversion()) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 3ab97eb52..09e6fc4e7 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -413,9 +413,6 @@ def already_running_popup(): from infi.systray import SysTrayIcon # isort: on - def _(x: str) -> str: - """Fake the l10n translation functions for typing.""" - return x import tkinter as tk import tkinter.filedialog @@ -432,7 +429,7 @@ def _(x: str) -> str: from dashboard import dashboard from edmc_data import ship_name_map from hotkey import hotkeymgr -from l10n import Translations +from l10n import translations as tr from monitor import monitor from theme import theme from ttkHyperlinkLabel import HyperlinkLabel @@ -590,7 +587,7 @@ def open_window(systray: 'SysTrayIcon') -> None: self.button = ttk.Button( frame, name='update_button', - text=_('Update'), # LANG: Main UI Update button + text=tr.tl('Update'), # LANG: Main UI Update button width=28, default=tk.ACTIVE, state=tk.DISABLED @@ -660,7 +657,7 @@ def open_window(systray: 'SysTrayIcon') -> None: self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) self.system_menu.add_separator() # LANG: Appearance - Label for checkbox to select if application always on top - self.system_menu.add_checkbutton(label=_('Always on top'), + self.system_menu.add_checkbutton(label=tr.tl('Always on top'), variable=self.always_ontop, command=self.ontop_changed) # Appearance setting self.menubar.add_cascade(menu=self.system_menu) @@ -765,7 +762,7 @@ def open_window(systray: 'SysTrayIcon') -> None: # Check for Valid Providers validate_providers() if monitor.cmdr is None: - self.status['text'] = _("Awaiting Full CMDR Login") # LANG: Await Full CMDR Login to Game + self.status['text'] = tr.tl("Awaiting Full CMDR Login") # LANG: Await Full CMDR Login to Game # Start a protocol handler to handle cAPI registration. Requires main loop to be running. self.w.after_idle(lambda: protocol.protocolhandler.start(self.w)) @@ -795,7 +792,7 @@ def update_suit_text(self) -> None: suit = monitor.state.get('SuitCurrent') if suit is None: - self.suit['text'] = f'<{_("Unknown")}>' # LANG: Unknown suit + self.suit['text'] = f'<{tr.tl("Unknown")}>' # LANG: Unknown suit return suitname = suit['edmcName'] @@ -851,45 +848,45 @@ def postprefs(self, dologin: bool = True): # (Re-)install log monitoring if not monitor.start(self.w): # LANG: ED Journal file location appears to be in error - self.status['text'] = _('Error: Check E:D journal file location') + self.status['text'] = tr.tl('Error: Check E:D journal file location') if dologin and monitor.cmdr: self.login() # Login if not already logged in with this Cmdr def set_labels(self): """Set main window labels, e.g. after language change.""" - self.cmdr_label['text'] = _('Cmdr') + ':' # LANG: Label for commander name in main window + self.cmdr_label['text'] = tr.tl('Cmdr') + ':' # LANG: Label for commander name in main window # LANG: 'Ship' or multi-crew role label in main window, as applicable - self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or _('Ship')) + ':' # Main window - self.suit_label['text'] = _('Suit') + ':' # LANG: Label for 'Suit' line in main UI - self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI - self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI - self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window - self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title - self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title - self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title - self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title - self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title - self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title + self.ship_label['text'] = (monitor.state['Captain'] and tr.tl('Role') or tr.tl('Ship')) + ':' # Main window + self.suit_label['text'] = tr.tl('Suit') + ':' # LANG: Label for 'Suit' line in main UI + self.system_label['text'] = tr.tl('System') + ':' # LANG: Label for 'System' line in main UI + self.station_label['text'] = tr.tl('Station') + ':' # LANG: Label for 'Station' line in main UI + self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window + self.menubar.entryconfigure(1, label=tr.tl('File')) # LANG: 'File' menu title + self.menubar.entryconfigure(2, label=tr.tl('Edit')) # LANG: 'Edit' menu title + self.menubar.entryconfigure(3, label=tr.tl('Help')) # LANG: 'Help' menu title + self.theme_file_menu['text'] = tr.tl('File') # LANG: 'File' menu title + self.theme_edit_menu['text'] = tr.tl('Edit') # LANG: 'Edit' menu title + self.theme_help_menu['text'] = tr.tl('Help') # LANG: 'Help' menu title # File menu - self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status - self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data... - self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings - self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit + self.file_menu.entryconfigure(0, label=tr.tl('Status')) # LANG: File > Status + self.file_menu.entryconfigure(1, label=tr.tl('Save Raw Data...')) # LANG: File > Save Raw Data... + self.file_menu.entryconfigure(2, label=tr.tl('Settings')) # LANG: File > Settings + self.file_menu.entryconfigure(4, label=tr.tl('Exit')) # LANG: File > Exit # Help menu - self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation - self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting - self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug - self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy - self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes - self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates... - self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App - self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder + self.help_menu.entryconfigure(0, label=tr.tl('Documentation')) # LANG: Help > Documentation + self.help_menu.entryconfigure(1, label=tr.tl('Troubleshooting')) # LANG: Help > Troubleshooting + self.help_menu.entryconfigure(2, label=tr.tl('Report A Bug')) # LANG: Help > Report A Bug + self.help_menu.entryconfigure(3, label=tr.tl('Privacy Policy')) # LANG: Help > Privacy Policy + self.help_menu.entryconfigure(4, label=tr.tl('Release Notes')) # LANG: Help > Release Notes + self.help_menu.entryconfigure(5, label=tr.tl('Check for Updates...')) # LANG: Help > Check for Updates... + self.help_menu.entryconfigure(6, label=tr.tl("About {APP}").format(APP=applongname)) # LANG: Help > About App + self.help_menu.entryconfigure(7, label=tr.tl('Open Log Folder')) # LANG: Help > Open Log Folder # Edit menu - self.edit_menu.entryconfigure(0, label=_('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste' + self.edit_menu.entryconfigure(0, label=tr.tl('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste' def login(self): """Initiate CAPI/Frontier login and set other necessary state.""" @@ -900,12 +897,12 @@ def login(self): if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') # LANG: CAPI auth aborted because of killswitch - self.status['text'] = _('CAPI auth disabled by killswitch') + self.status['text'] = tr.tl('CAPI auth disabled by killswitch') return if not self.status['text']: # LANG: Status - Attempting to get a Frontier Auth Access Token - self.status['text'] = _('Logging in...') + self.status['text'] = tr.tl('Logging in...') self.button['state'] = self.theme_button['state'] = tk.DISABLED @@ -916,7 +913,7 @@ def login(self): try: if companion.session.login(monitor.cmdr, monitor.is_beta): # LANG: Successfully authenticated with the Frontier website - self.status['text'] = _('Authentication successful') + self.status['text'] = tr.tl('Authentication successful') self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data @@ -947,17 +944,17 @@ def export_market_data(self, data: 'CAPIData') -> bool: # noqa: CCR001 # Signal as error because the user might actually be docked # but the server hosting the Companion API hasn't caught up # LANG: Player is not docked at a station, when we expect them to be - self._handle_status(_("You're not docked at a station!")) + self._handle_status(tr.tl("You're not docked at a station!")) return False # Ignore possibly missing shipyard info if output_flags & config.OUT_EDDN_SEND_STATION_DATA and not (has_commodities or has_modules): # LANG: Status - Either no market or no modules data for station from Frontier CAPI - self._handle_status(_("Station doesn't have anything!")) + self._handle_status(tr.tl("Station doesn't have anything!")) elif not has_commodities: # LANG: Status - No station market data from Frontier CAPI - self._handle_status(_("Station doesn't have a market!")) + self._handle_status(tr.tl("Station doesn't have a market!")) elif output_flags & commodities_flag: # Fixup anomalies in the comodity data @@ -995,7 +992,7 @@ def capi_request_data(self, event=None) -> None: # noqa: CCR001 if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') # LANG: CAPI auth query aborted because of killswitch - self.status['text'] = _('CAPI auth disabled by killswitch') + self.status['text'] = tr.tl('CAPI auth disabled by killswitch') hotkeymgr.play_bad() return @@ -1005,37 +1002,37 @@ def capi_request_data(self, event=None) -> None: # noqa: CCR001 if not monitor.cmdr: logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown') # LANG: CAPI queries aborted because Cmdr name is unknown - self.status['text'] = _('CAPI query aborted: Cmdr name unknown') + self.status['text'] = tr.tl('CAPI query aborted: Cmdr name unknown') return if not monitor.mode: logger.trace_if('capi.worker', 'Aborting Query: Game Mode unknown') # LANG: CAPI queries aborted because game mode unknown - self.status['text'] = _('CAPI query aborted: Game mode unknown') + self.status['text'] = tr.tl('CAPI query aborted: Game mode unknown') return if monitor.state['GameVersion'] is None: logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown') # LANG: CAPI queries aborted because GameVersion unknown - self.status['text'] = _('CAPI query aborted: GameVersion unknown') + self.status['text'] = tr.tl('CAPI query aborted: GameVersion unknown') return if not monitor.state['SystemName']: logger.trace_if('capi.worker', 'Aborting Query: Current star system unknown') # LANG: CAPI queries aborted because current star system name unknown - self.status['text'] = _('CAPI query aborted: Current system unknown') + self.status['text'] = tr.tl('CAPI query aborted: Current system unknown') return if monitor.state['Captain']: logger.trace_if('capi.worker', 'Aborting Query: In multi-crew') # LANG: CAPI queries aborted because player is in multi-crew on other Cmdr's ship - self.status['text'] = _('CAPI query aborted: In other-ship multi-crew') + self.status['text'] = tr.tl('CAPI query aborted: In other-ship multi-crew') return if monitor.mode == 'CQC': logger.trace_if('capi.worker', 'Aborting Query: In CQC') # LANG: CAPI queries aborted because player is in CQC (Arena) - self.status['text'] = _('CAPI query aborted: CQC (Arena) detected') + self.status['text'] = tr.tl('CAPI query aborted: CQC (Arena) detected') return if companion.session.state == companion.Session.STATE_AUTH: @@ -1056,7 +1053,7 @@ def capi_request_data(self, event=None) -> None: # noqa: CCR001 hotkeymgr.play_good() # LANG: Status - Attempting to retrieve data from Frontier CAPI - self.status['text'] = _('Fetching data...') + self.status['text'] = tr.tl('Fetching data...') self.button['state'] = self.theme_button['state'] = tk.DISABLED self.w.update_idletasks() @@ -1086,20 +1083,20 @@ def capi_request_fleetcarrier_data(self, event=None) -> None: if should_return: logger.warning('capi.fleetcarrier has been disabled via killswitch. Returning.') # LANG: CAPI fleetcarrier query aborted because of killswitch - self.status['text'] = _('CAPI fleetcarrier disabled by killswitch') + self.status['text'] = tr.tl('CAPI fleetcarrier disabled by killswitch') hotkeymgr.play_bad() return if not monitor.cmdr: logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown') # LANG: CAPI fleetcarrier query aborted because Cmdr name is unknown - self.status['text'] = _('CAPI query aborted: Cmdr name unknown') + self.status['text'] = tr.tl('CAPI query aborted: Cmdr name unknown') return if monitor.state['GameVersion'] is None: logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown') # LANG: CAPI fleetcarrier query aborted because GameVersion unknown - self.status['text'] = _('CAPI query aborted: GameVersion unknown') + self.status['text'] = tr.tl('CAPI query aborted: GameVersion unknown') return if not companion.session.retrying: @@ -1108,7 +1105,7 @@ def capi_request_fleetcarrier_data(self, event=None) -> None: return # LANG: Status - Attempting to retrieve data from Frontier CAPI - self.status['text'] = _('Fetching data...') + self.status['text'] = tr.tl('Fetching data...') self.w.update_idletasks() query_time = int(time()) @@ -1151,11 +1148,11 @@ def capi_handle_response(self, event=None): # noqa: C901, CCR001 # Validation if 'name' not in capi_response.capi_data: # LANG: No data was returned for the fleetcarrier from the Frontier CAPI - err = self.status['text'] = _('CAPI: No fleetcarrier data returned') + err = self.status['text'] = tr.tl('CAPI: No fleetcarrier data returned') elif not capi_response.capi_data.get('name', {}).get('callsign'): # LANG: We didn't have the fleetcarrier callsign when we should have - err = self.status['text'] = _("CAPI: Fleetcarrier data incomplete") # Shouldn't happen + err = self.status['text'] = tr.tl("CAPI: Fleetcarrier data incomplete") # Shouldn't happen else: if __debug__: # Recording @@ -1174,24 +1171,24 @@ def capi_handle_response(self, event=None): # noqa: C901, CCR001 elif 'commander' not in capi_response.capi_data: # This can happen with EGS Auth if no commander created yet # LANG: No data was returned for the commander from the Frontier CAPI - err = self.status['text'] = _('CAPI: No commander data returned') + err = self.status['text'] = tr.tl('CAPI: No commander data returned') elif not capi_response.capi_data.get('commander', {}).get('name'): # LANG: We didn't have the commander name when we should have - err = self.status['text'] = _("Who are you?!") # Shouldn't happen + err = self.status['text'] = tr.tl("Who are you?!") # Shouldn't happen elif (not capi_response.capi_data.get('lastSystem', {}).get('name') or (capi_response.capi_data['commander'].get('docked') and not capi_response.capi_data.get('lastStarport', {}).get('name'))): # LANG: We don't know where the commander is, when we should - err = self.status['text'] = _("Where are you?!") # Shouldn't happen + err = self.status['text'] = tr.tl("Where are you?!") # Shouldn't happen elif ( not capi_response.capi_data.get('ship', {}).get('name') or not capi_response.capi_data.get('ship', {}).get('modules') ): # LANG: We don't know what ship the commander is in, when we should - err = self.status['text'] = _("What are you flying?!") # Shouldn't happen + err = self.status['text'] = tr.tl("What are you flying?!") # Shouldn't happen elif monitor.cmdr and capi_response.capi_data['commander']['name'] != monitor.cmdr: # Companion API Commander doesn't match Journal @@ -1318,7 +1315,7 @@ def capi_handle_response(self, event=None): # noqa: C901, CCR001 except companion.ServerConnectionError as comp_err: # LANG: Frontier CAPI server error when fetching data - self.status['text'] = _('Frontier CAPI server error') + self.status['text'] = tr.tl('Frontier CAPI server error') logger.warning(f'Exception while contacting server: {comp_err}') err = self.status['text'] = str(comp_err) play_bad = True @@ -1327,7 +1324,7 @@ def capi_handle_response(self, event=None): # noqa: C901, CCR001 # We need to 'close' the auth else it'll see STATE_OK and think login() isn't needed companion.session.reinit_session() # LANG: Frontier CAPI Access Token expired, trying to get a new one - self.status['text'] = _('CAPI: Refreshing access token...') + self.status['text'] = tr.tl('CAPI: Refreshing access token...') if companion.session.login(): logger.debug('Initial query failed, but login() just worked, trying again...') companion.session.retrying = True @@ -1366,7 +1363,7 @@ def capi_handle_response(self, event=None): # noqa: C901, CCR001 if not err: # not self.status['text']: # no errors # LANG: Time when we last obtained Frontier CAPI data - self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(capi_response.query_time)) + self.status['text'] = strftime(tr.tl('Last updated at %H:%M:%S'), localtime(capi_response.query_time)) if capi_response.play_sound and play_bad: hotkeymgr.play_bad() @@ -1394,9 +1391,9 @@ def crewroletext(role: str) -> str: return { None: '', 'Idle': '', - 'FighterCon': _('Fighter'), # LANG: Multicrew role - 'FireCon': _('Gunner'), # LANG: Multicrew role - 'FlightCon': _('Helm'), # LANG: Multicrew role + 'FighterCon': tr.tl('Fighter'), # LANG: Multicrew role + 'FireCon': tr.tl('Gunner'), # LANG: Multicrew role + 'FlightCon': tr.tl('Helm'), # LANG: Multicrew role }.get(role, role) if monitor.thread is None: @@ -1419,7 +1416,7 @@ def crewroletext(role: str) -> str: else: self.cmdr['text'] = f'{monitor.cmdr}' - self.ship_label['text'] = _('Role') + ':' # LANG: Multicrew role label in main window + self.ship_label['text'] = tr.tl('Role') + ':' # LANG: Multicrew role label in main window self.ship.configure(state=tk.NORMAL, text=crewroletext(monitor.state['Role']), url=None) elif monitor.cmdr: @@ -1429,7 +1426,7 @@ def crewroletext(role: str) -> str: else: self.cmdr['text'] = monitor.cmdr - self.ship_label['text'] = _('Ship') + ':' # LANG: 'Ship' label in main UI + self.ship_label['text'] = tr.tl('Ship') + ':' # LANG: 'Ship' label in main UI # TODO: Show something else when on_foot if monitor.state['ShipName']: @@ -1452,7 +1449,7 @@ def crewroletext(role: str) -> str: else: self.cmdr['text'] = '' - self.ship_label['text'] = _('Ship') + ':' # LANG: 'Ship' label in main UI + self.ship_label['text'] = tr.tl('Ship') + ':' # LANG: 'Ship' label in main UI self.ship['text'] = '' if monitor.cmdr and monitor.is_beta: @@ -1589,7 +1586,7 @@ def auth(self, event=None) -> None: try: companion.session.auth_callback() # LANG: Successfully authenticated with the Frontier website - self.status['text'] = _('Authentication successful') + self.status['text'] = tr.tl('Authentication successful') self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data @@ -1675,10 +1672,10 @@ def cooldown(self) -> None: # Update button in main window cooldown_time = int(self.capi_query_holdoff_time - time()) # LANG: Cooldown on 'Update' button - self.button['text'] = self.theme_button['text'] = _('cooldown {SS}s').format(SS=cooldown_time) + self.button['text'] = self.theme_button['text'] = tr.tl('cooldown {SS}s').format(SS=cooldown_time) self.w.after(1000, self.cooldown) else: - self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window + self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window self.button['state'] = self.theme_button['state'] = ( monitor.cmdr and monitor.mode and @@ -1743,7 +1740,7 @@ def __init__(self, parent: tk.Tk) -> None: self.parent = parent # LANG: Help > About App - self.title(_('About {APP}').format(APP=applongname)) + self.title(tr.tl('About {APP}').format(APP=applongname)) if parent.winfo_viewable(): self.transient(parent) @@ -1780,7 +1777,7 @@ def __init__(self, parent: tk.Tk) -> None: self.appversion_label.config(state=tk.DISABLED, bg=frame.cget("background"), font="TkDefaultFont") self.appversion_label.grid(row=row, column=0, sticky=tk.E) # LANG: Help > Release Notes - self.appversion = HyperlinkLabel(frame, compound=tk.RIGHT, text=_('Release Notes'), + self.appversion = HyperlinkLabel(frame, compound=tk.RIGHT, text=tr.tl('Release Notes'), url='https://github.com/EDCD/EDMarketConnector/releases/tag/Release/' f'{appversion_nobuild()}', underline=True) @@ -1806,7 +1803,7 @@ def __init__(self, parent: tk.Tk) -> None: ttk.Label(frame).grid(row=row, column=0) # spacer row += 1 # LANG: Generic 'OK' button label - button = ttk.Button(frame, text=_('OK'), command=self.apply) + button = ttk.Button(frame, text=tr.tl('OK'), command=self.apply) button.grid(row=row, column=2, sticky=tk.E) button.bind("", lambda event: self.apply()) self.protocol("WM_DELETE_WINDOW", self._destroy) @@ -1874,7 +1871,7 @@ def onexit(self, event=None) -> None: # Let the user know we're shutting down. # LANG: The application is shutting down - self.status['text'] = _('Shutting down...') + self.status['text'] = tr.tl('Shutting down...') self.w.update_idletasks() logger.info('Starting shutdown procedures...') @@ -2059,10 +2056,10 @@ def validate_providers(): return # LANG: Popup-text about Reset Providers - popup_text = _(r'One or more of your URL Providers were invalid, and have been reset:\r\n\r\n') + popup_text = tr.tl(r'One or more of your URL Providers were invalid, and have been reset:\r\n\r\n') for provider in reset_providers: # LANG: Text About What Provider Was Reset - popup_text += _(r'{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n') + popup_text += tr.tl(r'{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n') popup_text = popup_text.format( PROVIDER=provider, OLDPROV=reset_providers[provider][0], @@ -2074,7 +2071,7 @@ def validate_providers(): tk.messagebox.showinfo( # LANG: Popup window title for Reset Providers - _('EDMC: Default Providers Reset'), + tr.tl('EDMC: Default Providers Reset'), popup_text ) @@ -2200,7 +2197,7 @@ def test_prop(self): # Plain, not via `logger` print(f'{applongname} {appversion()}') - Translations.install(config.get_str('language')) # Can generate errors so wait til log set up + tr.install(config.get_str('language')) # Can generate errors so wait til log set up setup_killswitches(args.killswitches_file) @@ -2235,7 +2232,7 @@ def messagebox_broken_plugins(): """Display message about 'broken' plugins that failed to load.""" if plug.PLUGINS_broken: # LANG: Popup-text about 'broken' plugins that failed to load - popup_text = _( + popup_text = tr.tl( "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' " "tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py " r"file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by " @@ -2244,9 +2241,9 @@ def messagebox_broken_plugins(): # Substitute in the other words. popup_text = popup_text.format( - PLUGINS=_('Plugins'), # LANG: Settings > Plugins tab - FILE=_('File'), # LANG: 'File' menu - SETTINGS=_('Settings'), # LANG: File > Settings + PLUGINS=tr.tl('Plugins'), # LANG: Settings > Plugins tab + FILE=tr.tl('File'), # LANG: 'File' menu + SETTINGS=tr.tl('Settings'), # LANG: File > Settings DISABLED='.disabled' ) # And now we do need these to be actual \r\n @@ -2255,7 +2252,7 @@ def messagebox_broken_plugins(): tk.messagebox.showinfo( # LANG: Popup window title for list of 'broken' plugins that failed to load - _('EDMC: Broken Plugins'), + tr.tl('EDMC: Broken Plugins'), popup_text ) @@ -2264,7 +2261,7 @@ def messagebox_not_py3(): plugins_not_py3_last = config.get_int('plugins_not_py3_last', default=0) if (plugins_not_py3_last + 86400) < int(time()) and plug.PLUGINS_not_py3: # LANG: Popup-text about 'active' plugins without Python 3.x support - popup_text = _( + popup_text = tr.tl( "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the " "list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an " "updated version available, else alert the developer that they need to update the code for " @@ -2274,9 +2271,9 @@ def messagebox_not_py3(): # Substitute in the other words. popup_text = popup_text.format( - PLUGINS=_('Plugins'), # LANG: Settings > Plugins tab - FILE=_('File'), # LANG: 'File' menu - SETTINGS=_('Settings'), # LANG: File > Settings + PLUGINS=tr.tl('Plugins'), # LANG: Settings > Plugins tab + FILE=tr.tl('File'), # LANG: 'File' menu + SETTINGS=tr.tl('Settings'), # LANG: File > Settings DISABLED='.disabled' ) # And now we do need these to be actual \r\n @@ -2285,7 +2282,7 @@ def messagebox_not_py3(): tk.messagebox.showinfo( # LANG: Popup window title for list of 'enabled' plugins that don't work with Python 3.x - _('EDMC: Plugins Without Python 3.x Support'), + tr.tl('EDMC: Plugins Without Python 3.x Support'), popup_text ) config.set('plugins_not_py3_last', int(time())) @@ -2298,7 +2295,7 @@ def check_fdev_ids(): if fdevid_file.is_file(): continue # LANG: Popup-text about missing FDEVID Files - popup_text = _( + popup_text = tr.tl( "FDevID Files not found! Some functionality regarding commodities " r"may be disabled.\r\n\r\n Do you want to open the Wiki page on " "how to set up submodules?" @@ -2309,7 +2306,7 @@ def check_fdev_ids(): openwikipage = tk.messagebox.askquestion( # LANG: Popup window title for missing FDEVID files - _('FDevIDs: Missing Commodity Files'), + tr.tl('FDevIDs: Missing Commodity Files'), popup_text ) if openwikipage == "yes": diff --git a/companion.py b/companion.py index d00307482..1b9c693a1 100644 --- a/companion.py +++ b/companion.py @@ -37,12 +37,11 @@ from edmc_data import companion_category_map as category_map from EDMCLogging import get_main_logger from monitor import monitor +from l10n import translations as tr logger = get_main_logger() if TYPE_CHECKING: - def _(x: str): return x - UserDict = collections.UserDict[str, Any] # indicate to our type checkers what this generic class holds normally else: UserDict = collections.UserDict # Otherwise simply use the actual class @@ -224,7 +223,7 @@ def __init__(self, *args) -> None: self.args = args if not args: # LANG: Frontier CAPI didn't respond - self.args = (_("Error: Frontier CAPI didn't respond"),) + self.args = (tr.tl("Error: Frontier CAPI didn't respond"),) class ServerConnectionError(ServerError): @@ -243,7 +242,7 @@ def __init__(self, *args) -> None: self.args = args if not args: # LANG: Frontier CAPI data doesn't agree with latest Journal game location - self.args = (_('Error: Frontier server is lagging'),) + self.args = (tr.tl('Error: Frontier server is lagging'),) class NoMonitorStation(Exception): @@ -259,7 +258,7 @@ def __init__(self, *args) -> None: self.args = args if not args: # LANG: Commander is docked at an EDO settlement, got out and back in, we forgot the station - self.args = (_("Docked but unknown station: EDO Settlement?"),) + self.args = (tr.tl("Docked but unknown station: EDO Settlement?"),) class CredentialsError(Exception): @@ -269,7 +268,7 @@ def __init__(self, *args) -> None: self.args = args if not args: # LANG: Generic "something went wrong with Frontier Auth" error - self.args = (_('Error: Invalid Credentials'),) + self.args = (tr.tl('Error: Invalid Credentials'),) class CredentialsRequireRefresh(Exception): @@ -294,7 +293,7 @@ def __init__(self, *args) -> None: self.args = args if not args: # LANG: Frontier CAPI authorisation not for currently game-active commander - self.args = (_('Error: Wrong Cmdr'),) + self.args = (tr.tl('Error: Wrong Cmdr'),) class Auth: @@ -429,7 +428,7 @@ def authorize(self, payload: str) -> str: # noqa: CCR001 '' ) # LANG: Generic error prefix - following text is from Frontier auth service - raise CredentialsError(f'{_("Error")}: {error!r}') + raise CredentialsError(f'{tr.tl("Error")}: {error!r}') r = None try: @@ -472,18 +471,18 @@ def authorize(self, payload: str) -> str: # noqa: CCR001 if (usr := data_decode.get('usr')) is None: logger.error('No "usr" in /decode data') # LANG: Frontier auth, no 'usr' section in returned data - raise CredentialsError(_("Error: Couldn't check token customer_id")) + raise CredentialsError(tr.tl("Error: Couldn't check token customer_id")) if (customer_id := usr.get('customer_id')) is None: logger.error('No "usr"->"customer_id" in /decode data') # LANG: Frontier auth, no 'customer_id' in 'usr' section in returned data - raise CredentialsError(_("Error: Couldn't check token customer_id")) + raise CredentialsError(tr.tl("Error: Couldn't check token customer_id")) # All 'FID' seen in Journals so far have been 'F' # Frontier, Steam and Epic if f'F{customer_id}' != monitor.state.get('FID'): # LANG: Frontier auth customer_id doesn't match game session FID - raise CredentialsError(_("Error: customer_id doesn't match!")) + raise CredentialsError(tr.tl("Error: customer_id doesn't match!")) logger.info(f'Frontier CAPI Auth: New token for \"{self.cmdr}\"') cmdrs = config.get_list('cmdrs', default=[]) @@ -505,7 +504,7 @@ def authorize(self, payload: str) -> str: # noqa: CCR001 self.dump(r) # LANG: Failed to get Access Token from Frontier Auth service - raise CredentialsError(_('Error: unable to get token')) from e + raise CredentialsError(tr.tl('Error: unable to get token')) from e logger.error(f"Frontier CAPI Auth: Can't get token for \"{self.cmdr}\"") self.dump(r) @@ -514,7 +513,7 @@ def authorize(self, payload: str) -> str: # noqa: CCR001 '' ) # LANG: Generic error prefix - following text is from Frontier auth service - raise CredentialsError(f'{_("Error")}: {error!r}') + raise CredentialsError(f'{tr.tl("Error")}: {error!r}') @staticmethod def invalidate(cmdr: str | None) -> None: @@ -841,7 +840,7 @@ def capi_single_query( except Exception as e: logger.debug('Attempting GET', exc_info=e) # LANG: Frontier CAPI data retrieval failed - raise ServerError(f'{_("Frontier CAPI query failure")}: {capi_endpoint}') from e + raise ServerError(f'{tr.tl("Frontier CAPI query failure")}: {capi_endpoint}') from e if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data: logger.error('No commander in returned data') @@ -874,7 +873,7 @@ def handle_http_error(response: requests.Response, endpoint: str): if response.status_code == 418: # "I'm a teapot" - used to signal maintenance # LANG: Frontier CAPI returned 418, meaning down for maintenance - raise ServerError(_("Frontier CAPI down for maintenance")) + raise ServerError(tr.tl("Frontier CAPI down for maintenance")) logger.exception('Frontier CAPI: Misc. Error') raise ServerError('Frontier CAPI: Misc. Error') diff --git a/journal_lock.py b/journal_lock.py index 3a4dad52d..2aae20a69 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -13,17 +13,13 @@ from enum import Enum from os import getpid as os_getpid from tkinter import ttk -from typing import TYPE_CHECKING, Callable - +from typing import Callable +from l10n import translations as tr from config import config from EDMCLogging import get_main_logger logger = get_main_logger() -if TYPE_CHECKING: # pragma: no cover - def _(x: str) -> str: - return x - class JournalLockResult(Enum): """Enumeration of possible outcomes of trying to lock the Journal Directory.""" @@ -212,7 +208,7 @@ def __init__(self, parent: tk.Tk, callback: Callable) -> None: self.parent = parent self.callback = callback # LANG: Title text on popup when Journal directory already locked - self.title(_('Journal directory already locked')) + self.title(tr.tl('Journal directory already locked')) # remove decoration if sys.platform == 'win32': @@ -225,16 +221,17 @@ def __init__(self, parent: tk.Tk, callback: Callable) -> None: self.blurb = tk.Label(frame) # LANG: Text for when newly selected Journal directory is already locked - self.blurb['text'] = _("The new Journal Directory location is already locked.{CR}" - "You can either attempt to resolve this and then Retry, or choose to Ignore this.") + self.blurb['text'] = tr.tl("The new Journal Directory location is already locked.{CR}" + "You can either attempt to resolve this and then Retry, " + "or choose to Ignore this.") self.blurb.grid(row=1, column=0, columnspan=2, sticky=tk.NSEW) # LANG: Generic 'Retry' button label - self.retry_button = ttk.Button(frame, text=_('Retry'), command=self.retry) + self.retry_button = ttk.Button(frame, text=tr.tl('Retry'), command=self.retry) self.retry_button.grid(row=2, column=0, sticky=tk.EW) # LANG: Generic 'Ignore' button label - self.ignore_button = ttk.Button(frame, text=_('Ignore'), command=self.ignore) + self.ignore_button = ttk.Button(frame, text=tr.tl('Ignore'), command=self.ignore) self.ignore_button.grid(row=2, column=1, sticky=tk.EW) self.protocol("WM_DELETE_WINDOW", self._destroy) diff --git a/l10n.py b/l10n.py index 183d902a5..f3215062c 100755 --- a/l10n.py +++ b/l10n.py @@ -23,9 +23,6 @@ from config import config from EDMCLogging import get_main_logger -if TYPE_CHECKING: - def _(x: str) -> str: return x - # Note that this is also done in EDMarketConnector.py, and thus removing this here may not have a desired effect try: locale.setlocale(locale.LC_ALL, '') @@ -61,7 +58,16 @@ def _(x: str) -> str: return x GetNumberFormatEx.restype = ctypes.c_int -class _Translations: +class Translations: + """ + The Translation System. + + Contains all the logic needed to support multiple languages in EDMC. + DO NOT USE THIS DIRECTLY UNLESS YOU KNOW WHAT YOU'RE DOING. + In most cases, you'll want to import translations. + + For most cases: from l10n import translations as tr. + """ FALLBACK = 'en' # strings in this code are in English FALLBACK_NAME = 'English' @@ -79,6 +85,8 @@ def install_dummy(self) -> None: Use when translation is not desired or not available """ self.translations = {None: {}} + # WARNING: '_' is Deprecated. Will be removed in 6.0 or later. + # Migrate to calling Translations.translate directly. builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n') def install(self, lang: str | None = None) -> None: # noqa: CCR001 @@ -88,7 +96,7 @@ def install(self, lang: str | None = None) -> None: # noqa: CCR001 :param lang: The language to translate to, defaults to the preferred language """ available = self.available() - available.add(_Translations.FALLBACK) + available.add(Translations.FALLBACK) if not lang: # Choose the default language for preferred in Locale.preferred_languages(): @@ -122,6 +130,8 @@ def install(self, lang: str | None = None) -> None: # noqa: CCR001 except Exception: logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}') + # WARNING: '_' is Deprecated. Will be removed in 6.0 or later. + # Migrate to calling Translations.translate directly. builtins.__dict__['_'] = self.translate def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: @@ -135,12 +145,12 @@ def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: for line in h: if line.strip(): - match = _Translations.TRANS_RE.match(line) + match = Translations.TRANS_RE.match(line) if match: to_set = match.group(2).replace(r'\"', '"').replace('{CR}', '\n') translations[match.group(1).replace(r'\"', '"')] = to_set - elif not _Translations.COMMENT_RE.match(line): + elif not Translations.COMMENT_RE.match(line): logger.debug(f'Bad translation: {line.strip()}') h.close() @@ -149,6 +159,10 @@ def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: return translations + def tl(self, x: str, context: str | None = None) -> str: + """Use the shorthand Dummy loader for the translate function.""" + return self.translate(x, context) + def translate(self, x: str, context: str | None = None) -> str: """ Translate the given string to the current lang. @@ -182,11 +196,11 @@ def available_names(self) -> dict[str | None, str]: """Available language names by code.""" names: dict[str | None, str] = { # LANG: The system default language choice in Settings > Appearance - None: _('Default'), # Appearance theme and language setting + None: self.tl('Default'), # Appearance theme and language setting } names.update(sorted( [(lang, self.contents(lang).get(LANGUAGE_ID, lang)) for lang in self.available()] + - [(_Translations.FALLBACK, _Translations.FALLBACK_NAME)], + [(Translations.FALLBACK, Translations.FALLBACK_NAME)], key=lambda x: x[1] )) # Sort by name @@ -324,8 +338,21 @@ def preferred_languages(self) -> Iterable[str]: # singletons Locale = _Locale() -Translations = _Translations() +translations = Translations() + + +# WARNING: 'Translations' singleton is deprecated. Will be removed in 6.0 or later. +# Migrate to importing 'translations'. +# Begin Deprecation Zone +class _Translations(Translations): + def __init__(self): + logger.warning(DeprecationWarning('Translations and _Translations() are deprecated. ' + 'Please use translations and Translations() instead.')) + super().__init__() + +Translations: Translations = _Translations() # type: ignore # Yes, I know this is awful. But we need it for compat. +# End Deprecation Zone # generate template strings file - like xgettext # parsing is limited - only single ' or " delimited strings, and only one string per line diff --git a/monitor.py b/monitor.py index 02b7f91cc..f93473ba4 100644 --- a/monitor.py +++ b/monitor.py @@ -34,10 +34,6 @@ MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds MAX_FCMATERIALS_DISCREPANCY = 5 # Timestamp difference in seconds -if TYPE_CHECKING: - def _(x: str) -> str: - return x - if sys.platform == 'win32': import ctypes from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR diff --git a/plugins/coriolis.py b/plugins/coriolis.py index 07b4ac9f2..a9eabcedb 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -27,15 +27,11 @@ import json import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING import myNotebook as nb # noqa: N813 # its not my fault. from EDMCLogging import get_main_logger from plug import show_error from config import config - -if TYPE_CHECKING: - def _(s: str) -> str: - ... +from l10n import translations as tr class CoriolisConfig: @@ -45,15 +41,15 @@ def __init__(self): self.normal_url = '' self.beta_url = '' self.override_mode = '' - self.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto - self.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal - self.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta + self.override_text_old_auto = tr.tl('Auto') # LANG: Coriolis normal/beta selection - auto + self.override_text_old_normal = tr.tl('Normal') # LANG: Coriolis normal/beta selection - normal + self.override_text_old_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta self.normal_textvar = tk.StringVar() self.beta_textvar = tk.StringVar() self.override_textvar = tk.StringVar() - def initialize_urls(self): + def initialize_urls(self) -> None: """Initialize Coriolis URLs and override mode from configuration.""" self.normal_url = config.get_str('coriolis_normal_url', default=DEFAULT_NORMAL_URL) self.beta_url = config.get_str('coriolis_beta_url', default=DEFAULT_BETA_URL) @@ -63,10 +59,10 @@ def initialize_urls(self): self.beta_textvar.set(value=self.beta_url) self.override_textvar.set( value={ - 'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection - 'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection - 'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection - }.get(self.override_mode, _('Auto')) # LANG: 'Auto' label for Coriolis site override selection + 'auto': tr.tl('Auto'), # LANG: 'Auto' label for Coriolis site override selection + 'normal': tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection + 'beta': tr.tl('Beta') # LANG: 'Beta' label for Coriolis site override selection + }.get(self.override_mode, tr.tl('Auto')) # LANG: 'Auto' label for Coriolis site override selection ) @@ -91,38 +87,38 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr BOXY = 2 # noqa: N806 # box spacing # Save the old text values for the override mode, so we can update them if the language is changed - coriolis_config.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto - coriolis_config.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal - coriolis_config.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta + coriolis_config.override_text_old_auto = tr.tl('Auto') # LANG: Coriolis normal/beta selection - auto + coriolis_config.override_text_old_normal = tr.tl('Normal') # LANG: Coriolis normal/beta selection - normal + coriolis_config.override_text_old_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta conf_frame = nb.Frame(parent) conf_frame.columnconfigure(index=1, weight=1) cur_row = 0 # LANG: Settings>Coriolis: Help/hint for changing coriolis URLs - nb.Label(conf_frame, text=_( + nb.Label(conf_frame, text=tr.tl( "Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" )).grid(sticky=tk.EW, row=cur_row, column=0, padx=PADX, pady=PADY, columnspan=3) cur_row += 1 # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL - nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) + nb.Label(conf_frame, text=tr.tl('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) nb.EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - nb.Button(conf_frame, text=_("Reset"), + nb.Button(conf_frame, text=tr.tl("Reset"), command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) cur_row += 1 # LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL - nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) + nb.Label(conf_frame, text=tr.tl('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) nb.EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - nb.Button(conf_frame, text=_('Reset'), + nb.Button(conf_frame, text=tr.tl('Reset'), command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) @@ -130,16 +126,16 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr # TODO: This needs a help/hint text to be sure users know what it's for. # LANG: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL - nb.Label(conf_frame, text=_('Override Beta/Normal Selection')).grid( + nb.Label(conf_frame, text=tr.tl('Override Beta/Normal Selection')).grid( sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY ) nb.OptionMenu( conf_frame, coriolis_config.override_textvar, coriolis_config.override_textvar.get(), - _('Normal'), # LANG: 'Normal' label for Coriolis site override selection - _('Beta'), # LANG: 'Beta' label for Coriolis site override selection - _('Auto') # LANG: 'Auto' label for Coriolis site override selection + tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection + tr.tl('Beta'), # LANG: 'Beta' label for Coriolis site override selection + tr.tl('Auto') # LANG: 'Auto' label for Coriolis site override selection ).grid(sticky=tk.W, row=cur_row, column=1, padx=PADX, pady=BOXY) cur_row += 1 @@ -159,9 +155,9 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: # Convert to unlocalised names coriolis_config.override_mode = { - _('Normal'): 'normal', # LANG: Coriolis normal/beta selection - normal - _('Beta'): 'beta', # LANG: Coriolis normal/beta selection - beta - _('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto + tr.tl('Normal'): 'normal', # LANG: Coriolis normal/beta selection - normal + tr.tl('Beta'): 'beta', # LANG: Coriolis normal/beta selection - beta + tr.tl('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto }.get(coriolis_config.override_mode, coriolis_config.override_mode) # Check if the language was changed and the override_mode was valid before the change @@ -175,18 +171,19 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: if coriolis_config.override_mode in ('beta', 'normal', 'auto'): coriolis_config.override_textvar.set( value={ - 'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection - 'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection - 'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection + 'auto': tr.tl('Auto'), # LANG: 'Auto' label for Coriolis site override selection + 'normal': tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection + 'beta': tr.tl('Beta') # LANG: 'Beta' label for Coriolis site override selection # LANG: 'Auto' label for Coriolis site override selection - }.get(coriolis_config.override_mode, _('Auto')) + }.get(coriolis_config.override_mode, tr.tl('Auto')) ) # If the override mode is still invalid, default to auto if coriolis_config.override_mode not in ('beta', 'normal', 'auto'): logger.warning(f'Unexpected value {coriolis_config.override_mode=!r}. Defaulting to "auto"') coriolis_config.override_mode = 'auto' - coriolis_config.override_textvar.set(value=_('Auto')) # LANG: 'Auto' label for Coriolis site override selection + # LANG: 'Auto' label for Coriolis site override selection + coriolis_config.override_textvar.set(value=tr.tl('Auto')) config.set('coriolis_normal_url', coriolis_config.normal_url) config.set('coriolis_beta_url', coriolis_config.beta_url) @@ -196,7 +193,7 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: def _get_target_url(is_beta: bool) -> str: if coriolis_config.override_mode not in ('auto', 'normal', 'beta'): # LANG: Settings>Coriolis - invalid override mode found - show_error(_('Invalid Coriolis override mode!')) + show_error(tr.tl('Invalid Coriolis override mode!')) logger.warning(f'Unexpected override mode {coriolis_config.override_mode!r}! defaulting to auto!') coriolis_config.override_mode = 'auto' if coriolis_config.override_mode == 'beta': diff --git a/plugins/eddn.py b/plugins/eddn.py index 9504d1abc..2b2230363 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -31,13 +31,7 @@ from platform import system from textwrap import dedent from threading import Lock -from typing import ( - TYPE_CHECKING, - Any, - Iterator, - Mapping, - MutableMapping, -) +from typing import Any, Iterator, Mapping, MutableMapping import requests import companion import edmc_data @@ -52,10 +46,7 @@ from prefs import prefsVersion from ttkHyperlinkLabel import HyperlinkLabel from util import text - -if TYPE_CHECKING: - def _(x: str) -> str: - return x +from l10n import translations as tr logger = get_main_logger() @@ -441,7 +432,7 @@ def send_message(self, msg: str) -> bool: except requests.exceptions.RequestException as e: logger.debug('Failed sending', exc_info=e) # LANG: Error while trying to send data to EDDN - self.set_ui_status(_("Error: Can't connect to EDDN")) + self.set_ui_status(tr.tl("Error: Can't connect to EDDN")) except Exception as e: logger.debug('Failed sending', exc_info=e) @@ -566,17 +557,17 @@ def http_error_to_log(exception: requests.exceptions.HTTPError) -> str: if status_code == 429: # HTTP UPGRADE REQUIRED logger.warning('EDMC is sending schemas that are too old') # LANG: EDDN has banned this version of our client - return _('EDDN Error: EDMC is too old for EDDN. Please update.') + return tr.tl('EDDN Error: EDMC is too old for EDDN. Please update.') if status_code == 400: # we a validation check or something else. logger.warning(f'EDDN Error: {status_code} -- {exception.response}') # LANG: EDDN returned an error that indicates something about what we sent it was wrong - return _('EDDN Error: Validation Failed (EDMC Too Old?). See Log') + return tr.tl('EDDN Error: Validation Failed (EDMC Too Old?). See Log') logger.warning(f'Unknown status code from EDDN: {status_code} -- {exception.response}') # LANG: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number - return _('EDDN Error: Returned {STATUS} status code').format(STATUS=status_code) + return tr.tl('EDDN Error: Returned {STATUS} status code').format(STATUS=status_code) # TODO: a good few of these methods are static or could be classmethods. they should be created as such. @@ -2189,7 +2180,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: this.eddn_station_button = nb.Checkbutton( eddnframe, # LANG: Enable EDDN support for station data checkbox label - text=_('Send station data to the Elite Dangerous Data Network'), + text=tr.tl('Send station data to the Elite Dangerous Data Network'), variable=this.eddn_station, command=prefsvarchanged ) # Output setting @@ -2201,7 +2192,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: this.eddn_system_button = nb.Checkbutton( eddnframe, # LANG: Enable EDDN support for system and other scan data checkbox label - text=_('Send system and scan data to the Elite Dangerous Data Network'), + text=tr.tl('Send system and scan data to the Elite Dangerous Data Network'), variable=this.eddn_system, command=prefsvarchanged ) @@ -2213,7 +2204,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: this.eddn_delay_button = nb.Checkbutton( eddnframe, # LANG: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this - text=_('Delay sending until docked'), + text=tr.tl('Delay sending until docked'), variable=this.eddn_delay ) this.eddn_delay_button.grid(row=cur_row, padx=BUTTONX, pady=PADY, sticky=tk.W) @@ -2328,7 +2319,7 @@ def journal_entry( # noqa: C901, CCR001 """ should_return, new_data = killswitch.check_killswitch('plugins.eddn.journal', entry) if should_return: - plug.show_error(_('EDDN journal handler disabled. See Log.')) # LANG: Killswitch disabled EDDN + plug.show_error(tr.tl('EDDN journal handler disabled. See Log.')) # LANG: Killswitch disabled EDDN return None should_return, new_data = killswitch.check_killswitch(f'plugins.eddn.journal.event.{entry["event"]}', new_data) @@ -2530,7 +2521,7 @@ def journal_entry( # noqa: C901, CCR001 except requests.exceptions.RequestException as e: logger.debug('Failed in send_message', exc_info=e) - return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN + return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN except Exception as e: logger.debug('Failed in export_journal_generic', exc_info=e) @@ -2568,7 +2559,7 @@ def journal_entry( # noqa: C901, CCR001 except requests.exceptions.RequestException as e: logger.debug(f'Failed exporting {entry["event"]}', exc_info=e) - return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN + return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN except Exception as e: logger.debug(f'Failed exporting {entry["event"]}', exc_info=e) @@ -2622,7 +2613,8 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> str | None: # noqa: CCR001 status = this.parent.nametowidget(f".{appname.lower()}.status") old_status = status['text'] if not old_status: - status['text'] = _('Sending data to EDDN...') # LANG: Status text shown while attempting to send data + # LANG: Status text shown while attempting to send data + status['text'] = tr.tl('Sending data to EDDN...') status.update_idletasks() this.eddn.export_commodities(data, is_beta) @@ -2634,7 +2626,7 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> str | None: # noqa: CCR001 except requests.RequestException as e: logger.debug('Failed exporting data', exc_info=e) - return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN + return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN except Exception as e: logger.debug('Failed exporting data', exc_info=e) diff --git a/plugins/edsm.py b/plugins/edsm.py index b2a8035f5..5cd974d53 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -28,7 +28,7 @@ from threading import Thread from time import sleep from tkinter import ttk -from typing import TYPE_CHECKING, Any, Literal, Mapping, MutableMapping, cast, Sequence +from typing import Any, Literal, Mapping, MutableMapping, cast, Sequence import requests import killswitch import monitor @@ -39,10 +39,8 @@ from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel +from l10n import translations as tr -if TYPE_CHECKING: - def _(x: str) -> str: - return x # TODO: # 1) Re-factor EDSM API calls out of journal_entry() into own function. @@ -313,7 +311,8 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr this.log = tk.IntVar(value=config.get_int('edsm_out') and 1) this.log_button = nb.Checkbutton( frame, - text=_('Send flight log and CMDR status to EDSM'), # LANG: Settings>EDSM - Label on checkbox for 'send data' + # LANG: Settings>EDSM - Label on checkbox for 'send data' + text=tr.tl('Send flight log and CMDR status to EDSM'), variable=this.log, command=prefsvarchanged ) @@ -328,7 +327,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr this.label = HyperlinkLabel( frame, - text=_('Elite Dangerous Star Map credentials'), # LANG: Elite Dangerous Star Map credentials + text=tr.tl('Elite Dangerous Star Map credentials'), # LANG: Elite Dangerous Star Map credentials background=nb.Label().cget('background'), url='https://www.edsm.net/settings/api', underline=True @@ -336,21 +335,21 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr if this.label: this.label.grid(row=cur_row, columnspan=2, padx=PADX, pady=PADY, sticky=tk.W) cur_row += 1 - this.cmdr_label = nb.Label(frame, text=_('Cmdr')) # LANG: Game Commander name label in EDSM settings + this.cmdr_label = nb.Label(frame, text=tr.tl('Cmdr')) # LANG: Game Commander name label in EDSM settings this.cmdr_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) this.cmdr_text = nb.Label(frame) this.cmdr_text.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.W) cur_row += 1 # LANG: EDSM Commander name label in EDSM settings - this.user_label = nb.Label(frame, text=_('Commander Name')) + this.user_label = nb.Label(frame, text=tr.tl('Commander Name')) this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) this.user = nb.EntryMenu(frame) this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 # LANG: EDSM API key label - this.apikey_label = nb.Label(frame, text=_('API Key')) + this.apikey_label = nb.Label(frame, text=tr.tl('API Key')) this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) this.apikey = nb.EntryMenu(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) @@ -362,7 +361,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr show_password_checkbox = nb.Checkbutton( frame, - text=_('Show API Key'), # LANG: Text EDSM Show API Key + text=tr.tl('Show API Key'), # LANG: Text EDSM Show API Key variable=show_password_var, command=toggle_password_visibility ) @@ -398,7 +397,7 @@ def prefs_cmdr_changed(cmdr: str | None, is_beta: bool) -> None: # noqa: CCR001 else: if this.cmdr_text: # LANG: We have no data on the current commander - this.cmdr_text['text'] = _('None') + this.cmdr_text['text'] = tr.tl('None') to_set: Literal['normal'] | Literal['disabled'] = tk.DISABLED if cmdr and not is_beta and this.log and this.log.get(): @@ -519,7 +518,7 @@ def journal_entry( # noqa: C901, CCR001 should_return, new_entry = killswitch.check_killswitch('plugins.edsm.journal', entry, logger) if should_return: # LANG: EDSM plugin - Journal handling disabled by killswitch - plug.show_error(_('EDSM Handler disabled. See Log.')) + plug.show_error(tr.tl('EDSM Handler disabled. See Log.')) return '' should_return, new_entry = killswitch.check_killswitch( @@ -606,7 +605,7 @@ def journal_entry( # noqa: C901, CCR001 # LANG: The Inara API only accepts Live galaxy data, not Legacy galaxy data logger.info("EDSM only accepts Live galaxy data") this.legacy_galaxy_last_notified = datetime.now(timezone.utc) - return _("EDSM only accepts Live galaxy data") # LANG: EDSM - Only Live data + return tr.tl("EDSM only accepts Live galaxy data") # LANG: EDSM - Only Live data return '' @@ -778,7 +777,7 @@ def send_to_edsm( # noqa: CCR001 if msg_num // 100 == 2: logger.warning(f'EDSM\t{msg_num} {msg}\t{json.dumps(pending, separators=(",", ": "))}') # LANG: EDSM Plugin - Error message from EDSM API - plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg)) + plug.show_error(tr.tl('Error: EDSM {MSG}').format(MSG=msg)) else: if msg_num // 100 == 1: logger.trace_if('plugin.edsm.api', 'Overall OK') @@ -944,7 +943,7 @@ def worker() -> None: # noqa: CCR001 C901 else: # LANG: EDSM Plugin - Error connecting to EDSM API - plug.show_error(_("Error: Can't connect to EDSM")) + plug.show_error(tr.tl("Error: Can't connect to EDSM")) if entry['event'].lower() in ('shutdown', 'commander', 'fileheader'): # Game shutdown or new login, so we MUST not hang on to pending pending = [] @@ -1018,11 +1017,11 @@ def edsm_notify_system(reply: Mapping[str, Any]) -> None: if not reply: this.system_link['image'] = this._IMG_ERROR # LANG: EDSM Plugin - Error connecting to EDSM API - plug.show_error(_("Error: Can't connect to EDSM")) + plug.show_error(tr.tl("Error: Can't connect to EDSM")) elif reply['msgnum'] // 100 not in (1, 4): this.system_link['image'] = this._IMG_ERROR # LANG: EDSM Plugin - Error message from EDSM API - plug.show_error(_('Error: EDSM {MSG}').format(MSG=reply['msg'])) + plug.show_error(tr.tl('Error: EDSM {MSG}').format(MSG=reply['msg'])) elif reply.get('systemCreated'): this.system_link['image'] = this._IMG_NEW else: diff --git a/plugins/inara.py b/plugins/inara.py index 810f7523d..7c565ab3e 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -30,7 +30,7 @@ from operator import itemgetter from threading import Lock, Thread from tkinter import ttk -from typing import TYPE_CHECKING, Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union +from typing import Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union import requests import edmc_data import killswitch @@ -42,13 +42,10 @@ from EDMCLogging import get_main_logger from monitor import monitor from ttkHyperlinkLabel import HyperlinkLabel +from l10n import translations as tr logger = get_main_logger() -if TYPE_CHECKING: - def _(x: str) -> str: - return x - _TIMEOUT = 20 FAKE = ('CQC', 'Training', 'Destination') # Fake systems that shouldn't be sent to Inara @@ -264,7 +261,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: this.log = tk.IntVar(value=config.get_int('inara_out') and 1) this.log_button = nb.Checkbutton( frame, - text=_('Send flight log and Cmdr status to Inara'), # LANG: Checkbox to enable INARA API Usage + text=tr.tl('Send flight log and Cmdr status to Inara'), # LANG: Checkbox to enable INARA API Usage variable=this.log, command=prefsvarchanged ) @@ -280,7 +277,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: # Section heading in settings this.label = HyperlinkLabel( frame, - text=_('Inara credentials'), # LANG: Text for INARA API keys link ( goes to https://inara.cz/settings-api ) + text=tr.tl('Inara credentials'), # LANG: Text for INARA API keys link ( goes to https://inara.cz/settings-api ) background=nb.Label().cget('background'), url='https://inara.cz/settings-api', underline=True @@ -290,7 +287,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: cur_row += 1 # LANG: Inara API key label - this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting + this.apikey_label = nb.Label(frame, text=tr.tl('API Key')) # Inara setting this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) this.apikey = nb.EntryMenu(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) @@ -301,7 +298,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: show_password_var.set(False) # Password is initially masked show_password_checkbox = nb.Checkbutton( frame, - text=_('Show API Key'), # LANG: Text Inara Show API key + text=tr.tl('Show API Key'), # LANG: Text Inara Show API key variable=show_password_var, command=toggle_password_visibility, ) @@ -407,7 +404,7 @@ def journal_entry( # noqa: C901, CCR001 should_return, new_entry = killswitch.check_killswitch('plugins.inara.journal', entry, logger) if should_return: - plug.show_error(_('Inara disabled. See Log.')) # LANG: INARA support disabled via killswitch + plug.show_error(tr.tl('Inara disabled. See Log.')) # LANG: INARA support disabled via killswitch logger.trace('returning due to killswitch match') return '' @@ -432,9 +429,9 @@ def journal_entry( # noqa: C901, CCR001 and config.get_int('inara_out') and not (is_beta or this.multicrew or credentials(cmdr)) ): # LANG: The Inara API only accepts Live galaxy data, not Legacy galaxy data - logger.info(_("Inara only accepts Live galaxy data")) + logger.info(tr.tl("Inara only accepts Live galaxy data")) this.legacy_galaxy_last_notified = datetime.now(timezone.utc) - return _("Inara only accepts Live galaxy data") # LANG: Inara - Only Live data + return tr.tl("Inara only accepts Live galaxy data") # LANG: Inara - Only Live data return '' @@ -1642,7 +1639,7 @@ def handle_api_error(data: Mapping[str, Any], status: int, reply: dict[str, Any] logger.warning(f'Inara\t{status} {error_message}') logger.debug(f'JSON data:\n{json.dumps(data, indent=2, separators = (",", ": "))}') # LANG: INARA API returned some kind of error (error message will be contained in {MSG}) - plug.show_error(_('Error: Inara {MSG}').format(MSG=error_message)) + plug.show_error(tr.tl('Error: Inara {MSG}').format(MSG=error_message)) def handle_success_reply(data: Mapping[str, Any], reply: dict[str, Any]) -> None: @@ -1675,7 +1672,7 @@ def handle_individual_error(data_event: dict[str, Any], reply_status: int, reply if reply_status // 100 != 2: # LANG: INARA API returned some kind of error (error message will be contained in {MSG}) - plug.show_error(_('Error: Inara {MSG}').format( + plug.show_error(tr.tl('Error: Inara {MSG}').format( MSG=f'{data_event["eventName"]}, {reply_text}' )) diff --git a/prefs.py b/prefs.py index 285ef0d70..2d4c8d517 100644 --- a/prefs.py +++ b/prefs.py @@ -14,23 +14,19 @@ from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812 from tkinter import ttk from types import TracebackType -from typing import TYPE_CHECKING, Any, Callable, Optional, Type - +from typing import Any, Callable, Optional, Type import myNotebook as nb # noqa: N813 import plug from config import appversion_nobuild, config from EDMCLogging import edmclogger, get_main_logger from constants import appname from hotkey import hotkeymgr -from l10n import Translations +from l10n import translations as tr from monitor import monitor from theme import theme from ttkHyperlinkLabel import HyperlinkLabel logger = get_main_logger() -if TYPE_CHECKING: - def _(x: str) -> str: - return x # TODO: Decouple this from platform as far as possible @@ -224,7 +220,7 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): self.parent = parent self.callback = callback # LANG: File > Settings (macOS) - self.title(_('Settings')) + self.title(tr.tl('Settings')) if parent.winfo_viewable(): self.transient(parent) @@ -270,7 +266,7 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): buttonframe.columnconfigure(0, weight=1) ttk.Label(buttonframe).grid(row=0, column=0) # spacer # LANG: 'OK' button on Settings/Preferences window - button = ttk.Button(buttonframe, text=_('OK'), command=self.apply) + button = ttk.Button(buttonframe, text=tr.tl('OK'), command=self.apply) button.grid(row=0, column=1, sticky=tk.E) button.bind("", lambda event: self.apply()) self.protocol("WM_DELETE_WINDOW", self._destroy) @@ -313,13 +309,13 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: row = AutoInc(start=0) # LANG: Settings > Output - choosing what data to save to files - self.out_label = nb.Label(output_frame, text=_('Please choose what data to save')) + self.out_label = nb.Label(output_frame, text=tr.tl('Please choose what data to save')) self.out_label.grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) self.out_csv = tk.IntVar(value=1 if (output & config.OUT_MKT_CSV) else 0) self.out_csv_button = nb.Checkbutton( output_frame, - text=_('Market data in CSV format file'), # LANG: Settings > Output option + text=tr.tl('Market data in CSV format file'), # LANG: Settings > Output option variable=self.out_csv, command=self.outvarchanged ) @@ -328,7 +324,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: self.out_td = tk.IntVar(value=1 if (output & config.OUT_MKT_TD) else 0) self.out_td_button = nb.Checkbutton( output_frame, - text=_('Market data in Trade Dangerous format file'), # LANG: Settings > Output option + text=tr.tl('Market data in Trade Dangerous format file'), # LANG: Settings > Output option variable=self.out_td, command=self.outvarchanged ) @@ -338,7 +334,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: # Output setting self.out_ship_button = nb.Checkbutton( output_frame, - text=_('Ship loadout'), # LANG: Settings > Output option + text=tr.tl('Ship loadout'), # LANG: Settings > Output option variable=self.out_ship, command=self.outvarchanged ) @@ -348,7 +344,7 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: # Output setting self.out_auto_button = nb.Checkbutton( output_frame, - text=_('Automatically update on docking'), # LANG: Settings > Output option + text=tr.tl('Automatically update on docking'), # LANG: Settings > Output option variable=self.out_auto, command=self.outvarchanged ) @@ -357,14 +353,14 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: self.outdir = tk.StringVar() self.outdir.set(str(config.get_str('outdir'))) # LANG: Settings > Output - Label for "where files are located" - self.outdir_label = nb.Label(output_frame, text=_('File location')+':') # Section heading in settings + self.outdir_label = nb.Label(output_frame, text=tr.tl('File location')+':') # Section heading in settings # Type ignored due to incorrect type annotation. a 2 tuple does padding for each side self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) # type: ignore self.outdir_entry = ttk.Entry(output_frame, takefocus=False) self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - text = _('Browse...') # LANG: NOT-macOS Settings - files location selection button + text = tr.tl('Browse...') # LANG: NOT-macOS Settings - files location selection button self.outbutton = ttk.Button( output_frame, @@ -372,12 +368,12 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: # Technically this is different from the label in Settings > Output, as *this* is used # as the title of the popup folder selection window. # LANG: Settings > Output - Label for "where files are located" - command=lambda: self.filebrowse(_('File location'), self.outdir) + command=lambda: self.filebrowse(tr.tl('File location'), self.outdir) ) self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get()) # LANG: Label for 'Output' Settings/Preferences tab - root_notebook.add(output_frame, text=_('Output')) # Tab heading in settings + root_notebook.add(output_frame, text=tr.tl('Output')) # Tab heading in settings def __setup_plugin_tabs(self, notebook: ttk.Notebook) -> None: for plugin in plug.PLUGINS: @@ -403,19 +399,19 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 nb.Label( config_frame, # LANG: Settings > Configuration - Label for Journal files location - text=_('E:D journal file location')+':' + text=tr.tl('E:D journal file location')+':' ).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - text = _('Browse...') # LANG: NOT-macOS Setting - files location selection button + text = tr.tl('Browse...') # LANG: NOT-macOS Setting - files location selection button with row as cur_row: self.logbutton = ttk.Button( config_frame, text=text, # LANG: Settings > Configuration - Label for Journal files location - command=lambda: self.filebrowse(_('E:D journal file location'), self.logdir) + command=lambda: self.filebrowse(tr.tl('E:D journal file location'), self.logdir) ) self.logbutton.grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row) @@ -424,7 +420,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ttk.Button( config_frame, # LANG: Settings > Configuration - Label on 'reset journal files location to default' button - text=_('Default'), + text=tr.tl('Default'), command=self.logdir_reset, state=tk.NORMAL if config.get_str('journaldir') else tk.DISABLED ).grid(column=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row) @@ -438,13 +434,13 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 nb.Label( config_frame, - text=_('CAPI Settings') # LANG: Settings > Configuration - Label for CAPI section + text=tr.tl('CAPI Settings') # LANG: Settings > Configuration - Label for CAPI section ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) nb.Checkbutton( config_frame, # LANG: Configuration - Enable or disable the Fleet Carrier CAPI calls - text=_('Enable Fleetcarrier CAPI Queries'), + text=tr.tl('Enable Fleetcarrier CAPI Queries'), variable=self.capi_fleetcarrier ).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) @@ -460,7 +456,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 with row as cur_row: nb.Label( config_frame, - text=_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows + text=tr.tl('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) self.hotkey_text = ttk.Entry(config_frame, width=30, justify=tk.CENTER) @@ -469,7 +465,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # No hotkey/shortcut currently defined # TODO: display Only shows up on windows # LANG: No hotkey/shortcut set - hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None') + hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else tr.tl('None') ) self.hotkey_text.bind('', self.hotkeystart) @@ -480,7 +476,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.hotkey_only_btn = nb.Checkbutton( config_frame, # LANG: Configuration - Act on hotkey only when ED is in foreground - text=_('Only when Elite: Dangerous is the active app'), + text=tr.tl('Only when Elite: Dangerous is the active app'), variable=self.hotkey_only, state=tk.NORMAL if self.hotkey_code else tk.DISABLED ) @@ -491,7 +487,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.hotkey_play_btn = nb.Checkbutton( config_frame, # LANG: Configuration - play sound when hotkey used - text=_('Play sound'), + text=tr.tl('Play sound'), variable=self.hotkey_play, state=tk.NORMAL if self.hotkey_code else tk.DISABLED ) @@ -506,7 +502,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.disable_autoappupdatecheckingame_btn = nb.Checkbutton( config_frame, # LANG: Configuration - disable checks for app updates when in-game - text=_('Disable Automatic Application Updates Check when in-game'), + text=tr.tl('Disable Automatic Application Updates Check when in-game'), variable=self.disable_autoappupdatecheckingame, command=self.disable_autoappupdatecheckingame_changed ) @@ -521,7 +517,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # Settings prompt for preferred ship loadout, system and station info websites # LANG: Label for preferred shipyard, system and station 'providers' - nb.Label(config_frame, text=_('Preferred websites')).grid( + nb.Label(config_frame, text=tr.tl('Preferred websites')).grid( columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -532,7 +528,9 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) # Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis # LANG: Label for Shipyard provider selection - nb.Label(config_frame, text=_('Shipyard')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) + nb.Label(config_frame, text=tr.tl('Shipyard')).grid( + padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row + ) self.shipyard_button = nb.OptionMenu( config_frame, self.shipyard_provider, self.shipyard_provider.get(), *plug.provides('shipyard_url') ) @@ -544,7 +542,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.alt_shipyard_open_btn = nb.Checkbutton( config_frame, # LANG: Label for checkbox to utilise alternative Coriolis URL method - text=_('Use alternate URL method'), + text=tr.tl('Use alternate URL method'), variable=self.alt_shipyard_open, command=self.alt_shipyard_open_changed, ) @@ -558,7 +556,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) # LANG: Configuration - Label for selection of 'System' provider website - nb.Label(config_frame, text=_('System')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) + nb.Label(config_frame, text=tr.tl('System')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) self.system_button = nb.OptionMenu( config_frame, self.system_provider, @@ -576,7 +574,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) # LANG: Configuration - Label for selection of 'Station' provider website - nb.Label(config_frame, text=_('Station')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) + nb.Label(config_frame, text=tr.tl('Station')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) self.station_button = nb.OptionMenu( config_frame, self.station_provider, @@ -597,7 +595,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 nb.Label( config_frame, # LANG: Configuration - Label for selection of Log Level - text=_('Log Level') + text=tr.tl('Log Level') ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) current_loglevel = config.get_str('loglevel') @@ -624,7 +622,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ttk.Button( config_frame, # LANG: Label on button used to open a filesystem folder - text=_('Open Log Folder'), # Button that opens a folder in Explorer/Finder + text=tr.tl('Open Log Folder'), # Button that opens a folder in Explorer/Finder command=lambda: help_open_log_folder() ).grid(column=2, padx=self.PADX, pady=0, sticky=tk.NSEW, row=cur_row) @@ -632,7 +630,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 nb.Label(config_frame).grid(sticky=tk.W, row=row.get()) # LANG: Label for 'Configuration' tab in Settings - notebook.add(config_frame, text=_('Configuration')) + notebook.add(config_frame, text=tr.tl('Configuration')) def __setup_privacy_tab(self, notebook: ttk.Notebook) -> None: privacy_frame = nb.Frame(notebook) @@ -641,37 +639,37 @@ def __setup_privacy_tab(self, notebook: ttk.Notebook) -> None: row = AutoInc(start=0) # LANG: UI elements privacy section header in privacy tab of preferences - nb.Label(privacy_frame, text=_('Main UI privacy options')).grid( + nb.Label(privacy_frame, text=tr.tl('Main UI privacy options')).grid( row=row.get(), column=0, sticky=tk.W, padx=self.PADX, pady=self.PADY ) nb.Checkbutton( # LANG: Hide private group owner name from UI checkbox - privacy_frame, text=_('Hide private group name in UI'), + privacy_frame, text=tr.tl('Hide private group name in UI'), variable=self.hide_private_group ).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W) nb.Checkbutton( # LANG: Hide multicrew captain name from main UI checkbox - privacy_frame, text=_('Hide multi-crew captain name'), + privacy_frame, text=tr.tl('Hide multi-crew captain name'), variable=self.hide_multicrew_captain ).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W) - notebook.add(privacy_frame, text=_('Privacy')) # LANG: Preferences privacy tab title + notebook.add(privacy_frame, text=tr.tl('Privacy')) # LANG: Preferences privacy tab title def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: - self.languages = Translations.available_names() + self.languages = tr.available_names() # Appearance theme and language setting # LANG: The system default language choice in Settings > Appearance - self.lang = tk.StringVar(value=self.languages.get(config.get_str('language'), _('Default'))) + self.lang = tk.StringVar(value=self.languages.get(config.get_str('language'), tr.tl('Default'))) self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop'))) self.minimize_system_tray = tk.BooleanVar(value=config.get_bool('minimize_system_tray')) self.theme = tk.IntVar(value=config.get_int('theme')) self.theme_colors = [config.get_str('dark_text'), config.get_str('dark_highlight')] self.theme_prompts = [ # LANG: Label for Settings > Appeareance > selection of 'normal' text colour - _('Normal text'), # Dark theme color setting + tr.tl('Normal text'), # Dark theme color setting # LANG: Label for Settings > Appeareance > selection of 'highlightes' text colour - _('Highlighted text'), # Dark theme color setting + tr.tl('Highlighted text'), # Dark theme color setting ] row = AutoInc(start=0) @@ -680,7 +678,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: appearance_frame.columnconfigure(2, weight=1) with row as cur_row: # LANG: Appearance - Label for selection of application display language - nb.Label(appearance_frame, text=_('Language')).grid( + nb.Label(appearance_frame, text=tr.tl('Language')).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) self.lang_button = nb.OptionMenu(appearance_frame, self.lang, self.lang.get(), *self.languages.values()) @@ -692,28 +690,29 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: # Appearance setting # LANG: Label for Settings > Appearance > Theme selection - nb.Label(appearance_frame, text=_('Theme')).grid( + nb.Label(appearance_frame, text=tr.tl('Theme')).grid( columnspan=3, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) # Appearance theme and language setting nb.Radiobutton( # LANG: Label for 'Default' theme radio button - appearance_frame, text=_('Default'), variable=self.theme, + appearance_frame, text=tr.tl('Default'), variable=self.theme, value=theme.THEME_DEFAULT, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Appearance theme setting nb.Radiobutton( # LANG: Label for 'Dark' theme radio button - appearance_frame, text=_('Dark'), variable=self.theme, value=theme.THEME_DARK, command=self.themevarchanged + appearance_frame, text=tr.tl('Dark'), variable=self.theme, + value=theme.THEME_DARK, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) if sys.platform == 'win32': nb.Radiobutton( appearance_frame, # LANG: Label for 'Transparent' theme radio button - text=_('Transparent'), # Appearance theme setting + text=tr.tl('Transparent'), # Appearance theme setting variable=self.theme, value=theme.THEME_TRANSPARENT, command=self.themevarchanged @@ -727,7 +726,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: self.theme_button_0 = tk.Button( appearance_frame, # LANG: Appearance - Example 'Normal' text - text=_('Station'), + text=tr.tl('Station'), background='grey4', command=lambda: self.themecolorbrowse(0) ) @@ -759,7 +758,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: ) with row as cur_row: # LANG: Appearance - Label for selection of UI scaling - nb.Label(appearance_frame, text=_('UI Scale Percentage')).grid( + nb.Label(appearance_frame, text=tr.tl('UI Scale Percentage')).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) @@ -780,7 +779,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: self.ui_scaling_defaultis = nb.Label( appearance_frame, # LANG: Appearance - Help/hint text for UI scaling selection - text=_('100 means Default{CR}Restart Required for{CR}changes to take effect!') + text=tr.tl('100 means Default{CR}Restart Required for{CR}changes to take effect!') ).grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.E, row=cur_row) # Transparency slider @@ -790,7 +789,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: with row as cur_row: # LANG: Appearance - Label for selection of main window transparency - nb.Label(appearance_frame, text=_("Main window transparency")).grid( + nb.Label(appearance_frame, text=tr.tl("Main window transparency")).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) self.transparency = tk.IntVar() @@ -810,7 +809,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: nb.Label( appearance_frame, # LANG: Appearance - Help/hint text for Main window transparency selection - text=_( + text=tr.tl( "100 means fully opaque.{CR}" "Window is updated in real time" ).format(CR='\n') @@ -832,7 +831,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: self.ontop_button = nb.Checkbutton( appearance_frame, # LANG: Appearance - Label for checkbox to select if application always on top - text=_('Always on top'), + text=tr.tl('Always on top'), variable=self.always_ontop, command=self.themevarchanged ) @@ -844,7 +843,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: nb.Checkbutton( appearance_frame, # LANG: Appearance option for Windows "minimize to system tray" - text=_('Minimize to system tray'), + text=tr.tl('Minimize to system tray'), variable=self.minimize_system_tray, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Appearance setting @@ -852,7 +851,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: nb.Label(appearance_frame).grid(sticky=tk.W) # big spacer # LANG: Label for Settings > Appearance tab - notebook.add(appearance_frame, text=_('Appearance')) # Tab heading in settings + notebook.add(appearance_frame, text=tr.tl('Appearance')) # Tab heading in settings def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # Plugin settings and info @@ -864,7 +863,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # Section heading in settings # LANG: Label for location of third-party plugins folder - nb.Label(plugins_frame, text=_('Plugins folder') + ':').grid( + nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -877,13 +876,14 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 plugins_frame, # Help text in settings # LANG: Tip/label about how to disable plugins - text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled') + text=tr.tl( + "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled') ).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row) ttk.Button( plugins_frame, # LANG: Label on button used to open a filesystem folder - text=_('Open'), # Button that opens a folder in Explorer/Finder + text=tr.tl('Open'), # Button that opens a folder in Explorer/Finder command=lambda: webbrowser.open(f'file:///{config.plugin_dir_path}') ).grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.N, row=cur_row) @@ -895,7 +895,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 nb.Label( plugins_frame, # LANG: Label on list of enabled plugins - text=_('Enabled Plugins')+':' # List of plugins in settings + text=tr.tl('Enabled Plugins')+':' # List of plugins in settings ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) for plugin in enabled_plugins: @@ -915,13 +915,13 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) # LANG: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x - nb.Label(plugins_frame, text=_('Plugins Without Python 3.x Support')+':').grid( + nb.Label(plugins_frame, text=tr.tl('Plugins Without Python 3.x Support')+':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) HyperlinkLabel( # LANG: Plugins - Label on URL to documentation about migrating plugins from Python 2.7 - plugins_frame, text=_('Information on migrating plugins'), + plugins_frame, text=tr.tl('Information on migrating plugins'), background=nb.Label().cget('background'), url='https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#migration-from-python-27', underline=True @@ -943,7 +943,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 nb.Label( plugins_frame, # LANG: Label on list of user-disabled plugins - text=_('Disabled Plugins')+':' # List of plugins in settings + text=tr.tl('Disabled Plugins')+':' # List of plugins in settings ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) for plugin in disabled_plugins: @@ -958,7 +958,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) # LANG: Plugins - Label for list of 'broken' plugins that failed to load - nb.Label(plugins_frame, text=_('Broken Plugins')+':').grid( + nb.Label(plugins_frame, text=tr.tl('Broken Plugins')+':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -969,7 +969,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) # LANG: Label on Settings > Plugins tab - notebook.add(plugins_frame, text=_('Plugins')) # Tab heading in settings + notebook.add(plugins_frame, text=tr.tl('Plugins')) # Tab heading in settings def cmdrchanged(self, event=None): """ @@ -1122,7 +1122,7 @@ def hotkeyend(self, event: 'tk.Event[Any]') -> None: self.hotkey_text.insert( 0, # LANG: No hotkey/shortcut set - hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')) + hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else tr.tl('None')) def hotkeylisten(self, event: 'tk.Event[Any]') -> str: """ @@ -1155,7 +1155,7 @@ def hotkeylisten(self, event: 'tk.Event[Any]') -> str: else: # LANG: No hotkey/shortcut set - event.widget.insert(0, _('None')) + event.widget.insert(0, tr.tl('None')) self.hotkey_only_btn['state'] = tk.DISABLED self.hotkey_play_btn['state'] = tk.DISABLED @@ -1205,7 +1205,7 @@ def apply(self) -> None: lang_codes = {v: k for k, v in self.languages.items()} # Codes by name config.set('language', lang_codes.get(self.lang.get()) or '') # or '' used here due to Default being None above - Translations.install(config.get_str('language', default=None)) # type: ignore # This sets self in weird ways. + tr.install(config.get_str('language', default=None)) # type: ignore # This sets self in weird ways. # Privacy options config.set('hide_private_group', self.hide_private_group.get()) diff --git a/stats.py b/stats.py index c377e5d3a..db61f7892 100644 --- a/stats.py +++ b/stats.py @@ -12,20 +12,17 @@ import sys import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Any, AnyStr, Callable, NamedTuple, Sequence, cast +from typing import Any, AnyStr, Callable, NamedTuple, Sequence, cast import companion import EDMCLogging import myNotebook as nb # noqa: N813 from edmc_data import ship_name_map from hotkey import hotkeymgr -from l10n import Locale +from l10n import Locale, translations as tr from monitor import monitor logger = EDMCLogging.get_main_logger() -if TYPE_CHECKING: - def _(x: str) -> str: return x - if sys.platform == 'win32': import ctypes from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT @@ -60,32 +57,32 @@ def status(data: dict[str, Any]) -> list[list[str]]: """ # StatsResults assumes these three things are first res = [ - [_('Cmdr'), data['commander']['name']], # LANG: Cmdr stats - [_('Balance'), str(data['commander'].get('credits', 0))], # LANG: Cmdr stats - [_('Loan'), str(data['commander'].get('debt', 0))], # LANG: Cmdr stats + [tr.tl('Cmdr'), data['commander']['name']], # LANG: Cmdr stats + [tr.tl('Balance'), str(data['commander'].get('credits', 0))], # LANG: Cmdr stats + [tr.tl('Loan'), str(data['commander'].get('debt', 0))], # LANG: Cmdr stats ] _ELITE_RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime - _('Elite'), # LANG: Top rank - _('Elite I'), # LANG: Top rank +1 - _('Elite II'), # LANG: Top rank +2 - _('Elite III'), # LANG: Top rank +3 - _('Elite IV'), # LANG: Top rank +4 - _('Elite V'), # LANG: Top rank +5 + tr.tl('Elite'), # LANG: Top rank + tr.tl('Elite I'), # LANG: Top rank +1 + tr.tl('Elite II'), # LANG: Top rank +2 + tr.tl('Elite III'), # LANG: Top rank +3 + tr.tl('Elite IV'), # LANG: Top rank +4 + tr.tl('Elite V'), # LANG: Top rank +5 ] RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime # in output order # Names we show people, vs internal names - (_('Combat'), 'combat'), # LANG: Ranking - (_('Trade'), 'trade'), # LANG: Ranking - (_('Explorer'), 'explore'), # LANG: Ranking - (_('Mercenary'), 'soldier'), # LANG: Ranking - (_('Exobiologist'), 'exobiologist'), # LANG: Ranking - (_('CQC'), 'cqc'), # LANG: Ranking - (_('Federation'), 'federation'), # LANG: Ranking - (_('Empire'), 'empire'), # LANG: Ranking - (_('Powerplay'), 'power'), # LANG: Ranking + (tr.tl('Combat'), 'combat'), # LANG: Ranking + (tr.tl('Trade'), 'trade'), # LANG: Ranking + (tr.tl('Explorer'), 'explore'), # LANG: Ranking + (tr.tl('Mercenary'), 'soldier'), # LANG: Ranking + (tr.tl('Exobiologist'), 'exobiologist'), # LANG: Ranking + (tr.tl('CQC'), 'cqc'), # LANG: Ranking + (tr.tl('Federation'), 'federation'), # LANG: Ranking + (tr.tl('Empire'), 'empire'), # LANG: Ranking + (tr.tl('Powerplay'), 'power'), # LANG: Ranking # ??? , 'crime'), # LANG: Ranking # ??? , 'service'), # LANG: Ranking ] @@ -94,113 +91,113 @@ def status(data: dict[str, Any]) -> list[list[str]]: # These names are the fdev side name (but lower()ed) # http://elite-dangerous.wikia.com/wiki/Pilots_Federation#Ranks 'combat': [ - _('Harmless'), # LANG: Combat rank - _('Mostly Harmless'), # LANG: Combat rank - _('Novice'), # LANG: Combat rank - _('Competent'), # LANG: Combat rank - _('Expert'), # LANG: Combat rank - _('Master'), # LANG: Combat rank - _('Dangerous'), # LANG: Combat rank - _('Deadly'), # LANG: Combat rank + tr.tl('Harmless'), # LANG: Combat rank + tr.tl('Mostly Harmless'), # LANG: Combat rank + tr.tl('Novice'), # LANG: Combat rank + tr.tl('Competent'), # LANG: Combat rank + tr.tl('Expert'), # LANG: Combat rank + tr.tl('Master'), # LANG: Combat rank + tr.tl('Dangerous'), # LANG: Combat rank + tr.tl('Deadly'), # LANG: Combat rank ] + _ELITE_RANKS, 'trade': [ - _('Penniless'), # LANG: Trade rank - _('Mostly Penniless'), # LANG: Trade rank - _('Peddler'), # LANG: Trade rank - _('Dealer'), # LANG: Trade rank - _('Merchant'), # LANG: Trade rank - _('Broker'), # LANG: Trade rank - _('Entrepreneur'), # LANG: Trade rank - _('Tycoon'), # LANG: Trade rank + tr.tl('Penniless'), # LANG: Trade rank + tr.tl('Mostly Penniless'), # LANG: Trade rank + tr.tl('Peddler'), # LANG: Trade rank + tr.tl('Dealer'), # LANG: Trade rank + tr.tl('Merchant'), # LANG: Trade rank + tr.tl('Broker'), # LANG: Trade rank + tr.tl('Entrepreneur'), # LANG: Trade rank + tr.tl('Tycoon'), # LANG: Trade rank ] + _ELITE_RANKS, 'explore': [ - _('Aimless'), # LANG: Explorer rank - _('Mostly Aimless'), # LANG: Explorer rank - _('Scout'), # LANG: Explorer rank - _('Surveyor'), # LANG: Explorer rank - _('Trailblazer'), # LANG: Explorer rank - _('Pathfinder'), # LANG: Explorer rank - _('Ranger'), # LANG: Explorer rank - _('Pioneer'), # LANG: Explorer rank + tr.tl('Aimless'), # LANG: Explorer rank + tr.tl('Mostly Aimless'), # LANG: Explorer rank + tr.tl('Scout'), # LANG: Explorer rank + tr.tl('Surveyor'), # LANG: Explorer rank + tr.tl('Trailblazer'), # LANG: Explorer rank + tr.tl('Pathfinder'), # LANG: Explorer rank + tr.tl('Ranger'), # LANG: Explorer rank + tr.tl('Pioneer'), # LANG: Explorer rank ] + _ELITE_RANKS, 'soldier': [ - _('Defenceless'), # LANG: Mercenary rank - _('Mostly Defenceless'), # LANG: Mercenary rank - _('Rookie'), # LANG: Mercenary rank - _('Soldier'), # LANG: Mercenary rank - _('Gunslinger'), # LANG: Mercenary rank - _('Warrior'), # LANG: Mercenary rank - _('Gunslinger'), # LANG: Mercenary rank - _('Deadeye'), # LANG: Mercenary rank + tr.tl('Defenceless'), # LANG: Mercenary rank + tr.tl('Mostly Defenceless'), # LANG: Mercenary rank + tr.tl('Rookie'), # LANG: Mercenary rank + tr.tl('Soldier'), # LANG: Mercenary rank + tr.tl('Gunslinger'), # LANG: Mercenary rank + tr.tl('Warrior'), # LANG: Mercenary rank + tr.tl('Gunslinger'), # LANG: Mercenary rank + tr.tl('Deadeye'), # LANG: Mercenary rank ] + _ELITE_RANKS, 'exobiologist': [ - _('Directionless'), # LANG: Exobiologist rank - _('Mostly Directionless'), # LANG: Exobiologist rank - _('Compiler'), # LANG: Exobiologist rank - _('Collector'), # LANG: Exobiologist rank - _('Cataloguer'), # LANG: Exobiologist rank - _('Taxonomist'), # LANG: Exobiologist rank - _('Ecologist'), # LANG: Exobiologist rank - _('Geneticist'), # LANG: Exobiologist rank + tr.tl('Directionless'), # LANG: Exobiologist rank + tr.tl('Mostly Directionless'), # LANG: Exobiologist rank + tr.tl('Compiler'), # LANG: Exobiologist rank + tr.tl('Collector'), # LANG: Exobiologist rank + tr.tl('Cataloguer'), # LANG: Exobiologist rank + tr.tl('Taxonomist'), # LANG: Exobiologist rank + tr.tl('Ecologist'), # LANG: Exobiologist rank + tr.tl('Geneticist'), # LANG: Exobiologist rank ] + _ELITE_RANKS, 'cqc': [ - _('Helpless'), # LANG: CQC rank - _('Mostly Helpless'), # LANG: CQC rank - _('Amateur'), # LANG: CQC rank - _('Semi Professional'), # LANG: CQC rank - _('Professional'), # LANG: CQC rank - _('Champion'), # LANG: CQC rank - _('Hero'), # LANG: CQC rank - _('Gladiator'), # LANG: CQC rank + tr.tl('Helpless'), # LANG: CQC rank + tr.tl('Mostly Helpless'), # LANG: CQC rank + tr.tl('Amateur'), # LANG: CQC rank + tr.tl('Semi Professional'), # LANG: CQC rank + tr.tl('Professional'), # LANG: CQC rank + tr.tl('Champion'), # LANG: CQC rank + tr.tl('Hero'), # LANG: CQC rank + tr.tl('Gladiator'), # LANG: CQC rank ] + _ELITE_RANKS, # http://elite-dangerous.wikia.com/wiki/Federation#Ranks 'federation': [ - _('None'), # LANG: No rank - _('Recruit'), # LANG: Federation rank - _('Cadet'), # LANG: Federation rank - _('Midshipman'), # LANG: Federation rank - _('Petty Officer'), # LANG: Federation rank - _('Chief Petty Officer'), # LANG: Federation rank - _('Warrant Officer'), # LANG: Federation rank - _('Ensign'), # LANG: Federation rank - _('Lieutenant'), # LANG: Federation rank - _('Lieutenant Commander'), # LANG: Federation rank - _('Post Commander'), # LANG: Federation rank - _('Post Captain'), # LANG: Federation rank - _('Rear Admiral'), # LANG: Federation rank - _('Vice Admiral'), # LANG: Federation rank - _('Admiral') # LANG: Federation rank + tr.tl('None'), # LANG: No rank + tr.tl('Recruit'), # LANG: Federation rank + tr.tl('Cadet'), # LANG: Federation rank + tr.tl('Midshipman'), # LANG: Federation rank + tr.tl('Petty Officer'), # LANG: Federation rank + tr.tl('Chief Petty Officer'), # LANG: Federation rank + tr.tl('Warrant Officer'), # LANG: Federation rank + tr.tl('Ensign'), # LANG: Federation rank + tr.tl('Lieutenant'), # LANG: Federation rank + tr.tl('Lieutenant Commander'), # LANG: Federation rank + tr.tl('Post Commander'), # LANG: Federation rank + tr.tl('Post Captain'), # LANG: Federation rank + tr.tl('Rear Admiral'), # LANG: Federation rank + tr.tl('Vice Admiral'), # LANG: Federation rank + tr.tl('Admiral') # LANG: Federation rank ], # http://elite-dangerous.wikia.com/wiki/Empire#Ranks 'empire': [ - _('None'), # LANG: No rank - _('Outsider'), # LANG: Empire rank - _('Serf'), # LANG: Empire rank - _('Master'), # LANG: Empire rank - _('Squire'), # LANG: Empire rank - _('Knight'), # LANG: Empire rank - _('Lord'), # LANG: Empire rank - _('Baron'), # LANG: Empire rank - _('Viscount'), # LANG: Empire rank - _('Count'), # LANG: Empire rank - _('Earl'), # LANG: Empire rank - _('Marquis'), # LANG: Empire rank - _('Duke'), # LANG: Empire rank - _('Prince'), # LANG: Empire rank - _('King') # LANG: Empire rank + tr.tl('None'), # LANG: No rank + tr.tl('Outsider'), # LANG: Empire rank + tr.tl('Serf'), # LANG: Empire rank + tr.tl('Master'), # LANG: Empire rank + tr.tl('Squire'), # LANG: Empire rank + tr.tl('Knight'), # LANG: Empire rank + tr.tl('Lord'), # LANG: Empire rank + tr.tl('Baron'), # LANG: Empire rank + tr.tl('Viscount'), # LANG: Empire rank + tr.tl('Count'), # LANG: Empire rank + tr.tl('Earl'), # LANG: Empire rank + tr.tl('Marquis'), # LANG: Empire rank + tr.tl('Duke'), # LANG: Empire rank + tr.tl('Prince'), # LANG: Empire rank + tr.tl('King') # LANG: Empire rank ], # http://elite-dangerous.wikia.com/wiki/Ratings 'power': [ - _('None'), # LANG: No rank - _('Rating 1'), # LANG: Power rank - _('Rating 2'), # LANG: Power rank - _('Rating 3'), # LANG: Power rank - _('Rating 4'), # LANG: Power rank - _('Rating 5') # LANG: Power rank + tr.tl('None'), # LANG: No rank + tr.tl('Rating 1'), # LANG: Power rank + tr.tl('Rating 2'), # LANG: Power rank + tr.tl('Rating 3'), # LANG: Power rank + tr.tl('Rating 4'), # LANG: Power rank + tr.tl('Rating 5') # LANG: Power rank ], } @@ -212,7 +209,7 @@ def status(data: dict[str, Any]) -> list[list[str]]: res.append([title, names[rank] if rank < len(names) else f'Rank {rank}']) else: - res.append([title, _('None')]) # LANG: No rank + res.append([title, tr.tl('None')]) # LANG: No rank return res @@ -318,7 +315,7 @@ def showstats(self) -> None: if not monitor.cmdr: hotkeymgr.play_bad() # LANG: Current commander unknown when trying to use 'File' > 'Status' - self.status['text'] = _("Status: Don't yet know your Commander name") + self.status['text'] = tr.tl("Status: Don't yet know your Commander name") return # TODO: This needs to use cached data @@ -326,7 +323,7 @@ def showstats(self) -> None: logger.info('No cached data, aborting...') hotkeymgr.play_bad() # LANG: No Frontier CAPI data yet when trying to use 'File' > 'Status' - self.status['text'] = _("Status: No CAPI data yet") + self.status['text'] = tr.tl("Status: No CAPI data yet") return capi_data = json.loads( @@ -336,7 +333,7 @@ def showstats(self) -> None: if not capi_data.get('commander') or not capi_data['commander'].get('name', '').strip(): # Shouldn't happen # LANG: Unknown commander - self.status['text'] = _("Who are you?!") + self.status['text'] = tr.tl("Who are you?!") elif ( not capi_data.get('lastSystem') @@ -344,7 +341,7 @@ def showstats(self) -> None: ): # Shouldn't happen # LANG: Unknown location - self.status['text'] = _("Where are you?!") + self.status['text'] = tr.tl("Where are you?!") elif ( not capi_data.get('ship') or not capi_data['ship'].get('modules') @@ -352,7 +349,7 @@ def showstats(self) -> None: ): # Shouldn't happen # LANG: Unknown ship - self.status['text'] = _("What are you flying?!") + self.status['text'] = tr.tl("What are you flying?!") else: self.status['text'] = '' @@ -401,14 +398,14 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: self.addpagerow(page, thing, with_copy=True) ttk.Frame(page).grid(pady=5) # bottom spacer - notebook.add(page, text=_('Status')) # LANG: Status dialog title + notebook.add(page, text=tr.tl('Status')) # LANG: Status dialog title page = self.addpage(notebook, [ - _('Ship'), # LANG: Status dialog subtitle + tr.tl('Ship'), # LANG: Status dialog subtitle '', - _('System'), # LANG: Main window - _('Station'), # LANG: Status dialog subtitle - _('Value'), # LANG: Status dialog subtitle - CR value of ship + tr.tl('System'), # LANG: Main window + tr.tl('Station'), # LANG: Status dialog subtitle + tr.tl('Value'), # LANG: Status dialog subtitle - CR value of ship ]) shiplist = ships(data) @@ -417,7 +414,7 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: self.addpagerow(page, list(ship_data[1:-1]) + [self.credits(int(ship_data[-1]))], with_copy=True) ttk.Frame(page).grid(pady=5) # bottom spacer - notebook.add(page, text=_('Ships')) # LANG: Status dialog title + notebook.add(page, text=tr.tl('Ships')) # LANG: Status dialog title # wait for window to appear on screen before calling grab_set self.wait_visibility() diff --git a/theme.py b/theme.py index bbe62ef5f..94e99f7a6 100644 --- a/theme.py +++ b/theme.py @@ -16,17 +16,14 @@ from os.path import join from tkinter import font as tk_font from tkinter import ttk -from typing import TYPE_CHECKING, Callable - +from typing import Callable +from l10n import translations as tr from config import config from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel logger = get_main_logger() -if TYPE_CHECKING: - def _(x: str) -> str: ... - if __debug__: from traceback import print_exc @@ -291,7 +288,7 @@ def _colors(self, root: tk.Tk, theme: int) -> None: # Font only supports Latin 1 / Supplement / Extended, and a # few General Punctuation and Mathematical Operators # LANG: Label for commander name in main window - 'font': (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and + 'font': (theme > 1 and not 0x250 < ord(tr.tl('Cmdr')[0]) < 0x3000 and tk_font.Font(family='Euro Caps', size=10, weight=tk_font.NORMAL) or 'TkDefaultFont'), } diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 9bb3b9bff..d026f619d 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -25,10 +25,8 @@ import webbrowser from tkinter import font as tk_font from tkinter import ttk -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - def _(x: str) -> str: return x +from typing import Any +from l10n import translations as tr class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore @@ -55,7 +53,7 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non self.menu = tk.Menu(tearoff=tk.FALSE) # LANG: Label for 'Copy' as in 'Copy and Paste' - self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste + self.menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste self.bind('', self._contextmenu) self.bind('', self._enter) diff --git a/update.py b/update.py index 991558d64..52983a5d6 100644 --- a/update.py +++ b/update.py @@ -16,10 +16,10 @@ import semantic_version from config import appname, appversion_nobuild, config, update_feed from EDMCLogging import get_main_logger +from l10n import translations as tr if TYPE_CHECKING: import tkinter as tk - def _(x: str): return x logger = get_main_logger() @@ -200,7 +200,7 @@ def worker(self) -> None: if newversion and self.root: status = self.root.nametowidget(f'.{appname.lower()}.status') # LANG: Update Available Text - status['text'] = _("{NEWVER} is available").format(NEWVER=newversion.title) + status['text'] = tr.tl("{NEWVER} is available").format(NEWVER=newversion.title) self.root.update_idletasks() else: From f3fe146c6654d90e52c7d4af196ea9642b6132f3 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 22 Apr 2024 17:36:37 -0400 Subject: [PATCH 153/261] [1812] Additional Translation Handover --- myNotebook.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 070b28e12..b51ada787 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -12,11 +12,8 @@ import sys import tkinter as tk from tkinter import ttk, messagebox -from typing import TYPE_CHECKING from PIL import ImageGrab - -if TYPE_CHECKING: - def _(x: str) -> str: return x +from l10n import translations as tr if sys.platform == 'win32': PAGEFG = 'SystemWindowText' @@ -108,8 +105,8 @@ def paste(self) -> None: if img: # Hijack existing translation, yes it doesn't exactly match here. # LANG: Generic error prefix - following text is from Frontier auth service; - messagebox.showwarning(_('Error'), - _('Cannot paste non-text content.')) # LANG: Can't Paste Images or Files in Text + messagebox.showwarning(tr.tl('Error'), + tr.tl('Cannot paste non-text content.')) # LANG: Can't Paste Images or Files in Text return text = self.clipboard_get() if self.selection_present() and text: From 96c78dae008a91e0f5f23c3a2a89c42ad031598f Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 22 Apr 2024 17:40:30 -0400 Subject: [PATCH 154/261] [Minor] Flake8 is Grumpy --- myNotebook.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index b51ada787..43acfebdf 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -104,9 +104,10 @@ def paste(self) -> None: img = ImageGrab.grabclipboard() if img: # Hijack existing translation, yes it doesn't exactly match here. - # LANG: Generic error prefix - following text is from Frontier auth service; - messagebox.showwarning(tr.tl('Error'), - tr.tl('Cannot paste non-text content.')) # LANG: Can't Paste Images or Files in Text + messagebox.showwarning( + tr.tl('Error'), # LANG: Generic error prefix - following text is from Frontier auth service; + tr.tl('Cannot paste non-text content.') # LANG: Can't Paste Images or Files in Text + ) return text = self.clipboard_get() if self.selection_present() and text: From c62592e95e9dddaefee50180a969de3b07be1ca8 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 22 Apr 2024 18:05:03 -0400 Subject: [PATCH 155/261] [1812] Update Docs --- docs/Translations.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/Translations.md b/docs/Translations.md index 959897f2e..1de83c3b9 100644 --- a/docs/Translations.md +++ b/docs/Translations.md @@ -8,15 +8,20 @@ Translations are handled on [OneSky](https://oneskyapp.com/), specifically in [t ### Setting it up in the code -#### Call `_(...)` +#### Call `tr.tl(...)` If you add any new strings that appear in the application UI, e.g. new configuration options, then you should specify them as: - _('Text that appears in UI') -`_()` is a special global function that then handles the translation, using its single argument, plus the configured language, to look up the appropriate text. + tr.tl('Text that appears in UI') + +In order to do this, you must add the following import: + +`from l10n import translations as tr` + +`tr.tl()` is a function that then handles the translation, using its single argument, plus the configured language, to look up the appropriate text. If you need to specify something in the text that shouldn't be translated then use the form: - _('Some text with a {WORD} not translated').format(WORD='word') + tr.tl('Some text with a {WORD} not translated').format(WORD='word') This way 'word' will always be used literally. #### Add a LANG comment @@ -28,8 +33,9 @@ end of the line in your usage**. If both comments exist, the one on the current line is preferred over the one above ```py +from l10n import translations as tr # LANG: this says stuff. -_('stuff') +tr.tl('stuff') ``` #### Edit `L10n/en.template` to add the phrase @@ -43,7 +49,7 @@ e.g. "Authentication successful" = "Authentication successful"; which matches with: - self.status['text'] = _('Authentication successful') # Successfully authenticated with the Frontier website + self.status['text'] = tr.tl('Authentication successful') # Successfully authenticated with the Frontier website and @@ -51,12 +57,12 @@ and "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name"; which matches with: - nb.Label(plugsframe, text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')).grid( # Help text in settings + nb.Label(plugsframe, text=tr.tl("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')).grid( # Help text in settings `{CR}` is handled in `l10n.py`, translating to a unicode `\n`. See the code in`l10n.py` for any other such special substitutions. You can even use other translations within a given string, e.g.: - _("One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name.".format(PLUGINS=_('Plugins'), FILE=_('File'), SETTINGS=_('Settings'), DISABLED='.disabled')) + tr.tl("One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name.".format(PLUGINS=tr.tl('Plugins'), FILE=tr.tl('File'), SETTINGS=tr.tl('Settings'), DISABLED='.disabled')) /* Popup body: Warning about plugins without Python 3.x support [EDMarketConnector.py] */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name."; From 10be2c203199261f440a8b99a6b1ae95b4711033 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 22 Apr 2024 18:32:19 -0400 Subject: [PATCH 156/261] [Docs] Update Plugin Docs --- PLUGINS.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 3048f345f..03f9d31c9 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -1193,19 +1193,17 @@ widget if you need to display routine status information. ## Localisation You can localise your plugin to one of the languages that EDMarketConnector -itself supports. Add the following boilerplate near the top of each source +itself supports. Add the following import near the top of each source file that contains strings that needs translating: ```python -import l10n -import functools -_ = functools.partial(l10n.Translations.translate, context=__file__) +from l10n import translations as tr ``` -Wrap each string that needs translating with the `_()` function, e.g.: +Wrap each string that needs translating with the `tr.tl()` function, e.g.: ```python - somewidget["text"] = _("Happy!") + somewidget["text"] = tr.tl("Happy!") ``` If you display localized strings in EDMarketConnector's main window you should From 91e4e7998bf09bae760c1b0be246f52e0bee3587 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 22 Apr 2024 19:18:01 -0400 Subject: [PATCH 157/261] [1812] Update Docs and Fix Compat Layer --- PLUGINS.md | 16 ++++++++++++---- l10n.py | 6 +++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 03f9d31c9..d050afb08 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -1193,19 +1193,27 @@ widget if you need to display routine status information. ## Localisation You can localise your plugin to one of the languages that EDMarketConnector -itself supports. Add the following import near the top of each source +itself supports. Add the following boilerplate near the top of the source file that contains strings that needs translating: ```python -from l10n import translations as tr +import l10n +import functools +plugin_tl = functools.partial(l10n.translations.tl, context=__file__) + ``` -Wrap each string that needs translating with the `tr.tl()` function, e.g.: +Wrap each string that needs translating with the `plugin_tl()` function, e.g.: ```python - somewidget["text"] = tr.tl("Happy!") + somewidget["text"] = plugin_tl("Happy!") ``` +Note that you can name the "plugin_tl" function whatever you want - just make sure to stay consistent! +Many plugins use `_` as the singleton name. We discourage that in versions 5.11 onward, but it should still work. +If your plugin has multiple files that need translations, simply import the `plugin_tl` function to that location. +You should only need to add the boilerplate once. + If you display localized strings in EDMarketConnector's main window you should refresh them in your `prefs_changed` function in case the user has changed their preferred language. diff --git a/l10n.py b/l10n.py index f3215062c..e72499de9 100755 --- a/l10n.py +++ b/l10n.py @@ -86,7 +86,7 @@ def install_dummy(self) -> None: """ self.translations = {None: {}} # WARNING: '_' is Deprecated. Will be removed in 6.0 or later. - # Migrate to calling Translations.translate directly. + # Migrate to calling translations.translate or tr.tl directly. builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n') def install(self, lang: str | None = None) -> None: # noqa: CCR001 @@ -131,7 +131,7 @@ def install(self, lang: str | None = None) -> None: # noqa: CCR001 logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}') # WARNING: '_' is Deprecated. Will be removed in 6.0 or later. - # Migrate to calling Translations.translate directly. + # Migrate to calling translations.translate or tr.tl directly. builtins.__dict__['_'] = self.translate def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: @@ -351,7 +351,7 @@ def __init__(self): super().__init__() -Translations: Translations = _Translations() # type: ignore # Yes, I know this is awful. But we need it for compat. +Translations = translations # Yes, I know this is awful renaming garbage. But we need it for compat. # End Deprecation Zone # generate template strings file - like xgettext From 26c8a8be6e168aa9c6a44dc2ec40683cedaa52c5 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 22 Apr 2024 19:22:34 -0400 Subject: [PATCH 158/261] [Minor] [Incoherent Type Hinting Noises] --- l10n.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/l10n.py b/l10n.py index e72499de9..c4a92a679 100755 --- a/l10n.py +++ b/l10n.py @@ -351,7 +351,8 @@ def __init__(self): super().__init__() -Translations = translations # Yes, I know this is awful renaming garbage. But we need it for compat. +# Yes, I know this is awful renaming garbage. But we need it for compat. +Translations: Translations = translations # type: ignore # End Deprecation Zone # generate template strings file - like xgettext From 4a1a107e032a85074d232484acb43ad509ef0b10 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 24 Apr 2024 19:58:39 -0400 Subject: [PATCH 159/261] [1812] Adapt Localized String Search --- scripts/find_localised_strings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/find_localised_strings.py b/scripts/find_localised_strings.py index ef861bb71..5911d64ca 100644 --- a/scripts/find_localised_strings.py +++ b/scripts/find_localised_strings.py @@ -38,8 +38,9 @@ def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]: out = [] for n in ast.iter_child_nodes(statement): out.extend(find_calls_in_stmt(n)) - if isinstance(statement, ast.Call) and get_func_name(statement.func) == '_': - out.append(statement) + if isinstance(statement, ast.Call) and get_func_name(statement.func) in ('tr', 'translations'): + if ast.unparse(statement).find('.tl') != -1 or ast.unparse(statement).find('translate') != -1: + out.append(statement) return out From c8f2b6018cf0fc7e5244ece6d2a52052c19fee40 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 24 Apr 2024 20:03:04 -0400 Subject: [PATCH 160/261] [1173] Right-Click Provider Options --- EDMarketConnector.py | 10 ++++---- L10n/en.template | 3 +++ ttkHyperlinkLabel.py | 56 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 09e6fc4e7..62bb4f5da 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -515,13 +515,13 @@ def open_window(systray: 'SysTrayIcon') -> None: self.cmdr_label = tk.Label(frame, name='cmdr_label') self.cmdr = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='cmdr') self.ship_label = tk.Label(frame, name='ship_label') - self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.shipyard_url, name='ship') + self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.shipyard_url, name='ship', popup_copy=True) self.suit_label = tk.Label(frame, name='suit_label') self.suit = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='suit') self.system_label = tk.Label(frame, name='system_label') self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.system_url, popup_copy=True, name='system') self.station_label = tk.Label(frame, name='station_label') - self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station') + self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station', popup_copy=True) # system and station text is set/updated by the 'provider' plugins # edsm and inara. Look for: # @@ -1627,7 +1627,7 @@ def plugin_error(self, event=None) -> None: hotkeymgr.play_bad() def shipyard_url(self, shipname: str) -> str | None: - """Despatch a ship URL to the configured handler.""" + """Dispatch a ship URL to the configured handler.""" if not (loadout := monitor.ship()): logger.warning('No ship loadout, aborting.') return '' @@ -1654,13 +1654,13 @@ def shipyard_url(self, shipname: str) -> str | None: return f'file://localhost/{file_name}' def system_url(self, system: str) -> str | None: - """Despatch a system URL to the configured handler.""" + """Dispatch a system URL to the configured handler.""" return plug.invoke( config.get_str('system_provider', default='EDSM'), 'EDSM', 'system_url', monitor.state['SystemName'] ) def station_url(self, station: str) -> str | None: - """Despatch a station URL to the configured handler.""" + """Dispatch a station URL to the configured handler.""" return plug.invoke( config.get_str('station_provider', default='EDSM'), 'EDSM', 'station_url', monitor.state['SystemName'], monitor.state['StationName'] diff --git a/L10n/en.template b/L10n/en.template index 200743c82..5a78cba23 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -782,3 +782,6 @@ /* myNotebook.py: Can't Paste Images or Files in Text; */ "Cannot paste non-text content." = "Cannot paste non-text content."; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Open in {URL}"; \ No newline at end of file diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index d026f619d..3bb878a69 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -19,14 +19,17 @@ May be imported by plugins """ from __future__ import annotations - +from functools import partial import sys import tkinter as tk import webbrowser from tkinter import font as tk_font from tkinter import ttk from typing import Any +import plug +from config import config, logger from l10n import translations as tr +from monitor import monitor class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore @@ -64,6 +67,57 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non text=kw.get('text'), font=kw.get('font', ttk.Style().lookup('TLabel', 'font'))) + # Add Menu Options + self.plug_options = kw.pop('plug_options', None) + self.name = kw.get('name', None) + if self.name == 'ship' and not bool(config.get_int("use_alt_shipyard_open")): + self.menu.add_separator() + for url in plug.provides('shipyard_url'): + self.menu.add_command( + label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider + command=partial(self.open_shipyard, url) + ) + + if self.name == 'station': + self.menu.add_separator() + for url in plug.provides('station_url'): + self.menu.add_command( + label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider + command=partial(self.open_station, url) + ) + + if self.name == 'system': + self.menu.add_separator() + for url in plug.provides('system_url'): + self.menu.add_command( + label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider + command=partial(self.open_system, url) + ) + + def open_shipyard(self, url: str): + """Open the Current Ship Loadout in the Selected Provider.""" + if loadout := monitor.ship(): + opener = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta) + if opener: + return webbrowser.open(opener) + logger.warning('No ship loadout, aborting.') + return '' + + def open_system(self, url: str): + """Open the Current System in the Selected Provider.""" + opener = plug.invoke(url, 'EDSM', 'system_url', monitor.state['SystemName']) + if opener: + return webbrowser.open(opener) + + def open_station(self, url: str): + """Open the Current Station in the Selected Provider.""" + opener = plug.invoke( + url, 'EDSM', 'station_url', + monitor.state['SystemName'], monitor.state['StationName'] + ) + if opener: + return webbrowser.open(opener) + def configure( # noqa: CCR001 self, cnf: dict[str, Any] | None = None, **kw: Any ) -> dict[str, tuple[str, str, str, Any, Any]] | None: From 770c0ed3a5b0f2f453aa78f7294f971c4814e538 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 26 Apr 2024 00:41:58 -0400 Subject: [PATCH 161/261] [2203] Catch Irrecoverable Errors --- EDMarketConnector.py | 26 +++++++++++++++++++++++++- L10n/en.template | 6 ++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 09e6fc4e7..8ec3ab755 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -11,9 +11,11 @@ import argparse import html import locale +import os import pathlib import queue import re +import signal import subprocess import sys import threading @@ -2226,7 +2228,29 @@ def test_prop(self): if theme.default_ui_scale is not None: root.tk.call('tk', 'scaling', theme.default_ui_scale * float(ui_scale) / 100.0) - app = AppWindow(root) + try: + app = AppWindow(root) + except Exception as err: + logger.exception(f"EDMC Critical Error: {err}") + title = tr.tl("Error") # LANG: Generic error prefix + message = tr.tl( # LANG: EDMC Critical Error Notification + "EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" + ) + err = f"{err.__class__.__name__}: {err}" # type: ignore # hijacking the existing exception detection + detail = tr.tl( # LANG: EDMC Critical Error Details + r"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" + ).format(ERR=err) + detail = detail.replace('\\n', '\n') + detail = detail.replace('\\r', '\r') + msg = tk.messagebox.askyesno( + title=title, message=message, detail=detail, icon=tkinter.messagebox.ERROR, type=tkinter.messagebox.YESNO + ) + if msg: + webbrowser.open( + "https://github.com/EDCD/EDMarketConnector/issues/new?" + "assignees=&labels=bug%2C+unconfirmed&projects=&template=bug_report.md&title=" + ) + os.kill(os.getpid(), signal.SIGTERM) def messagebox_broken_plugins(): """Display message about 'broken' plugins that failed to load.""" diff --git a/L10n/en.template b/L10n/en.template index 200743c82..b2c46d7cf 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -36,6 +36,12 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Error: unable to get token"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!"; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "Frontier CAPI down for maintenance"; From 8b116516a2238da8268e4b4549138185052d6fcf Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 26 Apr 2024 00:56:34 -0400 Subject: [PATCH 162/261] [2203] Simplify Duplicate Running Message --- EDMarketConnector.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 8ec3ab755..45bc28c7e 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -338,29 +338,14 @@ def enumwindowsproc(window_handle, l_param): # noqa: CCR001 def already_running_popup(): """Create the "already running" popup.""" - import tkinter as tk - from tkinter import ttk + from tkinter import messagebox # Check for CL arg that suppresses this popup. if args.suppress_dupe_process_popup: sys.exit(0) - root = tk.Tk(className=appname.lower()) - - frame = tk.Frame(root) - frame.grid(row=1, column=0, sticky=tk.NSEW) - - label = tk.Label(frame, text='An EDMarketConnector.exe process was already running, exiting.') - label.grid(row=1, column=0, sticky=tk.NSEW) - - button = ttk.Button(frame, text='OK', command=lambda: sys.exit(0)) - button.grid(row=2, column=0, sticky=tk.S) - - try: - root.mainloop() - except KeyboardInterrupt: - logger.info("Ctrl+C Detected, Attempting Clean Shutdown") - sys.exit() - logger.info('Exiting') + already_running_msg = "An EDMarketConnector process was already running, exiting." + messagebox.showerror(title=appname, message=already_running_msg) + sys.exit(0) journal_lock = JournalLock() locked = journal_lock.obtain_lock() From 3589bdd8e26c506189aa8e3892f1e6435a77f5d8 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 26 Apr 2024 00:57:23 -0400 Subject: [PATCH 163/261] [Minor] Why Use Two Line When One Line Do Trick --- EDMarketConnector.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 45bc28c7e..92ab3fe8d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -343,8 +343,7 @@ def already_running_popup(): if args.suppress_dupe_process_popup: sys.exit(0) - already_running_msg = "An EDMarketConnector process was already running, exiting." - messagebox.showerror(title=appname, message=already_running_msg) + messagebox.showerror(title=appname, message="An EDMarketConnector process was already running, exiting.") sys.exit(0) journal_lock = JournalLock() From 3d9e46d730dd55376da102778aa870f27410e8ce Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 26 Apr 2024 12:41:57 -0400 Subject: [PATCH 164/261] [Minor] Push Back Deprecation Date While these modules are deprecated, one minor version is a little too quick to remove. Pushes back to 6.0 for plugin developer compatibilty's sake. --- myNotebook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/myNotebook.py b/myNotebook.py index 43acfebdf..8a3cf9010 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -121,7 +121,7 @@ def paste(self) -> None: class Entry(EntryMenu): """Custom ttk.Entry class to fix some display issues.""" - # DEPRECATED: Migrate to EntryMenu. Will remove in 5.12 or later. + # DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): EntryMenu.__init__(self, master, **kw) @@ -139,7 +139,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class ColoredButton(tk.Button): """Custom tk.Button class to fix some display issues.""" - # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later. + # DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): tk.Button.__init__(self, master, **kw) From e573b8996647ccda1555e6bd6af5c694c333b630 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 27 Apr 2024 14:58:03 -0400 Subject: [PATCH 165/261] [1654] Updated setCommanderShip entry --- plugins/inara.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 810f7523d..47c857e6e 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -553,23 +553,6 @@ def journal_entry( # noqa: C901, CCR001 # Ship change if event_name == 'Loadout' and this.shipswap: - cur_ship = { - 'shipType': state['ShipType'], - 'shipGameID': state['ShipID'], - 'shipName': state['ShipName'], # Can be None - 'shipIdent': state['ShipIdent'], # Can be None - 'isCurrentShip': True, - } - - if state['HullValue']: - cur_ship['shipHullValue'] = state['HullValue'] - - if state['ModulesValue']: - cur_ship['shipModulesValue'] = state['ModulesValue'] - - cur_ship['shipRebuyCost'] = state['Rebuy'] - new_add_event('setCommanderShip', entry['timestamp'], cur_ship) - this.loadout = make_loadout(state) new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout) this.shipswap = False @@ -857,7 +840,7 @@ def journal_entry( # noqa: C901, CCR001 for ship in this.fleet: new_add_event('setCommanderShip', entry['timestamp'], ship) # Loadout - if event_name == 'Loadout' and not this.newsession: + if event_name == 'Loadout': loadout = make_loadout(state) if this.loadout != loadout: this.loadout = loadout @@ -871,6 +854,26 @@ def journal_entry( # noqa: C901, CCR001 new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout) + cur_ship = { + 'shipType': state['ShipType'], + 'shipGameID': state['ShipID'], + 'shipName': state['ShipName'], # Can be None + 'shipIdent': state['ShipIdent'], # Can be None + 'isCurrentShip': True, + 'shipMaxJumpRange': entry['MaxJumpRange'], + 'shipCargoCapacity': entry['CargoCapacity'] + } + if state['HullValue']: + cur_ship['shipHullValue'] = state['HullValue'] + + if state['ModulesValue']: + cur_ship['shipModulesValue'] = state['ModulesValue'] + + if state['Rebuy']: + cur_ship['shipRebuyCost'] = state['Rebuy'] + + new_add_event('setCommanderShip', entry['timestamp'], cur_ship) + # Stored modules if event_name == 'StoredModules': items = {mod['StorageSlot']: mod for mod in entry['Items']} # Impose an order From f03ae7809f1591322543f332a8a28b7ff28673b4 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 27 Apr 2024 15:27:22 -0400 Subject: [PATCH 166/261] [2166] Update Watchdog Type Hints Watchdog 4.0.0 included an update to the public API where FileSystemEvent, and subclasses, are now dataclasses, and their repr() has changed. As such, (insofar as I can tell) the FileCreatedEvent is now a FileSystemEvent. MyPy also suggests that this is a FileSystemEvent as well. --- monitor.py | 6 +++--- requirements.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monitor.py b/monitor.py index 02b7f91cc..1fd8a1d83 100644 --- a/monitor.py +++ b/monitor.py @@ -42,7 +42,7 @@ def _(x: str) -> str: import ctypes from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR - from watchdog.events import FileCreatedEvent, FileSystemEventHandler + from watchdog.events import FileSystemEventHandler, FileSystemEvent from watchdog.observers import Observer from watchdog.observers.api import BaseObserver @@ -64,7 +64,7 @@ def _(x: str) -> str: FileSystemEventHandler = object # dummy if TYPE_CHECKING: # this isn't ever used, but this will make type checking happy - from watchdog.events import FileCreatedEvent + from watchdog.events import FileSystemEvent from watchdog.observers import Observer from watchdog.observers.api import BaseObserver @@ -350,7 +350,7 @@ def running(self) -> bool: """ return bool(self.thread and self.thread.is_alive()) - def on_created(self, event: 'FileCreatedEvent') -> None: + def on_created(self, event: 'FileSystemEvent') -> None: """Watchdog callback when, e.g. client (re)started.""" if not event.is_directory and self._RE_LOGFILE.search(basename(event.src_path)): diff --git a/requirements.txt b/requirements.txt index 75ea40411..2e0fb39f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pillow==10.3.0 # requests depends on this now ? charset-normalizer==3.3.2 -watchdog==3.0.0 +watchdog==4.0.0 # Commented out because this doesn't package well with py2exe infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep From af207f60e801138928d590be86b0be1333a18332 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 27 Apr 2024 20:10:09 -0400 Subject: [PATCH 167/261] [1801] FDEV ID Local Updater This removes the FDEV ID Submodule, and instead hands over responsibility for maintaining the fdev id files to the internal system updater. Instead of the full submodule, we only pull the two files we need. --- .gitmodules | 3 - EDMC.py | 8 +- EDMarketConnector.py | 31 ----- FDevIDs | 1 - FDevIDs/commodity.csv | 255 +++++++++++++++++++++++++++++++++++++ FDevIDs/rare_commodity.csv | 143 +++++++++++++++++++++ L10n/en.template | 6 - build.py | 2 + update.py | 34 +++++ 9 files changed, 440 insertions(+), 43 deletions(-) delete mode 160000 FDevIDs create mode 100644 FDevIDs/commodity.csv create mode 100644 FDevIDs/rare_commodity.csv diff --git a/.gitmodules b/.gitmodules index cd89b4654..c65e62f1a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "coriolis-data"] path = coriolis-data url = https://github.com/EDCD/coriolis-data.git -[submodule "FDevIDs"] - path = FDevIDs - url = https://github.com/EDCD/FDevIDs.git diff --git a/EDMC.py b/EDMC.py index 39d989824..cbb95165d 100755 --- a/EDMC.py +++ b/EDMC.py @@ -43,7 +43,7 @@ def _(x: str): return x from commodity import COMMODITY_DEFAULT from config import appcmdname, appversion, config from monitor import monitor -from update import EDMCVersion, Updater +from update import EDMCVersion, Updater, check_for_fdev_updates sys.path.append(config.internal_plugin_dir) # This import must be after the sys.path.append. @@ -498,6 +498,10 @@ def main(): # noqa: C901, CCR001 if __name__ == '__main__': - main() + try: + check_for_fdev_updates(silent=True) + main() + except KeyboardInterrupt: + logger.info("Ctrl+C Detected, Attempting Clean Shutdown") logger.debug('Exiting') sys.exit(EXIT_SUCCESS) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 3ab97eb52..17fac3d9c 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -2290,35 +2290,6 @@ def messagebox_not_py3(): ) config.set('plugins_not_py3_last', int(time())) - def check_fdev_ids(): - """Display message about missing FDEVID files.""" - fdev_files = {'commodity.csv', 'rare_commodity.csv'} - for file in fdev_files: - fdevid_file = pathlib.Path(config.respath_path / 'FDevIDs' / file) - if fdevid_file.is_file(): - continue - # LANG: Popup-text about missing FDEVID Files - popup_text = _( - "FDevID Files not found! Some functionality regarding commodities " - r"may be disabled.\r\n\r\n Do you want to open the Wiki page on " - "how to set up submodules?" - ) - # And now we do need these to be actual \r\n - popup_text = popup_text.replace('\\n', '\n') - popup_text = popup_text.replace('\\r', '\r') - - openwikipage = tk.messagebox.askquestion( - # LANG: Popup window title for missing FDEVID files - _('FDevIDs: Missing Commodity Files'), - popup_text - ) - if openwikipage == "yes": - webbrowser.open( - "https://github.com/EDCD/EDMarketConnector/wiki/Running-from-source" - "#obtain-a-copy-of-the-application-source" - ) - break - # UI Transparency ui_transparency = config.get_int('ui_transparency') if ui_transparency == 0: @@ -2331,8 +2302,6 @@ def check_fdev_ids(): root.after(1, messagebox_not_py3) # Show warning popup for killswitches matching current version root.after(2, show_killswitch_poppup, root) - # Check for FDEV IDs - root.after(3, check_fdev_ids) # Start the main event loop try: root.mainloop() diff --git a/FDevIDs b/FDevIDs deleted file mode 160000 index 9b3f40612..000000000 --- a/FDevIDs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2 diff --git a/FDevIDs/commodity.csv b/FDevIDs/commodity.csv new file mode 100644 index 000000000..1a541cb2c --- /dev/null +++ b/FDevIDs/commodity.csv @@ -0,0 +1,255 @@ +id,symbol,category,name +128049152,Platinum,Metals,Platinum +128049153,Palladium,Metals,Palladium +128049154,Gold,Metals,Gold +128049155,Silver,Metals,Silver +128049156,Bertrandite,Minerals,Bertrandite +128049157,Indite,Minerals,Indite +128049158,Gallite,Minerals,Gallite +128049159,Coltan,Minerals,Coltan +128049160,Uraninite,Minerals,Uraninite +128049161,Lepidolite,Minerals,Lepidolite +128049162,Cobalt,Metals,Cobalt +128049163,Rutile,Minerals,Rutile +128049165,Bauxite,Minerals,Bauxite +128049166,Water,Chemicals,Water +128049168,Beryllium,Metals,Beryllium +128049169,Indium,Metals,Indium +128049170,Gallium,Metals,Gallium +128049171,Tantalum,Metals,Tantalum +128049172,Uranium,Metals,Uranium +128049173,Lithium,Metals,Lithium +128049174,Titanium,Metals,Titanium +128049175,Copper,Metals,Copper +128049176,Aluminium,Metals,Aluminium +128049177,Algae,Foods,Algae +128049178,FruitAndVegetables,Foods,Fruit and Vegetables +128049180,Grain,Foods,Grain +128049182,Animalmeat,Foods,Animal Meat +128049183,Fish,Foods,Fish +128049184,FoodCartridges,Foods,Food Cartridges +128049185,SyntheticMeat,Foods,Synthetic Meat +128049188,Tea,Foods,Tea +128049189,Coffee,Foods,Coffee +128049190,Leather,Textiles,Leather +128049191,NaturalFabrics,Textiles,Natural Fabrics +128049193,SyntheticFabrics,Textiles,Synthetic Fabrics +128049197,Polymers,Industrial Materials,Polymers +128049199,Semiconductors,Industrial Materials,Semiconductors +128049200,Superconductors,Industrial Materials,Superconductors +128049202,HydrogenFuel,Chemicals,Hydrogen Fuel +128049203,MineralOil,Chemicals,Mineral Oil +128049204,Explosives,Chemicals,Explosives +128049205,Pesticides,Chemicals,Pesticides +128049208,AgriculturalMedicines,Medicines,Agri-Medicines +128049209,PerformanceEnhancers,Medicines,Performance Enhancers +128049210,BasicMedicines,Medicines,Basic Medicines +128049212,BasicNarcotics,Legal Drugs,Narcotics +128049213,Tobacco,Legal Drugs,Tobacco +128049214,Beer,Legal Drugs,Beer +128049215,Wine,Legal Drugs,Wine +128049216,Liquor,Legal Drugs,Liquor +128049217,PowerGenerators,Machinery,Power Generators +128049218,WaterPurifiers,Machinery,Water Purifiers +128049220,HeliostaticFurnaces,Machinery,Microbial Furnaces +128049221,MineralExtractors,Machinery,Mineral Extractors +128049222,CropHarvesters,Machinery,Crop Harvesters +128049223,MarineSupplies,Machinery,Marine Equipment +128049225,ComputerComponents,Technology,Computer Components +128049226,HazardousEnvironmentSuits,Technology,H.E. Suits +128049227,Robotics,Technology,Robotics +128049228,AutoFabricators,Technology,Auto-Fabricators +128049229,AnimalMonitors,Technology,Animal Monitors +128049230,AquaponicSystems,Technology,Aquaponic Systems +128049231,AdvancedCatalysers,Technology,Advanced Catalysers +128049232,TerrainEnrichmentSystems,Technology,Land Enrichment Systems +128049233,PersonalWeapons,Weapons,Personal Weapons +128049234,BattleWeapons,Weapons,Battle Weapons +128049235,ReactiveArmour,Weapons,Reactive Armour +128049236,NonLethalWeapons,Weapons,Non-Lethal Weapons +128049238,DomesticAppliances,Consumer Items,Domestic Appliances +128049240,ConsumerTechnology,Consumer Items,Consumer Technology +128049241,Clothing,Consumer Items,Clothing +128049243,Slaves,Slavery,Slaves +128049244,Biowaste,Waste,Biowaste +128049245,ToxicWaste,Waste,Toxic Waste +128049246,ChemicalWaste,Waste,Chemical Waste +128049248,Scrap,Waste,Scrap +128049669,ProgenitorCells,Medicines,Progenitor Cells +128049670,CombatStabilisers,Medicines,Combat Stabilisers +128049671,ResonatingSeparators,Technology,Resonating Separators +128049672,BioReducingLichen,Technology,Bioreducing Lichen +128064028,AtmosphericExtractors,Machinery,Atmospheric Processors +128066403,Drones,NonMarketable,Limpets +128666752,USSCargoBlackBox,Salvage,Black Box +128666754,USSCargoTradeData,Salvage,Trade Data +128666755,USSCargoMilitaryPlans,Salvage,Military Plans +128666756,USSCargoAncientArtefact,Salvage,Ancient Artefact +128666757,USSCargoRareArtwork,Salvage,Rare Artwork +128666758,USSCargoExperimentalChemicals,Salvage,Experimental Chemicals +128666759,USSCargoRebelTransmissions,Salvage,Rebel Transmissions +128666760,USSCargoPrototypeTech,Salvage,Prototype Tech +128666761,USSCargoTechnicalBlueprints,Salvage,Technical Blueprints +128667728,ImperialSlaves,Slavery,Imperial Slaves +128668547,UnknownArtifact,Salvage,Thargoid Sensor +128668548,AiRelics,Salvage,AI Relics +128668549,Hafnium178,Metals,Hafnium 178 +128668550,Painite,Minerals,Painite +128668551,Antiquities,Salvage,Antiquities +128668552,MilitaryIntelligence,Salvage,Military Intelligence +128671118,Osmium,Metals,Osmium +128671443,SAP8CoreContainer,Salvage,SAP 8 Core Container +128671444,TrinketsOfFortune,Consumer Items,Trinkets of Hidden Fortune +128672123,WreckageComponents,Salvage,Wreckage Components +128672124,EncriptedDataStorage,Salvage,Encrypted Data Storage +128672125,OccupiedCryoPod,Salvage,Occupied Escape Pod +128672126,PersonalEffects,Salvage,Personal Effects +128672127,ComercialSamples,Salvage,Commercial Samples +128672128,TacticalData,Salvage,Tactical Data +128672129,AssaultPlans,Salvage,Assault Plans +128672130,EncryptedCorrespondence,Salvage,Encrypted Correspondence +128672131,DiplomaticBag,Salvage,Diplomatic Bag +128672132,ScientificResearch,Salvage,Scientific Research +128672133,ScientificSamples,Salvage,Scientific Samples +128672134,PoliticalPrisoner,Salvage,Political Prisoners +128672135,Hostage,Salvage,Hostages +128672136,LargeExplorationDataCash,Salvage,Large Survey Data Cache +128672137,SmallExplorationDataCash,Salvage,Small Survey Data Cache +128672159,AntiqueJewellery,Salvage,Antique Jewellery +128672160,PreciousGems,Salvage,Precious Gems +128672161,EarthRelics,Salvage,Earth Relics +128672162,GeneBank,Salvage,Gene Bank +128672163,TimeCapsule,Salvage,Time Capsule +128672294,Cryolite,Minerals,Cryolite +128672295,Goslarite,Minerals,Goslarite +128672296,Moissanite,Minerals,Moissanite +128672297,Pyrophyllite,Minerals,Pyrophyllite +128672298,Lanthanum,Metals,Lanthanum +128672299,Thallium,Metals,Thallium +128672300,Bismuth,Metals,Bismuth +128672301,Thorium,Metals,Thorium +128672302,CeramicComposites,Industrial Materials,Ceramic Composites +128672303,SyntheticReagents,Chemicals,Synthetic Reagents +128672304,NerveAgents,Chemicals,Nerve Agents +128672305,SurfaceStabilisers,Chemicals,Surface Stabilisers +128672306,BootlegLiquor,Legal Drugs,Bootleg Liquor +128672307,GeologicalEquipment,Machinery,Geological Equipment +128672308,ThermalCoolingUnits,Machinery,Thermal Cooling Units +128672309,BuildingFabricators,Machinery,Building Fabricators +128672310,MuTomImager,Technology,Muon Imager +128672311,StructuralRegulators,Technology,Structural Regulators +128672312,Landmines,Weapons,Landmines +128672313,SkimerComponents,Machinery,Skimmer Components +128672314,EvacuationShelter,Consumer Items,Evacuation Shelter +128672315,GeologicalSamples,Salvage,Geological Samples +128672701,MetaAlloys,Industrial Materials,Meta-Alloys +128672775,Taaffeite,Minerals,Taaffeite +128672776,Jadeite,Minerals,Jadeite +128672810,UnstableDataCore,Salvage,Unstable Data Core +128672811,DamagedEscapePod,Salvage,Damaged Escape Pod +128673845,Praseodymium,Metals,Praseodymium +128673846,Bromellite,Minerals,Bromellite +128673847,Samarium,Metals,Samarium +128673848,LowTemperatureDiamond,Minerals,Low Temperature Diamonds +128673850,HydrogenPeroxide,Chemicals,Hydrogen Peroxide +128673851,LiquidOxygen,Chemicals,Liquid oxygen +128673852,MethanolMonohydrateCrystals,Minerals,Methanol Monohydrate Crystals +128673853,LithiumHydroxide,Minerals,Lithium Hydroxide +128673854,MethaneClathrate,Minerals,Methane Clathrate +128673855,InsulatingMembrane,Industrial Materials,Insulating Membrane +128673856,CMMComposite,Industrial Materials,CMM Composite +128673857,CoolingHoses,Industrial Materials,Micro-weave Cooling Hoses +128673858,NeofabricInsulation,Industrial Materials,Neofabric Insulation +128673859,ArticulationMotors,Machinery,Articulation Motors +128673860,HNShockMount,Machinery,HN Shock Mount +128673861,EmergencyPowerCells,Machinery,Emergency Power Cells +128673862,PowerConverter,Machinery,Power Converter +128673863,PowerGridAssembly,Machinery,Energy Grid Assembly +128673864,PowerTransferConduits,Machinery,Power Transfer Bus +128673865,RadiationBaffle,Machinery,Radiation Baffle +128673866,ExhaustManifold,Machinery,Exhaust Manifold +128673867,ReinforcedMountingPlate,Machinery,Reinforced Mounting Plate +128673868,HeatsinkInterlink,Machinery,Heatsink Interlink +128673869,MagneticEmitterCoil,Machinery,Magnetic Emitter Coil +128673870,ModularTerminals,Machinery,Modular Terminals +128673871,Nanobreakers,Technology,Nanobreakers +128673872,TelemetrySuite,Technology,Telemetry Suite +128673873,MicroControllers,Technology,Micro Controllers +128673874,IonDistributor,Machinery,Ion Distributor +128673875,DiagnosticSensor,Technology,Hardware Diagnostic Sensor +128673876,UnknownArtifact2,Salvage,Thargoid Probe +128682044,ConductiveFabrics,Textiles,Conductive Fabrics +128682045,MilitaryGradeFabrics,Textiles,Military Grade Fabrics +128682046,AdvancedMedicines,Medicines,Advanced Medicines +128682047,MedicalDiagnosticEquipment,Technology,Medical Diagnostic Equipment +128682048,SurvivalEquipment,Consumer Items,Survival Equipment +128682049,DataCore,Salvage,Data Core +128682051,MysteriousIdol,Salvage,Mysterious Idol +128682052,ProhibitedResearchMaterials,Salvage,Prohibited Research Materials +128682053,AntimatterContainmentUnit,Salvage,Antimatter Containment Unit +128682054,SpacePioneerRelics,Salvage,Space Pioneer Relics +128682055,FossilRemnants,Salvage,Fossil Remnants +128732183,AncientRelic,Salvage,Guardian Relic +128732184,AncientOrb,Salvage,Guardian Orb +128732185,AncientCasket,Salvage,Guardian Casket +128732186,AncientTablet,Salvage,Guardian Tablet +128732187,AncientUrn,Salvage,Guardian Urn +128732188,AncientTotem,Salvage,Guardian Totem +128737287,UnknownResin,Salvage,Thargoid Resin +128737288,UnknownBiologicalMatter,Salvage,Thargoid Biological Matter +128737289,UnknownTechnologySamples,Salvage,Thargoid Technology Samples +128740752,UnknownArtifact3,Salvage,Thargoid Link +128793127,ThargoidHeart,Salvage,Thargoid Heart +128793128,ThargoidTissueSampleType1,Salvage,Thargoid Cyclops Tissue Sample +128793129,ThargoidTissueSampleType2,Salvage,Thargoid Basilisk Tissue Sample +128793130,ThargoidTissueSampleType3,Salvage,Thargoid Medusa Tissue Sample +128824468,ThargoidScoutTissueSample,Salvage,Thargoid Scout Tissue Sample +128888499,AncientKey,Salvage,Ancient Key +128902652,ThargoidTissueSampleType4,Salvage,Thargoid Hydra Tissue Sample +128922517,M_TissueSample_Fluid,Salvage,Mollusc Fluid +128922518,M_TissueSample_Soft,Salvage,Mollusc Soft Tissue +128922519,M_TissueSample_Nerves,Salvage,Mollusc Brain Tissue +128922520,S_TissueSample_Cells,Salvage,Pod Core Tissue +128922521,S_TissueSample_Surface,Salvage,Pod Dead Tissue +128922522,S_TissueSample_Core,Salvage,Pod Surface Tissue +128922523,P_ParticulateSample,Salvage,Anomaly Particles +128922781,S9_TissueSample_Shell,Salvage,Pod Tissue +128922782,M3_TissueSample_Membrane,Salvage,Mollusc Membrane +128922783,M3_TissueSample_Mycelium,Salvage,Mollusc Mycelium +128922784,M3_TissueSample_Spores,Salvage,Mollusc Spores +128922785,S6_TissueSample_Mesoglea,Salvage,Pod Mesoglea +128922786,S6_TissueSample_Cells,Salvage,Pod Outer Tissue +128922787,S6_TissueSample_Coenosarc,Salvage,Pod Shell Tissue +128924325,Rhodplumsite,Minerals,Rhodplumsite +128924326,Serendibite,Minerals,Serendibite +128924327,Monazite,Minerals,Monazite +128924328,Musgravite,Minerals,Musgravite +128924329,Benitoite,Minerals,Benitoite +128924330,Grandidierite,Minerals,Grandidierite +128924331,Alexandrite,Minerals,Alexandrite +128924332,Opal,Minerals,Void Opal +128924333,RockforthFertiliser,Chemicals,Rockforth Fertiliser +128924334,AgronomicTreatment,Chemicals,Agronomic Treatment +128961249,Tritium,Chemicals,Tritium +128983059,OnionHeadC,Legal Drugs,Onionhead Gamma Strain +129015433,AncientRelicTG,Salvage,Unclassified Relic +129019258,ThargoidTissueSampleType5,Salvage,Thargoid Orthrus Tissue Sample +129019259,ThargoidGeneratorTissueSample,Salvage,Caustic Tissue Sample +129022087,UnocuppiedEscapePod,Salvage,Unoccupied Escape Pod +129022395,ThargoidTissueSampleType6,Salvage,Thargoid Glaive Tissue Sample +129022396,ThargoidTissueSampleType7,Salvage,Thargoid Scythe Tissue Sample +129022398,ThargoidTissueSampleType9a,Salvage,Titan Deep Tissue Sample +129022399,ThargoidTissueSampleType9b,Salvage,Titan Tissue Sample +129022400,ThargoidTissueSampleType9c,Salvage,Titan Partial Tissue Sample +129022402,ThargoidTissueSampleType10a,Salvage,Titan Maw Deep Tissue Sample +129022403,ThargoidTissueSampleType10b,Salvage,Titan Maw Tissue Sample +129022404,ThargoidTissueSampleType10c,Salvage,Titan Maw Partial Tissue Sample +129022405,UnknownSack,Salvage,Protective Membrane Scrap +129022406,ThargoidPod,Salvage,Xenobiological Prison Pod +129022407,CoralSap,Salvage,Coral Sap +129022408,UnknownMineral,Salvage,Impure Spire Mineral +129022409,UnknownRefinedMineral,Salvage,Semi-Refined Spire Mineral +129030459,ThargoidTitanDriveComponent,Salvage,Titan Drive Component +129030460,ThargoidCystSpecimen,Salvage,Cyst Specimen +129030461,ThargoidBoneFragments,Salvage,Bone Fragments +129030462,ThargoidOrganSample,Salvage,Organ Sample diff --git a/FDevIDs/rare_commodity.csv b/FDevIDs/rare_commodity.csv new file mode 100644 index 000000000..9997f1715 --- /dev/null +++ b/FDevIDs/rare_commodity.csv @@ -0,0 +1,143 @@ +id,symbol,market_id,category,name +128666746,EraninPearlWhisky,128001536,Legal Drugs,Eranin Pearl Whisky +128666747,LavianBrandy,128106744,Legal Drugs,Lavian Brandy +128667019,HIP10175BushMeat,3223234816,Foods,HIP 10175 Bush Meat +128667020,AlbinoQuechuaMammoth,3222822912,Foods,Albino Quechua Mammoth Meat +128667021,UtgaroarMillenialEggs,128037120,Foods,Utgaroar Millennial Eggs +128667022,WitchhaulKobeBeef,3223358720,Foods,Witchhaul Kobe Beef +128667023,KarsukiLocusts,3225028096,Foods,Karsuki Locusts +128667024,GiantIrukamaSnails,3225345792,Foods,Giant Irukama Snails +128667025,BaltahSineVacuumKrill,128088056,Foods,Baltah'sine Vacuum Krill +128667026,CetiRabbits,3222560000,Foods,Ceti Rabbits +128667027,KachiriginLeaches,3221595648,Medicines,Kachirigin Filter Leeches +128667028,LyraeWeed,3226417152,Legal Drugs,Lyrae Weed +128667029,OnionHead,128129272,Legal Drugs,Onionhead +128667030,TarachTorSpice,128041984,Legal Drugs,Tarach Spice +128667031,Wolf1301Fesh,128084984,Legal Drugs,Wolf Fesh +128667032,BorasetaniPathogenetics,3229638400,Weapons,Borasetani Pathogenetics +128667033,HIP118311Swarm,3223177472,Weapons,HIP 118311 Swarm +128667034,KonggaAle,3226978048,Legal Drugs,Kongga Ale +128667035,WuthieloKuFroth,3222155776,Legal Drugs,Wuthielo Ku Froth +128667036,AlacarakmoSkinArt,3231373824,Consumer Items,Alacarakmo Skin Art +128667037,EleuThermals,3230624768,Consumer Items,Eleu Thermals +128667038,EshuUmbrellas,3222295552,Consumer Items,Eshu Umbrellas +128667039,KaretiiCouture,3227333120,Consumer Items,Karetii Couture +128667040,NjangariSaddles,3222416896,Consumer Items,Njangari Saddles +128667041,AnyNaCoffee,3229880064,Foods,Any Na Coffee +128667042,CD75CatCoffee,3228566016,Foods,CD-75 Kitten Brand Coffee +128667043,GomanYauponCoffee,3224449792,Foods,Goman Yaupon Coffee +128667044,VolkhabBeeDrones,3227831808,Machinery,Volkhab Bee Drones +128667045,KinagoInstruments,3227394304,Consumer Items,Kinago Violins +128667046,NgunaModernAntiques,3221538304,Consumer Items,Nguna Modern Antiques +128667047,RajukruStoves,3227512320,Consumer Items,Rajukru Multi-Stoves +128667048,TiolceWaste2PasteUnits,3224141312,Consumer Items,Tiolce Waste2Paste Units +128667049,ChiEridaniMarinePaste,128128760,Foods,Chi Eridani Marine Paste +128667050,EsusekuCaviar,3226919680,Foods,Esuseku Caviar +128667051,LiveHecateSeaWorms,128042496,Foods,Live Hecate Sea Worms +128667052,HelvetitjPearls,3231094528,Metals,Helvetitj Pearls +128667053,HIP41181Squid,3227995392,Foods,HIP Proto-Squid +128667054,CoquimSpongiformVictuals,3223832576,Foods,Coquim Spongiform Victuals +128667055,AerialEdenApple,128083448,Foods,Eden Apples of Aerial +128667056,NeritusBerries,3228206080,Foods,Neritus Berries +128667057,OchoengChillies,3226719232,Foods,Ochoeng Chillies +128667058,DeuringasTruffles,3229713408,Foods,Deuringas Truffles +128667059,HR7221Wheat,3226170880,Foods,HR 7221 Wheat +128667060,JarouaRice,3224698112,Foods,Jaroua Rice +128667061,BelalansRayLeather,3223537152,Textiles,Belalans Ray Leather +128667062,DamnaCarapaces,3227751936,Textiles,Damna Carapaces +128667063,RapaBaoSnakeSkins,3222875648,Textiles,Rapa Bao Snake Skins +128667064,VanayequiRhinoFur,3227289856,Textiles,Vanayequi Ceratomorpha Fur +128667065,BastSnakeGin,128086776,Legal Drugs,Bast Snake Gin +128667066,ThrutisCream,3226522368,Legal Drugs,Thrutis Cream +128667067,WulpaHyperboreSystems,3221388032,Machinery,Wulpa Hyperbore Systems +128667068,AganippeRush,128012800,Medicines,Aganippe Rush +128667069,TerraMaterBloodBores,128051466,Medicines,Terra Mater Blood Bores +128667070,HolvaDuellingBlades,3222713088,Weapons,Holva Duelling Blades +128667071,KamorinHistoricWeapons,3221669632,Weapons,Kamorin Historic Weapons +128667072,GilyaSignatureWeapons,3226857216,Weapons,Gilya Signature Weapons +128667073,DeltaPhoenicisPalms,128045312,Chemicals,Delta Phoenicis Palms +128667074,ToxandjiVirocide,3230258688,Chemicals,Toxandji Virocide +128667075,XiheCompanions,3224133120,Technology,Xihe Biomorphic Companions +128667076,SanumaMEAT,3230331136,Foods,Sanuma Decorative Meat +128667077,EthgrezeTeaBuds,3229524992,Foods,Ethgreze Tea Buds +128667078,CeremonialHeikeTea,3227417856,Foods,Ceremonial Heike Tea +128667079,TanmarkTranquilTea,128057866,Foods,Tanmark Tranquil Tea +128667080,AZCancriFormula42,3228400128,Technology,AZ Cancri Formula 42 +128667081,KamitraCigars,3225450752,Legal Drugs,Kamitra Cigars +128667082,RusaniOldSmokey,3229255680,Legal Drugs,Rusani Old Smokey +128667083,YasoKondiLeaf,3223088640,Legal Drugs,Yaso Kondi Leaf +128667084,ChateauDeAegaeon,3228416768,Legal Drugs,Chateau De Aegaeon +128667085,WatersOfShintara,128666762,Medicines,The Waters of Shintara +128667668,OphiuchiExinoArtefacts,3228939264,Consumer Items,Ophiuch Exino Artefacts +128667669,BakedGreebles,3229378560,Foods,Baked Greebles +128667670,CetiAepyornisEgg,3222560256,Foods,Aepyornis Egg +128667671,SaxonWine,3227986432,Legal Drugs,Saxon Wine +128667672,CentauriMegaGin,3228728832,Legal Drugs,Centauri Mega Gin +128667673,AnduligaFireWorks,3230243584,Consumer Items,Anduliga Fire Works +128667674,BankiAmphibiousLeather,3228346112,Textiles,Banki Amphibious Leather +128667675,CherbonesBloodCrystals,3229594624,Metals,Cherbones Blood Crystals +128667676,MotronaExperienceJelly,3229750528,Legal Drugs,Motrona Experience Jelly +128667677,GeawenDanceDust,3230954752,Legal Drugs,Geawen Dance Dust +128667678,GerasianGueuzeBeer,3228047360,Legal Drugs,Gerasian Gueuze Beer +128667679,HaidneBlackBrew,3226557696,Foods,Haiden Black Brew +128667680,HavasupaiDreamCatcher,3221438976,Consumer Items,Havasupai Dream Catcher +128667681,BurnhamBileDistillate,3230224384,Legal Drugs,Burnham Bile Distillate +128667682,HIPOrganophosphates,3227036160,Chemicals,HIP Organophosphates +128667683,JaradharrePuzzlebox,3230754816,Consumer Items,Jaradharre Puzzle Box +128667684,KorroKungPellets,3228726272,Chemicals,Koro Kung Pellets +128667685,LFTVoidExtractCoffee,3229028864,Foods,Void Extract Coffee +128667686,HonestyPills,3229561344,Medicines,Honesty Pills +128667687,NonEuclidianExotanks,3224135424,Machinery,Non Euclidian Exotanks +128667688,LTTHyperSweet,3224166400,Foods,LTT Hyper Sweet +128667689,MechucosHighTea,3228398848,Foods,Mechucos High Tea +128667690,MedbStarlube,3228762368,Chemicals,Medb Starlube +128667691,MokojingBeastFeast,3229612800,Foods,Mokojing Beast Feast +128667692,MukusubiiChitinOs,3221719296,Foods,Mukusubii Chitin-os +128667693,MulachiGiantFungus,3228892672,Foods,Mulachi Giant Fungus +128667694,NgadandariFireOpals,3226127872,Metals,Ngadandari Fire Opals +128667695,TiegfriesSynthSilk,3227726848,Textiles,Tiegfries Synth Silk +128667696,UzumokuLowGWings,3226474496,Consumer Items,Uzumoku Low-G Wings +128667697,VHerculisBodyRub,3228959232,Medicines,V Herculis Body Rub +128667698,WheemeteWheatCakes,3225032704,Foods,Wheemete Wheat Cakes +128667699,VegaSlimWeed,128149240,Medicines,Vega Slimweed +128667700,AltairianSkin,128151032,Consumer Items,Altairian Skin +128667701,PavonisEarGrubs,128117240,Legal Drugs,Pavonis Ear Grubs +128667702,JotunMookah,128078840,Textiles,Jotun Mookah +128667703,GiantVerrix,128121336,Machinery,Giant Verrix +128667704,IndiBourbon,128118520,Legal Drugs,Indi Bourbon +128667705,AroucaConventualSweets,128098040,Foods,Arouca Conventual Sweets +128667706,TauriChimes,128134648,Consumer Items,Tauri Chimes +128667707,ZeesszeAntGlue,128125432,Consumer Items,Zeessze Ant Grub Glue +128667708,PantaaPrayerSticks,3228824064,Medicines,Pantaa Prayer Sticks +128667709,FujinTea,128134392,Foods,Fujin Tea +128667710,ChameleonCloth,3223418880,Textiles,Chameleon Cloth +128667711,OrrerianViciousBrew,128166392,Foods,Orrerian Vicious Brew +128667712,UszaianTreeGrub,128164856,Foods,Uszaian Tree Grub +128667713,MomusBogSpaniel,128075256,Consumer Items,Momus Bog Spaniel +128667714,DisoMaCorn,128161016,Foods,Diso Ma Corn +128667715,LeestianEvilJuice,128639992,Legal Drugs,Leestian Evil Juice +128667716,BlueMilk,128639992,Foods,Azure Milk +128667717,AlienEggs,128164088,Consumer Items,Leathery Eggs +128667718,AlyaBodilySoap,3221638400,Medicines,Alya Body Soap +128667719,VidavantianLace,3231082240,Consumer Items,Vidavantian Lace +128667760,TransgenicOnionHead,128057866,Legal Drugs,Lucan Onionhead +128668017,JaquesQuinentianStill,128667761,Consumer Items,Jaques Quinentian Still +128668018,SoontillRelics,3225348096,Consumer Items,Soontill Relics +128671119,Advert1,3227172352,Consumer Items,Ultra-Compact Processor Prototypes +128672121,TheHuttonMug,3228728832,Consumer Items,The Hutton Mug +128672122,SothisCrystallineGold,128668557,Metals,Sothis Crystalline Gold +128672316,MasterChefs,128123640,Slavery,Master Chefs +128672431,PersonalGifts,128081912,Salvage,Festive Gifts +128672432,CrystallineSpheres,128059402,Salvage,Crystalline Spheres +128672812,OnionHeadA,3226977024,Legal Drugs,Onionhead Alpha Strain +128673069,OnionHeadB,3223027200,Legal Drugs,Onionhead Beta Strain +128682050,GalacticTravelGuide,128673074,Salvage,Galactic Travel Guide +128727921,AnimalEffigies,3228463360,Legal Drugs,Crom Silver Fesh +128732551,ShansCharisOrchid,128107768,Consumer Items,Shan's Charis Orchid +128748428,BuckyballBeerMats,128745551,Consumer Items,Buckyball Beer Mats +128793113,HarmaSilverSeaRum,3221575424,Legal Drugs,Harma Silver Sea Rum +128793114,PlatinumAloy,3223779840,Metals,Platinum Alloy +128913661,Nanomedicines,3226651904,Medicines,Nanomedicines +128922524,Duradrives,3223453184,Consumer Items,Duradrives +128958679,ApaVietii,128958681,Legal Drugs,Apa Vietii +129002574,ClassifiedExperimentalEquipment,128986325,Technology,Classified Experimental Equipment diff --git a/L10n/en.template b/L10n/en.template index 200743c82..a6418debc 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -207,12 +207,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: Missing Commodity Files"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Plugins"; diff --git a/build.py b/build.py index 851f391b2..440fd414f 100644 --- a/build.py +++ b/build.py @@ -21,6 +21,7 @@ _static_appversion, update_interval ) +from update import check_for_fdev_updates def iss_build(template_path: str, output_file: str) -> None: @@ -198,4 +199,5 @@ def build() -> None: if __name__ == "__main__": + check_for_fdev_updates() build() diff --git a/update.py b/update.py index 991558d64..e1dc4976d 100644 --- a/update.py +++ b/update.py @@ -7,6 +7,7 @@ """ from __future__ import annotations +import pathlib import sys import threading from traceback import print_exc @@ -25,6 +26,37 @@ def _(x: str): return x logger = get_main_logger() +def check_for_fdev_updates(silent: bool = False) -> None: # noqa: CCR001 + """Check for and download FDEV ID file updates.""" + files_urls = [ + ('commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/commodity.csv'), + ('rare_commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/rare_commodity.csv') + ] + + for file, url in files_urls: + fdevid_file = pathlib.Path(config.respath_path / 'FDevIDs' / file) + try: + with open(fdevid_file, newline='', encoding='utf-8') as f: + local_content = f.read() + except FileNotFoundError: + local_content = None + + response = requests.get(url) + if response.status_code != 200: + if not silent: + logger.error(f'Failed to download {file}! Unable to continue.') + continue + + if local_content == response.text: + if not silent: + logger.info(f'FDEV ID file {file} already up to date.') + else: + if not silent: + logger.info(f'FDEV ID file {file} not up to date. Downloading...') + with open(fdevid_file, 'w', newline='', encoding='utf-8') as csvfile: + csvfile.write(response.text) + + class EDMCVersion: """ Hold all the information about an EDMC version. @@ -135,6 +167,8 @@ def check_for_updates(self) -> None: elif sys.platform == 'win32' and self.updater: self.updater.win_sparkle_check_update_with_ui() + check_for_fdev_updates() + def check_appcast(self) -> EDMCVersion | None: """ Manually (no Sparkle or WinSparkle) check the update_feed appcast file. From 4cc41a8c494f42023307694524cb81a02e499250 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 28 Apr 2024 15:30:24 -0400 Subject: [PATCH 168/261] [1801] Remove Default Files We don't need to track these in our repo. They are pulled automatically. Yay! --- FDevIDs/commodity.csv | 255 ------------------------------------- FDevIDs/rare_commodity.csv | 143 --------------------- 2 files changed, 398 deletions(-) delete mode 100644 FDevIDs/commodity.csv delete mode 100644 FDevIDs/rare_commodity.csv diff --git a/FDevIDs/commodity.csv b/FDevIDs/commodity.csv deleted file mode 100644 index 1a541cb2c..000000000 --- a/FDevIDs/commodity.csv +++ /dev/null @@ -1,255 +0,0 @@ -id,symbol,category,name -128049152,Platinum,Metals,Platinum -128049153,Palladium,Metals,Palladium -128049154,Gold,Metals,Gold -128049155,Silver,Metals,Silver -128049156,Bertrandite,Minerals,Bertrandite -128049157,Indite,Minerals,Indite -128049158,Gallite,Minerals,Gallite -128049159,Coltan,Minerals,Coltan -128049160,Uraninite,Minerals,Uraninite -128049161,Lepidolite,Minerals,Lepidolite -128049162,Cobalt,Metals,Cobalt -128049163,Rutile,Minerals,Rutile -128049165,Bauxite,Minerals,Bauxite -128049166,Water,Chemicals,Water -128049168,Beryllium,Metals,Beryllium -128049169,Indium,Metals,Indium -128049170,Gallium,Metals,Gallium -128049171,Tantalum,Metals,Tantalum -128049172,Uranium,Metals,Uranium -128049173,Lithium,Metals,Lithium -128049174,Titanium,Metals,Titanium -128049175,Copper,Metals,Copper -128049176,Aluminium,Metals,Aluminium -128049177,Algae,Foods,Algae -128049178,FruitAndVegetables,Foods,Fruit and Vegetables -128049180,Grain,Foods,Grain -128049182,Animalmeat,Foods,Animal Meat -128049183,Fish,Foods,Fish -128049184,FoodCartridges,Foods,Food Cartridges -128049185,SyntheticMeat,Foods,Synthetic Meat -128049188,Tea,Foods,Tea -128049189,Coffee,Foods,Coffee -128049190,Leather,Textiles,Leather -128049191,NaturalFabrics,Textiles,Natural Fabrics -128049193,SyntheticFabrics,Textiles,Synthetic Fabrics -128049197,Polymers,Industrial Materials,Polymers -128049199,Semiconductors,Industrial Materials,Semiconductors -128049200,Superconductors,Industrial Materials,Superconductors -128049202,HydrogenFuel,Chemicals,Hydrogen Fuel -128049203,MineralOil,Chemicals,Mineral Oil -128049204,Explosives,Chemicals,Explosives -128049205,Pesticides,Chemicals,Pesticides -128049208,AgriculturalMedicines,Medicines,Agri-Medicines -128049209,PerformanceEnhancers,Medicines,Performance Enhancers -128049210,BasicMedicines,Medicines,Basic Medicines -128049212,BasicNarcotics,Legal Drugs,Narcotics -128049213,Tobacco,Legal Drugs,Tobacco -128049214,Beer,Legal Drugs,Beer -128049215,Wine,Legal Drugs,Wine -128049216,Liquor,Legal Drugs,Liquor -128049217,PowerGenerators,Machinery,Power Generators -128049218,WaterPurifiers,Machinery,Water Purifiers -128049220,HeliostaticFurnaces,Machinery,Microbial Furnaces -128049221,MineralExtractors,Machinery,Mineral Extractors -128049222,CropHarvesters,Machinery,Crop Harvesters -128049223,MarineSupplies,Machinery,Marine Equipment -128049225,ComputerComponents,Technology,Computer Components -128049226,HazardousEnvironmentSuits,Technology,H.E. Suits -128049227,Robotics,Technology,Robotics -128049228,AutoFabricators,Technology,Auto-Fabricators -128049229,AnimalMonitors,Technology,Animal Monitors -128049230,AquaponicSystems,Technology,Aquaponic Systems -128049231,AdvancedCatalysers,Technology,Advanced Catalysers -128049232,TerrainEnrichmentSystems,Technology,Land Enrichment Systems -128049233,PersonalWeapons,Weapons,Personal Weapons -128049234,BattleWeapons,Weapons,Battle Weapons -128049235,ReactiveArmour,Weapons,Reactive Armour -128049236,NonLethalWeapons,Weapons,Non-Lethal Weapons -128049238,DomesticAppliances,Consumer Items,Domestic Appliances -128049240,ConsumerTechnology,Consumer Items,Consumer Technology -128049241,Clothing,Consumer Items,Clothing -128049243,Slaves,Slavery,Slaves -128049244,Biowaste,Waste,Biowaste -128049245,ToxicWaste,Waste,Toxic Waste -128049246,ChemicalWaste,Waste,Chemical Waste -128049248,Scrap,Waste,Scrap -128049669,ProgenitorCells,Medicines,Progenitor Cells -128049670,CombatStabilisers,Medicines,Combat Stabilisers -128049671,ResonatingSeparators,Technology,Resonating Separators -128049672,BioReducingLichen,Technology,Bioreducing Lichen -128064028,AtmosphericExtractors,Machinery,Atmospheric Processors -128066403,Drones,NonMarketable,Limpets -128666752,USSCargoBlackBox,Salvage,Black Box -128666754,USSCargoTradeData,Salvage,Trade Data -128666755,USSCargoMilitaryPlans,Salvage,Military Plans -128666756,USSCargoAncientArtefact,Salvage,Ancient Artefact -128666757,USSCargoRareArtwork,Salvage,Rare Artwork -128666758,USSCargoExperimentalChemicals,Salvage,Experimental Chemicals -128666759,USSCargoRebelTransmissions,Salvage,Rebel Transmissions -128666760,USSCargoPrototypeTech,Salvage,Prototype Tech -128666761,USSCargoTechnicalBlueprints,Salvage,Technical Blueprints -128667728,ImperialSlaves,Slavery,Imperial Slaves -128668547,UnknownArtifact,Salvage,Thargoid Sensor -128668548,AiRelics,Salvage,AI Relics -128668549,Hafnium178,Metals,Hafnium 178 -128668550,Painite,Minerals,Painite -128668551,Antiquities,Salvage,Antiquities -128668552,MilitaryIntelligence,Salvage,Military Intelligence -128671118,Osmium,Metals,Osmium -128671443,SAP8CoreContainer,Salvage,SAP 8 Core Container -128671444,TrinketsOfFortune,Consumer Items,Trinkets of Hidden Fortune -128672123,WreckageComponents,Salvage,Wreckage Components -128672124,EncriptedDataStorage,Salvage,Encrypted Data Storage -128672125,OccupiedCryoPod,Salvage,Occupied Escape Pod -128672126,PersonalEffects,Salvage,Personal Effects -128672127,ComercialSamples,Salvage,Commercial Samples -128672128,TacticalData,Salvage,Tactical Data -128672129,AssaultPlans,Salvage,Assault Plans -128672130,EncryptedCorrespondence,Salvage,Encrypted Correspondence -128672131,DiplomaticBag,Salvage,Diplomatic Bag -128672132,ScientificResearch,Salvage,Scientific Research -128672133,ScientificSamples,Salvage,Scientific Samples -128672134,PoliticalPrisoner,Salvage,Political Prisoners -128672135,Hostage,Salvage,Hostages -128672136,LargeExplorationDataCash,Salvage,Large Survey Data Cache -128672137,SmallExplorationDataCash,Salvage,Small Survey Data Cache -128672159,AntiqueJewellery,Salvage,Antique Jewellery -128672160,PreciousGems,Salvage,Precious Gems -128672161,EarthRelics,Salvage,Earth Relics -128672162,GeneBank,Salvage,Gene Bank -128672163,TimeCapsule,Salvage,Time Capsule -128672294,Cryolite,Minerals,Cryolite -128672295,Goslarite,Minerals,Goslarite -128672296,Moissanite,Minerals,Moissanite -128672297,Pyrophyllite,Minerals,Pyrophyllite -128672298,Lanthanum,Metals,Lanthanum -128672299,Thallium,Metals,Thallium -128672300,Bismuth,Metals,Bismuth -128672301,Thorium,Metals,Thorium -128672302,CeramicComposites,Industrial Materials,Ceramic Composites -128672303,SyntheticReagents,Chemicals,Synthetic Reagents -128672304,NerveAgents,Chemicals,Nerve Agents -128672305,SurfaceStabilisers,Chemicals,Surface Stabilisers -128672306,BootlegLiquor,Legal Drugs,Bootleg Liquor -128672307,GeologicalEquipment,Machinery,Geological Equipment -128672308,ThermalCoolingUnits,Machinery,Thermal Cooling Units -128672309,BuildingFabricators,Machinery,Building Fabricators -128672310,MuTomImager,Technology,Muon Imager -128672311,StructuralRegulators,Technology,Structural Regulators -128672312,Landmines,Weapons,Landmines -128672313,SkimerComponents,Machinery,Skimmer Components -128672314,EvacuationShelter,Consumer Items,Evacuation Shelter -128672315,GeologicalSamples,Salvage,Geological Samples -128672701,MetaAlloys,Industrial Materials,Meta-Alloys -128672775,Taaffeite,Minerals,Taaffeite -128672776,Jadeite,Minerals,Jadeite -128672810,UnstableDataCore,Salvage,Unstable Data Core -128672811,DamagedEscapePod,Salvage,Damaged Escape Pod -128673845,Praseodymium,Metals,Praseodymium -128673846,Bromellite,Minerals,Bromellite -128673847,Samarium,Metals,Samarium -128673848,LowTemperatureDiamond,Minerals,Low Temperature Diamonds -128673850,HydrogenPeroxide,Chemicals,Hydrogen Peroxide -128673851,LiquidOxygen,Chemicals,Liquid oxygen -128673852,MethanolMonohydrateCrystals,Minerals,Methanol Monohydrate Crystals -128673853,LithiumHydroxide,Minerals,Lithium Hydroxide -128673854,MethaneClathrate,Minerals,Methane Clathrate -128673855,InsulatingMembrane,Industrial Materials,Insulating Membrane -128673856,CMMComposite,Industrial Materials,CMM Composite -128673857,CoolingHoses,Industrial Materials,Micro-weave Cooling Hoses -128673858,NeofabricInsulation,Industrial Materials,Neofabric Insulation -128673859,ArticulationMotors,Machinery,Articulation Motors -128673860,HNShockMount,Machinery,HN Shock Mount -128673861,EmergencyPowerCells,Machinery,Emergency Power Cells -128673862,PowerConverter,Machinery,Power Converter -128673863,PowerGridAssembly,Machinery,Energy Grid Assembly -128673864,PowerTransferConduits,Machinery,Power Transfer Bus -128673865,RadiationBaffle,Machinery,Radiation Baffle -128673866,ExhaustManifold,Machinery,Exhaust Manifold -128673867,ReinforcedMountingPlate,Machinery,Reinforced Mounting Plate -128673868,HeatsinkInterlink,Machinery,Heatsink Interlink -128673869,MagneticEmitterCoil,Machinery,Magnetic Emitter Coil -128673870,ModularTerminals,Machinery,Modular Terminals -128673871,Nanobreakers,Technology,Nanobreakers -128673872,TelemetrySuite,Technology,Telemetry Suite -128673873,MicroControllers,Technology,Micro Controllers -128673874,IonDistributor,Machinery,Ion Distributor -128673875,DiagnosticSensor,Technology,Hardware Diagnostic Sensor -128673876,UnknownArtifact2,Salvage,Thargoid Probe -128682044,ConductiveFabrics,Textiles,Conductive Fabrics -128682045,MilitaryGradeFabrics,Textiles,Military Grade Fabrics -128682046,AdvancedMedicines,Medicines,Advanced Medicines -128682047,MedicalDiagnosticEquipment,Technology,Medical Diagnostic Equipment -128682048,SurvivalEquipment,Consumer Items,Survival Equipment -128682049,DataCore,Salvage,Data Core -128682051,MysteriousIdol,Salvage,Mysterious Idol -128682052,ProhibitedResearchMaterials,Salvage,Prohibited Research Materials -128682053,AntimatterContainmentUnit,Salvage,Antimatter Containment Unit -128682054,SpacePioneerRelics,Salvage,Space Pioneer Relics -128682055,FossilRemnants,Salvage,Fossil Remnants -128732183,AncientRelic,Salvage,Guardian Relic -128732184,AncientOrb,Salvage,Guardian Orb -128732185,AncientCasket,Salvage,Guardian Casket -128732186,AncientTablet,Salvage,Guardian Tablet -128732187,AncientUrn,Salvage,Guardian Urn -128732188,AncientTotem,Salvage,Guardian Totem -128737287,UnknownResin,Salvage,Thargoid Resin -128737288,UnknownBiologicalMatter,Salvage,Thargoid Biological Matter -128737289,UnknownTechnologySamples,Salvage,Thargoid Technology Samples -128740752,UnknownArtifact3,Salvage,Thargoid Link -128793127,ThargoidHeart,Salvage,Thargoid Heart -128793128,ThargoidTissueSampleType1,Salvage,Thargoid Cyclops Tissue Sample -128793129,ThargoidTissueSampleType2,Salvage,Thargoid Basilisk Tissue Sample -128793130,ThargoidTissueSampleType3,Salvage,Thargoid Medusa Tissue Sample -128824468,ThargoidScoutTissueSample,Salvage,Thargoid Scout Tissue Sample -128888499,AncientKey,Salvage,Ancient Key -128902652,ThargoidTissueSampleType4,Salvage,Thargoid Hydra Tissue Sample -128922517,M_TissueSample_Fluid,Salvage,Mollusc Fluid -128922518,M_TissueSample_Soft,Salvage,Mollusc Soft Tissue -128922519,M_TissueSample_Nerves,Salvage,Mollusc Brain Tissue -128922520,S_TissueSample_Cells,Salvage,Pod Core Tissue -128922521,S_TissueSample_Surface,Salvage,Pod Dead Tissue -128922522,S_TissueSample_Core,Salvage,Pod Surface Tissue -128922523,P_ParticulateSample,Salvage,Anomaly Particles -128922781,S9_TissueSample_Shell,Salvage,Pod Tissue -128922782,M3_TissueSample_Membrane,Salvage,Mollusc Membrane -128922783,M3_TissueSample_Mycelium,Salvage,Mollusc Mycelium -128922784,M3_TissueSample_Spores,Salvage,Mollusc Spores -128922785,S6_TissueSample_Mesoglea,Salvage,Pod Mesoglea -128922786,S6_TissueSample_Cells,Salvage,Pod Outer Tissue -128922787,S6_TissueSample_Coenosarc,Salvage,Pod Shell Tissue -128924325,Rhodplumsite,Minerals,Rhodplumsite -128924326,Serendibite,Minerals,Serendibite -128924327,Monazite,Minerals,Monazite -128924328,Musgravite,Minerals,Musgravite -128924329,Benitoite,Minerals,Benitoite -128924330,Grandidierite,Minerals,Grandidierite -128924331,Alexandrite,Minerals,Alexandrite -128924332,Opal,Minerals,Void Opal -128924333,RockforthFertiliser,Chemicals,Rockforth Fertiliser -128924334,AgronomicTreatment,Chemicals,Agronomic Treatment -128961249,Tritium,Chemicals,Tritium -128983059,OnionHeadC,Legal Drugs,Onionhead Gamma Strain -129015433,AncientRelicTG,Salvage,Unclassified Relic -129019258,ThargoidTissueSampleType5,Salvage,Thargoid Orthrus Tissue Sample -129019259,ThargoidGeneratorTissueSample,Salvage,Caustic Tissue Sample -129022087,UnocuppiedEscapePod,Salvage,Unoccupied Escape Pod -129022395,ThargoidTissueSampleType6,Salvage,Thargoid Glaive Tissue Sample -129022396,ThargoidTissueSampleType7,Salvage,Thargoid Scythe Tissue Sample -129022398,ThargoidTissueSampleType9a,Salvage,Titan Deep Tissue Sample -129022399,ThargoidTissueSampleType9b,Salvage,Titan Tissue Sample -129022400,ThargoidTissueSampleType9c,Salvage,Titan Partial Tissue Sample -129022402,ThargoidTissueSampleType10a,Salvage,Titan Maw Deep Tissue Sample -129022403,ThargoidTissueSampleType10b,Salvage,Titan Maw Tissue Sample -129022404,ThargoidTissueSampleType10c,Salvage,Titan Maw Partial Tissue Sample -129022405,UnknownSack,Salvage,Protective Membrane Scrap -129022406,ThargoidPod,Salvage,Xenobiological Prison Pod -129022407,CoralSap,Salvage,Coral Sap -129022408,UnknownMineral,Salvage,Impure Spire Mineral -129022409,UnknownRefinedMineral,Salvage,Semi-Refined Spire Mineral -129030459,ThargoidTitanDriveComponent,Salvage,Titan Drive Component -129030460,ThargoidCystSpecimen,Salvage,Cyst Specimen -129030461,ThargoidBoneFragments,Salvage,Bone Fragments -129030462,ThargoidOrganSample,Salvage,Organ Sample diff --git a/FDevIDs/rare_commodity.csv b/FDevIDs/rare_commodity.csv deleted file mode 100644 index 9997f1715..000000000 --- a/FDevIDs/rare_commodity.csv +++ /dev/null @@ -1,143 +0,0 @@ -id,symbol,market_id,category,name -128666746,EraninPearlWhisky,128001536,Legal Drugs,Eranin Pearl Whisky -128666747,LavianBrandy,128106744,Legal Drugs,Lavian Brandy -128667019,HIP10175BushMeat,3223234816,Foods,HIP 10175 Bush Meat -128667020,AlbinoQuechuaMammoth,3222822912,Foods,Albino Quechua Mammoth Meat -128667021,UtgaroarMillenialEggs,128037120,Foods,Utgaroar Millennial Eggs -128667022,WitchhaulKobeBeef,3223358720,Foods,Witchhaul Kobe Beef -128667023,KarsukiLocusts,3225028096,Foods,Karsuki Locusts -128667024,GiantIrukamaSnails,3225345792,Foods,Giant Irukama Snails -128667025,BaltahSineVacuumKrill,128088056,Foods,Baltah'sine Vacuum Krill -128667026,CetiRabbits,3222560000,Foods,Ceti Rabbits -128667027,KachiriginLeaches,3221595648,Medicines,Kachirigin Filter Leeches -128667028,LyraeWeed,3226417152,Legal Drugs,Lyrae Weed -128667029,OnionHead,128129272,Legal Drugs,Onionhead -128667030,TarachTorSpice,128041984,Legal Drugs,Tarach Spice -128667031,Wolf1301Fesh,128084984,Legal Drugs,Wolf Fesh -128667032,BorasetaniPathogenetics,3229638400,Weapons,Borasetani Pathogenetics -128667033,HIP118311Swarm,3223177472,Weapons,HIP 118311 Swarm -128667034,KonggaAle,3226978048,Legal Drugs,Kongga Ale -128667035,WuthieloKuFroth,3222155776,Legal Drugs,Wuthielo Ku Froth -128667036,AlacarakmoSkinArt,3231373824,Consumer Items,Alacarakmo Skin Art -128667037,EleuThermals,3230624768,Consumer Items,Eleu Thermals -128667038,EshuUmbrellas,3222295552,Consumer Items,Eshu Umbrellas -128667039,KaretiiCouture,3227333120,Consumer Items,Karetii Couture -128667040,NjangariSaddles,3222416896,Consumer Items,Njangari Saddles -128667041,AnyNaCoffee,3229880064,Foods,Any Na Coffee -128667042,CD75CatCoffee,3228566016,Foods,CD-75 Kitten Brand Coffee -128667043,GomanYauponCoffee,3224449792,Foods,Goman Yaupon Coffee -128667044,VolkhabBeeDrones,3227831808,Machinery,Volkhab Bee Drones -128667045,KinagoInstruments,3227394304,Consumer Items,Kinago Violins -128667046,NgunaModernAntiques,3221538304,Consumer Items,Nguna Modern Antiques -128667047,RajukruStoves,3227512320,Consumer Items,Rajukru Multi-Stoves -128667048,TiolceWaste2PasteUnits,3224141312,Consumer Items,Tiolce Waste2Paste Units -128667049,ChiEridaniMarinePaste,128128760,Foods,Chi Eridani Marine Paste -128667050,EsusekuCaviar,3226919680,Foods,Esuseku Caviar -128667051,LiveHecateSeaWorms,128042496,Foods,Live Hecate Sea Worms -128667052,HelvetitjPearls,3231094528,Metals,Helvetitj Pearls -128667053,HIP41181Squid,3227995392,Foods,HIP Proto-Squid -128667054,CoquimSpongiformVictuals,3223832576,Foods,Coquim Spongiform Victuals -128667055,AerialEdenApple,128083448,Foods,Eden Apples of Aerial -128667056,NeritusBerries,3228206080,Foods,Neritus Berries -128667057,OchoengChillies,3226719232,Foods,Ochoeng Chillies -128667058,DeuringasTruffles,3229713408,Foods,Deuringas Truffles -128667059,HR7221Wheat,3226170880,Foods,HR 7221 Wheat -128667060,JarouaRice,3224698112,Foods,Jaroua Rice -128667061,BelalansRayLeather,3223537152,Textiles,Belalans Ray Leather -128667062,DamnaCarapaces,3227751936,Textiles,Damna Carapaces -128667063,RapaBaoSnakeSkins,3222875648,Textiles,Rapa Bao Snake Skins -128667064,VanayequiRhinoFur,3227289856,Textiles,Vanayequi Ceratomorpha Fur -128667065,BastSnakeGin,128086776,Legal Drugs,Bast Snake Gin -128667066,ThrutisCream,3226522368,Legal Drugs,Thrutis Cream -128667067,WulpaHyperboreSystems,3221388032,Machinery,Wulpa Hyperbore Systems -128667068,AganippeRush,128012800,Medicines,Aganippe Rush -128667069,TerraMaterBloodBores,128051466,Medicines,Terra Mater Blood Bores -128667070,HolvaDuellingBlades,3222713088,Weapons,Holva Duelling Blades -128667071,KamorinHistoricWeapons,3221669632,Weapons,Kamorin Historic Weapons -128667072,GilyaSignatureWeapons,3226857216,Weapons,Gilya Signature Weapons -128667073,DeltaPhoenicisPalms,128045312,Chemicals,Delta Phoenicis Palms -128667074,ToxandjiVirocide,3230258688,Chemicals,Toxandji Virocide -128667075,XiheCompanions,3224133120,Technology,Xihe Biomorphic Companions -128667076,SanumaMEAT,3230331136,Foods,Sanuma Decorative Meat -128667077,EthgrezeTeaBuds,3229524992,Foods,Ethgreze Tea Buds -128667078,CeremonialHeikeTea,3227417856,Foods,Ceremonial Heike Tea -128667079,TanmarkTranquilTea,128057866,Foods,Tanmark Tranquil Tea -128667080,AZCancriFormula42,3228400128,Technology,AZ Cancri Formula 42 -128667081,KamitraCigars,3225450752,Legal Drugs,Kamitra Cigars -128667082,RusaniOldSmokey,3229255680,Legal Drugs,Rusani Old Smokey -128667083,YasoKondiLeaf,3223088640,Legal Drugs,Yaso Kondi Leaf -128667084,ChateauDeAegaeon,3228416768,Legal Drugs,Chateau De Aegaeon -128667085,WatersOfShintara,128666762,Medicines,The Waters of Shintara -128667668,OphiuchiExinoArtefacts,3228939264,Consumer Items,Ophiuch Exino Artefacts -128667669,BakedGreebles,3229378560,Foods,Baked Greebles -128667670,CetiAepyornisEgg,3222560256,Foods,Aepyornis Egg -128667671,SaxonWine,3227986432,Legal Drugs,Saxon Wine -128667672,CentauriMegaGin,3228728832,Legal Drugs,Centauri Mega Gin -128667673,AnduligaFireWorks,3230243584,Consumer Items,Anduliga Fire Works -128667674,BankiAmphibiousLeather,3228346112,Textiles,Banki Amphibious Leather -128667675,CherbonesBloodCrystals,3229594624,Metals,Cherbones Blood Crystals -128667676,MotronaExperienceJelly,3229750528,Legal Drugs,Motrona Experience Jelly -128667677,GeawenDanceDust,3230954752,Legal Drugs,Geawen Dance Dust -128667678,GerasianGueuzeBeer,3228047360,Legal Drugs,Gerasian Gueuze Beer -128667679,HaidneBlackBrew,3226557696,Foods,Haiden Black Brew -128667680,HavasupaiDreamCatcher,3221438976,Consumer Items,Havasupai Dream Catcher -128667681,BurnhamBileDistillate,3230224384,Legal Drugs,Burnham Bile Distillate -128667682,HIPOrganophosphates,3227036160,Chemicals,HIP Organophosphates -128667683,JaradharrePuzzlebox,3230754816,Consumer Items,Jaradharre Puzzle Box -128667684,KorroKungPellets,3228726272,Chemicals,Koro Kung Pellets -128667685,LFTVoidExtractCoffee,3229028864,Foods,Void Extract Coffee -128667686,HonestyPills,3229561344,Medicines,Honesty Pills -128667687,NonEuclidianExotanks,3224135424,Machinery,Non Euclidian Exotanks -128667688,LTTHyperSweet,3224166400,Foods,LTT Hyper Sweet -128667689,MechucosHighTea,3228398848,Foods,Mechucos High Tea -128667690,MedbStarlube,3228762368,Chemicals,Medb Starlube -128667691,MokojingBeastFeast,3229612800,Foods,Mokojing Beast Feast -128667692,MukusubiiChitinOs,3221719296,Foods,Mukusubii Chitin-os -128667693,MulachiGiantFungus,3228892672,Foods,Mulachi Giant Fungus -128667694,NgadandariFireOpals,3226127872,Metals,Ngadandari Fire Opals -128667695,TiegfriesSynthSilk,3227726848,Textiles,Tiegfries Synth Silk -128667696,UzumokuLowGWings,3226474496,Consumer Items,Uzumoku Low-G Wings -128667697,VHerculisBodyRub,3228959232,Medicines,V Herculis Body Rub -128667698,WheemeteWheatCakes,3225032704,Foods,Wheemete Wheat Cakes -128667699,VegaSlimWeed,128149240,Medicines,Vega Slimweed -128667700,AltairianSkin,128151032,Consumer Items,Altairian Skin -128667701,PavonisEarGrubs,128117240,Legal Drugs,Pavonis Ear Grubs -128667702,JotunMookah,128078840,Textiles,Jotun Mookah -128667703,GiantVerrix,128121336,Machinery,Giant Verrix -128667704,IndiBourbon,128118520,Legal Drugs,Indi Bourbon -128667705,AroucaConventualSweets,128098040,Foods,Arouca Conventual Sweets -128667706,TauriChimes,128134648,Consumer Items,Tauri Chimes -128667707,ZeesszeAntGlue,128125432,Consumer Items,Zeessze Ant Grub Glue -128667708,PantaaPrayerSticks,3228824064,Medicines,Pantaa Prayer Sticks -128667709,FujinTea,128134392,Foods,Fujin Tea -128667710,ChameleonCloth,3223418880,Textiles,Chameleon Cloth -128667711,OrrerianViciousBrew,128166392,Foods,Orrerian Vicious Brew -128667712,UszaianTreeGrub,128164856,Foods,Uszaian Tree Grub -128667713,MomusBogSpaniel,128075256,Consumer Items,Momus Bog Spaniel -128667714,DisoMaCorn,128161016,Foods,Diso Ma Corn -128667715,LeestianEvilJuice,128639992,Legal Drugs,Leestian Evil Juice -128667716,BlueMilk,128639992,Foods,Azure Milk -128667717,AlienEggs,128164088,Consumer Items,Leathery Eggs -128667718,AlyaBodilySoap,3221638400,Medicines,Alya Body Soap -128667719,VidavantianLace,3231082240,Consumer Items,Vidavantian Lace -128667760,TransgenicOnionHead,128057866,Legal Drugs,Lucan Onionhead -128668017,JaquesQuinentianStill,128667761,Consumer Items,Jaques Quinentian Still -128668018,SoontillRelics,3225348096,Consumer Items,Soontill Relics -128671119,Advert1,3227172352,Consumer Items,Ultra-Compact Processor Prototypes -128672121,TheHuttonMug,3228728832,Consumer Items,The Hutton Mug -128672122,SothisCrystallineGold,128668557,Metals,Sothis Crystalline Gold -128672316,MasterChefs,128123640,Slavery,Master Chefs -128672431,PersonalGifts,128081912,Salvage,Festive Gifts -128672432,CrystallineSpheres,128059402,Salvage,Crystalline Spheres -128672812,OnionHeadA,3226977024,Legal Drugs,Onionhead Alpha Strain -128673069,OnionHeadB,3223027200,Legal Drugs,Onionhead Beta Strain -128682050,GalacticTravelGuide,128673074,Salvage,Galactic Travel Guide -128727921,AnimalEffigies,3228463360,Legal Drugs,Crom Silver Fesh -128732551,ShansCharisOrchid,128107768,Consumer Items,Shan's Charis Orchid -128748428,BuckyballBeerMats,128745551,Consumer Items,Buckyball Beer Mats -128793113,HarmaSilverSeaRum,3221575424,Legal Drugs,Harma Silver Sea Rum -128793114,PlatinumAloy,3223779840,Metals,Platinum Alloy -128913661,Nanomedicines,3226651904,Medicines,Nanomedicines -128922524,Duradrives,3223453184,Consumer Items,Duradrives -128958679,ApaVietii,128958681,Legal Drugs,Apa Vietii -129002574,ClassifiedExperimentalEquipment,128986325,Technology,Classified Experimental Equipment From a6b46acc7531af5f6874f94e164f7b00a0f63484 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 28 Apr 2024 15:31:25 -0400 Subject: [PATCH 169/261] [1801] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a7da806e6..df50e32b1 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ pylint.txt # Ignore Submodule data directory coriolis-data/ +FDevIDs/ \ No newline at end of file From 960fb2dc828c00b56e98da1fdbaf3a27de07e583 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 28 Apr 2024 15:34:02 -0400 Subject: [PATCH 170/261] [1808] Add Folder If Not Exist --- update.py | 1 + 1 file changed, 1 insertion(+) diff --git a/update.py b/update.py index e1dc4976d..f7d56a5c1 100644 --- a/update.py +++ b/update.py @@ -35,6 +35,7 @@ def check_for_fdev_updates(silent: bool = False) -> None: # noqa: CCR001 for file, url in files_urls: fdevid_file = pathlib.Path(config.respath_path / 'FDevIDs' / file) + fdevid_file.parent.mkdir(parents=True, exist_ok=True) try: with open(fdevid_file, newline='', encoding='utf-8') as f: local_content = f.read() From 6dbdfe50b9f0249b86f0b6593742a6265e7f4a77 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 28 Apr 2024 16:28:28 -0400 Subject: [PATCH 171/261] [Docs] Add CodeQL Workflow and Security Guide --- .github/SECURITY.md | 13 +++++ .github/pull_request_template.md | 18 ++++++ .github/workflows/codeql.yml | 96 ++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 .github/SECURITY.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/codeql.yml diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 000000000..b8a0b2444 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,13 @@ +# Reporting Security Issues + +EDMC takes security very seriously. Our users trust us to provide a secure and safe tool to support their experience in Elite. + +In general, the best way to report a major security issue with us that should not be publically discussed is to email our maintainer teams. + +The best point of contact for this is rixxan@hullseals.space. When contacting, be sure to include as much information in your report. + +As soon as your report is processed, we'll get in touch to make sure we quickly move ahead with fixing the issue and will lay out a timeline for public disclosure and fixes. + +Another method of reporting vulnerabilities is to open a new Bug Report [here](https://github.com/EDCD/EDMarketConnector/issues/new?assignees=&labels=bug%2C+unconfirmed&projects=&template=bug_report.md&title=). + +If reporting a security issue here, do not include details as to the issue or steps to reproduce, simply indicate you have found a potential security bug and would like us to contact you directly. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..0d1c9ac12 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ + +# Description + + +# Example Images + + +# Type of Change + + +# How Tested + + +# Notes + diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..35a792fa9 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,96 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches-ignore: + - 'main' + - 'stable' + - 'releases' + - 'beta' + pull_request: + branches: [ develop ] + schedule: + - cron: '38 5 * * 4' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: 'ubuntu-latest' + timeout-minutes: 360 + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" From c48df655d1358c0a379c34e3d0a3437e00f226cb Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 30 Apr 2024 23:16:51 -0400 Subject: [PATCH 172/261] [#1462] LoadModule Replacement, Take Two The previous attempt was almost correct - but we needed to ensure that the system was aware of the module in order to handle it correctly. --- plug.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/plug.py b/plug.py index dad08bc60..11ed96bc0 100644 --- a/plug.py +++ b/plug.py @@ -47,14 +47,14 @@ def __init__(self) -> None: class Plugin: """An EDMC plugin.""" - def __init__(self, name: str, loadfile: str | None, plugin_logger: logging.Logger | None): + def __init__(self, name: str, loadfile: str | None, plugin_logger: logging.Logger | None): # noqa: CCR001 """ Load a single plugin. :param name: Base name of the file being loaded from. :param loadfile: Full path/filename of the plugin. :param plugin_logger: The logging instance for this plugin to use. - :raises Exception: Typically ImportError or OSError + :raises Exception: Typically, ImportError or OSError """ self.name: str = name # Display name. self.folder: str | None = name # basename of plugin folder. None for internal plugins. @@ -66,19 +66,23 @@ def __init__(self, name: str, loadfile: str | None, plugin_logger: logging.Logge try: filename = 'plugin_' filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_') - module = importlib.machinery.SourceFileLoader( - filename, - loadfile - ).load_module() - if getattr(module, 'plugin_start3', None): - newname = module.plugin_start3(os.path.dirname(loadfile)) - self.name = str(newname) if newname else self.name - self.module = module - elif getattr(module, 'plugin_start', None): - logger.warning(f'plugin {name} needs migrating\n') - PLUGINS_not_py3.append(self) + spec = importlib.util.spec_from_file_location(filename, loadfile) + + if spec is not None and spec.loader is not None: # Check if spec and spec.loader are not None + module = importlib.util.module_from_spec(spec) + sys.modules[module.__name__] = module + spec.loader.exec_module(module) + if getattr(module, 'plugin_start3', None): + newname = module.plugin_start3(os.path.dirname(loadfile)) + self.name = str(newname) if newname else self.name + self.module = module + elif getattr(module, 'plugin_start', None): + logger.warning(f'plugin {name} needs migrating\n') + PLUGINS_not_py3.append(self) + else: + logger.error(f'plugin {name} has no plugin_start3() function') else: - logger.error(f'plugin {name} has no plugin_start3() function') + logger.error(f'Failed to load Plugin "{name}" from file "{loadfile}"') except Exception: logger.exception(f': Failed for Plugin "{name}"') raise From 47f36a035c723c28b63f23944298f536e56860e2 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 1 May 2024 11:08:27 -0400 Subject: [PATCH 173/261] [1462] Remove Useless Comment Add Slightly More Useful Comment --- plug.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plug.py b/plug.py index 11ed96bc0..bd98f2bbd 100644 --- a/plug.py +++ b/plug.py @@ -67,8 +67,8 @@ def __init__(self, name: str, loadfile: str | None, plugin_logger: logging.Logge filename = 'plugin_' filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_') spec = importlib.util.spec_from_file_location(filename, loadfile) - - if spec is not None and spec.loader is not None: # Check if spec and spec.loader are not None + # Replaces older load_module() code. Includes a safety check that the module name is set. + if spec is not None and spec.loader is not None: module = importlib.util.module_from_spec(spec) sys.modules[module.__name__] = module spec.loader.exec_module(module) From fc2426d8b989df6ca21507c03063ca1790ada266 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 17:29:32 +0000 Subject: [PATCH 174/261] build(deps-dev): bump safety from 3.0.1 to 3.2.0 Bumps [safety](https://github.com/pyupio/safety) from 3.0.1 to 3.2.0. - [Release notes](https://github.com/pyupio/safety/releases) - [Changelog](https://github.com/pyupio/safety/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyupio/safety/compare/3.0.1...3.2.0) --- updated-dependencies: - dependency-name: safety dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 41c3a9281..428779078 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,7 +20,7 @@ flake8-use-fstring==1.4 mypy==1.9.0 pep8-naming==0.13.3 -safety==3.0.1 +safety==3.2.0 types-requests==2.31.0.20240311 types-pkg-resources==0.1.3 From 45cd577bec11d78caf309d750dfb8cca922a3417 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 17:29:49 +0000 Subject: [PATCH 175/261] build(deps-dev): bump pytest-cov from 4.1.0 to 5.0.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.1.0 to 5.0.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.1.0...v5.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 41c3a9281..ef2728ca2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -39,7 +39,7 @@ py2exe==0.13.0.1; sys_platform == 'win32' # Testing pytest==8.1.1 -pytest-cov==4.1.0 # Pytest code coverage support +pytest-cov==5.0.0 # Pytest code coverage support coverage[toml]==7.4.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 # For manipulating folder permissions and the like. From 9ce7206cae156247221d048e6e5c5f812ecc3929 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 17:29:51 +0000 Subject: [PATCH 176/261] build(deps-dev): bump flake8-json from 23.7.0 to 24.4.0 Bumps [flake8-json](https://github.com/pycqa/flake8-json) from 23.7.0 to 24.4.0. - [Commits](https://github.com/pycqa/flake8-json/compare/23.7.0...24.4.0) --- updated-dependencies: - dependency-name: flake8-json dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 41c3a9281..fdab1681e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -13,7 +13,7 @@ flake8-annotations-coverage==0.0.6 flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.14.0 flake8-docstrings==1.7.0 -flake8-json==23.7.0 +flake8-json==24.4.0 flake8-noqa==1.4.0 flake8-polyfill==1.0.2 flake8-use-fstring==1.4 From 53dd3e3ee5bd35e984007a6f063ecd437b2902f0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 1 May 2024 17:31:06 +0000 Subject: [PATCH 177/261] updating submodules --- coriolis-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coriolis-data b/coriolis-data index 05b16a4c7..8adfd86b6 160000 --- a/coriolis-data +++ b/coriolis-data @@ -1 +1 @@ -Subproject commit 05b16a4c716980ea95a46d29205f7d3b1f957fb4 +Subproject commit 8adfd86b64e8c14e873d2f5123d88ca6743420b9 From 763d8cc649dc51bdf58a07a8fb4196aa39a096e9 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 1 May 2024 16:21:57 -0400 Subject: [PATCH 178/261] [806] System Profiler Utility --- .github/ISSUE_TEMPLATE/bug_report.md | 35 +++-- EDMC_System_Profiler.py | 206 +++++++++++++++++++++++++++ EDMarketConnector.py | 2 + build.py | 11 +- prefs.py | 11 ++ 5 files changed, 252 insertions(+), 13 deletions(-) create mode 100644 EDMC_System_Profiler.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a75b9ef8b..7eb2c0f52 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,22 +7,19 @@ assignees: '' --- -**Please check the [Known Issues](https://github.com/EDCD/EDMarketConnector/issues/618) in case this has already been reported.** - -**Please also check if the issue is covered in our [Troubleshooting Guide](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting).** It might be something with a known work around, or where a third party (such as EDSM) is causing logging that is harmless. - **Please complete the following information:** - - Version: [e.g. 4.0.6 - See 'Help > About E:D Market Connector'. If running from source using git then please paste the output of `git log --decorate=full | head -1`] + +[//]: # (You can gather most of this information with the EDMC System Profiler) + + - Version: [e.g. 5.10.4+39af6c34`] - Game Version: [e.g. 'Live' or 'Odyssey'] - OS: [e.g. Windows 10, Linux Debian 10.6, etc.] - OS Locale: [e.g. English, French, Serbian...] - If applicable: Browser [e.g. chrome, safari] - - Please attach **BOTH** log files, by dragging and dropping them into this input: - 1. `%TEMP%\EDMarketConnector.log` from *immediately* after the bug occurs (re-running the application overwrites this file). - 1. `%TEMP%\EDMarketConnector\EDMarketConnector-debug.log`. See [Debug Log File](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#debug-log-files). NB: If you don't have this log file then you're not running the latest version of the application and should update first to see if we already fixed the bug you're reporting. **Describe the bug** -A clear and concise description of what the bug is. + +[//]: # (A clear and concise description of what the bug is.) **To Reproduce** Steps to reproduce the behavior: @@ -32,10 +29,24 @@ Steps to reproduce the behavior: 4. See error **Expected behavior** -A clear and concise description of what you expected to happen. + +[//]: # (A clear and concise description of what you expected to happen.) **Screenshots** -If applicable, add screenshots to help explain your problem. + +[//]: # (If applicable, add screenshots to help explain your problem.) **Additional context** -Add any other context about the problem here. + +**Please Confirm the Following...** + +[//]: # (Add any other context about the problem here.) +- [ ] I have checked the [Known Issues](https://github.com/EDCD/EDMarketConnector/issues/618) list to ensure this is not a duplicate +- [ ] I have checked the [Troubleshooting Guide](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting) to check for known workarounds + +**Logs** +Please attach both the EDMarketConnector.log and EDMarketConnector-debug.log if available. + +You can find these logs at `%TEMP%\EDMarketConnector.log` and `%TEMP%\EDMarketConnector\EDMarketConnector-debug.log` + +See [Debug Log File](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#debug-log-files) for information on the Debug Log files diff --git a/EDMC_System_Profiler.py b/EDMC_System_Profiler.py new file mode 100644 index 000000000..44dad70d4 --- /dev/null +++ b/EDMC_System_Profiler.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +""" +EDMC_System_Profiler.py - GUI or Command-Line Tool to Print Diagnostic Information about EDMC. + +Copyright (c) EDCD, All Rights Reserved +Licensed under the GNU General Public License. +See LICENSE file. +""" +import argparse +import locale +import webbrowser +import platform +import sys +from os import chdir, environ, path +import pathlib +import logging +from journal_lock import JournalLock + +if getattr(sys, "frozen", False): + # Under py2exe sys.path[0] is the executable name + if sys.platform == "win32": + chdir(path.dirname(sys.path[0])) + # Allow executable to be invoked from any cwd + environ["TCL_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tcl") + environ["TK_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tk") + +else: + # We still want to *try* to have CWD be where the main script is, even if + # not frozen. + chdir(pathlib.Path(__file__).parent) + +import config +from config import appversion, appname +import tkinter as tk +from tkinter import ttk +from tkinter import messagebox +from monitor import monitor +from EDMCLogging import get_main_logger + + +def get_sys_report(config: config.AbstractConfig) -> str: + """Gather system information about Elite, the Host Computer, and EDMC.""" + # Calculate Requested Information + plt = platform.uname() + locale.setlocale(locale.LC_ALL, "") + lcl = locale.getlocale() + monitor.currentdir = config.get_str( + "journaldir", default=config.default_journal_dir + ) + if not monitor.currentdir: + monitor.currentdir = config.default_journal_dir + try: + logfile = monitor.journal_newest_filename(monitor.currentdir) + if logfile is None: + raise ValueError("None from monitor.journal_newest_filename") + + with open(logfile, "rb", 0) as loghandle: + for line in loghandle: + try: + monitor.parse_entry(line) + except Exception as e: + exception_type = e.__class__.__name__ + monitor.state["GameVersion"] = ( + exception_type + if not monitor.state["GameVersion"] + else monitor.state["GameVersion"] + ) + monitor.state["GameBuild"] = ( + exception_type + if not monitor.state["GameBuild"] + else monitor.state["GameBuild"] + ) + monitor.state["Odyssey"] = ( + exception_type + if not monitor.state["Odyssey"] + else monitor.state["Odyssey"] + ) + except Exception as e: + exception_type = e.__class__.__name__ + monitor.state["GameVersion"] = exception_type + monitor.state["GameBuild"] = exception_type + monitor.state["Odyssey"] = exception_type + + journal_lock = JournalLock() + lockable = journal_lock.open_journal_dir_lockfile() + + report = f"EDMC Version: \n - {appversion()}\n\n" + report += "OS Details:\n" + report += f"- Operating System: {plt.system} {plt.release}\n" + report += f"- Version: {plt.version}\n" + report += f"- Machine: {plt.machine}\n" + report += f"- Python Version: {platform.python_version()}\n" + report += "\nEnvironment Details\n" + report += f"- Detected Locale: {lcl[0]}\n" + report += f"- Detected Encoding: {lcl[1]}\n" + report += f"- Journal Directory: {monitor.currentdir}\n" + report += f"- Game Version: {monitor.state['GameVersion']}\n" + report += f"- Game Build: {monitor.state['GameBuild']}\n" + report += f"- Using Odyssey: {monitor.state['Odyssey']}\n" + report += f"- Journal Dir Lockable: {lockable}\n" + return report + + +def copy_sys_report(root: tk.Tk, report: str) -> None: + """Copy the system info to the keyboard.""" + root.clipboard_clear() + root.clipboard_append(report) + messagebox.showinfo("System Profiler", "System Report copied to Clipboard") + + +def main() -> None: + """Entry Point for the System Profiler.""" + # Now Let's Begin + root: tk.Tk = tk.Tk() + root.withdraw() # Hide the window initially to calculate the dimensions + try: + icon_image = tk.PhotoImage( + file=path.join(cur_config.respath_path, "io.edcd.EDMarketConnector.png") + ) + + root.iconphoto(True, icon_image) + except tk.TclError: + root.iconbitmap(path.join(cur_config.respath_path, "EDMarketConnector.ico")) + + sys_report = get_sys_report(cur_config) + + # Set up styling + style = ttk.Style(root) + style.configure("Title.TLabel", font=("Helvetica", 10, "bold"), foreground="#333") + style.configure("Subtitle.TLabel", font=("Helvetica", 8), foreground="#555") + style.configure("Details.TLabel", font=("Helvetica", 8), foreground="#222") + + # Build UI + title_lbl = ttk.Label( + root, text="EDMarketConnector System Profiler", style="Title.TLabel" + ) + title_lbl.grid(row=0, column=0, padx=20, pady=10) + + system_details_lbl = ttk.Label( + root, text="System Details:", style="Subtitle.TLabel" + ) + system_details_lbl.grid(row=1, column=0, padx=20, pady=0, sticky="w") + + details_lbl = ttk.Label( + root, text=sys_report, style="Details.TLabel", justify="left" + ) + details_lbl.grid(row=2, column=0, padx=20, pady=5, sticky="w") + + # Buttons + sys_report_btn = ttk.Button( + root, + text="Copy System Report", + command=lambda: copy_sys_report(root, sys_report), + ) + sys_report_btn.grid(row=3, column=0, padx=20, pady=10, sticky="w") + + github_btn = ttk.Button( + root, + text="Open GitHub Bug Report", + command=lambda: webbrowser.open( + "https://github.com/EDCD/EDMarketConnector/issues/new?assignees=" + "&labels=bug%2C+unconfirmed&projects=&template=bug_report.md&title=" + ), + ) + github_btn.grid(row=3, column=0, padx=20, pady=10, sticky="e") + + # Update and get window dimensions + root.update() + width = root.winfo_reqwidth() + 20 + height = root.winfo_reqheight() + 20 + + # Set window size and show + root.geometry(f"{width}x{height}") + root.title("EDMarketConnector") + root.deiconify() + root.resizable(False, False) + + root.mainloop() + + +if __name__ == "__main__": + # Args: Only work if not frozen + parser = argparse.ArgumentParser( + prog=appname, + description="Prints diagnostic and debugging information about the current EDMC configuration.", + ) + parser.add_argument( + "--out-console", + help="write the system information to the console", + action="store_true", + ) + args = parser.parse_args() + + # Suppress Logger + logger = get_main_logger() + logger.setLevel(logging.CRITICAL) + if getattr(sys, "frozen", False): + sys.stderr._error = "inhibit log creation" # type: ignore + + cur_config = config.get_config() + if args.out_console: + sys_report = get_sys_report(cur_config) + print(sys_report) + sys.exit(0) + + main() diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 09e6fc4e7..31f4106eb 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -649,6 +649,7 @@ def open_window(systray: 'SysTrayIcon') -> None: # About E:D Market Connector self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w)) self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder + self.help_menu.add_command(command=prefs.help_open_system_profiler) # Open Log Folder self.menubar.add_cascade(menu=self.help_menu) if sys.platform == 'win32': @@ -884,6 +885,7 @@ def set_labels(self): self.help_menu.entryconfigure(5, label=tr.tl('Check for Updates...')) # LANG: Help > Check for Updates... self.help_menu.entryconfigure(6, label=tr.tl("About {APP}").format(APP=applongname)) # LANG: Help > About App self.help_menu.entryconfigure(7, label=tr.tl('Open Log Folder')) # LANG: Help > Open Log Folder + self.help_menu.entryconfigure(8, label=tr.tl('Open System Profiler')) # LANG: Help > Open System Profiler # Edit menu self.edit_menu.entryconfigure(0, label=tr.tl('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste' diff --git a/build.py b/build.py index 851f391b2..21b9b4507 100644 --- a/build.py +++ b/build.py @@ -175,10 +175,19 @@ def build() -> None: ], } + checker_config: dict = { + "dest_base": "EDMCSystemProfiler", + "script": "EDMC_System_Profiler.py", + "icon_resources": [(0, f"{appname}.ico")], + "other_resources": [ + (24, 1, pathlib.Path(f"resources/{appname}.manifest").read_text(encoding="UTF8")) + ], + } + try: py2exe.freeze( version_info=version_info, - windows=[windows_config], + windows=[windows_config, checker_config], console=[console_config], data_files=data_files, options=options, diff --git a/prefs.py b/prefs.py index 2d4c8d517..ae8d9c235 100644 --- a/prefs.py +++ b/prefs.py @@ -5,6 +5,7 @@ import contextlib import logging import pathlib +import subprocess import sys import tempfile import tkinter as tk @@ -50,6 +51,16 @@ def help_open_log_folder() -> None: system(f'xdg-open "{logfile_loc}"') +def help_open_system_profiler() -> None: + """Open the EDMC System Profiler.""" + profiler_path = pathlib.Path(config.respath_path) + if getattr(sys, 'frozen', False): + profiler_path /= 'EDMCSystemProfiler.exe' + subprocess.run(profiler_path) + else: + subprocess.run(['python', "EDMC_System_Profiler.py"], shell=True) + + class PrefsVersion: """ PrefsVersion contains versioned preferences. From ce2e20639ceb985947eabcf471bfb65ae58111a3 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 1 May 2024 20:00:59 -0400 Subject: [PATCH 179/261] [806] Add Translation --- L10n/en.template | 3 +++ 1 file changed, 3 insertions(+) diff --git a/L10n/en.template b/L10n/en.template index 200743c82..5d29f83ba 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -87,6 +87,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Check for Updates..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Open System Profiler"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Save Raw Data..."; From e60d0c8813192b3c0d5ec788c58c4eeb41329993 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 00:04:43 +0000 Subject: [PATCH 180/261] build(deps-dev): bump coverage[toml] from 7.4.4 to 7.5.0 Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 7.4.4 to 7.5.0. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.4.4...7.5.0) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ef2728ca2..8446d00aa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -40,7 +40,7 @@ py2exe==0.13.0.1; sys_platform == 'win32' # Testing pytest==8.1.1 pytest-cov==5.0.0 # Pytest code coverage support -coverage[toml]==7.4.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 # For manipulating folder permissions and the like. pywin32==306; sys_platform == 'win32' From 5cdf4b9ce3ff4bfa3e168bb79786b5fd728588e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 00:07:11 +0000 Subject: [PATCH 181/261] build(deps-dev): bump pytest from 8.1.1 to 8.2.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.1 to 8.2.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.1.1...8.2.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8446d00aa..247f589e6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -38,7 +38,7 @@ grip==4.6.2 py2exe==0.13.0.1; sys_platform == 'win32' # Testing -pytest==8.1.1 +pytest==8.2.0 pytest-cov==5.0.0 # Pytest code coverage support coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 From 88bfd8ca8bb9b4f05efa6daf65120397ba7fef59 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 2 May 2024 15:02:43 -0400 Subject: [PATCH 182/261] [1268] Handover to Path.is_reserved() --- util_ships.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/util_ships.py b/util_ships.py index 8bbfd813b..b2105106a 100644 --- a/util_ships.py +++ b/util_ships.py @@ -5,18 +5,21 @@ Licensed under the GNU General Public License. See LICENSE file. """ +from pathlib import Path from edmc_data import ship_name_map def ship_file_name(ship_name: str, ship_type: str) -> str: """Return a ship name suitable for a filename.""" name = str(ship_name or ship_name_map.get(ship_type.lower(), ship_type)).strip() - if name.endswith('.'): - name = name[:-2] - if name.lower() in ('con', 'prn', 'aux', 'nul', - 'com0', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', - 'lpt0', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9'): - name += '_' + # Handle suffix using Pathlib's with_suffix method + name = Path(name).with_suffix("").name - return name.translate({ord(x): '_' for x in ('\0', '<', '>', ':', '"', '/', '\\', '|', '?', '*')}) + # Check if the name is a reserved filename + if Path(name).is_reserved(): + name += "_" + + return name.translate( + {ord(x): "_" for x in ("\0", "<", ">", ":", '"', "/", "\\", "|", "?", "*")} + ) From d7ccd19832d02e75bf7c53c024dea411afca4b3b Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 2 May 2024 18:33:24 -0400 Subject: [PATCH 183/261] [806] Rename Script --- EDMC_System_Profiler.py => EDMCSystemProfiler.py | 2 +- build.py | 2 +- prefs.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename EDMC_System_Profiler.py => EDMCSystemProfiler.py (98%) diff --git a/EDMC_System_Profiler.py b/EDMCSystemProfiler.py similarity index 98% rename from EDMC_System_Profiler.py rename to EDMCSystemProfiler.py index 44dad70d4..90a96ceee 100644 --- a/EDMC_System_Profiler.py +++ b/EDMCSystemProfiler.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -EDMC_System_Profiler.py - GUI or Command-Line Tool to Print Diagnostic Information about EDMC. +EDMCSystemProfiler.py - GUI or Command-Line Tool to Print Diagnostic Information about EDMC. Copyright (c) EDCD, All Rights Reserved Licensed under the GNU General Public License. diff --git a/build.py b/build.py index 21b9b4507..966799c47 100644 --- a/build.py +++ b/build.py @@ -177,7 +177,7 @@ def build() -> None: checker_config: dict = { "dest_base": "EDMCSystemProfiler", - "script": "EDMC_System_Profiler.py", + "script": "EDMCSystemProfiler.py", "icon_resources": [(0, f"{appname}.ico")], "other_resources": [ (24, 1, pathlib.Path(f"resources/{appname}.manifest").read_text(encoding="UTF8")) diff --git a/prefs.py b/prefs.py index ae8d9c235..fc66a5371 100644 --- a/prefs.py +++ b/prefs.py @@ -58,7 +58,7 @@ def help_open_system_profiler() -> None: profiler_path /= 'EDMCSystemProfiler.exe' subprocess.run(profiler_path) else: - subprocess.run(['python', "EDMC_System_Profiler.py"], shell=True) + subprocess.run(['python', "EDMCSystemProfiler.py"], shell=True) class PrefsVersion: From 2469ca2132e374441fee435a9a9d233b91ca956d Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 2 May 2024 20:11:07 -0400 Subject: [PATCH 184/261] [Minor] Correct Email Address --- .github/SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/SECURITY.md b/.github/SECURITY.md index b8a0b2444..a926715bf 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -4,7 +4,7 @@ EDMC takes security very seriously. Our users trust us to provide a secure and s In general, the best way to report a major security issue with us that should not be publically discussed is to email our maintainer teams. -The best point of contact for this is rixxan@hullseals.space. When contacting, be sure to include as much information in your report. +The best point of contact for this is edmc@hullseals.space. When contacting, be sure to include as much information in your report. As soon as your report is processed, we'll get in touch to make sure we quickly move ahead with fixing the issue and will lay out a timeline for public disclosure and fixes. From 83fdaab61b7b472352c38d42a17af83c6e25297b Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 2 May 2024 20:28:16 -0400 Subject: [PATCH 185/261] Update codeql.yml --- .github/workflows/codeql.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 35a792fa9..a709c7dca 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -14,7 +14,6 @@ name: "CodeQL" on: push: branches-ignore: - - 'main' - 'stable' - 'releases' - 'beta' From 8198d779c3e7479bfc75dfae6faa47a25dc9a3ca Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 2 May 2024 21:34:58 -0400 Subject: [PATCH 186/261] [830] Open Log Folder Natively --- EDMarketConnector.py | 6 +++--- prefs.py | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 3ab97eb52..1ce34628f 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -18,6 +18,7 @@ import sys import threading import webbrowser +import tempfile from os import chdir, environ, path from time import localtime, strftime, time from typing import TYPE_CHECKING, Any, Literal @@ -47,8 +48,6 @@ # output until after this redirect is done, if needed. if getattr(sys, 'frozen', False): # By default py2exe tries to write log to dirname(sys.executable) which fails when installed - import tempfile - # unbuffered not allowed for text in python3, so use `1 for line buffering log_file_path = path.join(tempfile.gettempdir(), f'{appname}.log') sys.stdout = sys.stderr = open(log_file_path, mode='wt', buffering=1) # Do NOT use WITH here. @@ -651,7 +650,8 @@ def open_window(systray: 'SysTrayIcon') -> None: self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates... # About E:D Market Connector self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w)) - self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder + logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname + self.help_menu.add_command(command=lambda: prefs.open_folder(logfile_loc)) # Open Log Folder self.menubar.add_cascade(menu=self.help_menu) if sys.platform == 'win32': diff --git a/prefs.py b/prefs.py index 285ef0d70..818852576 100644 --- a/prefs.py +++ b/prefs.py @@ -8,14 +8,12 @@ import sys import tempfile import tkinter as tk -import webbrowser from os import system from os.path import expanduser, expandvars, join, normpath from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812 from tkinter import ttk from types import TracebackType from typing import TYPE_CHECKING, Any, Callable, Optional, Type - import myNotebook as nb # noqa: N813 import plug from config import appversion_nobuild, config @@ -44,14 +42,21 @@ def _(x: str) -> str: def help_open_log_folder() -> None: """Open the folder logs are stored in.""" - logfile_loc = pathlib.Path(tempfile.gettempdir()) - logfile_loc /= f'{appname}' + logger.warning( + DeprecationWarning("This function is deprecated, use open_log_folder instead. " + "This function will be removed in 6.0 or later") + ) + open_folder(pathlib.Path(tempfile.gettempdir()) / appname) + + +def open_folder(file: pathlib.Path) -> None: + """Open the given file in the OS file explorer.""" if sys.platform.startswith('win'): # On Windows, use the "start" command to open the folder - system(f'start "" "{logfile_loc}"') + system(f'start "" "{file}"') elif sys.platform.startswith('linux'): # On Linux, use the "xdg-open" command to open the folder - system(f'xdg-open "{logfile_loc}"') + system(f'xdg-open "{file}"') class PrefsVersion: @@ -300,6 +305,9 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): ): self.geometry(f"+{position.left}+{position.top}") + # Set Log Directory + self.logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname + def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: output_frame = nb.Frame(root_notebook) output_frame.columnconfigure(0, weight=1) @@ -625,7 +633,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 config_frame, # LANG: Label on button used to open a filesystem folder text=_('Open Log Folder'), # Button that opens a folder in Explorer/Finder - command=lambda: help_open_log_folder() + command=lambda: open_folder(self.logfile_loc) ).grid(column=2, padx=self.PADX, pady=0, sticky=tk.NSEW, row=cur_row) # Big spacer @@ -884,7 +892,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 plugins_frame, # LANG: Label on button used to open a filesystem folder text=_('Open'), # Button that opens a folder in Explorer/Finder - command=lambda: webbrowser.open(f'file:///{config.plugin_dir_path}') + command=lambda: open_folder(config.plugin_dir_path) ).grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.N, row=cur_row) enabled_plugins = list(filter(lambda x: x.folder and x.module, plug.PLUGINS)) From b6e373decd6459d123101e206dadf053273ca03f Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 3 May 2024 21:34:03 -0400 Subject: [PATCH 187/261] [#519] Framework Beta Update Track --- EDMarketConnector.py | 7 ++++++- config/__init__.py | 21 ++++++++++++++++++--- prefs.py | 30 +++++++++++++++++++++++++++--- update.py | 6 +++--- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 09e6fc4e7..150332a65 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -829,7 +829,7 @@ def toggle_suit_row(self, visible: bool | None = None) -> None: self.suit.grid_forget() self.suit_shown = False - def postprefs(self, dologin: bool = True): + def postprefs(self, dologin: bool = True, **postargs): """Perform necessary actions after the Preferences dialog is applied.""" self.prefsdialog = None self.set_labels() # in case language has changed @@ -853,6 +853,11 @@ def postprefs(self, dologin: bool = True): if dologin and monitor.cmdr: self.login() # Login if not already logged in with this Cmdr + if postargs.get('Update') and postargs.get('Track'): + # LANG: Inform the user the Update Track has changed + self.status['text'] = tr.tl('Update Track Changed to {TRACK}').format(TRACK=postargs.get('Track')) + self.updater.check_for_updates() + def set_labels(self): """Set main window labels, e.g. after language change.""" self.cmdr_label['text'] = tr.tl('Cmdr') + ':' # LANG: Label for commander name in main window diff --git a/config/__init__.py b/config/__init__.py index 27d9b4506..8ed6209f0 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -17,7 +17,6 @@ 'applongname', 'appcmdname', 'copyright', - 'update_feed', 'update_interval', 'debug_senders', 'trace_on', @@ -29,7 +28,9 @@ 'user_agent', 'appversion_nobuild', 'AbstractConfig', - 'config' + 'config', + 'get_update_feed', + 'update_feed' ] import abc @@ -58,7 +59,7 @@ _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' -update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' + update_interval = 8*60*60 # 8 Hours # Providers marked to be in debug mode. Generally this is expected to switch to sending data to a log file debug_senders: list[str] = [] @@ -479,3 +480,17 @@ def get_config(*args, **kwargs) -> AbstractConfig: config = get_config() + + +# TODO: Set Proper Beta XML, Bring XML from Releases to Live, Translations, wiki on Updates (WILL NOT DOWNGRADE), link in label +def get_update_feed() -> str: + """Select the proper update feed for the current update track.""" + if config.get_bool('beta_optin'): + print("Checking Using Beta") + return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' + print("Checking Live") + return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' + + +# TODO: Add Dep Warning +update_feed = get_update_feed() diff --git a/prefs.py b/prefs.py index 2d4c8d517..a4fee54bc 100644 --- a/prefs.py +++ b/prefs.py @@ -494,10 +494,29 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 self.hotkey_play_btn.grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) - # Option to disabled Automatic Check For Updates whilst in-game + # Options to select the Update Path and Disable Automatic Checks For Updates whilst in-game ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid( columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) + + with row as curr_row: + nb.Label(config_frame, text=tr.tl('Update Track')).grid( # LANG: Select the Update Track (Beta, Stable) + padx=self.PADX, pady=self.PADY, sticky=tk.W, row=curr_row + ) + self.curr_update_track = "Beta" if config.get_bool('beta_optin') else "Stable" + self.update_paths = tk.StringVar(value=self.curr_update_track) + # TODO: LANG + update_paths = [ + tr.tl("Stable"), # LANG: Stable Version of EDMC + tr.tl("Beta") # LANG: Beta Version of EDMC + ] + self.update_track = nb.OptionMenu( + config_frame, self.update_paths, self.update_paths.get(), *update_paths + ) + + self.update_track.configure(width=15) + self.update_track.grid(column=1, pady=self.BOXY, sticky=tk.W, row=curr_row) + self.disable_autoappupdatecheckingame = tk.IntVar(value=config.get_int('disable_autoappupdatecheckingame')) self.disable_autoappupdatecheckingame_btn = nb.Checkbutton( config_frame, @@ -1196,7 +1215,7 @@ def apply(self) -> None: config.set('hotkey_mods', self.hotkey_mods) config.set('hotkey_always', int(not self.hotkey_only.get())) config.set('hotkey_mute', int(not self.hotkey_play.get())) - + config.set('beta_optin', 0 if self.update_paths.get() == "Stable" else 1) config.set('shipyard_provider', self.shipyard_provider.get()) config.set('system_provider', self.system_provider.get()) config.set('station_provider', self.station_provider.get()) @@ -1220,9 +1239,14 @@ def apply(self) -> None: config.set('dark_highlight', self.theme_colors[1]) theme.apply(self.parent) + # Send to the Post Config if we updated the update branch + post_flags = { + 'Update': True if self.curr_update_track != self.update_paths.get() else False, + 'Track': self.update_paths.get() + } # Notify if self.callback: - self.callback() + self.callback(**post_flags) plug.notify_prefs_changed(monitor.cmdr, monitor.is_beta) diff --git a/update.py b/update.py index 52983a5d6..858d922dc 100644 --- a/update.py +++ b/update.py @@ -14,7 +14,7 @@ from xml.etree import ElementTree import requests import semantic_version -from config import appname, appversion_nobuild, config, update_feed +from config import appname, appversion_nobuild, config, get_update_feed from EDMCLogging import get_main_logger from l10n import translations as tr @@ -90,7 +90,7 @@ def __init__(self, tkroot: tk.Tk | None = None, provider: str = 'internal'): self.updater: ctypes.CDLL | None = ctypes.cdll.WinSparkle # Set the appcast URL - self.updater.win_sparkle_set_appcast_url(update_feed.encode()) + self.updater.win_sparkle_set_appcast_url(get_update_feed().encode()) # Set the appversion *without* build metadata, as WinSparkle # doesn't do proper Semantic Version checks. @@ -146,7 +146,7 @@ def check_appcast(self) -> EDMCVersion | None: newversion = None items = {} try: - request = requests.get(update_feed, timeout=10) + request = requests.get(get_update_feed(), timeout=10) except requests.RequestException as ex: logger.exception(f'Error retrieving update_feed file: {ex}') From 1d052e493043fec14949f9c0c1d7a6267d50b612 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 5 May 2024 18:17:00 -0400 Subject: [PATCH 188/261] [519] Add Downgrade Warning --- EDMarketConnector.py | 20 +++++++++++++++++++- L10n/en.template | 13 +++++++++++++ config/__init__.py | 8 +++----- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 150332a65..cdfa7480a 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -854,9 +854,27 @@ def postprefs(self, dologin: bool = True, **postargs): self.login() # Login if not already logged in with this Cmdr if postargs.get('Update') and postargs.get('Track'): + track = postargs.get('Track') # LANG: Inform the user the Update Track has changed - self.status['text'] = tr.tl('Update Track Changed to {TRACK}').format(TRACK=postargs.get('Track')) + self.status['text'] = tr.tl('Update Track Changed to {TRACK}').format(TRACK=track) self.updater.check_for_updates() + if track == "Stable": + # LANG: Inform the user the Update Track has changed + title = tr.tl('Update Track Changed to {TRACK}').format(TRACK=track) + update_msg = tr.tl( # LANG: Inform User of Beta -> Stable Transition Risks + 'Update track changed to Stable from Beta. ' + 'You will no longer receive Beta updates. You will stay on your current Beta ' + r'version until the next Stable release.\r\n\r\n' + 'You can manually revert to the latest Stable version. To do so, you must download and install ' + 'the latest Stable version manually. Note that this may introduce bugs or break completely' + r' if downgrading between major versions with significant changes.\r\n\r\n' + 'Do you want to open GitHub to download the latest release?' + ) + update_msg = update_msg.replace('\\n', '\n') + update_msg = update_msg.replace('\\r', '\r') + stable_popup = tk.messagebox.askyesno(title=title, message=update_msg) + if stable_popup: + webbrowser.open("https://github.com/edCD/eDMarketConnector/releases/latest") def set_labels(self): """Set main window labels, e.g. after language change.""" diff --git a/L10n/en.template b/L10n/en.template index 200743c82..ca0802962 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -780,5 +780,18 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} is available"; +/* prefs.py: Stable Version of EDMC; */ +"Stable" = "Stable"; + +/* prefs.py: Select the Update Track (Beta, Stable); */ +"Update Track" = "Update Track"; + +/* EDMarketConnector.py: Inform the user the Update Track has changed; */ +"Update Track Changed to {TRACK}" = "Update Track Changed to {TRACK}"; + + +/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */ +"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?"; + /* myNotebook.py: Can't Paste Images or Files in Text; */ "Cannot paste non-text content." = "Cannot paste non-text content."; diff --git a/config/__init__.py b/config/__init__.py index 8ed6209f0..fabf3eab9 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -482,15 +482,13 @@ def get_config(*args, **kwargs) -> AbstractConfig: config = get_config() -# TODO: Set Proper Beta XML, Bring XML from Releases to Live, Translations, wiki on Updates (WILL NOT DOWNGRADE), link in label +# TODO: Wiki on Updates (WILL NOT DOWNGRADE) def get_update_feed() -> str: """Select the proper update feed for the current update track.""" if config.get_bool('beta_optin'): - print("Checking Using Beta") - return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' - print("Checking Live") + return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/beta/edmarketconnector.xml' return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' -# TODO: Add Dep Warning +# WARNING: update_feed is deprecated, and will be removed in 6.0 or later. Please migrate to get_update_feed() update_feed = get_update_feed() From b30e0065754721048807621227cda986c543c8e3 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 5 May 2024 18:34:07 -0400 Subject: [PATCH 189/261] [519] Update Workflows and Python Version --- .github/workflows/pr-checks.yml | 2 +- .github/workflows/push-checks.yml | 2 +- .github/workflows/windows-build.yml | 2 +- .python-version | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 32deb018e..e1dcfe972 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -13,7 +13,7 @@ on: jobs: code-checks: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index 31ecfd6df..d1ac92a21 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -19,7 +19,7 @@ on: jobs: push_checks: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 7bc601367..6403ec3c1 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -71,7 +71,7 @@ jobs: windows_build: needs: [variables] name: Build EDMC - runs-on: windows-2019 + runs-on: windows-latest defaults: run: diff --git a/.python-version b/.python-version index d4b278f0a..2419ad5b0 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.11.7 +3.11.9 From 6020bbb05e6522bf5a9436691a845f59ae6e1576 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 5 May 2024 19:43:22 -0400 Subject: [PATCH 190/261] [#2149] Update Build System --- .github/workflows/windows-build.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 6403ec3c1..c46ba79f3 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -62,9 +62,9 @@ jobs: mv ../EDMarketConnector-release-${{ needs.variables.outputs.sem_ver }}.tar.gz . - name: Upload build files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: Built files + name: built-files-linux path: | EDMarketConnector-release-*.tar.gz @@ -131,22 +131,31 @@ jobs: Get-ChildItem -Path . -Filter "EDMarketConnector_Installer_*.exe" | Rename-Item -NewName {"EDMarketConnector_Installer_Unsigned_$($_.Name -replace '^EDMarketConnector_Installer_', '')"} - name: Upload build files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: Built files + name: built-files-windows path: | EDMarketConnector_Installer_*.exe EDMarketConnector-release-*.zip + Merge: + runs-on: ubuntu-latest + needs: [ windows_build, linux_build ] + steps: + - name: Merge Artifacts + uses: actions/upload-artifact/merge@v4 + with: + name: Built files + pattern: built-files-* release: name: Release new version runs-on: ubuntu-latest - needs: [ windows_build, linux_build ] + needs: Merge if: "${{ github.event_name != 'workflow_dispatch' }}" steps: - name: Download binary - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Built files path: ./ From 44d6e89bd8b18b8d89a62a201de1a3115d0fe4df Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 5 May 2024 20:36:30 -0400 Subject: [PATCH 191/261] [519] Wiki Page and Remove TODO --- config/__init__.py | 2 +- prefs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index fabf3eab9..2ed77a438 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -482,7 +482,7 @@ def get_config(*args, **kwargs) -> AbstractConfig: config = get_config() -# TODO: Wiki on Updates (WILL NOT DOWNGRADE) +# Wiki: https://github.com/EDCD/EDMarketConnector/wiki/Participating-in-Open-Betas-of-EDMC def get_update_feed() -> str: """Select the proper update feed for the current update track.""" if config.get_bool('beta_optin'): diff --git a/prefs.py b/prefs.py index a4fee54bc..936272fdf 100644 --- a/prefs.py +++ b/prefs.py @@ -505,7 +505,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) self.curr_update_track = "Beta" if config.get_bool('beta_optin') else "Stable" self.update_paths = tk.StringVar(value=self.curr_update_track) - # TODO: LANG + update_paths = [ tr.tl("Stable"), # LANG: Stable Version of EDMC tr.tl("Beta") # LANG: Beta Version of EDMC From 695417f4fc9ed8b8122f0bb00998986608a7daa1 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 6 May 2024 13:34:21 -0400 Subject: [PATCH 192/261] [#1249] Add Cache actions Will speed up workflow testing --- .github/workflows/pr-checks.yml | 1 + .github/workflows/push-checks.yml | 1 + .github/workflows/windows-build.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index e1dcfe972..843592a31 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -56,6 +56,7 @@ jobs: uses: actions/setup-python@v5 with: python-version-file: '.python-version' + cache: 'pip' # caching pip dependencies - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index d1ac92a21..a15a718cc 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -28,6 +28,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: + cache: 'pip' # caching pip dependencies python-version-file: '.python-version' - name: Install dependencies run: | diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index c46ba79f3..9ed4e5cae 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -105,6 +105,7 @@ jobs: with: python-version-file: '.python-version' architecture: "x86" + cache: 'pip' # caching pip dependencies - name: Install python tools run: | From de4e1c197fb9d21d61715a9eb6b13d77d86eaca6 Mon Sep 17 00:00:00 2001 From: ImgBotApp Date: Mon, 6 May 2024 17:49:31 +0000 Subject: [PATCH 193/261] [ImgBot] Optimize images *Total -- 146.57kb -> 109.96kb (24.98%) /img/UI-Athanasius-101-odyssey-docked-onfoot-cooled-down-l.png -- 6.88kb -> 3.53kb (48.69%) /img/UI-annotated.png -- 7.69kb -> 4.40kb (42.74%) /img/win.png -- 6.74kb -> 4.75kb (29.58%) /img/win_dark.png -- 5.26kb -> 3.87kb (26.49%) /img/win_transparent.png -- 71.73kb -> 53.46kb (25.47%) /img/Balance.png -- 48.27kb -> 39.95kb (17.24%) Signed-off-by: ImgBotApp --- img/Balance.png | Bin 49431 -> 40911 bytes ...01-odyssey-docked-onfoot-cooled-down-l.png | Bin 7043 -> 3614 bytes img/UI-annotated.png | Bin 7870 -> 4506 bytes img/win.png | Bin 6906 -> 4863 bytes img/win_dark.png | Bin 5390 -> 3962 bytes img/win_transparent.png | Bin 73449 -> 54738 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/img/Balance.png b/img/Balance.png index 8696f44c022f365f4ec98450edf5aae65620b5e9..e5d0c4641ea73d5b72bdc7a7d0eb8b2662282f09 100644 GIT binary patch literal 40911 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 z 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 literal 49431 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 z zgO!~fI|~cCtUw!b*9`gHb(=XT!{B0bJv<3nQ;Tf8tgTlqE98~$sz;*1KMF7dMvPJz zqzMnhl|ycCfrbKFF7RJtJ{ML5WT?+m0XT?pM4cw&!SgD|+hLSE%#>jQroTzj+-I+Y zROJ|)1bm>W@|gC=rH15nVyPo0T47rZSrF9GAwU-dEFnH6w;@R9F1MR{E%n|y-<7_I zsd8G}*+LR)fwm4#Mi?rXnwr|{vGNFLN5h0bDw0mLu`gTkv9bf^J5Cp+1wnBILaw$9 zurb8mJOS)8KnP2L6ag?q4{%cc?&(>Ci5)9h$QJxuL9i^;0}CV5k_O;&;dG+Wm^lL= zk^WWJI(3V_`q=~>P+&Uflo(&Fab3r}rkIE$kdW{6tMK*x`O^fgs#aiu9&JbYbwV2+ zx{Ls^gC4p9X#V=L1eF;01;O3UeV%cxyWN z8ah8cjB&p>JA@Q14EGYoKj1f!1F12@tHIG0I2|L^E?ltEW`kMe<;4jKQn6UR+vB^B|POF13>~9E#)1G**5lc)7hpF z3ZpwM~)yC{COy^{-Ef- H;oJWOtQE|Z diff --git a/img/UI-Athanasius-101-odyssey-docked-onfoot-cooled-down-l.png b/img/UI-Athanasius-101-odyssey-docked-onfoot-cooled-down-l.png index b2f8d68a74a9c42d4647e3d5bf358c0aca06b8dc..d3d60fad1b70da1b53b738af07d3a627c81b43c4 100644 GIT binary patch literal 3614 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 YsK@_*f>FHdFpmJSwQ{no!~3WG3%$|3`2YX_ literal 7870 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 Neyc8DBWwQYe*y7gg46&2 diff --git a/img/win.png b/img/win.png index 383ea91182bc79b53d843efea8c6c685a7378eaf..44c43d1c21b8b9a525e1af53ba03a348d54e113d 100644 GIT binary patch literal 4863 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 a`F&C^1Yh$qz`y=i!N^D|N>qp$eEUE3=|R^3 diff --git a/img/win_dark.png b/img/win_dark.png index 2b8608d2a7c559e52c5d9b53b9d9ca408e3f3912..90c7e931ef31d941581007f2a797c9a6caa1182d 100644 GIT binary patch literal 3962 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 R{zZ@g*dIJ(U2R27`!8Vp|EK@} literal 5390 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 DiMMEk diff --git a/img/win_transparent.png b/img/win_transparent.png index b4ff49d9d28ee641ad4e0b201ffdd4aa4599ffff..c450c0efe3159a110b4278196757b557f6641918 100644 GIT binary patch literal 54738 zcmV)^K!CrAP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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�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 DACJ&J From ebc62ae8e0c8653d9fd75df70e2e0205b167401f Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 7 May 2024 10:17:06 -0400 Subject: [PATCH 194/261] [Minor] Update modules.json --- modules.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules.json b/modules.json index 27e54626a..16e72f7fd 100644 --- a/modules.json +++ b/modules.json @@ -326,6 +326,9 @@ "hpt_antiunknownshutdown_tiny": { "mass": 1.3 }, + "hpt_antiunknownshutdown_tiny_v2": { + "mass": 3 + }, "hpt_atdumbfiremissile_fixed_large": { "mass": 8 }, @@ -446,6 +449,9 @@ "hpt_causticmissile_fixed_medium": { "mass": 4 }, + "hpt_causticsinklauncher_turret_tiny": { + "mass": 1.7 + }, "hpt_chafflauncher_tiny": { "mass": 1.3 }, @@ -833,6 +839,9 @@ "hpt_slugshot_turret_small": { "mass": 2 }, + "hpt_xenoscanner_advanced_tiny": { + "mass": 3 + }, "hpt_xenoscanner_basic_tiny": { "mass": 1.3 }, @@ -1352,6 +1361,12 @@ "int_engine_size8_class5": { "mass": 160 }, + "int_expmodulestabiliser_size3_class3": { + "mass": 8 + }, + "int_expmodulestabiliser_size5_class3": { + "mass": 20 + }, "int_fighterbay_size5_class1": { "mass": 20 }, From 690523c9169a88c90fd5b7073c898e216cd8551d Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 7 May 2024 16:11:30 -0400 Subject: [PATCH 195/261] [2228] Handle Unknown FSD Ranges --- edmc_data.py | 3 ++- edshipyard.py | 22 +++++++++++++--------- outfitting.py | 4 ++-- ships.json | 3 +++ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/edmc_data.py b/edmc_data.py index d76badc62..e8c9518b7 100644 --- a/edmc_data.py +++ b/edmc_data.py @@ -357,7 +357,7 @@ 'guardianpowerdistributor': 'Guardian Hybrid Power Distributor', 'guardianpowerplant': 'Guardian Hybrid Power Plant', 'hyperdrive': 'Frame Shift Drive', - ('hyperdrive', 'overcharge'): 'Frame Shift Drive', + ('hyperdrive', 'overcharge'): 'Frame Shift Drive (SCO)', 'lifesupport': 'Life Support', # 'planetapproachsuite': handled separately 'powerdistributor': 'Power Distributor', @@ -501,6 +501,7 @@ 'mamba': 'Mamba', 'orca': 'Orca', 'python': 'Python', + 'python_nx': 'Python Mk II', 'scout': 'Taipan Fighter', 'sidewinder': 'Sidewinder', 'testbuggy': 'Scarab', diff --git a/edshipyard.py b/edshipyard.py index 1660ad7e1..15936dfc9 100644 --- a/edshipyard.py +++ b/edshipyard.py @@ -106,7 +106,7 @@ def class_rating(module: __Module) -> str: else: name = module['name'] # type: ignore - if name == 'Frame Shift Drive': + if name == 'Frame Shift Drive' or name == 'Frame Shift Drive (SCO)': fsd = module # save for range calculation if mods.get('OutfittingFieldType_FSDOptimalMass'): @@ -167,15 +167,19 @@ def class_rating(module: __Module) -> str: try: mass += ships[ship_name_map[data['ship']['name'].lower()]]['hullMass'] string += f'Mass : {mass:.2f} T empty\n {mass + fuel + cargo:.2f} T full\n' + maxfuel = fsd.get('maxfuel', 0) # type: ignore + fuelmul = fsd.get('fuelmul', 0) # type: ignore - multiplier = pow(min(fuel, fsd['maxfuel']) / fsd['fuelmul'], 1.0 # type: ignore - / fsd['fuelpower']) * fsd['optmass'] # type: ignore - - range_unladen = multiplier / (mass + fuel) + jumpboost - range_laden = multiplier / (mass + fuel + cargo) + jumpboost - # As of 2021-04-07 edsy.org says text import not yet implemented, so ignore the possible issue with - # a locale that uses comma for decimal separator. - string += f'Range : {range_unladen:.2f} LY unladen\n {range_laden:.2f} LY laden\n' + try: + multiplier = pow(min(fuel, maxfuel) / fuelmul, 1.0 / fsd['fuelpower']) * fsd['optmass'] # type: ignore + range_unladen = multiplier / (mass + fuel) + jumpboost + range_laden = multiplier / (mass + fuel + cargo) + jumpboost + # As of 2021-04-07 edsy.org says text import not yet implemented, so ignore the possible issue with + # a locale that uses comma for decimal separator. + except ZeroDivisionError: + range_unladen = range_laden = 0.0 + string += (f'Range : {range_unladen:.2f} LY current without cargo\n' + f' {range_laden:.2f} LY current with cargo\n') except Exception: if __debug__: diff --git a/outfitting.py b/outfitting.py index 5632687c7..770c47cde 100644 --- a/outfitting.py +++ b/outfitting.py @@ -222,7 +222,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 (new['class'], new['rating']) = (str(name[2][4:]), 'H') elif len(name) > 4 and name[1] == 'hyperdrive': # e.g. Int_Hyperdrive_Overcharge_Size6_Class3 - (new['class'], new['rating']) = (str(name[4][-1:]), 'C') + (new['class'], new['rating']) = (str(name[3][-1:]), rating_map[name[4][-1:]]) else: if len(name) < 3: @@ -257,7 +257,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 if not m: print(f'No data for module {key}') - elif new['name'] == 'Frame Shift Drive': + elif new['name'] == 'Frame Shift Drive' or new['name'] == 'Frame Shift Drive (SCO)': assert 'mass' in m and 'optmass' in m and 'maxfuel' in m and 'fuelmul' in m and 'fuelpower' in m, m else: diff --git a/ships.json b/ships.json index 4a40ba4fa..65a2989e2 100644 --- a/ships.json +++ b/ships.json @@ -89,6 +89,9 @@ "Python": { "hullMass": 350 }, + "Python Mk II": { + "hullMass": 450 + }, "Sidewinder": { "hullMass": 25 }, From 8530b38306d845c1eea687f5dedfe804e555eb76 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 7 May 2024 18:06:10 -0400 Subject: [PATCH 196/261] Update ChangeLog.md --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 324d1f8d9..45f06aad3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -24,6 +24,11 @@ be added in a future update. * Fixed a bug where the new SCO modules would display as a normal Frame Shift Drive * Fixed a bug which could crash EDMC if the exact details of a Frame Shift Drive were unknown +**Plugin Developers** +* modules.p and ships.p are deprecated, and slated for removal in 5.11+! +* The `openurl()` function in ttkHyperlinkLabel has been deprecated, +and slated for removal in 5.11+! Please migrate to `webbrowser.open()`. + Release 5.10.4 === This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also From 37c6179272626ebb0557d35e922dac67bbfe8c38 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 7 May 2024 18:18:13 -0400 Subject: [PATCH 197/261] Update edmarketconnector.xml --- edmarketconnector.xml | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/edmarketconnector.xml b/edmarketconnector.xml index cc7977943..40fa6c000 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,10 +22,34 @@ - Release 5.10.4 + Release 5.10.5 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }

      We now test against, and package with, Python 3.11.7.

      As a result, we do not support Windows 7, 8, or 8.1.
      +

      Release 5.10.5

      +

      This release contains a fix for a bug that could crash EDMC's console versions when reading outfitting information from the new SCO Frame Shift Drive modules.

      +

      Please note that this does not offer full support for the new SCO modules or the Python Mk II. More support will be added in a future update.

      +

      We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC

      +

      Changes and Enhancements

      +
        +
      • Updated Translations
      • +
      • Added limited data regarding the Python Mk II
      • +
      • Added a few Coriolis module information entries
      • +
      +

      Bug Fixes

      +
        +
      • Fixed a bug that could cause the new SCO modules to display improper ratings or sizes
      • +
      • Fixed a bug where the new SCO modules would display as a normal Frame Shift Drive
      • +
      • Fixed a bug which could crash EDMC if the exact details of a Frame Shift Drive were unknown
      • +
      +

      Plugin Developers

      +
        +
      • modules.p and ships.p are deprecated, and slated for removal in 5.11+!
      • +
      • The openurl() function in ttkHyperlinkLabel has been deprecated, +and slated for removal in 5.11+! Please migrate to webbrowser.open().
      • +
      +

      Release 5.10.4

      This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also adds Turkish translations to EDMC!

      @@ -2231,7 +2255,7 @@ about this: PTS CAPI saying Commander is Docked after jumping to new system.

    ]]> - + From e5f99c29548356993a62ec743da8d61f1b2cad4b Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 8 May 2024 21:56:52 -0400 Subject: [PATCH 198/261] [#2228] Add SCO Module Information --- edshipyard.py | 4 +- modules.json | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 2 deletions(-) diff --git a/edshipyard.py b/edshipyard.py index 15936dfc9..2c29959ff 100644 --- a/edshipyard.py +++ b/edshipyard.py @@ -178,8 +178,8 @@ def class_rating(module: __Module) -> str: # a locale that uses comma for decimal separator. except ZeroDivisionError: range_unladen = range_laden = 0.0 - string += (f'Range : {range_unladen:.2f} LY current without cargo\n' - f' {range_laden:.2f} LY current with cargo\n') + string += (f'Range : {range_unladen:.2f} LY unladen\n' + f' {range_laden:.2f} LY laden\n') except Exception: if __debug__: diff --git a/modules.json b/modules.json index 16e72f7fd..b19441d44 100644 --- a/modules.json +++ b/modules.json @@ -1765,6 +1765,216 @@ "int_hullreinforcement_size5_class2": { "mass": 16 }, + "int_hyperdrive_overcharge_size2_class1": { + "mass": 2.5, + "optmass": 60, + "maxfuel": 0.6, + "fuelmul": 0.008, + "fuelpower": 2 + }, + "int_hyperdrive_overcharge_size2_class2": { + "mass": 2.5, + "optmass": 90, + "maxfuel": 0.9, + "fuelmul": 0.012, + "fuelpower": 2 + }, + "int_hyperdrive_overcharge_size2_class3": { + "mass": 2.5, + "optmass": 90, + "maxfuel": 0.9, + "fuelmul": 0.012, + "fuelpower": 2 + }, + "int_hyperdrive_overcharge_size2_class4": { + "mass": 2.5, + "optmass": 90, + "maxfuel": 0.9, + "fuelmul": 0.012, + "fuelpower": 2 + }, + "int_hyperdrive_overcharge_size2_class5": { + "mass": 2.5, + "optmass": 100, + "maxfuel": 1, + "fuelmul": 0.013, + "fuelpower": 2 + }, + "int_hyperdrive_overcharge_size3_class1": { + "mass": 5, + "optmass": 100, + "maxfuel": 1.2, + "fuelmul": 0.008, + "fuelpower": 2.15 + }, + "int_hyperdrive_overcharge_size3_class2": { + "mass": 2, + "optmass": 150, + "maxfuel": 1.8, + "fuelmul": 0.012, + "fuelpower": 2.15 + }, + "int_hyperdrive_overcharge_size3_class3": { + "mass": 5, + "optmass": 150, + "maxfuel": 1.8, + "fuelmul": 0.012, + "fuelpower": 2.15 + }, + "int_hyperdrive_overcharge_size3_class4": { + "mass": 5, + "optmass": 150, + "maxfuel": 1.8, + "fuelmul": 0.012, + "fuelpower": 2.15 + }, + "int_hyperdrive_overcharge_size3_class5": { + "mass": 5, + "optmass": 167, + "maxfuel": 1.9, + "fuelmul": 0.013, + "fuelpower": 2.15 + }, + "int_hyperdrive_overcharge_size4_class1": { + "mass": 10, + "optmass": 350, + "maxfuel": 2, + "fuelmul": 0.008, + "fuelpower": 2.3 + }, + "int_hyperdrive_overcharge_size4_class2": { + "mass": 4, + "optmass": 525, + "maxfuel": 3, + "fuelmul": 0.012, + "fuelpower": 2.3 + }, + "int_hyperdrive_overcharge_size4_class3": { + "mass": 10, + "optmass": 525, + "maxfuel": 3, + "fuelmul": 0.012, + "fuelpower": 2.3 + }, + "int_hyperdrive_overcharge_size4_class4": { + "mass": 10, + "optmass": 525, + "maxfuel": 3, + "fuelmul": 0.012, + "fuelpower": 2.3 + }, + "int_hyperdrive_overcharge_size4_class5": { + "mass": 10, + "optmass": 585, + "maxfuel": 3.2, + "fuelmul": 0.013, + "fuelpower": 2.3 + }, + "int_hyperdrive_overcharge_size5_class1": { + "mass": 20, + "optmass": 700, + "maxfuel": 3.3, + "fuelmul": 0.008, + "fuelpower": 2.45 + }, + "int_hyperdrive_overcharge_size5_class2": { + "mass": 8, + "optmass": 1050, + "maxfuel": 5, + "fuelmul": 0.012, + "fuelpower": 2.45 + }, + "int_hyperdrive_overcharge_size5_class3": { + "mass": 20, + "optmass": 1050, + "maxfuel": 5, + "fuelmul": 0.012, + "fuelpower": 2.45 + }, + "int_hyperdrive_overcharge_size5_class4": { + "mass": 20, + "optmass": 1050, + "maxfuel": 5, + "fuelmul": 0.012, + "fuelpower": 2.45 + }, + "int_hyperdrive_overcharge_size5_class5": { + "mass": 20, + "optmass": 1175, + "maxfuel": 5.2, + "fuelmul": 0.013, + "fuelpower": 2.45 + }, + "int_hyperdrive_overcharge_size6_class1": { + "mass": 40, + "optmass": 1200, + "maxfuel": 5.3, + "fuelmul": 0.008, + "fuelpower": 2.6 + }, + "int_hyperdrive_overcharge_size6_class2": { + "mass": 16, + "optmass": 1800, + "maxfuel": 8, + "fuelmul": 0.012, + "fuelpower": 2.6 + }, + "int_hyperdrive_overcharge_size6_class3": { + "mass": 40, + "optmass": 1800, + "maxfuel": 8, + "fuelmul": 0.012, + "fuelpower": 2.6 + }, + "int_hyperdrive_overcharge_size6_class4": { + "mass": 40, + "optmass": 1800, + "maxfuel": 8, + "fuelmul": 0.012, + "fuelpower": 2.6 + }, + "int_hyperdrive_overcharge_size6_class5": { + "mass": 40, + "optmass": 2000, + "maxfuel": 8.3, + "fuelmul": 0.013, + "fuelpower": 2.6 + }, + "int_hyperdrive_overcharge_size7_class1": { + "mass": 80, + "optmass": 1800, + "maxfuel": 8.5, + "fuelmul": 0.008, + "fuelpower": 2.75 + }, + "int_hyperdrive_overcharge_size7_class2": { + "mass": 32, + "optmass": 2700, + "maxfuel": 12.8, + "fuelmul": 0.012, + "fuelpower": 2.75 + }, + "int_hyperdrive_overcharge_size7_class3": { + "mass": 80, + "optmass": 2700, + "maxfuel": 12.8, + "fuelmul": 0.012, + "fuelpower": 2.75 + }, + "int_hyperdrive_overcharge_size7_class4": { + "mass": 80, + "optmass": 2700, + "maxfuel": 12.8, + "fuelmul": 0.012, + "fuelpower": 2.75 + }, + "int_hyperdrive_overcharge_size7_class5": { + "mass": 80, + "optmass": 3000, + "maxfuel": 13.1, + "fuelmul": 0.013, + "fuelpower": 2.75 + }, "int_hyperdrive_size2_class1": { "mass": 2.5, "optmass": 48, From 6ffcd91de5fd95c496b273ded83061a42b82eda6 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 10 May 2024 17:49:13 -0400 Subject: [PATCH 199/261] [2228] Add PyMkII Armor Details From upcoming Coriolis data --- modules.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules.json b/modules.json index b19441d44..e9e444d62 100644 --- a/modules.json +++ b/modules.json @@ -3363,6 +3363,21 @@ "python_armour_reactive": { "mass": 53 }, + "python_nx_armour_grade1": { + "mass": 0 + }, + "python_nx_armour_grade2": { + "mass": 26 + }, + "python_nx_armour_grade3": { + "mass": 53 + }, + "python_nx_armour_mirrored": { + "mass": 53 + }, + "python_nx_armour_reactive": { + "mass": 53 + }, "sidewinder_armour_grade1": { "mass": 0 }, From 6032e5202d74d30cc187e467fd1cbc49e6e5f2a4 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 11 May 2024 17:58:39 -0400 Subject: [PATCH 200/261] [RELEASE] 5.10.6 --- ChangeLog.md | 29 +++++++++++++++++++++++++++++ config/__init__.py | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 324d1f8d9..1dc357f1f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,27 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- +Release 5.10.6 +=== +This release contains the data information for the new SCO modules added in Elite update 18.04. +This should represent full support for the new Python Mk II. + +We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC + +**Changes and Enhancements** +* Added new SCO Module Details +* Reverted a change from the prior release due to breaking some consumers. +**Plugin Developers** +* modules.p and ships.p are deprecated, and slated for removal in 5.11+! +* The `openurl()` function in ttkHyperlinkLabel has been deprecated, +and slated for removal in 5.11+! Please migrate to `webbrowser.open()`. + +**Plugin Developers** +* modules.p and ships.p are deprecated, and slated for removal in 5.11+! +* The `openurl()` function in ttkHyperlinkLabel has been deprecated, +and slated for removal in 5.11+! Please migrate to `webbrowser.open()`. + Release 5.10.5 === This release contains a fix for a bug that could crash EDMC's console versions when reading outfitting information @@ -14,6 +35,9 @@ from the new SCO Frame Shift Drive modules. Please note that this does not offer full support for the new SCO modules or the Python Mk II. More support will be added in a future update. +We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC + **Changes and Enhancements** * Updated Translations * Added limited data regarding the Python Mk II @@ -24,6 +48,11 @@ be added in a future update. * Fixed a bug where the new SCO modules would display as a normal Frame Shift Drive * Fixed a bug which could crash EDMC if the exact details of a Frame Shift Drive were unknown +**Plugin Developers** +* modules.p and ships.p are deprecated, and slated for removal in 5.11+! +* The `openurl()` function in ttkHyperlinkLabel has been deprecated, +and slated for removal in 5.11+! Please migrate to `webbrowser.open()`. + Release 5.10.4 === This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also diff --git a/config/__init__.py b/config/__init__.py index a4a1e200c..7e81d9689 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -53,7 +53,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.10.5' +_static_appversion = '5.10.6' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 37b13fd81d1f9c0bea32f2fd474baf74b9fbb1d6 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 11 May 2024 18:10:32 -0400 Subject: [PATCH 201/261] [5.10.6] Update XML --- ChangeLog.md | 3 ++- edmarketconnector.xml | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 7d14ca9c6..e0d98bf40 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -15,8 +15,9 @@ We now sign our code! This does mean that built EXEs are now slightly modified o For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC **Changes and Enhancements** -* Added new SCO Module Details +* Added new SCO and Python Armor Module Details * Reverted a change from the prior release due to breaking some consumers. + **Plugin Developers** * modules.p and ships.p are deprecated, and slated for removal in 5.11+! * The `openurl()` function in ttkHyperlinkLabel has been deprecated, diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 40fa6c000..88ff05ce1 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,10 +22,27 @@ - Release 5.10.5 + Release 5.10.6 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }

    We now test against, and package with, Python 3.11.7.

    As a result, we do not support Windows 7, 8, or 8.1.
    +

    Release 5.10.6

    +

    This release contains the data information for the new SCO modules added in Elite update 18.04. +This should represent full support for the new Python Mk II.

    +

    We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. +For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC

    +

    Changes and Enhancements

    +
      +
    • Added new SCO and Python Armor Module Details
    • +
    • Reverted a change from the prior release due to breaking some consumers.
    • +
    +

    Plugin Developers

    +
      +
    • modules.p and ships.p are deprecated, and slated for removal in 5.11+!
    • +
    • The openurl() function in ttkHyperlinkLabel has been deprecated, +and slated for removal in 5.11+! Please migrate to webbrowser.open().
    • +
    +

    Release 5.10.5

    This release contains a fix for a bug that could crash EDMC's console versions when reading outfitting information from the new SCO Frame Shift Drive modules.

    Please note that this does not offer full support for the new SCO modules or the Python Mk II. More support will be added in a future update.

    @@ -2255,7 +2272,7 @@ about this: PTS CAPI saying Commander is Docked after jumping to new system.

]]>
- +
From ad16f0666dabc95a385942902e229fb034c5ecf8 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 12 May 2024 23:16:45 -0400 Subject: [PATCH 202/261] [2232] Fix Missing Preferences Fixes an issue that could present on clean installs where the EDSM keys weren't generated properly. When the heck did this bug get introduced??? --- plugins/edsm.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/edsm.py b/plugins/edsm.py index 5cd974d53..fd4d39257 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -491,6 +491,20 @@ def credentials(cmdr: str) -> tuple[str, str] | None: edsm_usernames = config.get_list('edsm_usernames') edsm_apikeys = config.get_list('edsm_apikeys') + if not edsm_usernames: # https://github.com/EDCD/EDMarketConnector/issues/2232 + edsm_usernames = ["" for _ in range(len(cmdrs))] + config.set('edsm_usernames', edsm_usernames) + else: # Check for Mismatched Length - fill with null values. + if len(edsm_usernames) < len(cmdrs): + edsm_usernames.extend(["" for _ in range(len(cmdrs) - len(edsm_usernames))]) + + if not edsm_apikeys: + edsm_apikeys = ["" for _ in range(len(cmdrs))] + config.set('edsm_apikeys', edsm_apikeys) + else: # Check for Mismatched Length - fill with null values. + if len(edsm_apikeys) < len(cmdrs): + edsm_apikeys.extend(["" for _ in range(len(cmdrs) - len(edsm_apikeys))]) + if cmdr in cmdrs and len(cmdrs) == len(edsm_usernames) == len(edsm_apikeys): idx = cmdrs.index(cmdr) if idx < len(edsm_usernames) and idx < len(edsm_apikeys): From eece4c2ba75e51c7062905c885ba8fd180b791cd Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 12 May 2024 23:18:24 -0400 Subject: [PATCH 203/261] [2232] Set EDSM Settings --- plugins/edsm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index fd4d39257..1d9f8cb85 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -493,17 +493,17 @@ def credentials(cmdr: str) -> tuple[str, str] | None: if not edsm_usernames: # https://github.com/EDCD/EDMarketConnector/issues/2232 edsm_usernames = ["" for _ in range(len(cmdrs))] - config.set('edsm_usernames', edsm_usernames) else: # Check for Mismatched Length - fill with null values. if len(edsm_usernames) < len(cmdrs): edsm_usernames.extend(["" for _ in range(len(cmdrs) - len(edsm_usernames))]) + config.set('edsm_usernames', edsm_usernames) if not edsm_apikeys: edsm_apikeys = ["" for _ in range(len(cmdrs))] - config.set('edsm_apikeys', edsm_apikeys) else: # Check for Mismatched Length - fill with null values. if len(edsm_apikeys) < len(cmdrs): edsm_apikeys.extend(["" for _ in range(len(cmdrs) - len(edsm_apikeys))]) + config.set('edsm_apikeys', edsm_apikeys) if cmdr in cmdrs and len(cmdrs) == len(edsm_usernames) == len(edsm_apikeys): idx = cmdrs.index(cmdr) From fb6206279a4ba47e171ee12feeba00adc05f9740 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 13 May 2024 12:27:39 +0000 Subject: [PATCH 204/261] updating submodules --- coriolis-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coriolis-data b/coriolis-data index 8adfd86b6..651aab5af 160000 --- a/coriolis-data +++ b/coriolis-data @@ -1 +1 @@ -Subproject commit 8adfd86b64e8c14e873d2f5123d88ca6743420b9 +Subproject commit 651aab5af6a22980a1f88dcbb9ed256244cd6dff From 64cd5b3cc5ad8e2463b1da121f1e2f0107972dca Mon Sep 17 00:00:00 2001 From: Phoebe <40956085+C1701D@users.noreply.github.com> Date: Tue, 14 May 2024 01:42:26 +0200 Subject: [PATCH 205/261] [1173] Add alt_URL check --- ttkHyperlinkLabel.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 3bb878a69..f77805660 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -19,6 +19,7 @@ May be imported by plugins """ from __future__ import annotations +import html from functools import partial import sys import tkinter as tk @@ -27,9 +28,11 @@ from tkinter import ttk from typing import Any import plug +from os import path from config import config, logger from l10n import translations as tr from monitor import monitor +from EDMarketConnector import SHIPYARD_HTML_TEMPLATE class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore @@ -70,7 +73,7 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non # Add Menu Options self.plug_options = kw.pop('plug_options', None) self.name = kw.get('name', None) - if self.name == 'ship' and not bool(config.get_int("use_alt_shipyard_open")): + if self.name == 'ship': self.menu.add_separator() for url in plug.provides('shipyard_url'): self.menu.add_command( @@ -96,12 +99,27 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non def open_shipyard(self, url: str): """Open the Current Ship Loadout in the Selected Provider.""" - if loadout := monitor.ship(): + if not (loadout := monitor.ship()): + logger.warning('No ship loadout, aborting.') + return '' + if not bool(config.get_int("use_alt_shipyard_open")): opener = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta) if opener: return webbrowser.open(opener) - logger.warning('No ship loadout, aborting.') - return '' + else: + # Avoid file length limits if possible + provider = config.get_str('shipyard_provider', default='EDSY') + target = plug.invoke(provider, 'EDSY', 'shipyard_url', loadout, monitor.is_beta) + file_name = path.join(config.app_dir_path, "last_shipyard.html") + + with open(file_name, 'w') as f: + f.write(SHIPYARD_HTML_TEMPLATE.format( + link=html.escape(str(target)), + provider_name=html.escape(str(provider)), + ship_name=html.escape("Ship") + )) + + webbrowser.open(f'file://localhost/{file_name}') def open_system(self, url: str): """Open the Current System in the Selected Provider.""" From 459881d618ee04cfcfc2875139570c3bd1f86de6 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 13 May 2024 19:45:53 -0400 Subject: [PATCH 206/261] [1173] Fix Circular Import --- EDMarketConnector.py | 17 +---------------- ttkHyperlinkLabel.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index d92430637..8eebd631a 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -431,25 +431,10 @@ def already_running_popup(): from l10n import translations as tr from monitor import monitor from theme import theme -from ttkHyperlinkLabel import HyperlinkLabel +from ttkHyperlinkLabel import HyperlinkLabel, SHIPYARD_HTML_TEMPLATE SERVER_RETRY = 5 # retry pause for Companion servers [s] -SHIPYARD_HTML_TEMPLATE = """ - - - - - Redirecting you to your {ship_name} at {provider_name}... - - - - You should be redirected to your {ship_name} at {provider_name} shortly... - - - -""" - class AppWindow: """Define the main application window.""" diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index f77805660..3ec0a26d7 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -32,7 +32,21 @@ from config import config, logger from l10n import translations as tr from monitor import monitor -from EDMarketConnector import SHIPYARD_HTML_TEMPLATE + +SHIPYARD_HTML_TEMPLATE = """ + + + + + Redirecting you to your {ship_name} at {provider_name}... + + + + You should be redirected to your {ship_name} at {provider_name} shortly... + + + +""" class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore From 68fac14c676a25263cb50b1618caa812a0ffc62e Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 14 May 2024 20:13:49 -0400 Subject: [PATCH 207/261] [Translations] Update Translations --- EDMarketConnector.py | 2 +- L10n/en.template | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4932ea0d8..15b068d36 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -2205,7 +2205,7 @@ def test_prop(self): logger.exception(f"EDMC Critical Error: {err}") title = tr.tl("Error") # LANG: Generic error prefix message = tr.tl( # LANG: EDMC Critical Error Notification - "EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" + "EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" ) err = f"{err.__class__.__name__}: {err}" # type: ignore # hijacking the existing exception detection detail = tr.tl( # LANG: EDMC Critical Error Details diff --git a/L10n/en.template b/L10n/en.template index ae8541340..8e82a9e40 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -37,7 +37,7 @@ "Error: unable to get token" = "Error: unable to get token"; /* EDMarketConnector.py: EDMC Critical Error Notification; */ -"EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!"; +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!"; /* EDMarketConnector.py: EDMC Critical Error Details; */ "Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?"; From 148c853b1e191aa0c8e049daabd8c4176666f85b Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 14 May 2024 23:04:16 -0400 Subject: [PATCH 208/261] *Grumble Grumble* --- L10n/en.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/L10n/en.template b/L10n/en.template index 8e82a9e40..307841b27 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -37,7 +37,7 @@ "Error: unable to get token" = "Error: unable to get token"; /* EDMarketConnector.py: EDMC Critical Error Notification; */ -"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!"; +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!"; /* EDMarketConnector.py: EDMC Critical Error Details; */ "Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?"; From 77720685de54e368fcdeff5eb405819df4543ce5 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 20 May 2024 11:50:33 -0400 Subject: [PATCH 209/261] [Minor] Update Version for Alpha Tracking --- config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index 7e81d9689..bf514186a 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -53,7 +53,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.10.6' +_static_appversion = '5.11.0-alpha3' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 950a204305e83639be904f68ecbeebc40f0f9099 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 05:59:14 +0000 Subject: [PATCH 210/261] --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b6a7482f3..34091caf3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ certifi==2024.2.2 -requests==2.31.0 +requests==2.32.0 # requests depends on this now ? charset-normalizer==3.3.2 From f6d7100f24ae070dd57836bebae7a6f39720bcb0 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 21 May 2024 16:54:49 -0400 Subject: [PATCH 211/261] [Fix] Update Translations, Fix Menu Creation, Fix Alt URL --- L10n/en.template | 9 ++++++ myNotebook.py | 10 ++++--- ttkHyperlinkLabel.py | 67 ++++++++++++++++++++++++-------------------- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/L10n/en.template b/L10n/en.template index 307841b27..56e458058 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -126,6 +126,15 @@ /* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ "Copy" = "Copy"; +/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */ +"Cut" = "Cut"; + +/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */ +"Paste" = "Paste"; + +/* myNotebook.py: Label for 'Select All'; */ +"Select All" = "Select All"; + /* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ "CAPI auth disabled by killswitch" = "CAPI auth disabled by killswitch"; diff --git a/myNotebook.py b/myNotebook.py index 8a3cf9010..635af5c12 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -67,12 +67,14 @@ def __init__(self, *args, **kwargs) -> None: ttk.Entry.__init__(self, *args, **kwargs) self.menu = tk.Menu(self, tearoff=False) - self.menu.add_command(label="Copy", command=self.copy) - self.menu.add_command(label="Cut", command=self.cut) + self.menu.add_command(label=tr.tl("Copy"), command=self.copy) # LANG: Label for 'Copy' as in 'Copy and Paste' + self.menu.add_command(label=tr.tl("Cut"), command=self.cut) # LANG: Label for 'Cut' as in 'Cut and Paste' self.menu.add_separator() - self.menu.add_command(label="Paste", command=self.paste) + # LANG: Label for 'Paste' as in 'Copy and Paste' + self.menu.add_command(label=tr.tl("Paste"), command=self.paste) self.menu.add_separator() - self.menu.add_command(label="Select All", command=self.select_all) + # LANG: Label for 'Select All' + self.menu.add_command(label=tr.tl("Select All"), command=self.select_all) self.bind("", self.display_popup) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 3ec0a26d7..6266d9fb2 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -70,10 +70,6 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non ttk.Label.__init__(self, master, **kw) self.bind('', self._click) - - self.menu = tk.Menu(tearoff=tk.FALSE) - # LANG: Label for 'Copy' as in 'Copy and Paste' - self.menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste self.bind('', self._contextmenu) self.bind('', self._enter) @@ -87,29 +83,6 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non # Add Menu Options self.plug_options = kw.pop('plug_options', None) self.name = kw.get('name', None) - if self.name == 'ship': - self.menu.add_separator() - for url in plug.provides('shipyard_url'): - self.menu.add_command( - label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider - command=partial(self.open_shipyard, url) - ) - - if self.name == 'station': - self.menu.add_separator() - for url in plug.provides('station_url'): - self.menu.add_command( - label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider - command=partial(self.open_station, url) - ) - - if self.name == 'system': - self.menu.add_separator() - for url in plug.provides('system_url'): - self.menu.add_command( - label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider - command=partial(self.open_system, url) - ) def open_shipyard(self, url: str): """Open the Current Ship Loadout in the Selected Provider.""" @@ -122,14 +95,13 @@ def open_shipyard(self, url: str): return webbrowser.open(opener) else: # Avoid file length limits if possible - provider = config.get_str('shipyard_provider', default='EDSY') - target = plug.invoke(provider, 'EDSY', 'shipyard_url', loadout, monitor.is_beta) + target = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta) file_name = path.join(config.app_dir_path, "last_shipyard.html") with open(file_name, 'w') as f: f.write(SHIPYARD_HTML_TEMPLATE.format( link=html.escape(str(target)), - provider_name=html.escape(str(provider)), + provider_name=html.escape(str(url)), ship_name=html.escape("Ship") )) @@ -212,8 +184,41 @@ def _click(self, event: tk.Event) -> None: webbrowser.open(url) def _contextmenu(self, event: tk.Event) -> None: + """ + Display the context menu when right-clicked. + + :param event: The event object. + """ + menu = tk.Menu(tearoff=tk.FALSE) + # LANG: Label for 'Copy' as in 'Copy and Paste' + menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste + + if self.name == 'ship': + menu.add_separator() + for url in plug.provides('shipyard_url'): + menu.add_command( + label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider + command=partial(self.open_shipyard, url) + ) + + if self.name == 'station': + menu.add_separator() + for url in plug.provides('station_url'): + menu.add_command( + label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider + command=partial(self.open_station, url) + ) + + if self.name == 'system': + menu.add_separator() + for url in plug.provides('system_url'): + menu.add_command( + label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider + command=partial(self.open_system, url) + ) + if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy): - self.menu.post(event.x_root, event.y_root) + menu.post(event.x_root, event.y_root) def copy(self) -> None: """Copy the current text to the clipboard.""" From 778ccaeaaa6d9e13db4ec61b01f7a2df5babac58 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 26 May 2024 14:42:12 -0400 Subject: [PATCH 212/261] [519] Fix Padding & Display Levels --- EDMarketConnector.py | 2 +- prefs.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 92c0f4919..cfa68ce75 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -844,7 +844,7 @@ def postprefs(self, dologin: bool = True, **postargs): ) update_msg = update_msg.replace('\\n', '\n') update_msg = update_msg.replace('\\r', '\r') - stable_popup = tk.messagebox.askyesno(title=title, message=update_msg) + stable_popup = tk.messagebox.askyesno(title=title, message=update_msg, parent=postargs.get('Parent')) if stable_popup: webbrowser.open("https://github.com/edCD/eDMarketConnector/releases/latest") diff --git a/prefs.py b/prefs.py index cdbb4fc47..5567e0942 100644 --- a/prefs.py +++ b/prefs.py @@ -535,7 +535,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ) self.update_track.configure(width=15) - self.update_track.grid(column=1, pady=self.BOXY, sticky=tk.W, row=curr_row) + self.update_track.grid(column=1, pady=self.BOXY, padx=self.PADX, sticky=tk.W, row=curr_row) self.disable_autoappupdatecheckingame = tk.IntVar(value=config.get_int('disable_autoappupdatecheckingame')) self.disable_autoappupdatecheckingame_btn = nb.Checkbutton( @@ -1262,8 +1262,9 @@ def apply(self) -> None: # Send to the Post Config if we updated the update branch post_flags = { 'Update': True if self.curr_update_track != self.update_paths.get() else False, - 'Track': self.update_paths.get() - } + 'Track': self.update_paths.get(), + 'Parent': self + } # Notify if self.callback: self.callback(**post_flags) From fe7afad5e7b8acb30a55a933087c7f2b47f019af Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 26 May 2024 15:14:02 -0400 Subject: [PATCH 213/261] [Fix] Set Parent for messageboxes --- EDMCSystemProfiler.py | 2 +- EDMarketConnector.py | 12 ++++++++---- myNotebook.py | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/EDMCSystemProfiler.py b/EDMCSystemProfiler.py index 90a96ceee..92502cc8b 100644 --- a/EDMCSystemProfiler.py +++ b/EDMCSystemProfiler.py @@ -105,7 +105,7 @@ def copy_sys_report(root: tk.Tk, report: str) -> None: """Copy the system info to the keyboard.""" root.clipboard_clear() root.clipboard_append(report) - messagebox.showinfo("System Profiler", "System Report copied to Clipboard") + messagebox.showinfo("System Profiler", "System Report copied to Clipboard", parent=root) def main() -> None: diff --git a/EDMarketConnector.py b/EDMarketConnector.py index cfa68ce75..7f870108f 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -2068,7 +2068,8 @@ def validate_providers(): tk.messagebox.showinfo( # LANG: Popup window title for Reset Providers tr.tl('EDMC: Default Providers Reset'), - popup_text + popup_text, + parent=root ) @@ -2237,7 +2238,8 @@ def test_prop(self): detail = detail.replace('\\n', '\n') detail = detail.replace('\\r', '\r') msg = tk.messagebox.askyesno( - title=title, message=message, detail=detail, icon=tkinter.messagebox.ERROR, type=tkinter.messagebox.YESNO + title=title, message=message, detail=detail, icon=tkinter.messagebox.ERROR, type=tkinter.messagebox.YESNO, + parent=root ) if msg: webbrowser.open( @@ -2271,7 +2273,8 @@ def messagebox_broken_plugins(): tk.messagebox.showinfo( # LANG: Popup window title for list of 'broken' plugins that failed to load tr.tl('EDMC: Broken Plugins'), - popup_text + popup_text, + parent=root ) def messagebox_not_py3(): @@ -2301,7 +2304,8 @@ def messagebox_not_py3(): tk.messagebox.showinfo( # LANG: Popup window title for list of 'enabled' plugins that don't work with Python 3.x tr.tl('EDMC: Plugins Without Python 3.x Support'), - popup_text + popup_text, + parent=root ) config.set('plugins_not_py3_last', int(time())) diff --git a/myNotebook.py b/myNotebook.py index 635af5c12..0b083c237 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -108,7 +108,8 @@ def paste(self) -> None: # Hijack existing translation, yes it doesn't exactly match here. messagebox.showwarning( tr.tl('Error'), # LANG: Generic error prefix - following text is from Frontier auth service; - tr.tl('Cannot paste non-text content.') # LANG: Can't Paste Images or Files in Text + tr.tl('Cannot paste non-text content.'), # LANG: Can't Paste Images or Files in Text + parent=self.master ) return text = self.clipboard_get() From bbb7d4bf32990c53df07c8deec205bbc14d11326 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 26 May 2024 16:56:16 -0400 Subject: [PATCH 214/261] [Lang] Update Languages --- L10n/cs.strings | 24 ----------------- L10n/de.strings | 45 +++++++++++-------------------- L10n/es.strings | 24 ----------------- L10n/fi.strings | 24 ----------------- L10n/fr.strings | 24 ----------------- L10n/hu.strings | 24 ----------------- L10n/it.strings | 51 +++++++++++++---------------------- L10n/ja.strings | 31 +--------------------- L10n/ko.strings | 24 ----------------- L10n/lv.strings | 24 ----------------- L10n/nl.strings | 24 ----------------- L10n/pl.strings | 31 +--------------------- L10n/pt-BR.strings | 58 +++++++++++++++++++--------------------- L10n/pt-PT.strings | 59 ++++++++++++++++++++--------------------- L10n/ru.strings | 45 +++++++++++-------------------- L10n/sl.strings | 24 ----------------- L10n/sr-Latn-BA.strings | 30 --------------------- L10n/sr-Latn.strings | 45 +++++++++++-------------------- L10n/sv-SE.strings | 24 ----------------- L10n/tr.strings | 45 +++++++++++-------------------- L10n/uk.strings | 24 ----------------- L10n/zh-Hans.strings | 24 ----------------- 22 files changed, 137 insertions(+), 591 deletions(-) diff --git a/L10n/cs.strings b/L10n/cs.strings index 071b5e69d..d432effe2 100644 --- a/L10n/cs.strings +++ b/L10n/cs.strings @@ -61,12 +61,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Upravit"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Zobrazit"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Okno"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Nápověda"; @@ -256,9 +250,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Chyba: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Nastavení"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Vyberte, která data chcete ukládat"; @@ -277,9 +268,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Cesta k souboru"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Změnit..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Procházet..."; @@ -289,21 +277,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "E:D umístění souboru deníku"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Klávesová zkratka"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Klávesová zkratka"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Restartujte {APP} pro aktivaci klávesových zkratek"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} vyžaduje oprávnění pro použití klávesových zkratek"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Otevřít Předvolby systému"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Pouze pokud je Elite: Dangerous aktivní"; diff --git a/L10n/de.strings b/L10n/de.strings index be36b877a..448607acb 100644 --- a/L10n/de.strings +++ b/L10n/de.strings @@ -36,6 +36,12 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Fehler: konnte Token nicht erhalten"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC ist in einen unwiederbringlichen kritischen Fehler gelaufen. EDMC fährt zum Selbstschutz herunter!"; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Das hat EDMC erkannt:\n\n{ERR}\n\nMöchtest Du einen Bug Report auf GitHub einreichen?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "Frontier CAPI offline wegen Wartung"; @@ -78,12 +84,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Bearbeiten"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Ansicht"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Fenster"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Hilfe"; @@ -93,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Auf Aktualisierungen überprüfen..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Öffne System Profiler"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Speichere Originaldaten..."; @@ -213,12 +216,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Eins oder mehr deiner aktivierten Plugins hat noch keinen Support für Python 3.x. Du kannst dir die Liste im '{PLUGINS}'-Tab unter '{FILE}' > '{SETTINGS}' ansehen. Du solltest prüfen, ob es für diese Updates gibt und ansonsten dem Entwickler Bescheid geben, dass sie ihren Code für Python 3.x aktualisieren müssen.\n\nDu kannst ein Plugin deaktivieren, indem du dessen Ordner ein '{DISABLED}' am Ende des Namens anhängst."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID Dateien nicht gefunden! Einige Funktionen in Bezug auf Waren sind möglicherweise deaktiviert.\n\nMöchten du die Wiki-Seite zum Einrichten von Submodulen öffnen?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: Warendateien fehlen"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Plugins"; @@ -351,9 +348,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Fehler: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Einstellungen"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Bitte wähle zu speichernde Daten aus"; @@ -372,9 +366,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Speicherort"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Ordner..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Durchsuchen..."; @@ -390,21 +381,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Fleet Carrier CAPI-Anfragen aktivieren"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Makro"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Hotkey"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Starte {APP} neu, um den Tastenkürzel nutzen zu können"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} benötigt für Tastenkürzel Systemrechte (Bedienungshilfen)"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Öffne Systemeinstellungen"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Nur wenn Elite: Dangerous geöffnet ist"; @@ -782,3 +761,9 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} ist verfügbar"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Kann keinen non-Text-Inhalt einfügen."; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Öffnen in {URL}"; diff --git a/L10n/es.strings b/L10n/es.strings index 18874ccf8..96bb83bce 100644 --- a/L10n/es.strings +++ b/L10n/es.strings @@ -67,12 +67,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Editar"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Visualización"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Ventana"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Ayuda"; @@ -271,9 +265,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Error: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Preferencias"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Seleccione qué datos quiere guardar"; @@ -292,9 +283,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Localización de archivos"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Cambiar..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Examinar..."; @@ -304,21 +292,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "Localización del archivo de Journal de E:D"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Tecla de acceso directo"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Tecla de acceso directo"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Reinicie {APP} para usar los accesos directos de teclado"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} necesita permisos para usar teclas de acceso directo"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Abrir Preferencias del Sistema"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Solo cuando Elite: Dangerous es la aplicación activa"; diff --git a/L10n/fi.strings b/L10n/fi.strings index d53d3bc82..198a0eba0 100644 --- a/L10n/fi.strings +++ b/L10n/fi.strings @@ -61,12 +61,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Muokkaa"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Näytä"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Ikkuna"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Apua"; @@ -229,9 +223,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Inaran virhe: {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Valinnat"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Valitse mitkä tiedot tallennetaan"; @@ -250,9 +241,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Tiedostosijainti"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Muuta..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Selaa..."; @@ -262,21 +250,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "E:D lokikirjan tiedostosijainti"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Näppäimistöoikopolku"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Pikanäppäin"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Käynnistä {APP} uudelleen käyttääksesi pikanäppäimiä"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} tarvitsee lupasi pikakuvakkeiden käyttämiseen"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Avaa järjestelmän asetukset"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Vain jos Elite:Dangerous on käynnissä"; diff --git a/L10n/fr.strings b/L10n/fr.strings index 5d53fd24a..d8e54ca08 100644 --- a/L10n/fr.strings +++ b/L10n/fr.strings @@ -78,12 +78,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Édition"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Présentation"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Fenêtre"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Aide"; @@ -222,9 +216,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Erreur : Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Préférences"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Veuillez choisir les données à sauvegarder"; @@ -243,9 +234,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Emplacement des fichiers"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Spécifier..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Parcourir..."; @@ -255,21 +243,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "Emplacement du journal E:D"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Raccourci clavier"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Raccourci clavier"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Rédémarrez {APP} pour utiliser les raccourcis"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} a besoin de permissions pour utiliser les raccourcis"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Ouvrir Préférences Système"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Seulement quand Elite: Dangerous est l'application active"; diff --git a/L10n/hu.strings b/L10n/hu.strings index c3311ac72..cf141709c 100644 --- a/L10n/hu.strings +++ b/L10n/hu.strings @@ -37,12 +37,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Szerkeszt"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Nézet"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Ablak"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Segítség"; @@ -169,9 +163,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Hiba:Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Beállítások"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Kérem válassza ki a menteni kivánt adatokat."; @@ -190,9 +181,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Fálj helye"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Módosítás..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Böngész"; @@ -202,21 +190,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "E:D journal fájl helye"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Billentyűparancsok"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Gyorsgomb"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Újranidítás {APP} parancsikonok használatával"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} engedély szükséges a parancsikon használatához"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Rendszerbeállítások megnyitáss"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Csak ha az Elite Dangerous fut"; diff --git a/L10n/it.strings b/L10n/it.strings index c1cd9b339..024a732ff 100644 --- a/L10n/it.strings +++ b/L10n/it.strings @@ -2,10 +2,10 @@ "Send flight log and CMDR status to EDSM" = "Invia il registro di volo e lo stato del CMDR a EDSM"; /* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */ -"Open Log Folder" = "Cartella Log"; +"Open Log Folder" = "Apri la Cartella dei Log"; /* inara.py:Text Inara Show API key; In files: inara.py:305; */ -"Show API Key" = "Mostra API Key"; +"Show API Key" = "Mostra l'API Key"; /* Language name */ "!Language" = "Italiano"; @@ -36,11 +36,17 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Errore: impossibile ottenere il token"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC ha incontrato un errore critico, e non riesce a risolvere. EDMC si spegnerà in via cautelativa!"; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Ecco cosa EDMC ha rilevato:\n\n{ERR}\n\nVuoi compilare un Rapporto di Bug su GitHub?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "CAPI di Frontier inattivo per manutenzione"; /* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */ -"Frontier CAPI query failure" = "Errore nella query CAPI di Frontier"; +"Frontier CAPI query failure" = "Query CAPI di Frontier fallita"; /* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */ "Update" = "Recupera"; @@ -78,12 +84,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Appunti"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Vista"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Finestra"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Guida"; @@ -93,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Controlla aggiornamenti..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Apri Profilazione di Sistema"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Salva i Dati Grezzi..."; @@ -213,12 +216,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Uno o più dei tuoi plugin non ha ancora il supporto per Python 3.x. Per favore guarda la lista '{PLUGINS}' nella tab a '{FILE}' > '{SETTINGS}'. Dovresti controllare se ci sono aggiornamenti, altrimenti avvisa gli sviluppatori che devono aggiornare il codice a Python 3.x.\n\nPuoi disabilitare il plugin rinominando la cartella aggiungendo '{DISABLED}' alla fine del nome."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "File FDevID non trovati! Alcune funzionalità relative alle commodities potrebbero essere disabilitate.\n\n Vuoi aprire la pagina Wiki su come impostare i sottomoduli?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevID: file delle Commodity mancanti"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Plugins"; @@ -351,9 +348,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Errore: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Preferenze"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Si prega di scegliere che dati salvare"; @@ -372,9 +366,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Percorso del file"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Cambia..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Sfoglia..."; @@ -390,21 +381,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Abilita le query CAPI della Fleetcarrier"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Scorciatoia di tastiera"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Hotkey"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Riavvia {APP} per usare le scorciatoie"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} ha bisogno dei permessi per usare le scorciatoie"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Apri Preferenze di Sistema"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Solo quando “Elite: Dangerous” è in primo piano"; @@ -803,3 +782,9 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} è disponibile"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Non si può incollare contenuto non testuale"; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Apri {URL}"; diff --git a/L10n/ja.strings b/L10n/ja.strings index 75acb2522..fcb336f1f 100644 --- a/L10n/ja.strings +++ b/L10n/ja.strings @@ -78,12 +78,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "編集"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "表示"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "ウィンドウ"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "ヘルプ"; @@ -213,12 +207,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "有効にしている1つ以上のプラグインがPython 3.xをサポートしていません。'{FILE}' > '{SETTINGS}'\nメニューで表示される設定ダイアログの'{PLUGINS}' タブの一覧を確認してください。更新済みのバージョンがあるかを確認し、なければ開発者にPython 3.xに対応するように開発者に連絡してください。\n\nプラグインを無効にするにはフォルダ名の最後に'{DISABLED}'を追加してください。"; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevIDファイルが見つかりません!商品に関するいくつかの機能が無効になるかもしれません。\n\nサブモジュールの設定方法についてのWikiページを開きますか?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: 商品ファイルが見つかりません"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "プラグイン"; @@ -351,9 +339,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "エラー: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "環境設定"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "保存するデータの種類を選択して下さい"; @@ -372,9 +357,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "ファイルの出力先"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "変更..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "参照..."; @@ -390,21 +372,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "フリートキャリアCAPIクエリを有効にする"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "ショートカット"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "ホットキー"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "ショートカットを利用するには {APP} を再起動してください"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} がショートカットを利用するには許可が必要です"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "システム環境設定を開く"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Elite: Dangerousがアクティブの時だけ有効"; @@ -803,3 +773,4 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} があります"; + diff --git a/L10n/ko.strings b/L10n/ko.strings index e29ad74fc..58e43ba49 100644 --- a/L10n/ko.strings +++ b/L10n/ko.strings @@ -70,12 +70,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "편집"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "보기"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "창"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "도움말"; @@ -289,9 +283,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "오류: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "설정"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "어느 데이터를 저장할 지 선택해주세요"; @@ -310,9 +301,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "파일 위치"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "바꾸기..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "찾아보기..."; @@ -322,21 +310,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "E:D 저널(journal) 파일 위치"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "단축키 설정"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "단축키"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "단축키를 사용하려면 {APP}을(를) 다시 시작하십시오"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "단축기 사용을 위해선 {APP}이 권한 허용을 필요로 합니다"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "시스템 설정 열기"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Elite: Dangerous가 활성 창 상태일때만"; diff --git a/L10n/lv.strings b/L10n/lv.strings index 3aa462845..a99cfd443 100644 --- a/L10n/lv.strings +++ b/L10n/lv.strings @@ -34,12 +34,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Labot"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Skats"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Logs"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Palīdzība"; @@ -157,9 +151,6 @@ /* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */ "Error: Can't connect to EDSM" = "Kļūda: Nevar savienoties ar EDSM"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Iestatījumi"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Lūdzu izvēlaties kādus datus saglabāt"; @@ -178,9 +169,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Faila atrašanās vieta"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Mainīt..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Pārlūkot..."; @@ -190,21 +178,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "E:D žurnāla faila atrašanās vieta"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Ātrais taustiņš"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Ātrais taustiņš"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Restartējiet {APP} lai izmantotu īssceļus"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} vajag atļaujas lai izmantotu īssceļus"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Atvērt sistēmas iestatījumus"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Tikai kad Elite: Dangerous ir aktīvā aplikācija"; diff --git a/L10n/nl.strings b/L10n/nl.strings index f6a4a32fe..a32320896 100644 --- a/L10n/nl.strings +++ b/L10n/nl.strings @@ -37,12 +37,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Bewerken"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Beeld"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Venster"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Help"; @@ -175,9 +169,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Fout: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Preferences"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Welke gegevens wilt u opslaan?"; @@ -196,9 +187,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Bestand locatie"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Wijzigen..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Bladeren..."; @@ -208,21 +196,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "E:D journaal bestand locatie"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Toetscombinaties"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Hotkey"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "{APP} heropstarten om toetscombinaties te gebruiken"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} heeft toestemming nodig om toetscombinaties te gebruiken"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Open Systeemvoorkeuren"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Alleen wanneer Elite: Dangerous de actieve applicatie is"; diff --git a/L10n/pl.strings b/L10n/pl.strings index efde05bee..3ab5992a3 100644 --- a/L10n/pl.strings +++ b/L10n/pl.strings @@ -78,12 +78,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Edycja"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Widok"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Okno"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Pomoc"; @@ -213,12 +207,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Co najmniej jeden z uruchomionych pluginów nie wspiera Python 3.x. Sprawdź listę w '{FILE}' > '{SETTINGS}', sekcja '{PLUGINS}'. Upewnij się, że dostępna jest aktualna wersja pluginu, w przeciwnym razie poinformuj twórcę, że należy zaktualizować kod do wersji Python 3.x.\n\nMożesz wyłączyć plugin, dodając '{DISABLED}' na koniec nazwy jego folderu."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "Pliki FDevID nie znalezione. Część funkcjonalności związanej z materiałami może nie działać. \n\nChcesz otworzyć stronę Wiki dotyczacą konfiguracji podmodułów?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: Brakuje plików z towarami."; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Pluginy"; @@ -351,9 +339,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Błąd: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Preferencje"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Jakie dane zapisywać"; @@ -372,9 +357,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Położenie pliku"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Zmień..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Przeglądaj..."; @@ -390,21 +372,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Włącz zapytania CAPI dla lotniskowca"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Skrót klawiaturowy"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Skr. Klaw."; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Zrestartuj {APP} by użyć{CR}skrótu klawiszowego."; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} wymaga uprawnień by{CR}móc korzystać ze skrótów klawiszowych."; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Otwórz Preferencje systemowe"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Tylko gdy Elite: Dangerous jest aplikacją aktywną"; @@ -803,3 +773,4 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "Dostępna nowa wersja {NEWVER}"; + diff --git a/L10n/pt-BR.strings b/L10n/pt-BR.strings index b4d9e933b..48922eb45 100644 --- a/L10n/pt-BR.strings +++ b/L10n/pt-BR.strings @@ -36,6 +36,12 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Erro: não foi possível obter o token"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC encontrou um erro crítico e não conseguiu recuperar-se. Encerrando o EDMC para proteção própria!"; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Aqui está o que o EDMC detectou:\n\n{ERR}\n\nGostaria de reportar o bug em nosso GitHub?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "CAPI da Frontier indisponível por manutenção"; @@ -78,12 +84,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Editar"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Visualizar"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Janela"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Ajuda"; @@ -93,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Verificar por Atualizações..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Abrir Perfil de Sistema"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Salvar dados brutos..."; @@ -213,12 +216,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Um ou mais dos plugins habilitados não possuem suporte ao Python 3.x. Você pode ver a lista na guia '{PLUGINS}' em '{FILE}' > '{SETTINGS}'. Você deve verificar se existe versão atualizada ou então avisar o desenvolvedor de que ele precisa atualizar o código para o Python 3.x.\n\nVocê pode desabilitar um plugin renomeando sua pasta para que seu nome termine com '{DISABLED}'."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "Arquivo FDevID não encontrados! Algumas funções de mercadorias podem estar indisponíveis.\n\nGostaria de abrir a Wiki com instruções para configurar sub-módulos?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: Arquivos de Mercadorias não encontrados"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Plugins"; @@ -351,9 +348,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Erro: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Preferências"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Por favor, escolha quais dados para salvar"; @@ -372,9 +366,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Localização do Arquivo"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Alterar..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Procurar..."; @@ -390,21 +381,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Atalho de teclado"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Tecla de atalho"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Reinicie o {APP} para usar os atalhos"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} precisa de permissão para usar atalhos"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Abrir preferências do sistema"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Somente quando o Elite: Dangerous é o aplicativo ativo"; @@ -803,3 +782,22 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} está disponível"; + +/* prefs.py: Stable Version of EDMC; */ +"Stable" = "Estável"; + +/* prefs.py: Select the Update Track (Beta, Stable); */ +"Update Track" = "Caminho de Atualizações"; + +/* EDMarketConnector.py: Inform the user the Update Track has changed; */ +"Update Track Changed to {TRACK}" = "Caminho de Atualizações modificado para {TRACK}"; + + +/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */ +"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Caminho de atualizações modificado de Estável para Beta. Você não receberá mais atualizações Beta. Você seguirá na versão Beta atual até a próxima atualização Estável.\n\nVocê pode reverter manualmente para a última versão Estável. Para fazer isso, faça download e instale a última versão Estável manualmente. Fique atento: isto pode introduzir bugs ou quebrar completamente a aplicação caso você o retorno volte para uma versão principal com mudanças significativas.\n\nGostaria de abrir o GitHub para fazer download da última versão?"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Só é possível colar texto."; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Abrir em {URL}"; diff --git a/L10n/pt-PT.strings b/L10n/pt-PT.strings index 0fd5b585c..68d4912da 100644 --- a/L10n/pt-PT.strings +++ b/L10n/pt-PT.strings @@ -36,6 +36,12 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Erro: Não é possível obter o token"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "O EDMC encontrou um erro crítico e não consegue recuperar. O EDMC irá desligar-se para proteção própria."; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "O EDMC detectou o seguinte:\n\n{ERR}\n\nDeseja preencher um Bug Report no GitHub?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "CAPI da Frontier em manutenção."; @@ -78,12 +84,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Editar"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Ver"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Janela"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Ajuda"; @@ -93,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Procurar por Actualizações..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Abrir Informações do Sistema"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Salvar dados em bruto..."; @@ -213,12 +216,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Um ou mais dos plugins activados não possui ainda suporte para Python 3.x. Por favor verifique a lista na aba '{PLUGINS}' em '{FILE}' > '{SETTINGS}'. Deverá verificar se está disponível uma versão mais actualizada, caso contrário, informe o desenvolvedor de que necessita de actualizar o código para suportar Python 3.x.\n\nPode desactivar um plugin colocando '{DISABLED}' no fim do nome da pasta correspondente."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "Ficheiros FDevID não encontrados. Algumas funcionalidades relacionadas com mercadorias poderão estar indisponíveis.\nDeseja abrir a página da Wiki sobre configuração de submódulos?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: Ficheiros de Mercadorias em falta."; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Plugins"; @@ -231,6 +228,18 @@ /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Um ou mais dos plugins activados não arrancou correctamente. Por favor consule a lista na aba {PLUGINS}', em '{FILE}' > '{SETTINGS}'. Isto poderá ser causado por má estrutura de pastas. O ficheiro load.py deverá estar dentro de plugins/NOME_DO_PLUGIN/load.py. \nPode desactivar um plugin colocando '{DISABLED}' no fim do nome da pasta."; +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Um ou vários dos Provedores de URLs eram inválidos e foram reconfigurados.\n"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} era {OLDPROV}, foi reconfigurado para {NEWPROV}"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: Provedores for defeito reconfigurados."; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Aguardando pelo login completo do CMDR."; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Directório do Diário já se encontra bloqueado"; @@ -339,9 +348,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Erro: Inara: {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Preferências"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Por favor escolher quais os dados a guardar"; @@ -360,9 +366,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Localização do Ficheiro"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Alterar..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Procurar..."; @@ -378,21 +381,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Ligar Consultas CAPI de Transportador de Frota"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Atalho de Teclado"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Tecla Rápida"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Reinicie o {APP} para usar os atalhos"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "A {APP} precisa de autorização para usar atalhos"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Abrir Preferências do Sistema"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Apenas quando o Elite:Dangerous é a aplicação activa"; @@ -789,3 +780,11 @@ /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Naves"; +/* update.py: Update Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} disponível."; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Só é possível colar conteúdo texto."; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Abrir em {URL}"; diff --git a/L10n/ru.strings b/L10n/ru.strings index 19449e21d..54f77d620 100644 --- a/L10n/ru.strings +++ b/L10n/ru.strings @@ -36,6 +36,12 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Ошибка: не удается получить токен"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC столкнулся с критической ошибкой и не может восстановиться. EDMC отключается для собственной защиты!"; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Вот что обнаружил EDMC:\n\n{ERR}\n\nВы хотите отправить сообщение об ошибке на GitHub?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "Frontier CAPI остановлен для технического обслуживания"; @@ -78,12 +84,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Правка"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Вид"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Окно"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Дополнительно"; @@ -93,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Проверить наличие обновлений..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Открыть системный профайлер"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Сохранить \"сырые\" данные.."; @@ -213,12 +216,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Один или несколько ваших подключённых плагинов ещё не имеют поддержки Python 3.x. Пожалуйста, ознакомьтесь со списком во вкладке '{PLUGINS}' '{FILE}' > '{SETTINGS}'. Вы должны проверить наличие обновлённой версии, в противном случае предупредите разработчика о необходимости обновления кода на Python 3.x.\n\nВы можете отключить плагин, переименовав его папку в '{DISABLED}'."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID файлы не найдены! Некоторые функции, связанные с товарами, могут быть отключены.\n\nВы хотите открыть страницу Wiki о том, как настроить вспомогательные модули?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: отсутствуют файлы товаров"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Плагины"; @@ -351,9 +348,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Ошибка: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Настройки"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Выберите какие данные стоит сохранять"; @@ -372,9 +366,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Путь хранения файлов"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Обзор..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Просмотреть..."; @@ -390,21 +381,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Включить запросы к CAPI о флотоносце"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Сочетание клавиш"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Горячая клавиша"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Перезапустите {APP}, чтобы использовать сочетание клавиш"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} требуется разрешение на использование глобальных сочетаний клавиш"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Открыты «Настройки системы»"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Только когда окно Elite: Dangerous активно"; @@ -803,3 +782,9 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} доступен"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Невозможно добавить нетекстовое содержимое."; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Открыть через {URL}"; diff --git a/L10n/sl.strings b/L10n/sl.strings index 1ecb17531..1c038e612 100644 --- a/L10n/sl.strings +++ b/L10n/sl.strings @@ -31,12 +31,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Uredi"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Pogled"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Okno"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Pomoč"; @@ -136,9 +130,6 @@ /* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */ "Error: Can't connect to EDSM" = "Napaka: Povezava z EDSM ni mogoča"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Nastavitve"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Izberite podatke, ki jih želite shraniti"; @@ -154,30 +145,15 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Lokacija datoteke"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Spremeni..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Prebrskaj..."; /* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:405; */ "Output" = "Izpis"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Bližnjica"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Bližnjica"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Ponovno poženi {APP} za uporabo bližnjic"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} potrebuje dovoljenja za uporabo bližnjic"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Odpri sistemske nastavitve"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Samo ko je Elite:Dangerous aktivna aplikacija "; diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings index f541416a0..2d79e7e98 100644 --- a/L10n/sr-Latn-BA.strings +++ b/L10n/sr-Latn-BA.strings @@ -78,12 +78,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Uredi"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Pogled"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Prozor"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Pomoć"; @@ -213,12 +207,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više aktiviranih dodataka (plugins) nemaju podršku za Python 3.x. Pogledajte listu u '{PLUGINS}' tabu u '{FILE}' > '{SETTINGS}'. Provjerite da li postoji nadograđena verzija ili obavijesite autora da treba da promijeni kod za Python 3.x.\n\nMožete deaktivirati dodatak (plugin) dodavanjem '{DISABLED}' na kraju imena njegovog foldera."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID fajlovi nisu pronađeni. Određene opcije vezane za robu bi mogle da budu nedostupne.\n\nDa li želite da otvorite Wiki stranicu sa uputstvom za podešavanje podmodula?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: Nedostaju Commodity fajlovi"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Dodaci (plugins)"; @@ -339,9 +327,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Greška: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Podešavanja"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Izaberite koji podaci se snimaju"; @@ -360,9 +345,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Lokacija fajlova"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Promijeni..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Potraži..."; @@ -378,21 +360,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Omogući Fleetcarrier CAPI Upite"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Prečica"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Prečica"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Restartujte {APP} da bi ste koristili prečice"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} traži dozvolu da koristi prečice"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Otvori sistemska podešavanja"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Samo kada je Elite: Dangerous aktivna aplikacija"; diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings index e801d0676..291a036b7 100644 --- a/L10n/sr-Latn.strings +++ b/L10n/sr-Latn.strings @@ -36,6 +36,12 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Greška: nemoguće dobaviti token"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC je naišao na kritičnu grešku i ne može da se oporavi. EDMC će biti ugašen radi svoje zaštite."; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Evo šta je EDMC detektovao:\n\n{ERR}\n\nDa li želite da napravite Bug Report na GitHub-u?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "Frontier CAPI isključen radi održavanja"; @@ -78,12 +84,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Izmeni"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Pogled"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Prozor"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Pomoć"; @@ -93,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Proveri da li postoje izmene..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Otvori System Profiler"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Snimi sirove podatke..."; @@ -213,12 +216,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više aktiviranih dodataka (plugins) nemaju podršku za Python 3.x. Pogledajte listu u '{PLUGINS}' tabu u '{FILE}' > '{SETTINGS}'. Proverite da li postoji nadograđena verzija ili obavesite autora da treba da promeni kod za Python 3.x.\n\nMožete deaktivirati dodatak (plugin) dodavanjem '{DISABLED}' na kraju imena njegovog foldera."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID fajlovi nisu pronađeni! Neke funkcionalnosti vezane za artikle će možda biti deaktivirane.\n\nDa li želite da otvorite Wiki stranu koja objašnjava kako da podesite pod-module?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: Nedostaju fajlovi za robu"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Dodaci (plugins)"; @@ -351,9 +348,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Greška: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Podešavanja"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Izaberite koji se podaci snimaju"; @@ -372,9 +366,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Lokacija fajlova"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Izmeni..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Potraži..."; @@ -390,21 +381,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Aktiviraj Fleetcarrier CAPIU Upite"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Skraćenica"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Skraćenica"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Restartujte {APP} da bi ste koristili skraćenice"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} traži dozvolu da koristi skraćenice"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Otvori sistemska podešavanja"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Samo kada je Elite: Dangerous aktivna aplikacija"; @@ -803,3 +782,9 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} je dostupna"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Nemoguće je nalepiti netekstualni sadržaj."; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Otvori na {URL}"; diff --git a/L10n/sv-SE.strings b/L10n/sv-SE.strings index 8f9441598..e0f63e0ce 100644 --- a/L10n/sv-SE.strings +++ b/L10n/sv-SE.strings @@ -37,12 +37,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Ändra"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "View"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Fönster"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Hjälp"; @@ -175,9 +169,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Fel: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Inställningar"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Välj vilket data som skall lagras"; @@ -196,9 +187,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Filsökväg"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Ändra..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Bläddra..."; @@ -208,21 +196,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "E:D journal-fil sökväg"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Genväg, tangentbord"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Snabbkommando"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Starta om {APP} för att använda genvägar"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} behöver rättigheter för att använda genvägar"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Öppna systeminställningar"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Bara när: Elite Dangerous är det aktiva programmet"; diff --git a/L10n/tr.strings b/L10n/tr.strings index 9d37dfa1d..a7fbabe1d 100644 --- a/L10n/tr.strings +++ b/L10n/tr.strings @@ -36,6 +36,12 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Hata: token erişilemedi"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC kritik bir hatayla karşılaştı ve kurtarılamıyor. EDMC kendi koruması için kapanıyor!"; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "EDMC tespit edilen hata:\n\n{ERR}\n\nGitHub'da bir Hata Raporu göndermek ister misiniz?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "Frontier CAPI bakım için kapalı durumda"; @@ -78,12 +84,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Düzenle"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Görüntüle"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Pencere"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Yardım"; @@ -93,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Güncellemeleri Denetle..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Sistem Profilcisi'ni açın"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Ham Verileri Kaydet..."; @@ -213,12 +216,6 @@ /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası henüz Python 3.x desteğine sahip değil. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Güncellenmiş bir sürümün mevcut olup olmadığını kontrol edin, aksi takdirde geliştiriciyi Python 3.x kodunu güncellemesi gerektiği konusunda uyarmalısınız.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz."; -/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */ -"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID Dosyaları bulunamadı! Ürünlerle ilgili bazı işlevler devre dışı bırakılabilir.\n\nAlt modüllerin nasıl kurulacağına ilişkin Wiki sayfasını açmak ister misiniz?"; - -/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */ -"FDevIDs: Missing Commodity Files" = "FDevIDs: Eksik Ürün Dosyaları"; - /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */ "Plugins" = "Eklentiler"; @@ -351,9 +348,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Hata: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Tercihler"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Lütfen hangi verilerin kaydedileceğini seçin"; @@ -372,9 +366,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Dosya konumu"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Değiştir..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Gezin..."; @@ -390,21 +381,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Filo Taşıyıcı CAPI sorgulamalarını etkinleştir"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Klavye Kısayolu"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Kısayoltuşu"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Kısayolları kullanmak için {APP}'i yeniden başlat"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP}'nin kısayolları kullanabilmesi için izne ihtiyacı var"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Sistem Tercihlerini Aç"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Sadece Elite: Dangerous aktif uygulama olduğunda"; @@ -803,3 +782,9 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} sürüm mevcut"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Metin dışı içerik yapıştırılamadı"; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Şurada aç {URL}"; diff --git a/L10n/uk.strings b/L10n/uk.strings index 35d2c2297..125200f1c 100644 --- a/L10n/uk.strings +++ b/L10n/uk.strings @@ -55,12 +55,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "Редагувати"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "Вид"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "Вікно"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "Довідка"; @@ -211,9 +205,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Помилка: Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "Уподобання"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "Оберіть дані які буде збережено"; @@ -232,9 +223,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "Розташування файлу"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "Внесення змін..."; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "Перегляд..."; @@ -244,21 +232,9 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "Розташування файлу-журналу E:D"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "Сполучення клавіш"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Гаряча клавіша"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "Перезавантажте {APP} для використання ярликів"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} необхідні дозволи для використання ярликів"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "Відкрити налаштування системи"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "Тільки тоді, коли Elite: Dangerous є активним додатком"; diff --git a/L10n/zh-Hans.strings b/L10n/zh-Hans.strings index 03e263dd7..2cc13543f 100644 --- a/L10n/zh-Hans.strings +++ b/L10n/zh-Hans.strings @@ -70,12 +70,6 @@ /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */ "Edit" = "编辑"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */ -"View" = "显示"; - -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */ -"Window" = "窗口"; - /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */ "Help" = "帮助"; @@ -319,9 +313,6 @@ /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "错误:Inara {MSG}"; -/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */ -"Preferences" = "偏好"; - /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */ "Please choose what data to save" = "请选择想要保存的数据"; @@ -340,9 +331,6 @@ /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */ "File location" = "保存位置"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */ -"Change..." = "更改…"; - /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */ "Browse..." = "浏览…"; @@ -358,21 +346,9 @@ /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "开启舰队母舰 (fleet carrier) CAPI 访问"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */ -"Keyboard shortcut" = "键盘快捷键"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "快捷键"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */ -"Re-start {APP} to use shortcuts" = "重启 {APP} 以使用快捷键"; - -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */ -"{APP} needs permission to use shortcuts" = "{APP} 需要权限以使用快捷键"; - -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */ -"Open System Preferences" = "打开系统偏好设置"; - /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */ "Only when Elite: Dangerous is the active app" = "仅当 Elite: Dangerous 在前台运行时生效"; From 1db12a48195532f153b079833c5c00c54676eea8 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 26 May 2024 20:43:55 -0400 Subject: [PATCH 215/261] Revert "Merge branch 'releases' into enhancement/519/add-beta" This reverts commit 6e557caac5508e6ebad020235a5bd62209d91573, reversing changes made to 1e1ab64b1a8c50e192056666850845881a21f68b. --- ChangeLog.md | 3 +-- edmarketconnector.xml | 45 ++----------------------------------------- 2 files changed, 3 insertions(+), 45 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index e0d98bf40..7d14ca9c6 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -15,9 +15,8 @@ We now sign our code! This does mean that built EXEs are now slightly modified o For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC **Changes and Enhancements** -* Added new SCO and Python Armor Module Details +* Added new SCO Module Details * Reverted a change from the prior release due to breaking some consumers. - **Plugin Developers** * modules.p and ships.p are deprecated, and slated for removal in 5.11+! * The `openurl()` function in ttkHyperlinkLabel has been deprecated, diff --git a/edmarketconnector.xml b/edmarketconnector.xml index 88ff05ce1..cc7977943 100644 --- a/edmarketconnector.xml +++ b/edmarketconnector.xml @@ -22,51 +22,10 @@
- Release 5.10.6 + Release 5.10.4 body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }

We now test against, and package with, Python 3.11.7.

As a result, we do not support Windows 7, 8, or 8.1.
-

Release 5.10.6

-

This release contains the data information for the new SCO modules added in Elite update 18.04. -This should represent full support for the new Python Mk II.

-

We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. -For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC

-

Changes and Enhancements

-
    -
  • Added new SCO and Python Armor Module Details
  • -
  • Reverted a change from the prior release due to breaking some consumers.
  • -
-

Plugin Developers

-
    -
  • modules.p and ships.p are deprecated, and slated for removal in 5.11+!
  • -
  • The openurl() function in ttkHyperlinkLabel has been deprecated, -and slated for removal in 5.11+! Please migrate to webbrowser.open().
  • -
-
-

Release 5.10.5

-

This release contains a fix for a bug that could crash EDMC's console versions when reading outfitting information from the new SCO Frame Shift Drive modules.

-

Please note that this does not offer full support for the new SCO modules or the Python Mk II. More support will be added in a future update.

-

We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. -For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC

-

Changes and Enhancements

-
    -
  • Updated Translations
  • -
  • Added limited data regarding the Python Mk II
  • -
  • Added a few Coriolis module information entries
  • -
-

Bug Fixes

-
    -
  • Fixed a bug that could cause the new SCO modules to display improper ratings or sizes
  • -
  • Fixed a bug where the new SCO modules would display as a normal Frame Shift Drive
  • -
  • Fixed a bug which could crash EDMC if the exact details of a Frame Shift Drive were unknown
  • -
-

Plugin Developers

-
    -
  • modules.p and ships.p are deprecated, and slated for removal in 5.11+!
  • -
  • The openurl() function in ttkHyperlinkLabel has been deprecated, -and slated for removal in 5.11+! Please migrate to webbrowser.open().
  • -
-

Release 5.10.4

This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also adds Turkish translations to EDMC!

@@ -2272,7 +2231,7 @@ about this: PTS CAPI saying Commander is Docked after jumping to new system.

]]>
- +
From 6f13f1d893691a117d51a791701d7caadace4be9 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:46:01 -0400 Subject: [PATCH 216/261] [Admin] RM XML File --- edmarketconnector.xml | 2237 ----------------------------------------- 1 file changed, 2237 deletions(-) delete mode 100644 edmarketconnector.xml diff --git a/edmarketconnector.xml b/edmarketconnector.xml deleted file mode 100644 index cc7977943..000000000 --- a/edmarketconnector.xml +++ /dev/null @@ -1,2237 +0,0 @@ - - - - E:D Market Connector - https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml - Most recent changes with links to updates. - - - Release 3.44 - h2 { font-size: 105%; } -

Release 3.44

-

CHANGE OF MAINTAINER

-

Due to a lack of time to give the project the attention it needs Marginal has handed over ownership of the EDMarketConnector GitHub repository to the EDCD (Elite Dangerous Community Developers) organisation.

-

Initially Athanasius will now be responsible for maintaining the code, including addressing any Pull Requests and Issues, and making releases. Unfortunately he has no access to hardware running MacOS so can't easily generate builds for that platform or test them. So for the time being releases will be for Windows 10 only. MacOS users are advised to look into running from source (see the github README).

-

Going forwards the intention is to move to the python 3.7 code as soon as possible. To facilitate this there will be one more python 2.7 release in addition to this one, with the main aim of that being to add code to alert the user about any plugins they use that have apparently not been updated to run under python 3.7.

-

See the project GitHub repository's README.md for further information.

-
    -
  • Version increased to 3.4.4.0 / 3.44.
  • -
  • URL the application checks for updates changed to point to github,
  • -
]]>
- -
- - - Release 5.10.4 - body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } -

We now test against, and package with, Python 3.11.7.

-

As a result, we do not support Windows 7, 8, or 8.1.
-

Release 5.10.4

-

This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also -adds Turkish translations to EDMC!

-

We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. -For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC

-

Changes and Enhancements

-
    -
  • Adds Turkish Translations to EDMC
  • -
  • Adds DockingDenied and DockingGranted EDDN Schemas
  • -
  • Updated FDevIDs Dependency
  • -
  • Updated Translations
  • -
  • Updated modules files to process several missing module types used for bug squishing or going fast
  • -
  • Updated Python Dependencies
  • -
-

Bug Fixes

-
    -
  • Fixed a bug on older Python versions which couldn't import updated type annotations
  • -
-

Plugin Developers

-
    -
  • modules.p and ships.p are deprecated, and slated for removal in 5.11+!
  • -
  • The openurl() function in ttkHyperlinkLabel has been deprecated, -and slated for removal in 5.11+! Please migrate to webbrowser.open().
  • -
-
-

Release 5.10.3

-

This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations.

-We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. -For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC -

Changes and Enhancements

-
    -
  • Updated French Translations
  • -
-

Bug Fixes

-
    -
  • Fixed a bug that crashed the outfitting system when encountering armor. (Thanks TCE team for identifying this one!)
  • -
-

Plugin Developers

-
    -
  • modules.p and ships.p are deprecated, and slated -for removal in the next major release! Please look for that change coming soon.
  • -
  • Note to plugin developers: The openurl() function in ttkHyperlinkLabel has been deprecated, -and slated for removal in the next major release! Please migrate to webbrowser.open().
  • -
- -
- -

Release 5.10.2

-

This release contains updated dependencies, some bug fixes, a few minor enhancements to some supporting files, -and some resorted resources as well as a new image for some of the built EXEs.

- -We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines. -For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC -

Changes and Enhancements

-
    -
  • Added additional logging to the Python build string in the case of missing files
  • -
  • Added a new icon to EDMC's Command-Line EXE
  • -
  • Added additional logging to the build system
  • -
  • Updated several dependencies
  • -
  • Updated FDEV IDs
  • -
  • Updated relevant copyright dates
  • -
  • Updated automatic build script to support code signing workflow
  • -
  • Updated translations to the latest versions
  • -
  • Moved a few unused files to the resources folder. These files have no references in the code
  • -
-

Bug Fixes

-
    -
  • Fixed a bug that could cause EDMC to handle SIGINT signals improperly
  • -
  • Fixed a bug that could result in URL providers to be set to invalid values
  • -
  • Fixed a bug that could result in Coriolis URL providers to revert back to "Auto" on language translations
  • -
  • Fixed a bug where Inara didn't understand being blown up by a Thargoid, and blew itself up instead
  • -
  • Fixed a printing issue for the localization system for unused strings
  • -
-

Removed Files

-
    -
  • Removed two unused manifest and MacOS icon files which are no longer in use.
  • -
-

Known Issues

- -

Plugin Developers

-
    -
  • modules.p and ships.p are deprecated, and slated -for removal in the next major release! Please look for that change coming soon.
  • -
  • Note to plugin developers: The openurl() function in ttkHyperlinkLabel has been deprecated, -and slated for removal in the next major release! Please migrate to webbrowser.open().
  • -
- -
- -

Release 5.10.1

-

This release contains a number of bugfixes, minor performance enhancements, workflow and dependency updates, and a function deprecation.

-Note to plugin developers: modules.p and ships.p are deprecated, and slated for removal in the next major release! Please look for that change coming soon. -Note to plugin developers: The openurl() function in ttkHyperlinkLabel has been deprecated, and slated for removal in the next major release! Please migrate to webbrowser.open(). - -

Changes and Enhancements

-
    -
  • Deprecated `openurl()`. Please migrate to `webbrowser.open()`
  • -
  • Updated a number of list comparisons to use more efficient tuple comparisons
  • -
  • Updated a few type hints
  • -
  • Updated a few binary comparitors to be more efficient
  • -
  • Moved `resources.json` and `modules.json` back to the top level for all users
  • -
  • Updated several dependencies
  • -
  • Updated Python version to 3.11.7
  • -
-

Bug Fixes

-
    -
  • Fixed an issue where resources files could be in different locations for different users.
      -
    • These files are now in the same location (top level) for all users on all distributions.
    • -
    -
  • -
  • Fixed an issue where CMDRs without the Git application installed would crash on start if running from Source.
      -
    • Thanks to the Flatpak team for pointing this one out!
    • -
    -
  • -
  • Fixed a bug where CMDRs running from source would have their git hash version displayed as UNKNOWN.
      -
    • We're now more failure tolerant and use the bundled .gitversion if no true git hash is provided.
    • -
    -
  • -
  • Fixed a bug where starting two copies of EDMC with a valid install would not generate a duplicate warning.
  • -
-
- -

Release 5.10.0

-

This release contains a number of under-the-hood changes to EDMC designed to improve performance, code maintainability, and stability of the EDMC application, while providing new features and quality-of-life fixes.

-Note to plugin developers: modules.p and ships.p are deprecated, and slated for removal in the next major release! Please look for that change coming soon. - -

Changes and Enhancements

-
    -
  • Added new modules.json and ships.json files to improve security and readability
  • -
  • Added a core Spansh URL provider plugin
  • -
  • Added a new auth response page for successful FDEV authentication
  • -
  • Added a new Open Log Folder option to the Help menu
  • -
  • Added a new --start_min command flag to force the application to start minimized
  • -
  • Added a new pop-up if plugins fail to load or are not supported
  • -
  • Updated commodities and module files to the latest versions
  • -
  • Updated core EDMC and core Plugin menus to a standardized layout
  • -
  • Updates the Inara URL formats to the new endpoints
  • -
-

Bug Fixes

-
    -
  • Fixed an issue where indentation of text strings in certain settings windows under various languages -would be unevenly indented
  • -
  • Fixed an issue where the Plugins Folder label in the Plugins settings window would cut off the -selection box for the plugin storage location
  • -
-

Code Clean Up

-
    -
  • Added future annotation imports to help with code compatibility
  • -
  • Added a few conditional checks on input processing
  • -
  • Simplified some RegEx expressions, complex functions, logic flows, and Import statements
  • -
  • Simplified the WinSparkle GitHub Build Action
  • -
  • Began to change single-character variables to more descriptive names
  • -
  • Moved a number of global variables into their requisite classes
  • -
  • Updated a number of dependencies to the latest versions
  • -
  • Updated GitHub Actions to the latest versions
  • -
  • Updated a number of resource-allocating functions to use more efficient closing logic
  • -
  • Updated some calls to arrays to be more efficient
  • -
  • Removed a number of old-style typing hints in favor of PEP 585 style hints
  • -
  • Removed a number of redundant if - return - else or raise - else statements for code readability
  • -
  • Removed some default parameter assignments
  • -
  • Removed some obsolete calls to Object
  • -
-

Plugin Developers

-
    -
  • modules.p and ships.p have been deprecated, and will be removed in 6.0. -If you are using these files, please update to use the new modules.json and ships.json files instead.
  • -
  • A new method of standardizing the paddings used in settings panels has been applied to the core settings panels. -We strongly encourage you to follow these style hints! A proper guide will be added to the wiki.
  • -

- -

Release 5.9.5

-

This release fixes an uncommon problem with the uninstaller logic if upgrading from a version prior to 5.9.0 to improve consistancy across versions.

-Note to plugin developers: modules.p and ships.p will be deprecated in the next version, and slated for removal in the next major release! Please look for that change coming soon. -
    -
  • Updates Module pickle files to latest values.
  • -
  • Fixes a problem with the uninstaller logic caused by prior versions having fluctuating GUIDs.
  • -

- -

Release 5.9.4

-

This release fixes a widely-reported bug that resulted in the cAPI Authentication flow being disrupted for a subset of users. Thank you to all the CMDRs who reported this to us and provided logs to us so that we could get the issue isolated.

-
    -
  • Fixes a missing registry issue that could cause the EDMC:// protocol to fail. (#2061, #2059, #2058, #2057)
  • -
  • Renames the default start menu shortcut to be more clear. (#2062)
  • -

- -

Release 5.9.3

-

This release is identical to 5.9.2, except reverts a bad change.

-
    -
  • REVERTS Deprecated load_module() is now retired (#1462)
  • -

- -

Release 5.9.2

-

This release fixes a critical issue on clean installs which would not update -the Windows registry to allow for protocol handling.

-

All users are strongly encouraged to update.

-
    -
  • Fixes a critical bug with the installer on new installs not creating registry keys (#2046)
  • -
  • Re-enables automatic submodule updates (#1443)
  • -
  • Help -> About Version String can now be copied to clipboard (#1936)
  • -
  • EDMC Task Manager Printout now is less useless (#2045)
  • -
  • Deprecated load_module() is now retired (#1462)
  • -
  • API Keys are masked in Settings (#2047)
  • -
  • Installer will now refuse to install on Win7 and Earlier (#1122)
  • -

- -

Release 5.9.1

-

This release updates the build system in use for EDMC to a more feature-rich installer, as well -as updating the commodity information to be up-to-date for Update 16.

-

NOTE: This version hands over the installer to an EXE file for Windows instead of an MSI. -This does not change any functionality or plugin capability of EDMC. You may need to -manually close EDMC during the update process if updating from version 5.9.0 or earlier.

-
    -
  • Removed the old WiX Build System
  • -
  • Handed over the Build system to Inno Setup
  • -
  • Broke apart the Build and Installer scripts for ease of development
  • -
  • Updated FDevIDs to latest version
  • -
  • Updated coriolis-data to latest version
  • -
  • Updated some internal documentation
  • -

- -

Release 5.9.0

-

This release is essentially the same as 5.9.0-rc1 with only a typo, the version and -this changelog updated.

-

This release contains the removal of the EDDB module, as well as a few under-the-hood -updates.

-
    -
  • Removes the EDDB plugin due to EDDB shutting down.
  • -
  • Unsets EDDB as the default handler for certain URL preferences.
  • -
  • Updates the FDevIDs to latest versions.
  • -
  • Removes EDDB references from help string documentations.
  • -
  • Updated a number of dependencies to their latest working versions
  • -

- -

Release 5.8.1

-

This fixes a bug where the Cmdr/APIKey sections on Settings > EDSM would never be shown.

-
- -

Release 5.8.0

-

This release is essentially the same as 5.8.0-rc3 with only the version and -this changelog updated.

-

It brings a new feature related to Fleetcarrier data, some convenience for -Linux users, some fixes, and otherwise some internal changes that should not -adversely affect either users or third-party plugins. For the latter, read -below for some new/changed things that could benefit you.

-
    -
  • -

    This release, and all future ones, now create two additional archive files -in the GitHub release:

    -
      -
    1. EDMarketConnector-release-<version>.zip
    2. -
    3. EDMarketConnector-release-<version>.tar.gz
    4. -
    -

    The advantage of these over the GitHub auto-generated ones is that they -have been hand-crafted to contain all the necessary files, and only -those files.

    -

    If you use the application from source, and not via a git clone, then we -highly recommend you use one of these archives, not the GitHub -auto-generated ones.

    -

    Anyone installing on Windows should continue to use the -EDMarketConnector_win_<version>.msi files as before.

    -
  • -
  • -

    New Feature - You can now have the application query the /fleetcarrier -CAPI endpoint for data about your Fleet Carrier. The data will then be -passed to interested plugins.

    -

    Note that there are some caveats:

    -
      -
    1. -

      This feature defaults to Off. The option is on the Configuration tab -of Settings as "Enable Fleetcarrier CAPI Queries". It is advised to only -enable this if you know you utilise plugins that make use of the data.

      -
    2. -
    3. -

      These queries are only triggered by CarrierBuy and CarrierStats -Journal events, i.e. upon buying a Fleetcarrier or opening the Carrier -Management UI in-game. NB: There is a 15 minute cooldown between -queries.

      -
    4. -
    5. -

      If you have Fleetcarrier cargo which got into the cargo hold through a lot -of individual transactions, or if you have a lot of separate buy/sell -orders then these queries can take a long time to complete.

      -

      If this happens with your game account then all other CAPI queries will -be blocked until the /fleetcarrier query completes. 'Other CAPI -queries' means those usually triggered upon docking to gather station -market, shipyard and outfitting data. To ameliorate the effects of this -there is currently a timeout of 60 seconds on /fleetcarrier queries, -and will not occur more often than every 15 minutes.

      -

      We plan to address this by moving the /fleetcarrier queries into their -own separate thread in the future.

      -
    6. -
    -
  • -
  • -

    The code for choosing the 'Output' folder is now simply the tkinter -function for such a dialogue, rather than a special case on Windows. In -the past the former had issues with Unicode characters, but in testing no -such issue was observed (on a supported OS).

    -
  • -
  • -

    There are two new items on the "Help" menu:

    -
      -
    1. Troubleshooting -> Wiki:Troubleshooting -
    2. -
    3. Report A Bug -> Issues - New Bug Report -
    4. -
    -
  • -
  • -

    Translations have been updated. Thanks again to our volunteer translators!

    -
  • -
  • -

    If we ever activate any functionality killswitches, the popup denoting which -are active has been made more readable.

    -
  • -
  • -

    There's a new section in Contributing.md - "Python Environment". This -should aid any new developers in getting things set up.

    -
  • -
-

Linux Users

-

We now ship an io.edcd.EDMarketConnector.desktop file. To make use of this -you should run scripts/linux-setup.sh once. This will:

-
    -
  1. -

    Check that you have $HOME/bin in your PATH. If not, it will abort.

    -
  2. -
  3. -

    Create a shell script edmarketconnector in $HOME/bin to launch the -application.

    -

    NB: This relies on the filesystem location you placed the source in not -changing. So if you move the source you will need to re-run the script.

    -
  4. -
  5. -

    Copy the .desktop and .icon files into appropriate locations. The .desktop -file utilises the shell script created in step 2, and thus relies on it -existing and on it being in a directory that is in your PATH.

    -
  6. -
-

Once this has been completed any XDG-compliant desktops should have an entry -for "E:D Market Connector" in their "Games" menu.

-

Fixes

-
    -
  • -

    The tracking of a Cmdr's location that was being performed by the core EDDN -plugin has been moved into the Journal monitoring code. This results in -the tracking being correct upon application (re)start, reflecting the state -from the latest Journal file, rather than only picking up with any -subsequent new Journal events.

    -

    This change should remove instances of "Wrong System! Missed Jump ?" and -similar sanity-check "errors" when continuing to play after a user restarts -the application whilst the game is running.

    -

    Plugin developers, see below for how this change can positively affect you.

    -
  • -
  • -

    The name of the files written by "File" > "Save Raw Data" now have a . -between the system and station names.

    -
  • -
  • -

    Use of CAPI data in EDMC.exe when invoked with either -s or -n -arguments hadn't been updated for prior changes, causing such invocations to -fail. This has been fixed.

    -
  • -
-

Plugin Developers

-
    -
  • -

    Each plugin is now handed its own sub-frame as the parent parameter passed -to plugin_app() instead of the actual main UI frame. These new Frames -are placed in the position that plugin UI would have gone into. This should -have no side effects on well-behaved plugins.

    -

    However, if you have code that attempts to do things like parent.children() -or the like in your plugin_app() implementation, this might have stopped -working. You shouldn't be trying to do anything with any of the UI outside -your plugin anyway, but if you definitely have a need then look things up -using .nametowidget(). There are examples in the core plugins (which DO -have good reason, due to maintaining main UI label values).

    -

    All of the plugins listed on our Wiki were given perfunctory testing and no -issues from this change were observed.

    -

    This is a necessary first step to some pending plugin/UI work:

    - -
  • -
  • -

    New - capi_fleetcarrier() function to receive the data from a CAPI -/fleetcarrier query. See PLUGINS.md for details.

    -
  • -
  • -

    It was found that the ShutDown event (note the capitalisation, this is -distinct from the actual Journal Shutdown event) synthesized for plugins -when it is detected that the game has exited was never actually being -delivered. Instead this was erroneously replaced with a synthesized StartUp -event. This has been fixed.

    -
  • -
  • -

    As the location tracking has been moved out of the core EDDN plugin, and into -monitor.py all of it is now available as members of the state dictionary -which is passed to journal_entry().

    -

    This both means that no plugin should need to perform such location state -tracking itself and they can take advantage of it being fully up to date -when a user restarts the application with the game running.

    -

    A reminder: When performing 'catch up' on the newest Journal file found at -startup, the application does not pass any events to the -journal_entry() method in plugins. This is to avoid spamming with -data/state that has possibly already been handled, and in the case of the -Cmdr moving around will end up not being relevant by the time the end of the -file is reached. This limitation was also why the core EDDN plugin couldn't -properly initiate its location tracking state in this scenario.

    -

    See PLUGINS.md for details of the new state members. Pay particular -attention to the footnote that details the caveats around Body tracking.

    -

    Careful testing has been done for only the following. So, if you make use -of any of the other new state values and spot a bug, please report it:

    -
      -
    1. SystemName
    2. -
    3. SystemAddress
    4. -
    5. Body (Name)
    6. -
    7. BodyID
    8. -
    9. BodyType
    10. -
    11. StationName
    12. -
    13. StationType
    14. -
    15. (Station) MarketID
    16. -
    -
  • -
  • -

    There is an additional property request_cmdr on CAPIData objects, which -records the name of the Cmdr the request was made for.

    -
  • -
  • -

    FDevIDs files are their latest versions at time of this version's build.

    -
  • -
  • -

    examples\plugintest - dropped the "pre-5.0.0 config" code, as it's long -since irrelevant.

    -
  • -
-

Developers

-
    -
  • -

    If you utilise a git clone of the source code, you should also ensure the -sub-modules are initialised and synchronised. -wiki:Running from source -has been updated to include the necessary commands.

    -
  • -
  • -

    The coriolis-data git sub-module now uses an HTTPS, not "git" URL, so won't -require authentication for a simple git pull.

    -
  • -
  • -

    If you have a dump directory in CWD when running EDMarketConnector.py under -a debugger you will get files in that location when CAPI queries complete. -This will now include files with names of the form -FleetCarrier.<callsign>.<timstamp>.json for /fleetcarrier data.

    -
  • -
  • -

    All the main UI tk widgets are now properly named. This might make things -easier if debugging UI widgets as you'll no longer see a bunch of !label1, -!frame1 and the like.

    -

    Each plugin's separator is named as per the scheme plugin_hr_<X>, and when -a plugin has UI its new container Frame is named plugin_X. Both of these -start with 1, not 0.

    -
  • -
-
- - -

Release 5.7.0

-

This release re-enables CAPI queries for Legacy players. As a result, the -'Update' button functionality is now restored for Legacy players, along with -"Automatically update on docking" functionality.

-
    -
  • -

    We now test against, and package with, Python 3.11.1, 32-bit.

    -
  • -
  • -

    This release is functionally identical to 5.7.0-rc1, as no problems were -reported with that.

    -
  • -
  • -

    As noted above, Legacy players now have CAPI functionality once more. -Plugin developers check below for how you can determine the source galaxy -of such data.

    -
  • -
  • -

    Due to a bug it turned out that a workaround for "old browsers don't support -very long URLs" had been inactive since late 2019. As no-one has noticed -or complained we've now removed the defunct code in favour of the simple -webbrowser.open(<url>).

    -

    Testing showed that all of Firefox, Chrome and Chrome-based Edge worked with -very long URLs without issues.

    -
  • -
  • -

    EDMC.exe -n had been broken for a while, it now functions once more.

    -
  • -
  • -

    Some output related to detecting and parsing gameversion from Journals -has been moved from INFO to DEBUG. This returns the output of any EDMC.exe -command to the former, quieter, version.

    -
  • -
-

Bugs

-
    -
  • -

    A corner case of "game not running" and "user presses 'Update' button" would -result in an empty uploaderID string being sent to EDDN. Such messages are -still accepted by the EDDN Gateway, and the Relay then obfuscates this field -anyway. So, at worse, this would make it look like the same uploader was in -lots of different places. This has been fixed.

    -
  • -
  • -

    The message about converting legacy replay.jsonl was being emitted even -when there was no file to convert. This has been fixed.

    -
  • -
-

Plugin Developers

-
    -
  • -

    An erroneous statement about "all of Python stdlib" in PLUGINS.md has been -corrected. We don't/can't easily include all of this. Ask if any part of it -you require is missing.

    -
  • -
  • -

    In order to not pass Legacy data to plugins without them being aware of it -there is now a new function cmdr_data_legacy(), which mirrors the -functionality of cmdr_data(), but for Legacy data only. See PLUGINS.md -for more details.

    -
  • -
  • -

    The data passed to cmdr_data() and cmdr_data_legacy() is now correctly -typed as CAPIData. This is a sub-class of UserDict, so you can continue -to use it as such. However, it also has one extra property, source_host, -which can be used to determine if the data was from the Live or Legacy -CAPI endpoint host. See PLUGINS.md for more details.

    -
  • -
  • -

    If any plugin had been attempting to make use of config.get_int('theme'), -then be aware that we've finally moved from hard-coded values to actual -defined constants. Example use would be as in:

    -
    from config import config
    -from theme import theme
    -
    -active_theme = config.get_int('theme')
    -if active_theme == theme.THEME_DARK:
    -    ...
    -elif active_theme == theme.THEME_TRANSPARENT:
    -    ...
    -elif active_theme == theme.THEME_DEFAULT:
    -    ...
    -else:
    -    ...
    -

    But remember that all tkinter widgets in plugins will inherit the main UI -current theme colours anyway.

    -
  • -
  • -

    The contents of NavRoute.json will now be loaded during 'catch-up' when -EDMarketConnector is (re-)started. The synthetic StartUp (note the -capitalisation) event that is emitted after the catch-up ends will have -state['NavRoute'] containing this data.

    -

    However, the Fileheader event from detecting a subsequent new Journal file -will blank this data again. Thus, if you're interested in "last plotted -route" on startup you should react to the StartUp event. Also, note that -the contents will indicate a NavRouteClear if that was the last such -event.

    -

    PLUGINS.md has been updated to reflect this.

    -
  • -
  • -

    If you've ever been in the habit of running our develop branch, please -don't. Whilst we try to ensure that any code merged into this branch doesn't -contain bugs, it hasn't at that point undergone more thorough testing. -Please use the stable branch unless otherwise directed.

    -
  • -
  • -

    Some small updates have been made in edmc_data as a part of reviewing the -latest update to coriolis-data. -We make no guarantee about keeping these parts of edmc_data up to date. -Any plugins attempting to use that data should look at alternatives, such -as FDevIDs/outfitting.csv.

    -

    A future update might remove those maps, or at least fully deprecate their -use by plugins. Please contact us now if you actually make use of this -data.

    -
  • -
-
- - -

Release 5.6.1

-

This release addresses some minor bugs and annoyances with v5.6.0, especially -for Legacy galaxy players.

-

In general, at this early stage of the galaxy split, we prefer to continue to -warn Legacy users who have 'send data' options active for sites that only -accept Live data. In the future this might be reviewed and such warnings -removed such that the functionality fails silently. This might be of use -to users who actively play in both galaxies.

-
    -
  • -

    CAPI queries will now only be attempted for Live galaxy players This is -a stop-gap whilst the functionality is implemented for Legacy galaxy players. -Doing so prevents using Live galaxy data whilst playing Legacy galaxy, which -would be increasingly wrong and misleading.

    -
      -
    1. 'Automatic update on docking' will do nothing for Legacy players.
    2. -
    3. Pressing the 'Update' button whilst playing Legacy will result in a status -line message "CAPI for Legacy not yet supported", and otherwise achieve -nothing. The only function of this button is to query CAPI data and -pass it to plugins, which does not include Inara and EDSM.
    4. -
    5. A Legacy player trying to use "File" > "Status" will get the message -"Status: No CAPI data yet" due to depending on CAPI data.
    6. -
    -

    It is hoped to implement CAPI data retrieval and use for Legacy players soon, -although this will likely entail extending the plugins API to include a new -function specifically for this. Thus only updated plugins would support -this.

    -
  • -
  • -

    EDDN: Where data has been sourced from the CAPI this application now sends -a header->gameversion in the format "CAPI-(Live|Legacy)-<endpoint" as per -the updated documentation.

    -
      -
    1. -

      As this version only queries CAPI for Live players that will only be -"CAPI-Live-<endpoint>" for the time being.

      -
    2. -
    3. -

      If, somehow, the CAPI host queried matches neither the -current Live host, the Legacy host, nor the past beta host, you will see -"CAPI-UNKNOWN-<endpoint>".

      -
    4. -
    5. -

      As that statement implies, this application will also signal 'Live' if -pts-companion.orerve.net has been used, due to detecting an alpha or beta -version of the game. However, in that case the /test schemas will be used.

      -
    6. -
    -

    Closes #1734.

    -
  • -
  • -

    Inara: Only warn about Legacy data if sending is enabled in Settings > Inara.

    -

    Closes #1730.

    -
  • -
  • -

    Inara: Handling of some events has had a sanity check added so that the -Inara API doesn't complain about empty strings being sent. In these cases -the event will simply not be sent.

    -

    Closes #1732.

    -
  • -
  • -

    EDSM: EDSM has decided to accept only Live data on its API. Thus, this -application will only attempt to send data for Live galaxy players.

    -

    If a Legacy galaxy player has the Settings > EDSM > "Send flight log and -Cmdr status to EDSM" option active then they will receive an error about -this at most once every 5 minutes. Disabling that option will prevent the -warning.

    -
  • -
-

Plugin Developers

-
    -
  • PLUGINS.md has been updated to make it clear that the only use of imports -from the config module are for setting/getting/removing a plugin's own -configuration, or detecting application shutdown in progress.
  • -
  • PLUGINS.md has also been updated to add a note about how the data passed -to a plugin cmdr_data() is, strictly speaking, an instance of CAPIData, -which is an extension of UserDict. It has some extra properties on it, -but these are for internal use only and no plugin should rely on them.
  • -
  • As noted above, implementing CAPI data for Legacy players will likely entail -an additional function in the API provided to plugins. See -#1728 for discussion -about this.
  • -
-
- - -

Release 5.6.0

-

Tha major reason for this release is to address the Live versus Legacy galaxy -split coming in Update 14 of the game. -See the section "Update 14 and the Galaxy Split" below for how this might -impact you.

-

Changes

-
    -
  • -

    We now test against, and package with, Python 3.10.8.

    -
  • -
  • -

    The code for sending data to EDDN has been reworked. This changes the -'replay log' from utilising an internal array, backed by a flat file -(replay.jsonl), to an sqlite3 database.

    -

    As a result:

    -
      -
    1. Any messages stored in the old replay.jsonl are converted at startup, -if that file is present, and then the file removed.
    2. -
    3. All new messages are stored in this new sqlite3 queue before any attempt -is made to send them. An immediate attempt is then made to send any -message not affected by "Delay sending until docked".
    4. -
    5. Sending of queued messages will be attempted every 5 minutes, unless -"Delay sending until docked" is active and the Cmdr is not docked in -their own ship. This is in case a message failed to send due to an issue -communicating with the EDDN Gateway.
    6. -
    7. When you dock in your own ship an immediate attempt to send all queued -messages will be initiated.
    8. -
    9. When processing queued messages the same 0.4-second inter-message delay -as with the old code has been implemented. This serves to not suddenly -flood the EDDN Gateway. If any message fails to send for Gateway reasons, -i.e. not a bad message, then this processing is abandoned to wait for -the next invocation.
    10. -
    -

    The 5-minute timer in point 3 differs from the old code, where almost any -new message sending attempt could initiate processing of the queue. At -application startup this delay is only 10 seconds.

    -

    Currently, the feedback of "Sending data to EDDN..." in the UI status line -has been removed.

    -

    If you do not have "Delay sending until docked" active, then the only -messages that will be at all delayed will be where there was a communication -problem with the EDDN Gateway, or it otherwise indicated a problem other -than 'your message is bad'.

    -
  • -
  • -

    As a result of this EDDN rework this application now sends appropriate -gameversion and gamebuild strings in EDDN message headers. -The rework was necessary in order to enable this, in case of any queued -or delayed messages which did not contain this information in the legacy -replay.jsonl format.

    -
  • -
  • -

    For EDSM there is a very unlikely set of circumstances that could, in theory -lead to some events not being sent. This is so as to safeguard against -sending a batch with a gameversion/build claimed that does not match for -all of the events in that batch.

    -

    It would take a combination of "communications with EDSM are slow", more -events (the ones that would be lost), a game client crash, and starting -a new game client before the 'more events' are sent.

    -
  • -
-

Update 14 and the Galaxy Split

-

Due to the galaxy split announced by Frontier -there are some changes to the major third-party websites and tools.

-
    -
  • -

    Inara has chosen -to only accept Live galaxy data on its API.

    -

    This application will not even process Journal data for Inara after -2022-11-29T09:00:00+00:00 unless the gameversion indicates a Live client. -This explicitly checks that the game's version is semantically equal to or -greater than '4.0.0'.

    -

    If a Live client is not detected, then there is an INFO level logging -message "Inara only accepts Live galaxy data", which is also set as the main -UI status line. This message will repeat, at most, every 5 minutes.

    -

    If you continue to play in the Legacy galaxy only then you probably want to -just disable the Inara plugin with the checkbox on Settings > Inara.

    -
  • -
  • -

    All batches of events sent to EDSM will be tagged with a gameversion, in -a similar manner to the EDDN header.

    -

    Ref: EDSM api-journal-v1

    -
  • -
  • -

    All EDDN messages will now have appropriate gameversion and gamebuild -fields in the header as per -EDDN/docs/Developers.md.

    -

    As a result of this you can expect third-party sites to choose to filter data -based on that.

    -

    Look for announcements by individual sites/tools as to what they have chosen -to do.

    -
  • -
-

Known Bugs

-

In testing if it had been broken at all due to 5.5.0 -> 5.6.0 changes it has -come to light that EDMC.EXE -n, to send data to EDDN, was already broken in -5.5.0.

-

In addition, there is now some extra 'INFO' logging output which will be -produced by any invocation of EDMC.EXE. This might break third-party use of -it, e.g. Trade Computer Extension Mk.II. -This will be fixed as soon as the dust settles from Update 14, with emphasis -being on ensuring the GUI EDMarketConnector.exe functions properly.

-

Notes for EDDN Listeners

-
    -
  • -

    Where EDMC sourced data from the Journal files it will set gameversion -and gamebuild as per their values in Fileheader or LoadGame, whichever -was more recent (there are some events that occur between these).

    -
  • -
  • -

    If any message was already delayed such that it did not -have the EDDN header recorded, then the gameversion and gamebuild will -be empty strings. In order to indicate this the softwareName will have - (legacy replay) appended to it, e.g. E:D Market Connector Connector [Windows] (legacy replay). In general this indicates that the message was -queued up using a version of EDMC prior to this one. If you're only -interested in Live galaxy data then you might want to ignore such messages.

    -
  • -
  • -

    Where EDMC sourced data from a CAPI endpoint, the resulting EDDN message -will have a gameversion of CAPI-<endpoint> set, e.g. CAPI-market. -At this time it is not 100% certain which galaxy this data will be for, so -all listeners are advised to ignore/queue such data until this is clarified.

    -

    gamebuild will be an empty string for all CAPI-sourced data.

    -
  • -
-

Plugin Developers

-
    -
  • There is a new flag in state passed to plugins, IsDocked. See PLUGINS.md -for details.
  • -
-
- -

Release 5.5.0

-
    -
  • Virus Total scan results for this release.
  • -
  • We now test against, and package with, Python 3.10.7.
  • -
  • EDDN: Support added for the FCMaterials schemas to aid third-party sites in -offering searches for where to buy and sell Odyssey Micro Resources, -including on Fleet Carriers with the bar tender facility.
  • -
-

Bug Fixes

-
    -
  • EDDN: Abort fsssignaldiscovered sending of message if no signals passed -the checks.
  • -
  • EDDN: Add Horizons check for location on fsssignaldiscovered messages.
  • -
  • Don't alert the user if the first attempted load of NavRoute.json contains -no route.
  • -
  • Inara: Don't set marketID for ApproachSettlement unless it's actually -present in the event.
  • -
-

Plugin Developers

-
    -
  • -

    We now build using the new, setuptools mediated py2exe freeze() method, -so we're in the clear for when distutils is removed in Python 3.12.

    -

    This shouldn't have any adverse effects on plugins, i.e. all of the same -Python modules are still packaged as before.

    -
  • -
  • -

    Support has been added for the NavRouteClear event. We do send this -through to plugins, so that they know the player has cleared the route, -but we keep the previously plotted route details in state['NavRoute'].

    -
  • -
  • -

    The documentation of the return type of journal_entry() has been corrected -to Optional[str].

    -
  • -
  • -

    FDevIDs files (commodity.csv rare_commodity.csv) updated to latest -versions.

    -
  • -
-

Developers

-
    -
  • We now build using the new, setuptools mediated py2exe freeze() method, -so we're in the clear for when distutils is removed in Python 3.12.
  • -
  • The old setup.py file, along with associated py2exe.cmd have been removed -in favour of the new Build-exe-and-msi.py file. Documentation updated.
  • -
-
- -

Release 5.4.1

-
    -
  • -

    If for any reason EDMarketConnector.exe fails to shutdown and exit when -asked to by the upgrade process this should no longer result in a spontaneous -system reboot. Closes #1492.

    -

    A manual reboot will still be required to complete the EDMarketConnector -upgrade process and we make no guarantees about the stability of the -application until this is done.

    -
  • -
  • -

    The new EDDN fsssignaldiscovered/1 schema has been implemented.

    -
  • -
  • -

    EDSM trace level logging will no longer log API credentials unless explicitly -asked to, separately from other EDSM API trace logging.

    -
  • -
-

-Bug Fixes

-
    -
  • EDDN: Ensure we always remove all _Localised suffix keys in data. This -was missed in some recent new schemas and turned out to be an issue for at -least approachsettlement/1.
  • -
-
- -

Release 5.4.0

-
    -
  • We now test against, and package with, Python 3.10.4.
  • -
  • New EDDN schema fssbodysignals is now supported.
  • -
  • Odyssey Update 12 will add BodyID to CodexEntry journal events, so don't -overwrite this with an augmentation if it is already present. We've also -added the same for BodyName in case Frontier ever add that.
  • -
  • -Translations updated. -Thanks again to all the contributors.
  • -
-

-Bug Fixes

-
    -
  • Cross-check the MarketID in CAPI data, not only the station name, to ensure -the data is for the correct station. Closes #1572.
  • -
  • Location cross-check paranoia added to several EDDN message types to ensure -no bad data is sent.
  • -
  • Ensure we don't send bad BodyID/Name for an orbital station if the player -uses a taxi. -Closes #1522.
  • -
-

-Developers

-
    -
  • Odyssey Update 12 adds a new Journal event, and file, FCMaterials.json, -detailing the available trades at a Fleet Carrier's bar tender. Support has -been added for this. Plugin developers are sent an FCMaterials event -with the full contents of the file.
  • -
-

-EDMC.exe

-

This now uses specific exit codes in all cases, rather than a generic -EXIT_SYS_ERR (6) for some cases. See the appropriate line in EDMC.py for -details.

- - -

Release 5.3.4

-

Whilst EDMarketConnector.exe was fixed for the Odyssey Update 11 difference in Journal file names, EDMC.exe was not. If you're wondering, that's the command-line utility that, for instance, Trade Computer Extensions uses to obtain data.

-
    -
  • Use the new common function for finding latest journal file in EDMC.py.
  • -
  • Quietens some NavRoute related logging for the benefit of EDMC.py. This is -now at DEBUG level, rather than INFO.
  • -
- - -

Release 5.3.3

-

Unfortunately 5.3.2 failed to fully address the issues caused by the different -Journal filenames when using the Odyssey Update 11 client. It's fine if you -run EDMarketConnector first and then the game, as the code path that detects -a new file always does just that.

-

But the code for EDMarketConnector startup to find the current newest Journal -file relied on sorting the filenames and that would mean the new-style names -would always sort as 'oldest'.

-

This release fixes that code to properly use the file modification timestamp -to determine the newest file on startup.

- - -

Release 5.3.2

-

This release contains one change to cope with how Frontier decided to name -the Journal files differently in the Update 11 Odyssey client.

- - -

Release 5.3.1

-

This release addresses some issues with newer EDDN code which could cause erroneous alerts to the player, or sending of bad messages.

  • EDDN: Cope with ApproachSettlement on login occurring before Location, -such that we don't yet know the name of the star system the player is in.

    Closes #1484

    -
  • EDDN: Cope with ApproachSettlement missing planetary coordinates on login -at/near a settlement in Horizons.

    Closes #1476

    -
  • EDDN: Change the CodexEntry "empty string" checks to only apply to those -values where the schema enforces "must be at least one character".

    This prevents the big 'CodexEntry had empty string, PLEASE ALERT THE EDMC DEVELOPERS' message from triggering on, e.g. NearestDestination being -empty, which the schema allows.

    Closes #1481

    -

Plugin Developers

-
  • -

    If you use a sub-class for a widget the core code will no longer break if -your code raises an exception. e.g. a plugin was failing due to Python -3.10 using collections.abc instead of collections, and the plugin's -custom widget had a configure() method which was called by the core -theme code on startup or theme change. This then caused the whole -application UI to never show up on startup.

    -

    This also applies if you set up a button such that enter/leave on it, i.e. -mouse in/out, causes the theme.py code for that to trigger.

    -

    So, now in such cases the main UI should actually show up, although your -plugin's UI might look weird due to theming not being properly applied.

    -

    The plugin exception WILL be logged, at ERROR level.

    -
  • -
- - -

Release 5.3.0

-

As has sadly become routine now, please read -our statement about malware false positives -affecting our installers and/or the files they contain. We are as confident -as we can be, without detailed auditing of python.org's releases and all -the py2exe source and releases, that there is no malware in the files we make -available.

-

This release is primarily aimed at fixing some more egregious bugs, -shortcomings and annoyances with the application. It also adds support for -two additional -EDDN -schemas.

-
    -
  • -

    We now test and build using Python 3.10.2. We do not yet make use of any -features specific to Python 3.10 (or 3.9). Let us restate that we -absolutely reserve the right to commence doing so.

    -
  • -
  • -

    We now set a custom User-Agent header in all web requests, i.e. to EDDN, -EDSM and the like. This is of the form:

    -

    EDCD-EDMarketConnector-<version>

    -
  • -
  • -

    "File" -> "Status" will now show the new Odyssey ranks, both the new -categories and the new 'prestige' ranks, e.g. 'Elite I'.

    -

    NB: Due to an oversight there are currently no translations for these.

    -

    Closes #1369.

    -
  • -
  • -

    Running EDMarketConnector.exe --reset-ui will now also reset any changes to -the application "UI Scale" or geometry (position and size).

    -

    Closes #1155.

    -
  • -
  • -

    We now use UTC-based timestamps in the application's log files. Prior to -this change it was the "local time", but without any indication of the -applied timezone. Each line's timestamp has UTC as a suffix now. We -are assuming that your local clock is correct and the timezone is set -correctly, such that Python's time.gmtime() yields UTC times.

    -

    This should make it easier to correlate application logfiles with in-game -time and/or third-party service timestamps.

    -
  • -
  • -

    The process used to build the Windows installers should now always pick up -all the necessary files automatically. Prior to this we used a manual -process to update the installer configuration which was prone to both user -error and neglecting to update it as necessary.

    -
  • -
  • -

    If the application fails to load valid data from the NavRoute.json file -when processing a Journal NavRoute event, it will attempt to retry this -operation a number of times as it processes subsequent Journal events.

    -

    This should hopefully work around a race condition where the game might -not have yet updated NavRoute.json at all, or has truncated it to empty, -when we first attempt this.

    -

    We will also now NOT attempt to load NavRoute.json during the startup -'Journal catch-up' mode, which only sets internal state.

    -

    Closes #1348.

    -
  • -
  • -

    Inara: Use the <journal log>->Statistics->Bank_Account->Current_Wealth -value when sending a setCommanderCredits message to Inara to set -commanderAssets.

    -

    In addition, a setCommanderCredits message at game login will now only -ever be sent at game login. Yes, you will NEED to relog to send an -updated balance. This is the only way in which to sanely keep the -'Total Assets' value on Inara from bouncing around.

    -

    Refer to Inara:API:docs:setCommanderCredits.

    -

    Closes #1401.

    -
  • -
  • -

    Inara: Send a setCommanderRankPilot message when the player logs in to the -game on-foot. Previously you would HAVE to be in a ship at login time -for this to be sent.

    -

    Thus, you can now relog on-foot in order to update Inara with any Rank up -or progress since the session started.

    -

    Closes #1378.

    -
  • -
  • -

    Inara: Fix for always sending a Rank Progress of 0%.

    -

    Closes #1378.

    -
  • -
  • -

    Inara: You should once more see updates for any materials used in -Engineering. The bug was in our more general Journal event processing -code pertaining to EngineerCraft events, such that the state passed to -the Inara plugin hadn't been updated.

    -

    Such updates should happen 'immediately', but take into account that there -can be a delay of up to 35 seconds for any data sent to Inara, due to how -we avoid breaking the "2 messages a minute" limit on the Inara API.

    -

    Closes #1395.

    -
  • -
  • -

    EDDN: Implement new approachsettlement/1 -schema.

    -
  • -
  • -

    EDDN: Implement new fssallbodiesfound/1 -schema.

    -
  • -
  • -

    EDDN: We now compress all outgoing messages. This might help get some -particularly large navroute messages go through.

    -

    If any message is now rejected as 'too large' we will drop it, and thus -not retry it later. The application logs will reflect this.

    -

    NB: The EDDN Gateway was updated to allow messages up to 1 MiB in size -anyway. The old limit was 100 KiB.

    -

    Closes #1390.

    -
  • -
  • -

    EDDN: In an attempt to diagnose some errors observed on the EDDN Gateway -with respect to messages sent from this application some additional checks -and logging have been added.

    -

    NB: After some thorough investigation it was concluded that these EDDN -errors were likely the result of long-delayed messages due to use of -the "Delay sending until docked" option.

    -

    There should be no functional changes for users. But if you see any of -the following in this application's log files PLEASE OPEN -AN ISSUE ON GITHUB -with all the requested information, so that we can correct the relevant -code:

    -
      -
    • No system name in entry, and system_name was not set either! entry: ...
    • -
    • BodyName was present but not a string! ...
    • -
    • post-processing entry contains entry ...
    • -
    • this.body_id was not set properly: ...
    • -
    • system is falsey, can't add StarSystem
    • -
    • this.coordinates is falsey, can't add StarPos
    • -
    • this.systemaddress is falsey, can't add SystemAddress
    • -
    • this.status_body_name was not set properly: ...
    • -
    -

    You might also see any of the following in the application status text -(bottom of the window):

    -
      -
    • passed-in system_name is empty, can't add System
    • -
    • CodexEntry had empty string, PLEASE ALERT THE EDMC DEVELOPERS
    • -
    • system is falsey, can't add StarSystem
    • -
    • this.coordinates is falsey, can't add StarPos
    • -
    • this.systemaddress is falsey, can't add SystemAddress
    • -
    -

    Ref: #1403 -#1393.

    -
  • -
-

-Translations

-
    -
  • -

    Use a different workaround for OneSky (translations website) using "zh-Hans" -for Chinese (Simplified), whereas Windows will call this "zh-CN". This is -in-code and documented with a comment, as opposed to some 'magic' in the -Windows Installer configuration that had no such documentation. It's less -fragile than relying on that, or developers using a script/documented -process to rename the file.

    -
  • -
  • -

    As noted above we forgot to upload to -OneSky -after adding the Odyssey new ranks/categories. This has now been done, -and some new phrases await translation.

    -
  • -
- -

Release 5.2.4

-

This is a very minor update that simply imports the latest versions of -data files so that some niche functionality works properly.

-
    -
  • -

    Update commodity.csv and rare_commodity.csv from the latest -EDCD/FDevIDs versions. This addresses -an issue with export of market data in Trade Dangerous format containing -OnionHeadC rather than the correct name, Onionhead Gamma Strain, that -Trade Dangerous is expecting.

    -

    This will only have affected Trade Dangerous users who use EDMarketConnector -as a source of market data.

    -
  • -
-
- -

Release 5.2.3

-

This release fixes one bug and fixes some example code.

-
    -
  • -

    Odyssey changed the order of some Journal events. This caused our logic -for tracking the following to break, and thus not report them ever to Inara:

    -
      -
    • Ship Combat, Trade and Exploration ranks.
    • -
    • On-foot Combat and Exobiologist ranks.
    • -
    • Engineer unlocks and progress.
    • -
    • Reputations with Major Factions (Superpowers).
    • -
    -

    This is now fixed and the current state of all of these will be correctly -reported to Inara if you have API access for it configured.

    -
  • -
-

-Developers

-
    -
  • -

    Now built using Python 3.9.9.

    -
  • -
  • -

    Updated PLUGINS.md -to state that we don't actually include all of Python's standard library.

    -
  • -
  • -

    The click_counter -example plugin code has been corrected to both actually work fully, and pass -our linting.

    -
  • -
- - -

Release 5.2.2

-

This release adds one new feature and addresses some bugs. We've also -updated to using Python 3.9.8.

-
    -
  • -

    Windows now has "minimize to system tray" support.

    -
      -
    • The system tray icon will always be present.
    • -
    • There is a new option on the Settings > Appearance tab - -Minimize to system tray.
    • -
    • When this new option is active, minimizing the application will also -hide the taskbar icon.
    • -
    • When the new option is not active, the application will minimize to the -taskbar as normal.
    • -
    -
  • -
-

-Bug Fixex

-
    -
  • -

    If a CAPI query failed in such a way that no requests.Response object -was made available we attempted to blindly dump the non-existent object.
    -We now check that it actually exists, and log the specifics of the exception.

    -
  • -
  • -

    A user experienced the game writing a NavRoute.json file without a -Route array, which caused the application to attempt sending a badly formed -navroute message to EDDN. That message was then remembered and constantly -retried.

    -
      -
    • -

      We now sanity check the NavRoute.json contents to be sure there is a -Route array, even if it is empty. If it's not present no attempt -to send the EDDN message will be made.

      -

      If this scenario occurs the user will see a status line message No 'Route' array in NavRoute.json contents.

      -
    • -
    • -

      For any EDDN message that receives a 400 status back we will drop it -from the replay log.

      -
    • -
    -
  • -
-
- -

Release 5.2.1

-

This release primarily addresses the issue of the program asking for -Frontier authorization much too often.

-
    -
  • Actually utilise the Frontier Refresh Token when the CAPI response is -"Unauthorized". The re-factoring of this code to make CAPI queries -threaded inadvertently prevented this.
  • -
- -

Release 5.2.0

-
    -
  • -

    The 'Update' button is disabled if CQC/Arena is detected.

    -
  • -
  • -

    Frontier CAPI queries now run in their own thread. There should be no -change in functionality for users. This affects both EDMarketConnector -(GUI) and EDMC (command-line).

    -
  • -
  • -

    File > Status will now use cached CAPI data, rather than causing a fresh -query. Currently if data has not yet been cached nothing will happen when -trying to use this.

    -
  • -
  • -

    Trying to use File > Status when the current commander is unknown, or -there is has been no CAPI data retrieval yet, will now result in the 'bad' -sound being played and an appropriate status line message.

    -
  • -
  • -

    File > Save Raw Data also now uses the cached CAPI data, rather than -causing a fresh query. This will write an empty JSON {} if no data is -yet available.

    -
  • -
  • -

    New docs/Licenses/ directory containing all relevant -third-party licenses for the software this application uses.

    -
  • -
  • -

    Settings > Output > File Location 'Browse' button will now always be -available, even if no output options are active.

    -
  • -
  • -

    The 'no git installed' logging when running from source is now at INFO -level, not ERROR. This will look less scary.

    -
  • -
  • -

    EDMarketConnetor command-line arguments have been re-ordered into -logical groups for --help output.

    -
  • -
  • -

    Support added for several new EDDN schemas relating to specific Journal -events. The live EDDN server has been updated to support these.

    -

    Schema support added for:

    -
      -
    • codexentry/1
    • -
    • fssdiscoveryscan/1
    • -
    • navbeaconscan/1
    • -
    • navroute/1
    • -
    • scanbarycentre/1
    • -
    -
  • -
  • -

    If a message to EDDN gets an 'unknown schema' response it will NOT be -saved in the replaylog for later retries, instead being discarded.

    -
  • -
-

-Bug Fixes

-
    -
  • -

    Pressing the 'Update' button when in space (not docked, not on a body -surface) will no longer cause a spurious "Docked but unknown station: EDO -Settlement?" message.

    -
  • -
  • -

    A bug preventing --force-localserver-auth from working has been fixed.

    -
  • -
  • -

    horizons and odyssey flags should now always be set properly on all -EDDN messages. The horizons flag was missing from some.

    -
  • -
-

-Developers

-
    -
  • -

    Now built using Python 3.9.7.

    -
  • -
  • -

    New journal_entry_cqc() function for plugins to receive journal events -specifically and only when the player is in CQC/Arena. This allows -for tracking things that happen in CQC/Arena without polluting -journal_entry(). See PLUGINS.md for details.

    -
  • -
  • -

    Command-line argument --trace-all to force all possible --trace-on to be -active.

    -
  • -
  • -

    Contributing.md has been updated for how to properly use trace_on().

    -
  • -
  • -

    EDMC.(py,exe) now also makes use of --trace-on.

    -
  • -
  • -

    EDMarketConnector now has --capi-pretend-down to act as if the CAPI -server is down.

    -
  • -
  • -

    Killswitches now have support for removing key/values entirely, or forcing -the value. See docs/Killswitches.md for details.

    -
  • -
  • -

    state['Odyssey'] added, set from LoadGame journal event.

    -
  • -
  • -

    You can now test against a different EDDN server using --eddn-url -command-line argument. This needs to be the full 'upload' URL, i.e. for -the live instance this is https://eddn.edcd.io:4430/upload/.

    -
  • -
  • -

    New command-line argument --eddn-tracking-ui to track the EDDN plugin's -idea of the current BodyName and BodyID, from both the Journal and -Status.json.

    -
  • -
- -

Release 5.1.3

-
    -
  • -

    Attempt to flush any pending EDSM API data when a Journal Shutdown or -Fileheader event is seen. After this, the data is dropped. This ensures -that, if the user next logs in to a different commander, the data isn't then -sent to the wrong EDSM account.

    -
  • -
  • -

    Ensure a previous Journal file is fully read/drained before starting -processing of a new one. In particular, this ensures properly seeing the end -of a continued Journal file when opening the continuation file.

    -
  • -
  • -

    New config options, in a new Privacy tab, to hide the current Private -Group, or captain of a ship you're multi-crewing on. These usually appear -on the Commander line of the main UI, appended after your commander name, -with a / between.

    -
  • -
  • -

    EDO dockable settlement names with + characters appended will no longer -cause 'server lagging' reports.

    -
  • -
  • -

    Don't force DEBUG level logging to the -plain log file -if --trace isn't used to force TRACE level logging. This means logging -to the plain log file will once more respect the user-set Log Level, as in -the Configuration tab of Settings.

    -

    As its name implies, the debug log file -will always contain at least DEBUG level logging, or TRACE if forced.

    -
  • -
-

-(Plugin) Developers

-
    -
  • -

    New EDMarketConnector option --trace-on ... to control if certain TRACE -level logging is used or not. This helps keep the noise down whilst being -able to have users activate choice bits of logging to help track down bugs.

    -

    See Contributing.md for -details.

    -
  • -
  • -

    Loading of ShipLocker.json content is now tried up to 5 times, 10ms apart, -if there is a file loading, or JSON decoding, failure. This should -hopefully result in the data being loaded correctly if a race condition with -the game client actually writing to and closing the file is encountered.

    -
  • -
  • -

    config.get_bool('some_str', default=SomeDefault) will now actually honour -that specified default.

    -
  • -
- -

Release 5.1.2

-
    -
  • -

    A Journal event change in EDO Update 6 will have caused some translated -suit names to not be properly mapped to their sane versions. This change -has now been addressed and suit names should always come out as intended in -the EDMarketConnector.exe UI.

    -
  • -
  • -

    There is a new command-line argument to cause all Frontier Authorisation to -be forgotten: EDMarketConnector.exe --forget-frontier-auth.

    -
  • -
  • -

    Situations where Frontier CAPI data doesn't agree on the location we have -tracked from Journal events will now log more useful information.

    -
  • -
-

-Bug Fixes

-
    -
  • The code should now be robust against the case of any Journal event name -changing.
  • -
-

-Plugin Developers

-
    -
  • -

    We now store GameLanguage, GameVersion and GameBuild in the state -passed to journal_entry() from the LoadGame event.

    -
  • -
  • -

    Various suit data, i.e. class and mods, is now stored from relevant -Journal events, rather than only being available from CAPI data. In -general we now consider the Journal to be the canonical source of suit -data, with CAPI only as a backup.

    -
  • -
  • -

    Backpack contents should now track correctly if using the 'Resupply' option -available on the ship boarding menu.

    -
  • -
  • -

    We now cache the main application version when first determined, so -that subsequent references to config.appversion() won't cause extra log -spam (which was possible when, e.g. having a git command but using non-git -source).

    -
  • -
- -

Release 5.1.1

-

The big change in this is adjustments to be in line with Journal changes in -Elite Dangerous Odyssey 4.0.0.400, released 2021-06-10, with respect to the -Odyssey materials Inventory.

-

This update is mandatory if you want EDMarketConnector to update Inara.cz -with your Odyssey inventory.

-
    -
  • -

    ShipLockerMaterials is dead, long live ShipLocker. Along with other -changes to how backpack inventory is handled we should now actually be -able to fully track all Odyssey on-foot materials and consumables without -errors.

    -
  • -
  • -

    Inara plugin adjusted to send the new ShipLocker inventory to Inara.cz. -This is still only your ship inventory of Odyssey materials, not -anything currently in your backpack whilst on foot. -See this issue -for some quotes from Artie (Inara.cz developer) about not including -backpack contents in the Inara inventory.

    -
  • -
  • -

    Errors related to sending data to EDDN are now more specific to aid in -diagnoising issues.

    -
  • -
  • -

    Quietened some log output if we encounter connection errors trying to -utilise the Frontier CAPI service.

    -
  • -
-

-Translations

-

We believe that nothing should be worse in this version compared to 5.1.1, -although a small tweak or two might have leaked through.

-

We'll be fully addressing translations in a near-future release after we've -conclude the necessary code level work for the new system. Nothing should -change for those of you helping on OneSky, other than at most the -'comments' on each translation. They should be more useful!

-

Pending that work we've specifically chosen not to update any -translations in this release, so they'll be the same as released in 5.1.0.

-

-Bug Fixes

-
    -
  • -

    Handle where the Backpack.json file for a Backpack event is a zero length -file. Closes #1138.

    -
  • -
  • -

    Fixed case of 'Selection' in 'Override Beta/Normal Selection' text on -Settings > Configuration. This allows translations to work.

    -
  • -
-

-Plugin Developers

-
    -
  • -

    We've updated Contributing.md including:

    -
      -
    1. Re-ordered the sections to be in a more logcial and helpful order.
    2. -
    3. Added a section about choosing an appropriate log level for messages.
    4. -
    5. fstrings now mandatory, other than some use of .format() with respect to -translated strings.
    6. -
    -
  • -
  • -

    docs/Translations.md updated about a forthcoming -change to how we can programmatically check that all translation strings -have a proper comment in 'L10n/en.template' to aid translators.

    -
  • -
  • -

    state passed to journal_entry() now has ShipLockerJSON which contains -the json.load()-ed data from the new 'ShipLocker.json' file. We do -attempt to always load from this file, even when the ShipLocker Journal -event itself contains all of the data (which it does on startup, embark and -disembark), so it should always be populated when plugins see any event -related to Odyssey inventory.

    -
  • -
- - -

Release 5.1.0

-
    -
  • -

    Updates to how this application utilises the Inara.cz API.

    -
      -
    1. The current state of your ShipLockerMaterials (MicroResources for Odyssey -Suit and handheld Weapons upgrading and engineering) will now be sent. -Note that we can't reliably track this on the fly, so it will only -update when we see a full ShipLockerMaterials Journal event, such as -at login or when you disembark from any vehicle.
    2. -
    3. Odyssey Suits and their Loadouts will now be sent.
    4. -
    5. When you land on a body surface, be that in your own ship, in a Taxi, -or in a Dropship. Depending on the exact scenario a Station might be -sent along with this.
    6. -
    -
  • -
  • -

    You can now both edit the 'normal' and 'beta' coriolis.io URLs, and -choose which of them are used. 'Auto' means allowing the application to -use the normal one when you're running the live game, or the beta version -if running a beta version of the game.

    -
  • -
  • -

    Suit names will now be displayed correctly when we have pulled the data -from the Frontier CAPI, rather than Journal entries.

    -
  • -
  • -

    Many translations updated once more, especially for new strings. Thanks -as always to those contributing!

    -
  • -
-

-Bug Fixes

- -

-Plugin Developers

-

There are some new members of the state dictionary passed to -journal_entry(); Taxi, Dropship, Body and BodyType. See -PLUGINS.md for the details.

- -

Release 5.0.4

-

This is a minor bugfix release, ensuring that Odyssey Suit names (and loadout) -will actually display if you're in your ship on login and never leave it.

-

NB: This still requires a Frontier CAPI data pull, either automatically -because you're docked if you have that option set, or by pressing the -'Update' button. We can't display data when we don't have it from either -CAPI or Journal sources. You'll also see '<Unknown>' between the time we -see the Journal LoadGame event during login and when there's either a -Journal suit-related event, or a CAPI data pull completes.

- - -

Release 5.0.3

-
    -
  • -

    You can now click on a 'cell' in the "File" > "Status" popup to copy that -text to the clipboard. This was a relatively easy, and non-intrusive, code -change. We'll look at richer, fuller, copy functionality in the future.

    -
  • -
  • -

    Suit names, for all grades, should now be displaying as just the relevant -word, never a symbol, and with the redundant 'suit' word(s) from all -languages removed. Note that Frontier have not translated the -following, so neither do we: "Artemis", "Dominator", "Maverick". The 'Flight -Suit' should, approximately, use the Frontier-supplied translation for -'Flight' in this context. In essence the displayed name is now as short -as possible whilst disambiguating the suit names from each other.

    -
  • -
-

-Bug Fixes

-
    -
  • -

    The check for "source, but with extra changes?" in appversion will now -not cause an error if the "git" command isn't available. Also, the extra -text added to the build number is now ".DIRTY".

    -
  • -
  • -

    Actually properly handle the "you just made progress" version of the -EngineerProgress Journal event, so that it doesn't throw errors.

    -
  • -
-

-Plugin Developers

-
    -
  • -

    The backpack and ship locker tracking of micro-resources might now -actually be correct with respect to 'reality' in-game. This is in part -thanks to Frontier changes to some events in 4.0.0.200.

    -
  • -
  • -

    Suit names will now only be sourced from Journal events if the -application didn't (yet) have the equivalent CAPI data.

    -
  • -
  • -

    The displayed Suit name is stored in an extra "edmcName" key within -state['Suits'] and state['SuitCurrent']. What was found in the -Journal or CAPI data is still present in the "name" and "locName" values.

    -
  • -
  • -

    The "language", "gameversion" and "build" values from the "Fileheader" event -are all now stored in state[] fields. See PLUGINS.md for -updated documentation.

    -
  • -
  • -

    We have a new Contributing.md policy of adding -comments in a defined format when we add or change code such that there's a -'hack', 'magic' or 'workaround' in play. You might find some of this -enlightening going forwards.

    -
  • -
- -

Release 5.0.2

-

This release is primarily aimed at getting the UI "Suit: ..." line working -properly.

-
    -
  • -

    The "Suit: ..." UI line should now function as best it can given the -available data from the game. It should not appear if you have launched -the Horizons version of the game, even if your account has Odyssey -enabled. You might see "<Unknown>" as the text when this application -does not yet have the required data.

    -
  • -
  • -

    Changed the less than obvious "unable to get endpoint: /profile" error -message to "Frontier CAPI query failure: /profile", and similarly for the -other CAPI endpoints we attempt to access. This new form is potentially -translated, but translators need time to do that.

    -

    In addition the old message "Received error {r.status_code} from server" -has been changed to "Frontier CAPI server error: {r.status_code}" and is -potentially translated.

    -
  • -
  • -

    The filenames used for 'Market data in CSV format file' will now be sane, -and as they were before 5.0.0.

    -
  • -
  • -

    Linux: 'Shipyard provider' will no longer default to showing 'False' if -no specific provider has been selected.

    -
  • -
-

-Plugin Developers

-
    -
  • -

    Extra Flagse values added in the live release of Odyssey have been added to -edmc_data.py.

    -
  • -
  • -

    Odyssey 'BackPack' values should now track better, but might still not be -perfect due to Journal bugs/shortcomings.

    -
  • -
  • -

    state passed to journal_entry() now has a BackpackJSON (note the case) -member which is a copy of the data from the Backpack.json (yes, that's -currently the correct case) file that is written when there's a BackPack -(guess what, yes, that is currently the correct case) event written to -the Journal.

    -
  • -
  • -

    state['Credits'] tracking is almost certainly not perfect. We're -accounting for the credits component of SuitUpgrade now, but there -might be other such we've yet accounted for.

    -
  • -
  • -

    state['Suits'] and associated other keys should now be tracking from -Journal events, where possible, as well as CAPI data.

    -
  • -
  • -

    There is a section in PLUGINS.md about how to package an extra Python -module with your plugin. Note the new caveat in -PLUGINS.md:Avoiding-pitfalls -about the name of your plugin's directory.

    -
  • -
- -

Release 5.0.1

-

The main reason for this release is to add an 'odyssey' boolean flag to all -EDDN messages for the benefit of listeners, e.g. eddb.io, inara.cz, -edsm.net, spansh.co.uk, etc. Please do update so as to make their lives -easier once Odyssey has launched!

-
    -
  • -

    Translations have been updated again. Thanks to all the contributors. -See wiki:Translations -and Translations welcome -for links and discussion if you want to help.

    -
  • -
  • -

    Changed the error message "Error: Frontier server is down" to -"Error: Frontier CAPI didn't respond" to make it clear this pertains to -the CAPI and not the game servers.

    -
  • -
-

-Killswitches

-

In the 5.0.0 changelog we said:

-
We will **NOT** be using this merely to try and get some - laggards to upgrade.
-

However, from now on there is an exception to this. After this -release any subsequent -beta or -rc versions will be killswitched after -their full release is published.

-

For example, if we put out a 5.0.2-beta1 and 5.0.2-rc1 before the full -5.0.2, then when 5.0.2 was published we would activate all available -killswitches for versions 5.0.2-beta1 and 5.0.2-rc1. In this example -5.0.1 would not be killswitched as part of this policy (but still -could be if, e.g. a data corruption bug was found in it).

-

In general please do not linger on any -beta or -rc release if there -has been a subsequent release. Upgrade to the equivalent full release once it -is published.

-

-Plugin Developers

-
    -
  • -

    Please make the effort to subscribe to GitHub notifications of new -EDMarketConnector releases:

    -
      -
    1. Login to GitHub.
    2. -
    3. Navigate to EDMarketConnector.
    4. -
    5. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on -us). It's currently (2021-05-13) the left-most button of 3 near the -top-right of the page.
    6. -
    7. Click 'Custom'.
    8. -
    9. Ensure 'Releases' is selected.
    10. -
    11. Click 'Apply'.
    12. -
    -

    This way you'll be aware, as early as possible, of any -beta and -rc -changelogs and changes that might affect your work.

    -
  • -
  • -

    state passed to journal_entry() has a new member Odyssey (note the -capital O) which is a boolean indicating if the LoadGame event both has -an Odyssey key, and if so, what the value was. Defaults to False.

    -
  • -
  • -

    PLUGINS.md updated to document the state['Horizons'] flag that has been -present in it since version 3.0 of the game.

    -
  • -
  • -

    The stations.p and systems.p files that were deprecated in 5.0.0 have -now also been removed in git. As this release is made they will no -longer be in the develop, main or stable branches. If you truly -need to find a copy look at the Release/4.2.7 tag, but do read the 5.0.0 -changelog for why we stopped using them and what you can change to also -not need them.

    -
  • -
- -

Release 5.0.0

-
    -
  • -

    We now test against, and package with, Python 3.9.5.

    -

    As a consequence of this we no longer support Windows 7.
    -This is due to -Python 3.9.x itself not supporting Windows 7. -The application (both EDMarketConnector.exe and EDMC.exe) will crash on -startup due to a missing DLL.

    -

    This should have no other impact on users or plugin developers, other -than the latter now being free to use features that were introduced since the -Python 3.7 series.

    -

    Developers can check the contents of the .python-version file -in the source (it's not distributed with the Windows installer) for the -currently used version in a given branch.

    -
  • -
-

-This Update Is Mandatory

-

This release is a mandatory upgrade for the release of Elite Dangerous -Odyssey. Any bug reports against earlier releases, pertaining to Odyssey or -not, will be directed to reproduce them with 5.0.0 or later. There are also -minor bugs in 4.2.7 and earlier that have been fixed in this version. There -will NOT be another 4.2.x release.

-

The major version has been incremented not for Odyssey support, but because -we have made some minor breaking changes to the APIs we provide for plugin -developers.

-

Due to these plugin API changes (see below) users might need to update their -plugins. A check of all the -Plugins we know about -only found one with an issue related to the move to edmc_data.py, the -developer was informed and the issue addressed.

-

Other plugins should, at most, log deprecation warnings about the -config changes (again, see below).

-

In the first instance please report any issues with plugins to their -developers, not us. They can contact us about EDMC core code issues if -they find such in their investigations.

-

All plugin developers would benefit from having a GitHub account and then -setting up a watch on EDMarketConnector -of at least 'Releases' under 'Custom'.

-

NB: If you had any beta or -rc1 of 5.0.0 installed and see anything weird -with this full release it would be advisable to manually uninstall, confirm -the installation directory (default c:\Program Files (x86)\EDMarketConnector) -is empty, and then re-install 5.0.0 to be sure you have a clean, working, -install. Anyone upgrading from 4.2.7 or earlier shouldn't see any issues -with this.

-

-Changes and Enhancements

-
    -
  • -

    If the application detects it's running against a non-live (alpha or beta) -version of the game it will append " (beta)" to the Commander name on the -main UI.

    -
  • -
  • -

    Updated translations. Once more, thanks to all the translators!

    -
  • -
  • -

    We now sanity check a returned Frontier Authentication token to be sure -it's for the current Commander. If it's not you'll see -Error: customer_id doesn't match! on the bottom status line. Double-check -you're using the correct credentials when authing!

    -
  • -
  • -

    New 'Main window transparency' slider on Settings > Appearance.

    -
  • -
  • -

    New command-line argument for EDMarketConnector.exe --reset-ui. This will:

    -
      -
    1. Reset to the default Theme.
    2. -
    3. Reset the UI transparency to fully opaque.
    4. -
    -

    The intention is this can be used if you've lost sight of the main window -due to tweaking these options.

    -

    There is a new file EDMarketConnector - reset-ui.bat to make utilising -this easy on Windows.

    -
  • -
  • -

    New CL arg for EDMarketConnector.exe --force-edmc-protocol. -This is really only of use to core developers (its purpose being to force -use of the edmc:// protocol for Frontier Auth callbacks, even when not -'frozen').

    -
  • -
  • -

    Linux config will be flushed to disk after any change. This means that -EDMC.py can now actually make use of the latest CAPI auth if it's been -updated by EDMarketConnector.py since that started.

    -

    If you want to run multiple instances of the application under Linux then -please check the updated Troubleshooting: Multi-Accounting -wiki entry.

    -
  • -
  • -

    Linux and macOS: You can now set a font name and size in your config file.
    -Ensuring this is a TTF font, rather than a bitmap font, should allow the -application UI scaling to work.

    -
      -
    1. 'font' - the font name to attempt using
    2. -
    3. 'font_size' - the font size to attempt using.
    4. -
    -

    There is no UI for this in Preferences, you will need to edit your -config file -to set or change it, and then restart the application.

    -

    This is not supported on Windows so as not to risk weird bugs. UI -Scaling works on Windows without this.

    -
  • -
  • -

    We now also cite the git 'short hash' in the version string. For a Windows -install of the application this is sourced from the .gitversion file -(written during the build process).

    -

    When running from source we attempt to use the command git rev-parse --short HEAD -to obtain this. If this doesn't work it will be set to 'UNKNOWN'.

    -
  • -
  • -

    We have added a 'killswitch' feature to turn off specific functionality if it -is found to have a bug. An example use of this would be in an "oh -shit! we're sending bad data to EDDN!" moment so as to protect EDDN -listeners such as EDDB.

    -

    If we ever have to use this we'll announce it clearly and endeavour to -get a fixed version of the program released ASAP. We will NOT be -using this merely to try and get some laggards to upgrade.

    -

    Plugin Developers: See Killswitches.md for more -information about this.

    -
  • -
  • -

    Our logging code will make best efforts to still show class name and -other such fields if it has trouble finding any of the required data for -the calling frame. This means no longer seeing ??:??:?? when there is -an issue with this.

    -
  • -
  • -

    macOS: We've managed to test the latest code on macOS Catalina. Other than -keyboard shortcut support not working -it appears to be working.

    -
  • -
  • -

    We've pulled the latest Coriolis data which might have caused changes to -ship and module names as written out to some files.

    -
  • -
-

-Odyssey

-

Every effort was made during the Odyssey Alphas to ensure that this -application will continue to function correctly with it. As always, make a -Bug Report -if you find anything not working, but be sure to check our -Known Issues first.

-
    -
  • -

    A new UI element 'Suit' now appears below 'Ship' when applicable. It -details the type of suit you currently have equipped and its Loadout name.
    -This UI element is collapsed/hidden if no suit/on-foot state is detected, -e.g. not playing Odyssey.

    -
  • -
  • -

    Note that we can only reliably know about Suits and their Loadouts from a -CAPI data pull (which is what we do automatically on docking if -configured to do so, or when you press the 'Update' button). We do -attempt to gather this data from Journal events as well, but if you -switch to a Suit Loadout that hasn't been mentioned in them yet we won't -be able to display that until the next CAPI data pull.

    -
  • -
-

If anyone becomes aware of a 'suit loadouts' site/tool, a la Coriolis/EDSY -but for Odyssey Suits, do let us know so we can add support for it! -We're already kicking around ideas to e.g. place JSON text in the clipboard -if the Suit Loadout is clicked.

-

-Bug Fixes

-
    -
  • -

    Fix ship loadout export to files to not trip up in the face of file encoding -issues. This relates to the 'Ship Loadout' option on the 'Output' tab of -Settings/Preferences.

    -
  • -
  • -

    Ship Type/Name will now be greyed out, and not clickable, if we don't -currently have loadout information for it. This prevents trying to send an -empty loadout to your shipyard provider.

    -
  • -
  • -

    Bug fixed when handling CAPI-sourced shipyard information. This happens -due to a Frontier bug with not returning shipyard data at all for normal -stations.

    -

    It has been observed that Frontier has fixed this bug for Odyssey.

    -
  • -
  • -

    Don't try to get Ship information from LoadGame event if directly in CQC.

    -
  • -
  • -

    Inara: Don't attempt to send an empty -setCommanderReputationMajorFaction API call. This quietens an error -from the Inara API caused when a Cmdr literally has no Major Faction -Reputation yet.

    -
  • -
-

-Code Clean Up

-
    -
  • -

    Code pertaining to processing Journal events was reworked and noisy logging -reduced as a consequence.

    -
  • -
  • -

    A little TRACE logging output has been commented out for now.

    -
  • -
  • -

    The code for File > Status has been cleaned up.

    -
  • -
  • -

    Localisation code has been cleaned up.

    -
  • -
  • -

    Code handling the Frontier Authorisation callback on Windows has been -cleaned up.

    -
  • -
  • -

    A lot of general code cleanup relating to: Inara, outfitting, Frontier -CAPI, hotkey (manual Updates), dashboard (Status.json monitoring), -commodities files, and ED format ship loadout files.

    -
  • -
-

-Plugin Developers

-
    -
  • -

    The files stations.p and systems.p have been removed from the Windows -Installer. These were never intended for third-party use. Their use in -core code was for generating EDDB-id URLs, but we long since changed the -EDDB plugin's handlers for that to use alternate URL formats based on -game IDs or names.

    -

    If you were using either to lookup EDDB IDs for systems and/or stations -then please see how system_url() and station_url() now work in -plugins/eddb.py.

    -

    This change also removed the core (not plugin) eddb.py file which -generated these files. You can find it still in the git history if needs -be. It had gotten to the stage where generating systems.p took many -hours and required 64-bit Python to have any hope of working due to -memory usage.

    -
  • -
  • -

    All static data that is -cleared for use by plugins -is now in the file -edmc_data.py and should be imported from there, not any other module.

    -

    The one thing we didn't move was the 'bracket map' dictionaries in td.py -as they're for use only by the code in that file.

    -

    All future such data will be added to this file, and we'll endeavour not -to make breaking changes to any of it without increasing our Major version.

    -
  • -
  • -

    config.appversion() is now a function that returns a semantic_version.Version. -In contexts where you're expecting a string this should mostly -just work. If needs be wrap it in str().

    -

    For backwards compatibility with pre-5.0.0 you can use:

    -
  • -
-
    from config import appversion
-
-    if callable(appversion):
-        edmc_version = appversion()
-    else:
-        edmc_version = appversion
-
    -
  • -

    Example plugin -plugintest -updated. This includes an example of how to check core EDMC version if needs -be. This example is also in -PLUGINS.md.

    -
  • -
  • -

    config.py has undergone a major rewrite. You should no longer be using -config.get(...) or config.getint(...), which will both give a -deprecation warning.
    -Use instead the correct config.get_<type>() function:

    -
      -
    • config.get_list(<key>)
    • -
    • config.get_str(<key>)
    • -
    • config.get_bool(<key>)
    • -
    • config.get_int(<key>)
    • -
    -

    Setting still uses config.set(...).

    -

    So:

    -
      -
    1. Replace all instances of config.get() and config.getint() as above.
    2. -
    3. For ease of maintaining compatibility with pre-5.0.0 versions include -this code in at least one module/file (no harm in it being in all that -manipulate plugin config):
    4. -
    -
  • -
-
from config import config
-
-# For compatibility with pre-5.0.0
-if not hasattr(config, 'get_int'):
-    config.get_int = config.getint
-
-if not hasattr(config, 'get_str'):
-    config.get_str = config.get
-
-if not hasattr(config, 'get_bool'):
-    config.get_bool = lambda key: bool(config.getint(key))
-
-if not hasattr(config, 'get_list'):
-    config.get_list = config.get
-
-
    -
  • -

    Utilising our provided logging from a class-level, i.e. not a solid -instance of a class, property/function will now work.

    -
  • -
  • -

    We now change the current working directory of EDMarketConnector.exe to -its location as soon as possible in its execution. We're also -paranoid about ensuring we reference the full path to the .gitversion file.

    -

    However, no plugin should itself call os.chdir(...) or equivalent. You'll -change the current working directory for all core code and other plugins as -well (it's global to the whole process, not per-thread). Use full -absolute paths instead (pathlib is what to use for this).

    -
  • -
  • -

    The state dict passed to plugins in journal_entry() calls (which is -actually monitor.state in the core code) has received many additions -relating to Odyssey, as well as other fixes and enhancements.

    -
      -
    1. -

      Support has been added for the NavRoute (not Route as v28 of the -official Journal documentation erroneously labels it) Journal event and -its associated file NavRoute.json. See PLUGINS.md:Events documentation

      -
    2. -
    3. -

      Similarly, there is now support for the ModuleInfo event and its -associated ModulesInfo.json file.

      -
    4. -
    5. -

      state['Credits'] - until now no effort was made to keep this -record of the credits balance up to date after the initial LoadGame -event. This has now been addressed, and the balance should stay in sync -as best it can from the available Journal events. It will always correct -back to the actual balance on each CAPI data pull or game relog/restart.

      -
    6. -
    7. -

      state['Cargo'] now takes account of any CargoTransfer events. -This was added to the game in the Fleet Carriers update, but also covers -transfers to/from an SRV.

      -
    8. -
    9. -

      state['OnFoot'] is a new boolean, set true whenever we detect -the Cmdr is on-foot, i.e. not in any type of vehicle (Cmdr's own ship, -SRV, multi-crew in another Cmdr's ship, Apex taxi, or a Dropship).

      -
    10. -
    11. -

      state['Suits'] and state['SuitLoadouts'] added as dicts containing -information about the Cmdr's owned Suits and the Loadouts the Cmdr has -defined to utilise them (and on-foot weapons). -Note that in the raw CAPI data these are arrays if all members -contiguously exist, else a dictionary, but we have chosen to always coerce -these to a python dict for simplicity. They will be empty dicts, not -None if there is no data.
      -We use the CAPI data names for keys, not the Journal ones - e.g. slots -for weapons equipped, not Modules. -The id field found on e.g. weapon details in suit loadouts may be None -if we got the data from the Journal rather than the CAPI data. -NB: This data is only guaranteed up to date and correct after a fresh CAPI -data pull, as the current Journal events don't allow for updating it on the -fly (this should change in a future Odyssey patch).

      -
    12. -
    13. -

      state['SuitCurrent'] and state['SuitLoadoutCurrent'] contain the -obvious "currently in use" data as per the Suits/SuitLoadouts.

      -
    14. -
    15. -

      Tracking of the new Odyssey 'Microresources' has been added:

      -
        -
      1. -Component - dict for 'Ship Locker' inventory.
      2. -
      3. -Item - dict for 'Ship Locker' inventory.
      4. -
      5. -Consumable - dict for 'Ship Locker' inventory.
      6. -
      7. -Data - dict for 'Ship Locker' inventory.
      8. -
      9. -BackPack - on-foot inventory, a dict containing again -dicts for Component, Item, Consumable and Data. -However note that the lack of a Journal event when throwing a grenade, -along with no BackPackMaterials event if logging in on-foot means that -we can't track the BackPack inventory perfectly.
      10. -
      -
    16. -
    -

    See the updated PLUGINS.md file for details.

    -
  • -
  • -

    As Status.json, and thus the EDMC 'dashboard' output now has a 'flags2' -key we have added the associated constants to edmc_data.py with a -Flags2 prefix on the names.

    -
  • -
  • -

    Note that during the Odyssey Alpha it was observed that the CAPI -data['commander']['docked'] boolean was always true if the Cmdr was -in their ship. This is a regression from pre-Odyssey behaviour. The -core EDMC code copes with this. Please add a reproduction to the issue -about this: -PTS CAPI saying Commander is Docked after jumping to new system.

    -
  • -
]]>
- -
-
-
From a9c026c3ef1f0ed709077a759467bff1e32e5416 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:46:01 -0400 Subject: [PATCH 217/261] [Admin] RM XML File From dbeaac657bea0a85ee95a4756e99c09845fa3538 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:51:54 -0400 Subject: [PATCH 218/261] [Admin] Update GitIgnore and Path for XML --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index df50e32b1..778820c66 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ appcast_mac_*.xml EDMC_Installer_Config.iss EDMarketConnector.wxs wix/components.wxs +edmarketconnector.xml +edmarketconnector-beta.xml # Ignore Visual Elements Manifest file for Windows EDMarketConnector.VisualElementsManifest.xml From 99a7451b5dd5b12aeb7934cbd66ef5a4b64bbc4a Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:52:16 -0400 Subject: [PATCH 219/261] [519] Change Beta Location --- config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index 1a0ae24ae..2fca7922a 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -485,7 +485,7 @@ def get_config(*args, **kwargs) -> AbstractConfig: def get_update_feed() -> str: """Select the proper update feed for the current update track.""" if config.get_bool('beta_optin'): - return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/beta/edmarketconnector.xml' + return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector-beta.xml' return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' From 95e037c9bc180d0b927b3a4f255b3e9a8aae3468 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:51:54 -0400 Subject: [PATCH 220/261] [Admin] Update GitIgnore and Path for XML --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index a7da806e6..0cdfe6984 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ appcast_mac_*.xml EDMC_Installer_Config.iss EDMarketConnector.wxs wix/components.wxs +edmarketconnector.xml +edmarketconnector-beta.xml # Ignore Visual Elements Manifest file for Windows EDMarketConnector.VisualElementsManifest.xml From 9432078488afbbc424c8d73fcb97ea44e50707dd Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:46:01 -0400 Subject: [PATCH 221/261] [Admin] RM XML File From d1cc1ff54aaecf2107ef833c322c879c0625f883 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:51:54 -0400 Subject: [PATCH 222/261] [Admin] Update GitIgnore and Path for XML --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index a7da806e6..0cdfe6984 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ appcast_mac_*.xml EDMC_Installer_Config.iss EDMarketConnector.wxs wix/components.wxs +edmarketconnector.xml +edmarketconnector-beta.xml # Ignore Visual Elements Manifest file for Windows EDMarketConnector.VisualElementsManifest.xml From 0464990e25b8867b7cc72794d1bd9b26d1ef3439 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 10:00:21 -0400 Subject: [PATCH 223/261] [Admin] RM XML File --- edmarketconnector.xml | 252 ------------------------------------------ 1 file changed, 252 deletions(-) delete mode 100644 edmarketconnector.xml diff --git a/edmarketconnector.xml b/edmarketconnector.xml deleted file mode 100644 index a4187153f..000000000 --- a/edmarketconnector.xml +++ /dev/null @@ -1,252 +0,0 @@ - - - - E:D Market Connector - https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml - Most recent changes with links to updates. - - - - - Release 3.44 - - h2 { font-size: 105%; } -

Release 3.44

-

CHANGE OF MAINTAINER

-

Due to a lack of time to give the project the attention it needs Marginal has handed over ownership of the EDMarketConnector GitHub repository to the EDCD (Elite Dangerous Community Developers) organisation.

-

Initially Athanasius will now be responsible for maintaining the code, including addressing any Pull Requests and Issues, and making releases. Unfortunately he has no access to hardware running MacOS so can't easily generate builds for that platform or test them. So for the time being releases will be for Windows 10 only. MacOS users are advised to look into running from source (see the github README).

-

Going forwards the intention is to move to the python 3.7 code as soon as possible. To facilitate this there will be one more python 2.7 release in addition to this one, with the main aim of that being to add code to alert the user about any plugins they use that have apparently not been updated to run under python 3.7.

-

See the project GitHub repository's README.md for further information.

-
    -
  • Version increased to 3.4.4.0 / 3.44.
  • -
  • URL the application checks for updates changed to point to github,
  • -
- ]]> -
- -
- - - - - Release 4.2.3 - - body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } - -

Release 4.2.3

-

This release mostly addresses an issue when Frontier Authorisation gets stuck -on 'Logging in...' despite completing the authorisation on the Frontier -website.

-
    -
  • -

    Allow edmc... argument to EDMarketConnector.exe. This should only be -necessary when something has prevented your web browser from invoking the -edmc protocol via DDE.

    -

    If you were encountering the 'Logging in...' issue and still do with this -release then please try running the application via the new -EDMarketConnector - localserver-auth.bat file in the installation -directory.

    -

    This simply runs EDMarketConnector.exe with the ---force-localserver-for-auth command-line argument. This forces the code -to setup and use a webserver on a random port on localhost for the -Frontier Authorisation callback, the same way it already works on -non-Windows platforms.

    -
  • -
  • -

    Add Korean translation to both the application and the installer.

    -
  • -
- - -

Release 4.2.2

-

This release contains a minor bug-fix, actually properly checking a station's -ships list before operating on it.

-
    -
  • Check that ships['shipuard_list'] is a dict before trying to use -.values() on it. This fixes the issue with seeing list object has no attribute values in the application status line.
  • -
- -

Release 4.2.1

-

This is a bug-fix release.

-
    -
  • -

    Updated translations. Thanks once again to all those contributing as per -Translations.

    -
  • -
  • -

    PLUGINS.md: Clarify when CargoJSON is populated.

    -
  • -
  • -

    macOS: pip install -r requirements.txt will now include pyobjc so that -running this application works at all. Check the updated Running from -source -for some advice if attempting to run on macOS.

    -
  • -
  • -

    JournalLock: Handle when the Journal directory isn't set at all, rather than -erroring. Fixes #910 - Not launching (Linux).

    -
  • -
  • -

    Extra logging added to track down cause of #909 - Authentication not possible (PC) -. The debug log file might now indicate what's wrong, or we might need -you to run

    -
    "c:\Program Files (x86)\EDMarketConnector/EDMarketConnector.exe" --trace
    -
    -

    in order to increase the log level and gather some extra information. -Caution is advised if sharing a --trace log file as it will now contain -some of the actual auth data returned from Frontier.

    -
  • -
  • -

    Ensure that 'Save Raw Data' will work. Fixes #908 - Raw export of CAPI data broken.

    -
  • -
  • -

    Prevent EDDN plugin from erroring when we determine if the commander has -Horizons. Fixes #907 - Modules is a list not a dict on damaged stations

    -
  • -
- - -

Release 4.2.0

-

This release increases the Minor version due to the major change in how -multiple-instance checking is done.

-
    -
  • -

    Adds Steam and Epic to the list of "audiences" in the Frontier Auth callout -so that you can authorise using those accounts, rather than their associated -Frontier Account details.

    -
  • -
  • -

    New status message "CAPI: No commander data returned" if a /profile -request has no commander in the returned data. This can happen if you -literally haven't yet created a Commander on the account. Previously you'd -get a confusing 'commander' message shown.

    -
  • -
  • -

    Changes the "is there another process already running?" check to be based on -a lockfile in the configured Journals directory. The name of this file is -edmc-journal-lock.txt and upon successful locking it will contain text -like:

    -
    Path: <configured path to your Journals>
    -PID: <process ID of the application>
    -
    -

    The lock will be released and applied to the new directory if you change it -via Settings > Configuration. If the new location is already locked you'll -get a 'Retry/Ignore?' pop-up.

    -

    For most users things will operate no differently, although note that the -multiple instance check does now apply to platforms other than Windows.

    -

    For anyone wanting to run multiple instances of the program this is now -possible via:

    -

    runas /user:<USER> "\"c:\Program Files (x86)\EDMarketConnector\EDMarketConnector.exe\" --force-localserver-for-auth"

    -

    If anything has messed with the backslash characters there then know that you -need to have " (double-quote) around the entire command (path to program .exe -and any extra arguments), and as a result need to place a backslash before -any double-quote characters in the command (such as around the space-including -path to the program).

    -

    I've verified it renders correctly on GitHub.

    -

    The old check was based solely on there being a window present with the title -we expect. This prevented using runas /user:SOMEUSER ... to run a second -copy of the application, as the resulting window would still be within the -same desktop environment and thus be found in the check.

    -

    The new method does assume that the Journals directory is writable by the -user we're running as. This might not be true in the case of sharing the -file system to another host in a read-only manner. If we fail to open the -lock file read-write then the application aborts the checks and will simply -continue running as normal.

    -

    Note that any single instance of EDMarketConnector.exe will still only monitor -and act upon the latest Journal file in the configured location. If you run -Elite Dangerous for another Commander then the application will want to start -monitoring that separate Commander. See wiki:Troubleshooting#i-run-two-instances-of-ed-simultaneously-but-i-cant-run-two-instances-of-edmc -which will be updated when this change is in a full release.

    -
  • -
  • -

    Adds the command-line argument --force-localserver-for-auth. This forces -using a local webserver for the Frontier Auth callback. This should be used -when running multiple instances of the application for all instances -else there's no guarantee of the edmc:// protocol callback reaching the -correct process and Frontier Auth will fail.

    -
  • -
  • -

    Adds the command-line argument --suppress-dupe-process-popup to exit -without showing the warning popup in the case that EDMarketConnector found -another process already running.

    -

    This can be useful if wanting to blindly run both EDMC and the game from a -batch file or similar.

    -
  • -
- - - -

Release 4.1.6

-

We might have finally found the cause of the application hangs during shutdown. -Note that this became easier to track down due to the downtime -for migration of www.edsm.net around 2021-01-11. Before these fixes EDSM's -API not being available would cause an EDMC hang on shutdown.

-
    -
  • -

    We've applied extra paranoia to some of the application shutdown code to -ensure we're not still trying to handle journal events during this sequence.

    -

    We also re-ordered the shutdown sequence, which might help avoid the shutdown -hang.

    -

    If you encounter a shutdown hang then please add a comment and log files to -Application can leave a zombie process on shutdown #678 -to help us track down the cause and fix it.

    -
  • -
  • -

    We now avoid making Tk event_generate() calls whilst the appliction is -shutting down.

    -
  • -
  • -

    Plugins should actively avoid making any sort of Tk event_generate() call -during application shutdown.

    -

    This means using if not config.shutting_down: to gate any code in worker -threads that might attempt this. Also, be sure you're not attempting such -in your plugin_stop() function.

    -

    See plugins/edsm.py and plugins/inara.py for example of the usage.

    -
  • -
  • -

    Any use of plug.show_error() won't actually change the UI status line -during shutdown, but the text you tried to show will be logged instead.

    -
  • -
  • -

    Cargo tracking will now correctly count all instances of the same type of -cargo for different missions. Previously it only counted the cargo for -the last mission requiring that cargo type, as found in Cargo.json.

    -
  • -
  • -

    The loaded contents of Cargo.json can now be found in monitor.state['CargoJSON']. -monitor.state is what is passed to plugins as state in the -journal_entry() call.

    -
  • -
  • -

    Our logging code should now cope with logging from a property.

    -
  • -
  • -

    Logging from any name-mangled method should now work properly.

    -
  • -
  • -

    Miscellaneous updates to PLUGINS.md - mostly to clarify some things.

    -
  • -
- - ]]> -
- -
- -
-
From cf9d9106454a3b164a1cfea94185cd3a36dbe895 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 10:00:21 -0400 Subject: [PATCH 224/261] [Admin] RM XML File --- edmarketconnector.xml | 252 ------------------------------------------ 1 file changed, 252 deletions(-) delete mode 100644 edmarketconnector.xml diff --git a/edmarketconnector.xml b/edmarketconnector.xml deleted file mode 100644 index a4187153f..000000000 --- a/edmarketconnector.xml +++ /dev/null @@ -1,252 +0,0 @@ - - - - E:D Market Connector - https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml - Most recent changes with links to updates. - - - - - Release 3.44 - - h2 { font-size: 105%; } -

Release 3.44

-

CHANGE OF MAINTAINER

-

Due to a lack of time to give the project the attention it needs Marginal has handed over ownership of the EDMarketConnector GitHub repository to the EDCD (Elite Dangerous Community Developers) organisation.

-

Initially Athanasius will now be responsible for maintaining the code, including addressing any Pull Requests and Issues, and making releases. Unfortunately he has no access to hardware running MacOS so can't easily generate builds for that platform or test them. So for the time being releases will be for Windows 10 only. MacOS users are advised to look into running from source (see the github README).

-

Going forwards the intention is to move to the python 3.7 code as soon as possible. To facilitate this there will be one more python 2.7 release in addition to this one, with the main aim of that being to add code to alert the user about any plugins they use that have apparently not been updated to run under python 3.7.

-

See the project GitHub repository's README.md for further information.

-
    -
  • Version increased to 3.4.4.0 / 3.44.
  • -
  • URL the application checks for updates changed to point to github,
  • -
- ]]> -
- -
- - - - - Release 4.2.3 - - body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } - -

Release 4.2.3

-

This release mostly addresses an issue when Frontier Authorisation gets stuck -on 'Logging in...' despite completing the authorisation on the Frontier -website.

-
    -
  • -

    Allow edmc... argument to EDMarketConnector.exe. This should only be -necessary when something has prevented your web browser from invoking the -edmc protocol via DDE.

    -

    If you were encountering the 'Logging in...' issue and still do with this -release then please try running the application via the new -EDMarketConnector - localserver-auth.bat file in the installation -directory.

    -

    This simply runs EDMarketConnector.exe with the ---force-localserver-for-auth command-line argument. This forces the code -to setup and use a webserver on a random port on localhost for the -Frontier Authorisation callback, the same way it already works on -non-Windows platforms.

    -
  • -
  • -

    Add Korean translation to both the application and the installer.

    -
  • -
- - -

Release 4.2.2

-

This release contains a minor bug-fix, actually properly checking a station's -ships list before operating on it.

-
    -
  • Check that ships['shipuard_list'] is a dict before trying to use -.values() on it. This fixes the issue with seeing list object has no attribute values in the application status line.
  • -
- -

Release 4.2.1

-

This is a bug-fix release.

-
    -
  • -

    Updated translations. Thanks once again to all those contributing as per -Translations.

    -
  • -
  • -

    PLUGINS.md: Clarify when CargoJSON is populated.

    -
  • -
  • -

    macOS: pip install -r requirements.txt will now include pyobjc so that -running this application works at all. Check the updated Running from -source -for some advice if attempting to run on macOS.

    -
  • -
  • -

    JournalLock: Handle when the Journal directory isn't set at all, rather than -erroring. Fixes #910 - Not launching (Linux).

    -
  • -
  • -

    Extra logging added to track down cause of #909 - Authentication not possible (PC) -. The debug log file might now indicate what's wrong, or we might need -you to run

    -
    "c:\Program Files (x86)\EDMarketConnector/EDMarketConnector.exe" --trace
    -
    -

    in order to increase the log level and gather some extra information. -Caution is advised if sharing a --trace log file as it will now contain -some of the actual auth data returned from Frontier.

    -
  • -
  • -

    Ensure that 'Save Raw Data' will work. Fixes #908 - Raw export of CAPI data broken.

    -
  • -
  • -

    Prevent EDDN plugin from erroring when we determine if the commander has -Horizons. Fixes #907 - Modules is a list not a dict on damaged stations

    -
  • -
- - -

Release 4.2.0

-

This release increases the Minor version due to the major change in how -multiple-instance checking is done.

-
    -
  • -

    Adds Steam and Epic to the list of "audiences" in the Frontier Auth callout -so that you can authorise using those accounts, rather than their associated -Frontier Account details.

    -
  • -
  • -

    New status message "CAPI: No commander data returned" if a /profile -request has no commander in the returned data. This can happen if you -literally haven't yet created a Commander on the account. Previously you'd -get a confusing 'commander' message shown.

    -
  • -
  • -

    Changes the "is there another process already running?" check to be based on -a lockfile in the configured Journals directory. The name of this file is -edmc-journal-lock.txt and upon successful locking it will contain text -like:

    -
    Path: <configured path to your Journals>
    -PID: <process ID of the application>
    -
    -

    The lock will be released and applied to the new directory if you change it -via Settings > Configuration. If the new location is already locked you'll -get a 'Retry/Ignore?' pop-up.

    -

    For most users things will operate no differently, although note that the -multiple instance check does now apply to platforms other than Windows.

    -

    For anyone wanting to run multiple instances of the program this is now -possible via:

    -

    runas /user:<USER> "\"c:\Program Files (x86)\EDMarketConnector\EDMarketConnector.exe\" --force-localserver-for-auth"

    -

    If anything has messed with the backslash characters there then know that you -need to have " (double-quote) around the entire command (path to program .exe -and any extra arguments), and as a result need to place a backslash before -any double-quote characters in the command (such as around the space-including -path to the program).

    -

    I've verified it renders correctly on GitHub.

    -

    The old check was based solely on there being a window present with the title -we expect. This prevented using runas /user:SOMEUSER ... to run a second -copy of the application, as the resulting window would still be within the -same desktop environment and thus be found in the check.

    -

    The new method does assume that the Journals directory is writable by the -user we're running as. This might not be true in the case of sharing the -file system to another host in a read-only manner. If we fail to open the -lock file read-write then the application aborts the checks and will simply -continue running as normal.

    -

    Note that any single instance of EDMarketConnector.exe will still only monitor -and act upon the latest Journal file in the configured location. If you run -Elite Dangerous for another Commander then the application will want to start -monitoring that separate Commander. See wiki:Troubleshooting#i-run-two-instances-of-ed-simultaneously-but-i-cant-run-two-instances-of-edmc -which will be updated when this change is in a full release.

    -
  • -
  • -

    Adds the command-line argument --force-localserver-for-auth. This forces -using a local webserver for the Frontier Auth callback. This should be used -when running multiple instances of the application for all instances -else there's no guarantee of the edmc:// protocol callback reaching the -correct process and Frontier Auth will fail.

    -
  • -
  • -

    Adds the command-line argument --suppress-dupe-process-popup to exit -without showing the warning popup in the case that EDMarketConnector found -another process already running.

    -

    This can be useful if wanting to blindly run both EDMC and the game from a -batch file or similar.

    -
  • -
- - - -

Release 4.1.6

-

We might have finally found the cause of the application hangs during shutdown. -Note that this became easier to track down due to the downtime -for migration of www.edsm.net around 2021-01-11. Before these fixes EDSM's -API not being available would cause an EDMC hang on shutdown.

-
    -
  • -

    We've applied extra paranoia to some of the application shutdown code to -ensure we're not still trying to handle journal events during this sequence.

    -

    We also re-ordered the shutdown sequence, which might help avoid the shutdown -hang.

    -

    If you encounter a shutdown hang then please add a comment and log files to -Application can leave a zombie process on shutdown #678 -to help us track down the cause and fix it.

    -
  • -
  • -

    We now avoid making Tk event_generate() calls whilst the appliction is -shutting down.

    -
  • -
  • -

    Plugins should actively avoid making any sort of Tk event_generate() call -during application shutdown.

    -

    This means using if not config.shutting_down: to gate any code in worker -threads that might attempt this. Also, be sure you're not attempting such -in your plugin_stop() function.

    -

    See plugins/edsm.py and plugins/inara.py for example of the usage.

    -
  • -
  • -

    Any use of plug.show_error() won't actually change the UI status line -during shutdown, but the text you tried to show will be logged instead.

    -
  • -
  • -

    Cargo tracking will now correctly count all instances of the same type of -cargo for different missions. Previously it only counted the cargo for -the last mission requiring that cargo type, as found in Cargo.json.

    -
  • -
  • -

    The loaded contents of Cargo.json can now be found in monitor.state['CargoJSON']. -monitor.state is what is passed to plugins as state in the -journal_entry() call.

    -
  • -
  • -

    Our logging code should now cope with logging from a property.

    -
  • -
  • -

    Logging from any name-mangled method should now work properly.

    -
  • -
  • -

    Miscellaneous updates to PLUGINS.md - mostly to clarify some things.

    -
  • -
- - ]]> -
- -
- -
-
From edae51ff3303620cae151e2383427907d783a6d9 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:46:01 -0400 Subject: [PATCH 225/261] [Admin] RM XML File From f9c5677a57c1ea0a7400b6aec09d27311bb8356a Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 09:51:54 -0400 Subject: [PATCH 226/261] [Admin] Update GitIgnore and Path for XML --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index a7da806e6..0cdfe6984 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ appcast_mac_*.xml EDMC_Installer_Config.iss EDMarketConnector.wxs wix/components.wxs +edmarketconnector.xml +edmarketconnector-beta.xml # Ignore Visual Elements Manifest file for Windows EDMarketConnector.VisualElementsManifest.xml From 8b5e3b57604f596e7ad529a3e066df4ae0c4f537 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 10:00:21 -0400 Subject: [PATCH 227/261] [Admin] RM XML File --- edmarketconnector.xml | 252 ------------------------------------------ 1 file changed, 252 deletions(-) delete mode 100644 edmarketconnector.xml diff --git a/edmarketconnector.xml b/edmarketconnector.xml deleted file mode 100644 index a4187153f..000000000 --- a/edmarketconnector.xml +++ /dev/null @@ -1,252 +0,0 @@ - - - - E:D Market Connector - https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml - Most recent changes with links to updates. - - - - - Release 3.44 - - h2 { font-size: 105%; } -

Release 3.44

-

CHANGE OF MAINTAINER

-

Due to a lack of time to give the project the attention it needs Marginal has handed over ownership of the EDMarketConnector GitHub repository to the EDCD (Elite Dangerous Community Developers) organisation.

-

Initially Athanasius will now be responsible for maintaining the code, including addressing any Pull Requests and Issues, and making releases. Unfortunately he has no access to hardware running MacOS so can't easily generate builds for that platform or test them. So for the time being releases will be for Windows 10 only. MacOS users are advised to look into running from source (see the github README).

-

Going forwards the intention is to move to the python 3.7 code as soon as possible. To facilitate this there will be one more python 2.7 release in addition to this one, with the main aim of that being to add code to alert the user about any plugins they use that have apparently not been updated to run under python 3.7.

-

See the project GitHub repository's README.md for further information.

-
    -
  • Version increased to 3.4.4.0 / 3.44.
  • -
  • URL the application checks for updates changed to point to github,
  • -
- ]]> -
- -
- - - - - Release 4.2.3 - - body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; } - -

Release 4.2.3

-

This release mostly addresses an issue when Frontier Authorisation gets stuck -on 'Logging in...' despite completing the authorisation on the Frontier -website.

-
    -
  • -

    Allow edmc... argument to EDMarketConnector.exe. This should only be -necessary when something has prevented your web browser from invoking the -edmc protocol via DDE.

    -

    If you were encountering the 'Logging in...' issue and still do with this -release then please try running the application via the new -EDMarketConnector - localserver-auth.bat file in the installation -directory.

    -

    This simply runs EDMarketConnector.exe with the ---force-localserver-for-auth command-line argument. This forces the code -to setup and use a webserver on a random port on localhost for the -Frontier Authorisation callback, the same way it already works on -non-Windows platforms.

    -
  • -
  • -

    Add Korean translation to both the application and the installer.

    -
  • -
- - -

Release 4.2.2

-

This release contains a minor bug-fix, actually properly checking a station's -ships list before operating on it.

-
    -
  • Check that ships['shipuard_list'] is a dict before trying to use -.values() on it. This fixes the issue with seeing list object has no attribute values in the application status line.
  • -
- -

Release 4.2.1

-

This is a bug-fix release.

-
    -
  • -

    Updated translations. Thanks once again to all those contributing as per -Translations.

    -
  • -
  • -

    PLUGINS.md: Clarify when CargoJSON is populated.

    -
  • -
  • -

    macOS: pip install -r requirements.txt will now include pyobjc so that -running this application works at all. Check the updated Running from -source -for some advice if attempting to run on macOS.

    -
  • -
  • -

    JournalLock: Handle when the Journal directory isn't set at all, rather than -erroring. Fixes #910 - Not launching (Linux).

    -
  • -
  • -

    Extra logging added to track down cause of #909 - Authentication not possible (PC) -. The debug log file might now indicate what's wrong, or we might need -you to run

    -
    "c:\Program Files (x86)\EDMarketConnector/EDMarketConnector.exe" --trace
    -
    -

    in order to increase the log level and gather some extra information. -Caution is advised if sharing a --trace log file as it will now contain -some of the actual auth data returned from Frontier.

    -
  • -
  • -

    Ensure that 'Save Raw Data' will work. Fixes #908 - Raw export of CAPI data broken.

    -
  • -
  • -

    Prevent EDDN plugin from erroring when we determine if the commander has -Horizons. Fixes #907 - Modules is a list not a dict on damaged stations

    -
  • -
- - -

Release 4.2.0

-

This release increases the Minor version due to the major change in how -multiple-instance checking is done.

-
    -
  • -

    Adds Steam and Epic to the list of "audiences" in the Frontier Auth callout -so that you can authorise using those accounts, rather than their associated -Frontier Account details.

    -
  • -
  • -

    New status message "CAPI: No commander data returned" if a /profile -request has no commander in the returned data. This can happen if you -literally haven't yet created a Commander on the account. Previously you'd -get a confusing 'commander' message shown.

    -
  • -
  • -

    Changes the "is there another process already running?" check to be based on -a lockfile in the configured Journals directory. The name of this file is -edmc-journal-lock.txt and upon successful locking it will contain text -like:

    -
    Path: <configured path to your Journals>
    -PID: <process ID of the application>
    -
    -

    The lock will be released and applied to the new directory if you change it -via Settings > Configuration. If the new location is already locked you'll -get a 'Retry/Ignore?' pop-up.

    -

    For most users things will operate no differently, although note that the -multiple instance check does now apply to platforms other than Windows.

    -

    For anyone wanting to run multiple instances of the program this is now -possible via:

    -

    runas /user:<USER> "\"c:\Program Files (x86)\EDMarketConnector\EDMarketConnector.exe\" --force-localserver-for-auth"

    -

    If anything has messed with the backslash characters there then know that you -need to have " (double-quote) around the entire command (path to program .exe -and any extra arguments), and as a result need to place a backslash before -any double-quote characters in the command (such as around the space-including -path to the program).

    -

    I've verified it renders correctly on GitHub.

    -

    The old check was based solely on there being a window present with the title -we expect. This prevented using runas /user:SOMEUSER ... to run a second -copy of the application, as the resulting window would still be within the -same desktop environment and thus be found in the check.

    -

    The new method does assume that the Journals directory is writable by the -user we're running as. This might not be true in the case of sharing the -file system to another host in a read-only manner. If we fail to open the -lock file read-write then the application aborts the checks and will simply -continue running as normal.

    -

    Note that any single instance of EDMarketConnector.exe will still only monitor -and act upon the latest Journal file in the configured location. If you run -Elite Dangerous for another Commander then the application will want to start -monitoring that separate Commander. See wiki:Troubleshooting#i-run-two-instances-of-ed-simultaneously-but-i-cant-run-two-instances-of-edmc -which will be updated when this change is in a full release.

    -
  • -
  • -

    Adds the command-line argument --force-localserver-for-auth. This forces -using a local webserver for the Frontier Auth callback. This should be used -when running multiple instances of the application for all instances -else there's no guarantee of the edmc:// protocol callback reaching the -correct process and Frontier Auth will fail.

    -
  • -
  • -

    Adds the command-line argument --suppress-dupe-process-popup to exit -without showing the warning popup in the case that EDMarketConnector found -another process already running.

    -

    This can be useful if wanting to blindly run both EDMC and the game from a -batch file or similar.

    -
  • -
- - - -

Release 4.1.6

-

We might have finally found the cause of the application hangs during shutdown. -Note that this became easier to track down due to the downtime -for migration of www.edsm.net around 2021-01-11. Before these fixes EDSM's -API not being available would cause an EDMC hang on shutdown.

-
    -
  • -

    We've applied extra paranoia to some of the application shutdown code to -ensure we're not still trying to handle journal events during this sequence.

    -

    We also re-ordered the shutdown sequence, which might help avoid the shutdown -hang.

    -

    If you encounter a shutdown hang then please add a comment and log files to -Application can leave a zombie process on shutdown #678 -to help us track down the cause and fix it.

    -
  • -
  • -

    We now avoid making Tk event_generate() calls whilst the appliction is -shutting down.

    -
  • -
  • -

    Plugins should actively avoid making any sort of Tk event_generate() call -during application shutdown.

    -

    This means using if not config.shutting_down: to gate any code in worker -threads that might attempt this. Also, be sure you're not attempting such -in your plugin_stop() function.

    -

    See plugins/edsm.py and plugins/inara.py for example of the usage.

    -
  • -
  • -

    Any use of plug.show_error() won't actually change the UI status line -during shutdown, but the text you tried to show will be logged instead.

    -
  • -
  • -

    Cargo tracking will now correctly count all instances of the same type of -cargo for different missions. Previously it only counted the cargo for -the last mission requiring that cargo type, as found in Cargo.json.

    -
  • -
  • -

    The loaded contents of Cargo.json can now be found in monitor.state['CargoJSON']. -monitor.state is what is passed to plugins as state in the -journal_entry() call.

    -
  • -
  • -

    Our logging code should now cope with logging from a property.

    -
  • -
  • -

    Logging from any name-mangled method should now work properly.

    -
  • -
  • -

    Miscellaneous updates to PLUGINS.md - mostly to clarify some things.

    -
  • -
- - ]]> -
- -
- -
-
From 5fa8f689ba3fd69798c9ee5860fb289053a4b347 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 10:16:05 -0400 Subject: [PATCH 228/261] [5.11.0-rc1] Update Changelog, Init --- ChangeLog.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++ config/__init__.py | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 7d14ca9c6..8c810a53d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,70 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- +Release 5.11.0-rc1 +=== + +This release includes a number of new features and improvements, including a new Beta Update Track for testing future updates, enhanced context menus for text entry fields and UI elements, a revamp to the existing translation system and logging capabilities, and more. This release includes the Python Image Library (PIL) into our core bundle, adds a number of stability and configuration checks to the tool, and adds new schemas and configuration values to senders. + +This release also includes a number of bug fixes, performance enhancements, and updates to various aspects of the code to enhance maintainability are included. Notably, MacOS support has been removed due to a lack of support for this OS in Elite, and a number of functions have been deprecated and will be removed in later versions. Plugin developers, take note! + +**Changes and Enhancements** +* Established a Beta Update Track to allow users to assist in future update testing +* Added a global context menu for text entry fields that includes cut, copy, and paste options +* Added a context menu for Ship, System, and Station UI elements which allows opening the respective link in any of the available resource providers. +* Added translation hooks to the update available status string +* Added additional status logging when we're awaiting game log-in +* Added the Python Image Library (PIL) to the core EDMC library bundle +* Added respect for EDSM API limits to the default plugin +* Added EDDN stationType and carrierDockingAccess schemas to the sent events +* Added MaxJumpRange and CargoCapacity events to the Inara sender +* Added a high-level critical error handler to gracefully terminate the program in the event of a catastrophic error +* Added the ability to override the default language for a translation by adding the optional 'lang' parameter to the translate function for individual functions +* Added an updated template and new security reporting guidance to the documentation +* Added a new updater for the FDevID Files to keep the dependency up to date without requiring a new patch version push +* Added a System Profiler Utility to assist with gathering system and environment information for bug report purposes +* Added a new security policy for responsible disclosure of identified security issues +* Enabled security code scanning on the GitHub repository +* Updates the look and feel of the "Already Running" popup to reduce overhead and improve the look of the popup +* Updated translations to latest versions +* Updated documentation to reflect certain changes to the code +* Updated the GitHub Bug Report template +* Updated the GitHub Pull Request template +* Updated internal workflows to more recent versions +* Updated util_ships to avoid using Windows reserved file names as output +* Converted all usages of the unnecessary OrderedDict to use the standard dict +* Clarifies the hierarchy of parent classes for custom MyNotebook classes +* Renamed the default translation function from `_()` to `tr.tl()` +* Renamed the Translations base class to conform to Pythonic standards +* Deprecated the `_Translations` class +* Deprecated the `Translations` singleton in favor of `translations` +* Unpinned several dependencies that were already dependencies of other dependencies to prevent dependency conflicts (say that 5 times fast) +* Updated a few type hints to allow updates to more updated dependencies +* Changed the translation function import to no longer rely on forcing it into Python's builtins +* Handed over a few tk classes to their ttk equivalents for better styling +* Reworked the Plugin system to no longer use the deprecated importlib.load_module() +* Deprecated nb.Entry and nb.ColoredButton as they simply point toward other classes with no processing +* Removed macOS support +* Removed deprecated modules.p and ships.p files +* Removed deprecated openurl() function + +**Bug Fixes** +* Fixed a bug where certain types of exceptions from the Requests module wouldn't be handled properly regarding killswitches +* Fixed a rare bug where source builds running on 64-bit Python could generate an OverflowError in the monitor system +* Fixed a bug where EDMC would open directories in the webbrowser instead of the file explorer on Linux +* Fixed a rare bug that could cause the EDSM plugin to crash due to missing configuration values + +**Plugin Developers** +* nb.Entry is deprecated, and is slated for removal in 6.0 or later. Please migrate to nb.EntryMenu +* nb.ColoredButton is deprecated, and is slated for removal in 6.0 or later. Please migrate to tk.Button +* Calling internal translations with `_()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to importing `translations` and calling `translations.translate` or `translations.tl` directly +* `Translations` as the translate system singleton is deprecated, and is slated for removal in 6.0 or later. Please migrate to the `translations` singleton +* `help_open_log_folder()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to open_folder() +* `update_feed` is deprecated, and is slated for removal in 6.0 or later. Please migrate to `get_update_feed()`. +* modules.p and ships.p are deprecated, and have been removed +* The `openurl()` function in ttkHyperlinkLabel has been removed. Please migrate to `webbrowser.open()` + + Release 5.10.6 === This release contains the data information for the new SCO modules added in Elite update 18.04. diff --git a/config/__init__.py b/config/__init__.py index 2fca7922a..7a08d9635 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.0-alpha3' +_static_appversion = '5.11.0-rc1' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 1ecc460a9fbbacdf226ec27406552727d9cbce19 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 27 May 2024 10:16:05 -0400 Subject: [PATCH 229/261] [5.11.0-rc1] Update Changelog, Init --- ChangeLog.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++ config/__init__.py | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 7d14ca9c6..8c810a53d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,70 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- +Release 5.11.0-rc1 +=== + +This release includes a number of new features and improvements, including a new Beta Update Track for testing future updates, enhanced context menus for text entry fields and UI elements, a revamp to the existing translation system and logging capabilities, and more. This release includes the Python Image Library (PIL) into our core bundle, adds a number of stability and configuration checks to the tool, and adds new schemas and configuration values to senders. + +This release also includes a number of bug fixes, performance enhancements, and updates to various aspects of the code to enhance maintainability are included. Notably, MacOS support has been removed due to a lack of support for this OS in Elite, and a number of functions have been deprecated and will be removed in later versions. Plugin developers, take note! + +**Changes and Enhancements** +* Established a Beta Update Track to allow users to assist in future update testing +* Added a global context menu for text entry fields that includes cut, copy, and paste options +* Added a context menu for Ship, System, and Station UI elements which allows opening the respective link in any of the available resource providers. +* Added translation hooks to the update available status string +* Added additional status logging when we're awaiting game log-in +* Added the Python Image Library (PIL) to the core EDMC library bundle +* Added respect for EDSM API limits to the default plugin +* Added EDDN stationType and carrierDockingAccess schemas to the sent events +* Added MaxJumpRange and CargoCapacity events to the Inara sender +* Added a high-level critical error handler to gracefully terminate the program in the event of a catastrophic error +* Added the ability to override the default language for a translation by adding the optional 'lang' parameter to the translate function for individual functions +* Added an updated template and new security reporting guidance to the documentation +* Added a new updater for the FDevID Files to keep the dependency up to date without requiring a new patch version push +* Added a System Profiler Utility to assist with gathering system and environment information for bug report purposes +* Added a new security policy for responsible disclosure of identified security issues +* Enabled security code scanning on the GitHub repository +* Updates the look and feel of the "Already Running" popup to reduce overhead and improve the look of the popup +* Updated translations to latest versions +* Updated documentation to reflect certain changes to the code +* Updated the GitHub Bug Report template +* Updated the GitHub Pull Request template +* Updated internal workflows to more recent versions +* Updated util_ships to avoid using Windows reserved file names as output +* Converted all usages of the unnecessary OrderedDict to use the standard dict +* Clarifies the hierarchy of parent classes for custom MyNotebook classes +* Renamed the default translation function from `_()` to `tr.tl()` +* Renamed the Translations base class to conform to Pythonic standards +* Deprecated the `_Translations` class +* Deprecated the `Translations` singleton in favor of `translations` +* Unpinned several dependencies that were already dependencies of other dependencies to prevent dependency conflicts (say that 5 times fast) +* Updated a few type hints to allow updates to more updated dependencies +* Changed the translation function import to no longer rely on forcing it into Python's builtins +* Handed over a few tk classes to their ttk equivalents for better styling +* Reworked the Plugin system to no longer use the deprecated importlib.load_module() +* Deprecated nb.Entry and nb.ColoredButton as they simply point toward other classes with no processing +* Removed macOS support +* Removed deprecated modules.p and ships.p files +* Removed deprecated openurl() function + +**Bug Fixes** +* Fixed a bug where certain types of exceptions from the Requests module wouldn't be handled properly regarding killswitches +* Fixed a rare bug where source builds running on 64-bit Python could generate an OverflowError in the monitor system +* Fixed a bug where EDMC would open directories in the webbrowser instead of the file explorer on Linux +* Fixed a rare bug that could cause the EDSM plugin to crash due to missing configuration values + +**Plugin Developers** +* nb.Entry is deprecated, and is slated for removal in 6.0 or later. Please migrate to nb.EntryMenu +* nb.ColoredButton is deprecated, and is slated for removal in 6.0 or later. Please migrate to tk.Button +* Calling internal translations with `_()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to importing `translations` and calling `translations.translate` or `translations.tl` directly +* `Translations` as the translate system singleton is deprecated, and is slated for removal in 6.0 or later. Please migrate to the `translations` singleton +* `help_open_log_folder()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to open_folder() +* `update_feed` is deprecated, and is slated for removal in 6.0 or later. Please migrate to `get_update_feed()`. +* modules.p and ships.p are deprecated, and have been removed +* The `openurl()` function in ttkHyperlinkLabel has been removed. Please migrate to `webbrowser.open()` + + Release 5.10.6 === This release contains the data information for the new SCO modules added in Elite update 18.04. diff --git a/config/__init__.py b/config/__init__.py index 2fca7922a..7a08d9635 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.0-alpha3' +_static_appversion = '5.11.0-rc1' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 6660ab77f917e5dc0eee676247656c84efb42879 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 28 May 2024 08:27:36 -0400 Subject: [PATCH 230/261] [#1293][#1124] Resize Settings Window, Reorder Plugins --- prefs.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/prefs.py b/prefs.py index 5567e0942..8904eb5bb 100644 --- a/prefs.py +++ b/prefs.py @@ -232,7 +232,7 @@ class PreferencesDialog(tk.Toplevel): """The EDMC preferences dialog.""" def __init__(self, parent: tk.Tk, callback: Optional[Callable]): - tk.Toplevel.__init__(self, parent) + super().__init__(parent) self.parent = parent self.callback = callback @@ -242,25 +242,30 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): if parent.winfo_viewable(): self.transient(parent) - # position over parent - # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - # TODO this is fixed supposedly. + # Position over parent self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}') - # remove decoration + # Remove decoration if sys.platform == 'win32': self.attributes('-toolwindow', tk.TRUE) - self.resizable(tk.FALSE, tk.FALSE) + # Allow the window to be resizable + self.resizable(tk.TRUE, tk.TRUE) self.cmdr: str | bool | None = False # Note if Cmdr changes in the Journal self.is_beta: bool = False # Note if Beta status changes in the Journal self.cmdrchanged_alarm: Optional[str] = None # This stores an ID that can be used to cancel a scheduled call + # Set up the main frame frame = ttk.Frame(self) frame.grid(sticky=tk.NSEW) + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + frame.columnconfigure(0, weight=1) + frame.rowconfigure(0, weight=1) + frame.rowconfigure(1, weight=0) - notebook: ttk.Notebook = nb.Notebook(frame) + notebook: nb.Notebook = nb.Notebook(frame) notebook.bind('<>', self.tabchanged) # Recompute on tab change self.PADX = 10 @@ -268,16 +273,17 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): self.LISTX = 25 # indent listed items self.PADY = 1 # close spacing self.BOXY = 2 # box spacing - self.SEPY = 10 # seperator line spacing + self.SEPY = 10 # separator line spacing # Set up different tabs - self.__setup_output_tab(notebook) - self.__setup_plugin_tabs(notebook) self.__setup_config_tab(notebook) - self.__setup_privacy_tab(notebook) self.__setup_appearance_tab(notebook) + self.__setup_output_tab(notebook) + self.__setup_privacy_tab(notebook) self.__setup_plugin_tab(notebook) + self.__setup_plugin_tabs(notebook) + # Set up the button frame buttonframe = ttk.Frame(frame) buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW) buttonframe.columnconfigure(0, weight=1) @@ -298,7 +304,7 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): # wait for window to appear on screen before calling grab_set self.parent.update_idletasks() - self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on Linux + self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear on top of parent on Linux self.wait_visibility() self.grab_set() From 6e6a6814be2b7905305f92a6d53efff648e5cdfa Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 28 May 2024 08:40:28 -0400 Subject: [PATCH 231/261] [1283] Enforce Minimum Size --- prefs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/prefs.py b/prefs.py index 8904eb5bb..0e3e3dead 100644 --- a/prefs.py +++ b/prefs.py @@ -322,6 +322,12 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): # Set Log Directory self.logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname + # Set minimum size to prevent content cut-off + self.update_idletasks() # Update "requested size" from geometry manager + min_width = self.winfo_reqwidth() + min_height = self.winfo_reqheight() + self.wm_minsize(min_width, min_height) + def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: output_frame = nb.Frame(root_notebook) output_frame.columnconfigure(0, weight=1) From f53388e2117b048d53e7a8e45be4f98c9bb58a83 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 28 May 2024 08:53:18 -0400 Subject: [PATCH 232/261] [Minor] Update System Profiler Logging --- EDMarketConnector.py | 2 +- prefs.py | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 7f870108f..249b842f2 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -620,7 +620,7 @@ def open_window(systray: 'SysTrayIcon') -> None: self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w)) logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname self.help_menu.add_command(command=lambda: prefs.open_folder(logfile_loc)) # Open Log Folder - self.help_menu.add_command(command=prefs.help_open_system_profiler) # Open Log Folde + self.help_menu.add_command(command=lambda: prefs.help_open_system_profiler(self)) # Open Log Folde self.menubar.add_cascade(menu=self.help_menu) if sys.platform == 'win32': diff --git a/prefs.py b/prefs.py index 5567e0942..08d8c6a06 100644 --- a/prefs.py +++ b/prefs.py @@ -57,14 +57,18 @@ def open_folder(file: pathlib.Path) -> None: system(f'xdg-open "{file}"') -def help_open_system_profiler() -> None: +def help_open_system_profiler(parent) -> None: """Open the EDMC System Profiler.""" profiler_path = pathlib.Path(config.respath_path) - if getattr(sys, 'frozen', False): - profiler_path /= 'EDMCSystemProfiler.exe' - subprocess.run(profiler_path) - else: - subprocess.run(['python', "EDMCSystemProfiler.py"], shell=True) + try: + if getattr(sys, 'frozen', False): + profiler_path /= 'EDMCSystemProfiler.exe' + subprocess.run(profiler_path, check=True) + else: + subprocess.run(['python', "EDMCSystemProfiler.py"], shell=True, check=True) + except Exception as err: + parent.status["text"] = "Unable to Launch System Profiler" + logger.exception(err) class PrefsVersion: @@ -927,7 +931,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ).grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.N, row=cur_row) enabled_plugins = list(filter(lambda x: x.folder and x.module, plug.PLUGINS)) - if len(enabled_plugins): + if enabled_plugins: ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid( columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) @@ -949,7 +953,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ############################################################ # Show which plugins don't have Python 3.x support ############################################################ - if len(plug.PLUGINS_not_py3): + if plug.PLUGINS_not_py3: ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid( columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) @@ -975,7 +979,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # Show disabled plugins ############################################################ disabled_plugins = list(filter(lambda x: x.folder and not x.module, plug.PLUGINS)) - if len(disabled_plugins): + if disabled_plugins: ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid( columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) @@ -992,7 +996,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 ############################################################ # Show plugins that failed to load ############################################################ - if len(plug.PLUGINS_broken): + if plug.PLUGINS_broken: ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid( columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) From 8a27ca266d945af528165d7f0ce5c767a3c13ea4 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 28 May 2024 09:02:58 -0400 Subject: [PATCH 233/261] [Minor] Add Translation --- L10n/en.template | 3 +++ prefs.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/L10n/en.template b/L10n/en.template index b45dfed9e..3906b8978 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -489,6 +489,9 @@ /* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */ "Disabled Plugins" = "Disabled Plugins"; +/* prefs.py: Catch & Record Profiler Errors; */ +"Error in System Profiler" = "Error in System Profiler"; + /* stats.py: Cmdr stats; In files: stats.py:58; */ "Balance" = "Balance"; diff --git a/prefs.py b/prefs.py index 08d8c6a06..7e15bc3b2 100644 --- a/prefs.py +++ b/prefs.py @@ -67,7 +67,7 @@ def help_open_system_profiler(parent) -> None: else: subprocess.run(['python', "EDMCSystemProfiler.py"], shell=True, check=True) except Exception as err: - parent.status["text"] = "Unable to Launch System Profiler" + parent.status["text"] = tr.tl("Error in System Profiler") # LANG: Catch & Record Profiler Errors logger.exception(err) From bc2adfa655a7531761a79a6c24d33e5be1b7b7cc Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 28 May 2024 11:28:18 -0400 Subject: [PATCH 234/261] [5.11.0-rc2] Update Changelog and Init --- ChangeLog.md | 14 +++++++++++++- config/__init__.py | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 8c810a53d..5d480fb8e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,8 +6,20 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- -Release 5.11.0-rc1 +Pre-Release 5.11.0-rc2 === +This is a release candidate for 5.11.0. + +This release is identical to 5.11.0-rc1, with a few additions: + +**Changes and Enhancements** +* Adds Additional Error Processing to the System Profiler when launched from EDMC +* Adds the ability to resize the Settings window to larger than the initial default size +* Tweaked a few list length checks that could just be boolean to be bool + +Pre-Release 5.11.0-rc1 +=== +This is a release candidate for 5.11.0. This release includes a number of new features and improvements, including a new Beta Update Track for testing future updates, enhanced context menus for text entry fields and UI elements, a revamp to the existing translation system and logging capabilities, and more. This release includes the Python Image Library (PIL) into our core bundle, adds a number of stability and configuration checks to the tool, and adds new schemas and configuration values to senders. diff --git a/config/__init__.py b/config/__init__.py index 7a08d9635..ab0bdd0a3 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.0-rc1' +_static_appversion = '5.11.0-rc2' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 3dd3652c59a9dd41a6840420e9c50466348b16de Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 28 May 2024 21:14:12 -0400 Subject: [PATCH 235/261] [#1469] Add Translation Push Check --- .github/workflows/push-checks.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index a15a718cc..c38e2f6c4 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -69,3 +69,11 @@ jobs: - name: mypy type checks run: | ./scripts/mypy-all.sh --platform win32 + + - name: translation checks + run: | + output=$(python ./scripts/find_localised_strings.py --compare-lang L10n/en.template --directory . --ignore coriolis-data 2>&1) + if [ -n "$output" ]; then + echo $output + exit 1 + fi \ No newline at end of file From b6d4371d675b4b9e27f922b411d77a25fb3d8fcf Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 30 May 2024 18:22:35 -0400 Subject: [PATCH 236/261] [1469] Update Translation Scripting Also runs through the Black linter for parsability sake. --- .github/workflows/push-checks.yml | 6 +- scripts/find_localised_strings.py | 171 ++++++++++++++++++------------ 2 files changed, 106 insertions(+), 71 deletions(-) diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index c38e2f6c4..e238ad8fe 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -72,8 +72,4 @@ jobs: - name: translation checks run: | - output=$(python ./scripts/find_localised_strings.py --compare-lang L10n/en.template --directory . --ignore coriolis-data 2>&1) - if [ -n "$output" ]; then - echo $output - exit 1 - fi \ No newline at end of file + python ./scripts/find_localised_strings.py --compare-lang L10n/en.template --directory . --ignore coriolis-data diff --git a/scripts/find_localised_strings.py b/scripts/find_localised_strings.py index 5911d64ca..8187f262c 100644 --- a/scripts/find_localised_strings.py +++ b/scripts/find_localised_strings.py @@ -1,4 +1,5 @@ """Search all given paths recursively for localised string calls.""" + from __future__ import annotations import argparse @@ -17,20 +18,20 @@ def get_func_name(thing: ast.AST) -> str: if isinstance(thing, ast.Attribute): return get_func_name(thing.value) - return '' + return "" def get_arg(call: ast.Call) -> str: """Extract the argument string to the translate function.""" if len(call.args) > 1: - print('??? > 1 args', call.args, file=sys.stderr) + print("??? > 1 args", call.args, file=sys.stderr) arg = call.args[0] if isinstance(arg, ast.Constant): return arg.value if isinstance(arg, ast.Name): - return f'VARIABLE! CHECK CODE! {arg.id}' - return f'Unknown! {type(arg)=} {ast.dump(arg)} ||| {ast.unparse(arg)}' + return f"VARIABLE! CHECK CODE! {arg.id}" + return f"Unknown! {type(arg)=} {ast.dump(arg)} ||| {ast.unparse(arg)}" def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]: @@ -38,8 +39,14 @@ def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]: out = [] for n in ast.iter_child_nodes(statement): out.extend(find_calls_in_stmt(n)) - if isinstance(statement, ast.Call) and get_func_name(statement.func) in ('tr', 'translations'): - if ast.unparse(statement).find('.tl') != -1 or ast.unparse(statement).find('translate') != -1: + if isinstance(statement, ast.Call) and get_func_name(statement.func) in ( + "tr", + "translations", + ): + if ( + ast.unparse(statement).find(".tl") != -1 + or ast.unparse(statement).find("translate") != -1 + ): out.append(statement) return out @@ -53,11 +60,13 @@ def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]: The difference is necessary in order to tell if a 'above' LANG comment is for its own line (SAME_LINE), or meant to be for this following line (OWN_LINE). """ -COMMENT_SAME_LINE_RE = re.compile(r'^.*?(#.*)$') -COMMENT_OWN_LINE_RE = re.compile(r'^\s*?(#.*)$') +COMMENT_SAME_LINE_RE = re.compile(r"^.*?(#.*)$") +COMMENT_OWN_LINE_RE = re.compile(r"^\s*?(#.*)$") -def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> str | None: # noqa: CCR001 +def extract_comments( # noqa: CCR001 + call: ast.Call, lines: list[str], file: pathlib.Path +) -> str | None: """ Extract comments from source code based on the given call. @@ -83,23 +92,23 @@ def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> st match = COMMENT_OWN_LINE_RE.match(above_line) if match: above_comment = match.group(1).strip() - if not above_comment.startswith('# LANG:'): - bad_comment = f'Unknown comment for {file}:{call.lineno} {above_line}' + if not above_comment.startswith("# LANG:"): + bad_comment = f"Unknown comment for {file}:{call.lineno} {above_line}" above_comment = None else: - above_comment = above_comment.replace('# LANG:', '').strip() + above_comment = above_comment.replace("# LANG:", "").strip() if current_line is not None: match = COMMENT_SAME_LINE_RE.match(current_line) if match: current_comment = match.group(1).strip() - if not current_comment.startswith('# LANG:'): - bad_comment = f'Unknown comment for {file}:{call.lineno} {current_line}' + if not current_comment.startswith("# LANG:"): + bad_comment = f"Unknown comment for {file}:{call.lineno} {current_line}" current_comment = None else: - current_comment = current_comment.replace('# LANG:', '').strip() + current_comment = current_comment.replace("# LANG:", "").strip() if current_comment is not None: out = current_comment @@ -109,13 +118,13 @@ def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> st print(bad_comment, file=sys.stderr) if out is None: - print(f'No comment for {file}:{call.lineno} {current_line}', file=sys.stderr) + print(f"No comment for {file}:{call.lineno} {current_line}", file=sys.stderr) return out def scan_file(path: pathlib.Path) -> list[ast.Call]: """Scan a file for ast.Calls.""" - data = path.read_text(encoding='utf-8') + data = path.read_text(encoding="utf-8") lines = data.splitlines() parsed = ast.parse(data) out: list[ast.Call] = [] @@ -125,13 +134,15 @@ def scan_file(path: pathlib.Path) -> list[ast.Call]: # see if we can extract any comments for call in out: - setattr(call, 'comment', extract_comments(call, lines, path)) + setattr(call, "comment", extract_comments(call, lines, path)) out.sort(key=lambda c: c.lineno) return out -def scan_directory(path: pathlib.Path, skip: list[pathlib.Path] | None = None) -> dict[pathlib.Path, list[ast.Call]]: +def scan_directory( + path: pathlib.Path, skip: list[pathlib.Path] | None = None +) -> dict[pathlib.Path, list[ast.Call]]: """ Scan a directory for expected callsites. @@ -145,7 +156,7 @@ def scan_directory(path: pathlib.Path, skip: list[pathlib.Path] | None = None) - if any(same_path.name == thing.name for same_path in skip): continue - if thing.is_file() and thing.suffix == '.py': + if thing.is_file() and thing.suffix == ".py": out[thing] = scan_file(thing) elif thing.is_dir(): out.update(scan_directory(thing, skip)) @@ -163,10 +174,10 @@ def parse_template(path) -> set[str]: """ lang_re = re.compile(r'\s*"([^"]+)"\s*=\s*"([^"]+)"\s*;\s*$') out = set() - with open(path, encoding='utf-8') as file: + with open(path, encoding="utf-8") as file: for line in file: match = lang_re.match(line.strip()) - if match and match.group(1) != '!Language': + if match and match.group(1) != "!Language": out.add(match.group(1)) return out @@ -183,14 +194,16 @@ class FileLocation: line_end_col: int | None @staticmethod - def from_call(path: pathlib.Path, c: ast.Call) -> 'FileLocation': + def from_call(path: pathlib.Path, c: ast.Call) -> "FileLocation": """ Create a FileLocation from a Call and Path. :param path: Path to the file this FileLocation is in :param c: Call object to extract line information from """ - return FileLocation(path, c.lineno, c.col_offset, c.end_lineno, c.end_col_offset) + return FileLocation( + path, c.lineno, c.col_offset, c.end_lineno, c.end_col_offset + ) @dataclasses.dataclass @@ -238,95 +251,121 @@ def generate_lang_template(data: dict[pathlib.Path, list[ast.Call]]) -> str: entries: list[LangEntry] = [] for path, calls in data.items(): for c in calls: - entries.append(LangEntry([FileLocation.from_call(path, c)], get_arg(c), [getattr(c, 'comment')])) + entries.append( + LangEntry( + [FileLocation.from_call(path, c)], + get_arg(c), + [getattr(c, "comment")], + ) + ) deduped = dedupe_lang_entries(entries) - out = '''/* Language name */ + out = """/* Language name */ "!Language" = "English"; -''' - print(f'Done Deduping entries {len(entries)=} {len(deduped)=}', file=sys.stderr) +""" + print(f"Done Deduping entries {len(entries)=} {len(deduped)=}", file=sys.stderr) for entry in deduped: assert len(entry.comments) == len(entry.locations) comment_set = set() for comment, loc in zip(entry.comments, entry.locations): if comment: - comment_set.add(f'{loc.path.name}: {comment};') + comment_set.add(f"{loc.path.name}: {comment};") - files = 'In files: ' + entry.files() - comment = ' '.join(comment_set).strip() + files = "In files: " + entry.files() + comment = " ".join(comment_set).strip() - header = f'{comment} {files}'.strip() + header = f"{comment} {files}".strip() string = f'"{entry.string}"' - out += f'/* {header} */\n' - out += f'{string} = {string};\n\n' + out += f"/* {header} */\n" + out += f"{string} = {string};\n\n" return out -if __name__ == '__main__': +def main(): # noqa: CCR001 + """Run the Translation Checker.""" parser = argparse.ArgumentParser() - parser.add_argument('--directory', help='Directory to search from', default='.') - parser.add_argument('--ignore', action='append', help='directories to ignore', default=['venv', '.venv', '.git']) + parser.add_argument("--directory", help="Directory to search from", default=".") + parser.add_argument( + "--ignore", + action="append", + help="Directories to ignore", + default=["venv", ".venv", ".git"], + ) group = parser.add_mutually_exclusive_group() - group.add_argument('--json', action='store_true', help='JSON output') - group.add_argument('--lang', help='en.template "strings" output to specified file, "-" for stdout') - group.add_argument('--compare-lang', help='en.template file to compare against') + group.add_argument("--json", action="store_true", help="JSON output") + group.add_argument( + "--lang", help='en.template "strings" output to specified file, "-" for stdout' + ) + group.add_argument("--compare-lang", help="en.template file to compare against") args = parser.parse_args() directory = pathlib.Path(args.directory) res = scan_directory(directory, [pathlib.Path(p) for p in args.ignore]) - if args.compare_lang is not None and len(args.compare_lang) > 0: + output = [] + + if args.compare_lang: seen = set() template = parse_template(args.compare_lang) - for file, calls in res.items(): for c in calls: arg = get_arg(c) if arg in template: seen.add(arg) else: - print(f'NEW! {file}:{c.lineno}: {arg!r}') + output.append(f"NEW! {file}:{c.lineno}: {arg!r}") for old in set(template) ^ seen: - print(f'No longer used: {old!r}') + output.append(f"No longer used: {old!r}") elif args.json: to_print_data = [ { - 'path': str(path), - 'string': get_arg(c), - 'reconstructed': ast.unparse(c), - 'start_line': c.lineno, - 'start_offset': c.col_offset, - 'end_line': c.end_lineno, - 'end_offset': c.end_col_offset, - 'comment': getattr(c, 'comment', None) - } for (path, calls) in res.items() for c in calls + "path": str(path), + "string": get_arg(c), + "reconstructed": ast.unparse(c), + "start_line": c.lineno, + "start_offset": c.col_offset, + "end_line": c.end_lineno, + "end_offset": c.end_col_offset, + "comment": getattr(c, "comment", None), + } + for path, calls in res.items() + for c in calls ] - - print(json.dumps(to_print_data, indent=2)) + output.append(json.dumps(to_print_data, indent=2)) elif args.lang: - if args.lang == '-': - print(generate_lang_template(res)) - + lang_template = generate_lang_template(res) + if args.lang == "-": + output.append(lang_template) else: - with open(args.lang, mode='w+', newline='\n') as langfile: - langfile.writelines(generate_lang_template(res)) + with open(args.lang, mode="w+", newline="\n", encoding="UTF-8") as langfile: + langfile.writelines(lang_template) else: for path, calls in res.items(): - if len(calls) == 0: + if not calls: continue - - print(path) + output.append(str(path)) for c in calls: - print( - f' {c.lineno:4d}({c.col_offset:3d}):{c.end_lineno:4d}({c.end_col_offset:3d})\t', ast.unparse(c) + output.append( + f" {c.lineno:4d}({c.col_offset:3d}):{c.end_lineno:4d}({c.end_col_offset:3d})\t{ast.unparse(c)}" ) + output.append("") + + # Print all collected output at the end + if output: + print("\n".join(output)) + sys.exit(1) + - print() +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + sys.exit() From d5939d0d578326d400bca6db94a707f4ab8b1a80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:05:22 +0000 Subject: [PATCH 237/261] Bump autopep8 from 2.1.0 to 2.2.0 Bumps [autopep8](https://github.com/hhatto/autopep8) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/hhatto/autopep8/releases) - [Commits](https://github.com/hhatto/autopep8/compare/v2.1.0...v2.2.0) --- updated-dependencies: - dependency-name: autopep8 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index be08bd6e9..e0a240b2d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,7 +25,7 @@ types-requests==2.31.0.20240311 types-pkg-resources==0.1.3 # Code formatting tools -autopep8==2.1.0 +autopep8==2.2.0 # Git pre-commit checking pre-commit==3.6.2 From 595a22543363f1998fdce8d315ee9f903ea91227 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:05:29 +0000 Subject: [PATCH 238/261] Bump setuptools from 69.2.0 to 70.0.0 Bumps [setuptools](https://github.com/pypa/setuptools) from 69.2.0 to 70.0.0. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v69.2.0...v70.0.0) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index be08bd6e9..d5274c344 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ wheel # We can't rely on just picking this up from either the base (not venv), # or venv-init-time version. Specify here so that dependabot will prod us # about new versions. -setuptools==69.2.0 +setuptools==70.0.0 # Static analysis tools flake8==7.0.0 From 7b97cba62262b9d576f7977fcdd6bdf0258796c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:05:36 +0000 Subject: [PATCH 239/261] Bump mypy from 1.9.0 to 1.10.0 Bumps [mypy](https://github.com/python/mypy) from 1.9.0 to 1.10.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index be08bd6e9..7e6c1f85f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-noqa==1.4.0 flake8-polyfill==1.0.2 flake8-use-fstring==1.4 -mypy==1.9.0 +mypy==1.10.0 pep8-naming==0.13.3 safety==3.2.0 types-requests==2.31.0.20240311 From 3e88f72a54d5ee95b857f5ed5a3784e5a2927f2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:05:40 +0000 Subject: [PATCH 240/261] Bump requests from 2.31.0 to 2.32.3 Bumps [requests](https://github.com/psf/requests) from 2.31.0 to 2.32.3. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.31.0...v2.32.3) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a7f8aa7c8..83fa9c0d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -requests==2.31.0 +requests==2.32.3 pillow==10.3.0 watchdog==4.0.0 infi.systray==0.1.12; sys_platform == 'win32' From 9eead0b9176ee6fb10f48195866ff23c797d5360 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:41:54 +0000 Subject: [PATCH 241/261] Bump pre-commit from 3.6.2 to 3.7.1 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.6.2 to 3.7.1. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.6.2...v3.7.1) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 538a3579e..7c8e1ef5f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,7 +28,7 @@ types-pkg-resources==0.1.3 autopep8==2.2.0 # Git pre-commit checking -pre-commit==3.6.2 +pre-commit==3.7.1 # HTML changelogs grip==4.6.2 From d4960efa8c3fd413d7ad5e973b205b4b0a9c6057 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 3 Jun 2024 16:35:51 -0400 Subject: [PATCH 242/261] [Lang] Update Translations Also corrects fleetcarrier to proper Fleet Carrier --- EDMarketConnector.py | 8 +- L10n/de.strings | 26 +++- L10n/en.template | 12 +- L10n/it.strings | 4 +- L10n/ja.strings | 4 +- L10n/nl.strings | 128 +++++++++++++++++++ L10n/pl.strings | 4 +- L10n/pt-BR.strings | 13 +- L10n/pt-PT.strings | 4 +- L10n/ru.strings | 26 +++- L10n/sr-Latn-BA.strings | 63 +++++++++- L10n/sr-Latn.strings | 4 +- L10n/tr.strings | 26 +++- L10n/uk.strings | 263 ++++++++++++++++++++++++++++++++++++++++ L10n/zh-Hans.strings | 4 +- companion.py | 6 +- plug.py | 4 +- prefs.py | 2 +- 18 files changed, 559 insertions(+), 42 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 249b842f2..f512ccf19 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1067,7 +1067,7 @@ def capi_request_fleetcarrier_data(self, event=None) -> None: """ Perform CAPI fleetcarrier data retrieval and associated actions. - This is triggered by certain FleetCarrier journal events + This is triggered by certain Fleet Carrier journal events :param event: Tk generated event details. """ @@ -1105,7 +1105,7 @@ def capi_request_fleetcarrier_data(self, event=None) -> None: self.w.update_idletasks() query_time = int(time()) - logger.trace_if('capi.worker', 'Requesting fleetcarrier data') + logger.trace_if('capi.worker', 'Requesting Fleet Carrier data') config.set('fleetcarrierquerytime', query_time) logger.trace_if('capi.worker', 'Calling companion.session.fleetcarrier') companion.session.fleetcarrier( @@ -1144,11 +1144,11 @@ def capi_handle_response(self, event=None): # noqa: C901, CCR001 # Validation if 'name' not in capi_response.capi_data: # LANG: No data was returned for the fleetcarrier from the Frontier CAPI - err = self.status['text'] = tr.tl('CAPI: No fleetcarrier data returned') + err = self.status['text'] = tr.tl('CAPI: No Fleet Carrier data returned') elif not capi_response.capi_data.get('name', {}).get('callsign'): # LANG: We didn't have the fleetcarrier callsign when we should have - err = self.status['text'] = tr.tl("CAPI: Fleetcarrier data incomplete") # Shouldn't happen + err = self.status['text'] = tr.tl("CAPI: Fleet Carrier data incomplete") # Shouldn't happen else: if __debug__: # Recording diff --git a/L10n/de.strings b/L10n/de.strings index 448607acb..8631da0dc 100644 --- a/L10n/de.strings +++ b/L10n/de.strings @@ -126,6 +126,15 @@ /* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ "Copy" = "Kopieren"; +/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */ +"Cut" = "Ausschneiden"; + +/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */ +"Paste" = "Einfügen"; + +/* myNotebook.py: Label for 'Select All'; */ +"Select All" = "Alles auswählen"; + /* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ "CAPI auth disabled by killswitch" = "CAPI auth durch Killswitch deaktiviert"; @@ -168,12 +177,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPI Fleet Carrier durch Killswitch deaktiviert"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Keine Fleet Carrier-Daten erhalten"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Fleet Carrier-Daten unvollständig"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Keine Kommandanten-Daten erhalten"; @@ -378,9 +387,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "CAPI-Einstellungen"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Fleet Carrier CAPI-Anfragen aktivieren"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Hotkey"; @@ -762,6 +771,19 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} ist verfügbar"; +/* prefs.py: Stable Version of EDMC; */ +"Stable" = "Stabil"; + +/* prefs.py: Select the Update Track (Beta, Stable); */ +"Update Track" = "Update-Kanal"; + +/* EDMarketConnector.py: Inform the user the Update Track has changed; */ +"Update Track Changed to {TRACK}" = "Update-Kanal geändert auf {TRACK}"; + + +/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */ +"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Update-Kanal von Beta auf Stabil geändert. Du wirst keine weiteren Beta-Updates erhalten. Du wirst bis zum nächsten stabilen Release auf der aktuellen Beta-Version bleiben.\n\nDu kannst manuell zur aktuellen stabilen Version zurückkehren. Um dies zu tun, musst du die aktuelle stabile Version manuell herunterladen und installieren. Beachte, dass dies Fehler verursachen oder gar nicht funktionieren könnte, wenn du von einem größeren Versionssprung mit signifikanten Änderungen downgradest.\n\nMöchtest du GitHub öffnen, um den neuesten Release herunterzuladen?"; + /* myNotebook.py: Can't Paste Images or Files in Text; */ "Cannot paste non-text content." = "Kann keinen non-Text-Inhalt einfügen."; diff --git a/L10n/en.template b/L10n/en.template index 3906b8978..1c1e110f4 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -177,11 +177,11 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier disabled by killswitch"; -/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ -"CAPI: No fleetcarrier data returned" = "CAPI: No fleetcarrier data returned"; +/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No Fleet Carrier data returned" = "CAPI: No Fleet Carrier data returned"; -/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ -"CAPI: Fleetcarrier data incomplete" = "CAPI: Fleetcarrier data incomplete"; +/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleet Carrier data incomplete" = "CAPI: Fleet Carrier data incomplete"; /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: No commander data returned"; @@ -388,7 +388,7 @@ "CAPI Settings" = "CAPI Settings"; /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ -"Enable Fleetcarrier CAPI Queries" = "Enable Fleetcarrier CAPI Queries"; +"Enable Fleet Carrier CAPI Queries" = "Enable Fleet Carrier CAPI Queries"; /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Hotkey"; @@ -812,4 +812,4 @@ "Cannot paste non-text content." = "Cannot paste non-text content."; /* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ -"Open in {URL}" = "Open in {URL}"; \ No newline at end of file +"Open in {URL}" = "Open in {URL}"; diff --git a/L10n/it.strings b/L10n/it.strings index 024a732ff..003475f1d 100644 --- a/L10n/it.strings +++ b/L10n/it.strings @@ -168,12 +168,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "Fleetcarrier CAPI disabilitato dal killswitch"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Nessun dato della fleetcarrier ricevuto"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Dati della Fleetcarrier incompleti"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Nessun dato sul commandante"; @@ -378,9 +378,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "Impostazioni CAPI"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Abilita le query CAPI della Fleetcarrier"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Hotkey"; diff --git a/L10n/ja.strings b/L10n/ja.strings index fcb336f1f..3d0ef85e5 100644 --- a/L10n/ja.strings +++ b/L10n/ja.strings @@ -159,12 +159,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPIフリートキャリアはkillswitchによって無効にされています"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: フリートキャリアデータの返信なし"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: フリートキャリアデータが不完全"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: コマンダーのデータが返ってきませんでした"; @@ -369,9 +369,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "CAPI設定"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "フリートキャリアCAPIクエリを有効にする"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "ホットキー"; diff --git a/L10n/nl.strings b/L10n/nl.strings index a32320896..3586645a8 100644 --- a/L10n/nl.strings +++ b/L10n/nl.strings @@ -1,3 +1,14 @@ +/* prefs.py: Catch & Record Profiler Errors; */ +"Error in System Profiler" = "Fout in systeemprofiler"; + +/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ +"Send flight log and CMDR status to EDSM" = "Stuur logboek en CMDR-status naar EDSM"; + +/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */ +"Open Log Folder" = "Open logboekmap"; + +/* inara.py:Text Inara Show API key; In files: inara.py:305; */ +"Show API Key" = "Toon API key"; /* Language name */ "!Language" = "Nederlands"; @@ -10,12 +21,33 @@ /* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */ "Error: Wrong Cmdr" = "Fout: Verkeerde Cmdr"; +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */ +"Error" = "Fout"; + +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */ +"Error: customer_id doesn't match!" = "Fout: customer_id komt niet overeen!"; + +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ +"Error: unable to get token" = "Fout: kon token niet verkrijgen"; + +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "Er trad een kritieke fout op, en EDMC kan niet herstellen. Het programma sluit af uit zelfbescherming!"; + +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI offline voor onderhoud"; + /* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */ "Update" = "Bijwerken"; /* EDMarketConnector.py: Appearance - Label for checkbox to select if application always on top; prefs.py: Appearance - Label for checkbox to select if application always on top; In files: EDMarketConnector.py:710; prefs.py:875; */ "Always on top" = "Altijd op voorgrond"; +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */ +"Unknown" = "Onbekend"; + +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */ +"Error: Check E:D journal file location" = "Fout: Controleer locatie van E:D logboekbestand"; + /* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */ "Cmdr" = "Cmdr"; @@ -46,6 +78,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Controleren op updates..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Open systeemprofiler"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Sla onbewerkte gegevens op..."; @@ -55,6 +90,12 @@ /* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */ "Documentation" = "Documentatie"; +/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */ +"Troubleshooting" = "Probleemoplossing"; + +/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */ +"Report A Bug" = "Meld een bug"; + /* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */ "Privacy Policy" = "Privacybeleid"; @@ -70,6 +111,18 @@ /* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ "Copy" = "Kopieëren"; +/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */ +"Cut" = "Knippen"; + +/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */ +"Paste" = "Plakken"; + +/* myNotebook.py: Label for 'Select All'; */ +"Select All" = "Selecteer alles"; + +/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ +"CAPI auth disabled by killswitch" = "CAPI authenticatie uitgeschakeld door killswitch"; + /* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */ "Logging in..." = "Bezig met inloggen..."; @@ -85,9 +138,39 @@ /* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */ "Station doesn't have a market!" = "Station heeft geen markt!"; +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because Cmdr name is unknown; In files: EDMarketConnector.py:1077; EDMarketConnector.py:1164; */ +"CAPI query aborted: Cmdr name unknown" = "CAPI query onderbroken: Cmdr naam onbekend"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */ +"CAPI query aborted: Game mode unknown" = "CAPI query onderbroken: game mode onbekend"; + +/* EDMarketConnector.py: CAPI queries aborted because GameVersion unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because GameVersion unknown; In files: EDMarketConnector.py:1089; EDMarketConnector.py:1170; */ +"CAPI query aborted: GameVersion unknown" = "CAPI query onderbroken: GameVersion onbekend"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */ +"CAPI query aborted: Current system unknown" = "CAPI query onderbroken: huidig systeem onbekend"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:1101; */ +"CAPI query aborted: In other-ship multi-crew" = "CAPI query onderbroken: in multi-crew ander schip"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */ +"CAPI query aborted: CQC (Arena) detected" = "CAPI query onderbroken: in CQC (Arena)"; + /* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */ "Fetching data..." = "Data wordt opgehaald..."; +/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ +"CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier uitgeschakeld door killswitch"; + + +/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No fleetcarrier data returned" = "CAPI: geen fleet carrier data verkregen"; + +/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleetcarrier data incomplete" = "CAPI: data fleet carrier incompleet"; +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ +"CAPI: No commander data returned" = "CAPI: geen commander data verkregen"; + /* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1246; stats.py:333; */ "Who are you?!" = "Wie ben je?!"; @@ -97,6 +180,12 @@ /* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1259; stats.py:349; */ "What are you flying?!" = "Waar vlieg je in?!"; +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */ +"Frontier CAPI server error" = "Frontier CAPI serverfout"; + +/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */ +"CAPI: Refreshing access token..." = "CAPI: toegangstoken vernieuwen..."; + /* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */ "Last updated at %H:%M:%S" = "Voor het laatst bijgewerkt om %H:%M:%S"; @@ -115,6 +204,9 @@ /* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1864; prefs.py:292; */ "OK" = "OK"; +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1936; */ +"Shutting down..." = "Afsluiten..."; + /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Een of meer ingeschakelde plugins hebben nog geen Python 3.x ondersteuning. Kijk op de lijst op de '{PLUGINS}' tab van '{FILE}' > '{SETTINGS}'. Check of er een bijgewerkte versie beschikbaar is. Laat anders de developer weten dat ze de code moeten updaten voor Python 3.x.\n\nJe kan de plugin uitschakelen door het mapje te hernoemen met '{DISABLED}' aan het eind van de naam."; @@ -124,9 +216,45 @@ /* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */ "EDMC: Plugins Without Python 3.x Support" = "EDMS: Plugins zonder Python 3.x ondersteuning"; +/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ +"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Een of meer van je ingeschakelde plugins kon niet laden. Controleer de lijst op de '{PLUGINS}' tab van '{FILE}' > '{SETTINGS}'. Dit zou veroorzaakt kunnen worden door een verkeerde mappenstructuur. Het bestand load.py hoort op plugins/PLUGIN_NAAM/load.py\n\nJe kunt een plugin uitschakelen door '{DISABLED}' aan het einde van de naam van de plugin-map toe te voegen."; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Wachten op volledige CMDR login"; + +/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ +"Journal directory already locked" = "Logboekmap al vergrendeld"; + +/* journal_lock.py: Text for when newly selected Journal directory is already locked; In files: journal_lock.py:225:226; */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "De nieuwe logboekmaplocatie is al vergrendeld. {CR} Je kan dit probleem verhelpen en het dan opnieuw proberen, of dit probleem negeren."; + +/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:230; */ +"Retry" = "Probeer opnieuw"; + +/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:234; */ +"Ignore" = "Negeer"; + /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Standaard"; +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ +"Auto" = "Auto"; + +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ +"Normal" = "Normaal"; + +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ +"Beta" = "Beta"; + +/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */ +"Normal URL" = "Normale URL"; + +/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */ +"Reset" = "Reset"; + +/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */ +"Beta URL" = "Beta-URL"; + /* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:458; eddn.py:2413; eddn.py:2451; eddn.py:2519; */ "Error: Can't connect to EDDN" = "Fout: Kan geen verbinding maken met EDDN"; diff --git a/L10n/pl.strings b/L10n/pl.strings index 3ab5992a3..4b88c8b4e 100644 --- a/L10n/pl.strings +++ b/L10n/pl.strings @@ -159,12 +159,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "Fleetcarier CAPI wyłączony \"kill switchem\""; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Nie zwrócono danych dotyczących lotniskowca"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Niekompletne dane lotniskowca"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Nie zwrócono danych dowódcy"; @@ -369,9 +369,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "Ustawienia CAPI"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Włącz zapytania CAPI dla lotniskowca"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Skr. Klaw."; diff --git a/L10n/pt-BR.strings b/L10n/pt-BR.strings index 48922eb45..9bbb2c6c6 100644 --- a/L10n/pt-BR.strings +++ b/L10n/pt-BR.strings @@ -126,6 +126,15 @@ /* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ "Copy" = "Copiar"; +/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */ +"Cut" = "Recortar"; + +/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */ +"Paste" = "Colar"; + +/* myNotebook.py: Label for 'Select All'; */ +"Select All" = "Selecionar tudo"; + /* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ "CAPI auth disabled by killswitch" = "Autenticação CAPI desativada pelo botão de interrupção."; @@ -168,12 +177,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPI para porta-frotas desativado pelo botão de interrupção."; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Nenhum dado de porta-fortas retornado"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Dados de porta-frota incompletos"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Nenhum dado de comandante retornado"; @@ -378,9 +387,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "Configurações de CAPI"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Tecla de atalho"; diff --git a/L10n/pt-PT.strings b/L10n/pt-PT.strings index 68d4912da..2d2409179 100644 --- a/L10n/pt-PT.strings +++ b/L10n/pt-PT.strings @@ -168,12 +168,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "Consulta à CAPI de Transportador de Frota cancelada por botão"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Sem dados de Transportador de Frota"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Dados de Transportador de Frota incompletos"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Dados do Comandante não recebidos"; @@ -378,9 +378,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "Definições CAPI"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Ligar Consultas CAPI de Transportador de Frota"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Tecla Rápida"; diff --git a/L10n/ru.strings b/L10n/ru.strings index 54f77d620..f16ca3a62 100644 --- a/L10n/ru.strings +++ b/L10n/ru.strings @@ -126,6 +126,15 @@ /* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ "Copy" = "Копировать"; +/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */ +"Cut" = "Вырезать"; + +/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */ +"Paste" = "Вставить"; + +/* myNotebook.py: Label for 'Select All'; */ +"Select All" = "Выбрать все"; + /* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ "CAPI auth disabled by killswitch" = "CAPI аутентификация отключена с помощью killswitch"; @@ -168,12 +177,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPI кораблей-носителей отключен с помощью killswitch"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Нет данных о флотоносце"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Неполная информация о флотоносце"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Нет данных пилота"; @@ -378,9 +387,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "Настройки CAPI"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Включить запросы к CAPI о флотоносце"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Горячая клавиша"; @@ -783,6 +792,19 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} доступен"; +/* prefs.py: Stable Version of EDMC; */ +"Stable" = "Стабильная"; + +/* prefs.py: Select the Update Track (Beta, Stable); */ +"Update Track" = "Обновить маршрут"; + +/* EDMarketConnector.py: Inform the user the Update Track has changed; */ +"Update Track Changed to {TRACK}" = "Обновить маршрут. Изменено на {TRACK}"; + + +/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */ +"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Изменен путь обновления с \"Бета\" на \"Стабильный\". Вы больше не будете получать обновления \"Бета\". Вы останетесь на текущей бета-версии до следующего стабильного релиза.\n\nВы можете вручную вернуться к последней версии \"Стабильная\". Для этого необходимо загрузить и установить последнюю версию \"Стабильная\" вручную. Обратите внимание, что при переходе между основными версиями со значительными изменениями могут возникнуть ошибки или полная поломка.\n\nХотите открыть GitHub, чтобы загрузить последнюю версию?"; + /* myNotebook.py: Can't Paste Images or Files in Text; */ "Cannot paste non-text content." = "Невозможно добавить нетекстовое содержимое."; diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings index 2d79e7e98..00b6e0534 100644 --- a/L10n/sr-Latn-BA.strings +++ b/L10n/sr-Latn-BA.strings @@ -1,5 +1,5 @@ /* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ -"Send flight log and CMDR status to EDSM" = "Pošalji log leta i Cmdr status na EDSM"; +"Send flight log and CMDR status to EDSM" = "Pošalji log leta i CMDR status na EDSM"; /* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */ "Open Log Folder" = "Otvori folder sa logovima"; @@ -22,7 +22,7 @@ "Error: Invalid Credentials" = "Greška: Neispravni kredencijali"; /* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */ -"Error: Wrong Cmdr" = "Greška: Pogrešan Cmdr"; +"Error: Wrong Cmdr" = "Greška: Pogrešan CMDR"; /* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */ "Error" = "Greška"; @@ -36,6 +36,12 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Greška: Nemoguće dobaviti token"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC je naišao na kritičnu grešku i ne može da se oporavi. EDMC će biti ugašen zbog vlastite zaštite."; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "EDMC je detektovao sljedeće:\n\n{ERR}\n\nDa li želite da izvršite Bug Report na GitHub-u?"; + /* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ "Frontier CAPI down for maintenance" = "Frontier CAPI nedostupan zbog održavanja"; @@ -55,7 +61,7 @@ "Error: Check E:D journal file location" = "Greška: Provjerite lokaciju E:D journal fajla"; /* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */ -"Cmdr" = "Cmdr"; +"Cmdr" = "CMDR"; /* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:915; EDMarketConnector.py:1487; */ "Role" = "Uloga"; @@ -87,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Provjeri nadogradnje..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Otvori System Profiler"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Snimi sirove podatke..."; @@ -117,6 +126,15 @@ /* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ "Copy" = "Kopiraj"; +/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */ +"Cut" = "Izreži"; + +/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */ +"Paste" = "Nalijepi"; + +/* myNotebook.py: Label for 'Select All'; */ +"Select All" = "Selektuj sve"; + /* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ "CAPI auth disabled by killswitch" = "CAPI autentifikacija onemogućena putem sistemskog prekidača"; @@ -159,12 +177,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier onemogućen putem sistemskog prekidača"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Fleetcarrier podaci nisu dobijeni"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Fleetcarrier podaci nisu potpuni"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Nema podataka o komandantu"; @@ -219,6 +237,18 @@ /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više aktiviranih dodataka (plugins) nemaju podršku za Python 3.x. Pogledajte listu u '{PLUGINS}' tabu u '{FILE}' > '{SETTINGS}'. Pogrešna struktura foldera može da uzrokuje problem. Fajl load.py treba da bude smješten u plugins/PLUGIN_NAME/load.py.\n\nMožete deaktivirati dodatak (plugin) dodavanjem '{DISABLED}' na kraju imena njegovog foldera."; +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Jedan ili više URL Providera su pogrešni i zbog toga su resetovani:\n"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} je bio postavljen na {OLDPROV}, a sad je resetovan na {NEWPROV}\n"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: Standardni Provideri su resetovani"; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Čekam da se CMDR potpuno uloguje"; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Journal direktorijum je već zaključan"; @@ -313,7 +343,7 @@ "Error: Can't connect to EDSM" = "Greška: Nemoguće povezivanje sa EDSM"; /* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:257; */ -"Send flight log and Cmdr status to Inara" = "Pošalji log leta i Cmdr status na Inara"; +"Send flight log and Cmdr status to Inara" = "Pošalji log leta i CMDR status na Inara"; /* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */ "Inara credentials" = "Kredencijali za Inara"; @@ -357,9 +387,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "CAPI Podešavanja"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Omogući Fleetcarrier CAPI Upite"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Prečica"; @@ -759,3 +789,24 @@ /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Brodovi"; +/* update.py: Update Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} je dostupna"; + +/* prefs.py: Stable Version of EDMC; */ +"Stable" = "Stabilna"; + +/* prefs.py: Select the Update Track (Beta, Stable); */ +"Update Track" = "Kanal osvježavanja"; + +/* EDMarketConnector.py: Inform the user the Update Track has changed; */ +"Update Track Changed to {TRACK}" = "Kanal za osvježavanje je promijenjen u {TRACK}"; + + +/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */ +"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Kanal za osvježavanje je promijenjen iz Beta u Stabilni. Više nećete primati Beta osvježavanja. Ostaćete na trenutnoj Beta verziji do sljedeće Stabilne verzije.\n\nMožete se ručno vratiti na posljednju Stabilnu verziju. Da biste to učinili morate da skinete i ručno instališete posljednju Stabilnu verziju. Ova procedura može da uvede nove bugove ili da potpuno onesposobi program ako prelazite između verzija sa mnogo značajnih promjena.\n\nDa li želite da otvorite GitHub stranicu za download posljednje verzije?"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Nije moguće da se zalijepi netekstualni sadržaj."; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Otvori u {URL}"; diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings index 291a036b7..7aee7f411 100644 --- a/L10n/sr-Latn.strings +++ b/L10n/sr-Latn.strings @@ -168,12 +168,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier deaktiviran preko sistemskog prekidača"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Podaci o fleetcarrier-u nisu dobijeni"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Podaci o fleetcarrier-u su nepotpuni"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Nema podataka o komandiru"; @@ -378,9 +378,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "CAPI Podešavanja"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Aktiviraj Fleetcarrier CAPIU Upite"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Skraćenica"; diff --git a/L10n/tr.strings b/L10n/tr.strings index a7fbabe1d..e35c00a2e 100644 --- a/L10n/tr.strings +++ b/L10n/tr.strings @@ -126,6 +126,15 @@ /* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ "Copy" = "Kopyala"; +/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */ +"Cut" = "Kes"; + +/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */ +"Paste" = "Yapıştır"; + +/* myNotebook.py: Label for 'Select All'; */ +"Select All" = "Tümünü Seç"; + /* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ "CAPI auth disabled by killswitch" = "CAPI kimlik doğrulaması killswitch tarafından devre dışı bırakıldı"; @@ -168,12 +177,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPI filo taşıyıcısı killswitch tarafından devre dışı bırakıldı"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI: Filo taşıyıcı verisi gelmedi."; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI: Filo taşıyıcı verileri eksik"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Cmdr verisi döndürülmedi"; @@ -378,9 +387,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "CAPI ayarları"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "Filo Taşıyıcı CAPI sorgulamalarını etkinleştir"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Kısayoltuşu"; @@ -783,6 +792,19 @@ /* update.py: Update Available Text; In files: update.py:229; */ "{NEWVER} is available" = "{NEWVER} sürüm mevcut"; +/* prefs.py: Stable Version of EDMC; */ +"Stable" = "Stabil"; + +/* prefs.py: Select the Update Track (Beta, Stable); */ +"Update Track" = "Takibi Güncelle"; + +/* EDMarketConnector.py: Inform the user the Update Track has changed; */ +"Update Track Changed to {TRACK}" = "Aktif Takip {TRACK} olarak güncellendi."; + + +/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */ +"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Güncelleme takibi Beta'dan Kararlı olarak değiştirildi. Artık Beta güncellemelerini almayacaksınız. Bir sonraki Kararlı sürüme kadar mevcut Beta sürümünüzde kalacaksınız.\n\nEn son Stabil sürüme manuel olarak geri dönebilirsiniz. Bunu yapmak için en son Stabil sürümünü manuel olarak indirip yüklemeniz gerekir. Önemli değişiklikler içeren ana sürümler arasında sürüm düşürme durumunda bunun hatalara yol açabileceğini veya tamamen bozulabileceğini unutmayın.\n\nEn son sürümü indirmek için GitHub'u açmak istiyor musunuz?"; + /* myNotebook.py: Can't Paste Images or Files in Text; */ "Cannot paste non-text content." = "Metin dışı içerik yapıştırılamadı"; diff --git a/L10n/uk.strings b/L10n/uk.strings index 125200f1c..b4760202f 100644 --- a/L10n/uk.strings +++ b/L10n/uk.strings @@ -1,9 +1,23 @@ +/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ +"Send flight log and CMDR status to EDSM" = "Відправляти дані бортового журналу до EDSM"; + +/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */ +"Open Log Folder" = "Відкрити папку журналу"; + +/* inara.py:Text Inara Show API key; In files: inara.py:305; */ +"Show API Key" = "Показати ключ API"; /* Language name */ "!Language" = "Українська"; +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */ +"Error: Frontier CAPI didn't respond" = "Помилка: Frontier CAPI не відповідає"; + /* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */ "Error: Frontier server is lagging" = "Помилка: З`єднання з сервером гри з затримками!"; +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:261; */ +"Docked but unknown station: EDO Settlement?" = "Пристикований, проте станція невідома: поселення EDO?"; + /* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */ "Error: Invalid Credentials" = "Помилка: Невірні облікові дані!"; @@ -22,6 +36,18 @@ /* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */ "Error: unable to get token" = "Помилка: не вдалося отримати токен"; +/* EDMarketConnector.py: EDMC Critical Error Notification; */ +"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC виявив критичну помилку, і не може відновити функціонування. EDMC вимкнеться заради самозахисту!"; + +/* EDMarketConnector.py: EDMC Critical Error Details; */ +"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Ось що EDMC зміг з'ясувати:\n\n{ERR}\n\nХочете створити баг репорт на GitHub?"; + +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI відключений через технічне обслуговування"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */ +"Frontier CAPI query failure" = "Помилка запиту Frontier CAPI"; + /* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */ "Update" = "Оновлення"; @@ -31,6 +57,9 @@ /* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */ "Unknown" = "Невідомо"; +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */ +"Error: Check E:D journal file location" = "Помилка: перевірте розташування файлу журналу E:D"; + /* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */ "Cmdr" = "Км-др"; @@ -64,6 +93,9 @@ /* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */ "Check for Updates..." = "Перевірка оновлень..."; +/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */ +"Open System Profiler" = "Відкрити Профайлер Систем"; + /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */ "Save Raw Data..." = "Зберегти необроблені дані..."; @@ -73,6 +105,12 @@ /* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */ "Documentation" = "Документація"; +/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */ +"Troubleshooting" = "Усунення несправностей"; + +/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */ +"Report A Bug" = "Повідомити про помилку"; + /* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */ "Privacy Policy" = "Політика конфіденційності"; @@ -88,6 +126,18 @@ /* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */ "Copy" = "Копіювати"; +/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */ +"Cut" = "Вирізати"; + +/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */ +"Paste" = "Вставити"; + +/* myNotebook.py: Label for 'Select All'; */ +"Select All" = "Вибрати все"; + +/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */ +"CAPI auth disabled by killswitch" = "Автентифікація CAPI виключена функцією аварійного відключення"; + /* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */ "Logging in..." = "Вхід в..."; @@ -103,9 +153,36 @@ /* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */ "Station doesn't have a market!" = "На станції немає ринку!"; +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because Cmdr name is unknown; In files: EDMarketConnector.py:1077; EDMarketConnector.py:1164; */ +"CAPI query aborted: Cmdr name unknown" = "Запит CAPI скасований: Невідоме ім'я КМДР"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */ +"CAPI query aborted: Game mode unknown" = "Запит CAPI скасований: Невідомий режим гри"; + +/* EDMarketConnector.py: CAPI queries aborted because GameVersion unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because GameVersion unknown; In files: EDMarketConnector.py:1089; EDMarketConnector.py:1170; */ +"CAPI query aborted: GameVersion unknown" = "Запит CAPI скасований: Невідома версія гри"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */ +"CAPI query aborted: Current system unknown" = "Запит CAPI скасований: Невідома поточна система"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:1101; */ +"CAPI query aborted: In other-ship multi-crew" = "Запит CAPI скасований: в режимі мультиекіпажу іншого корабля"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */ +"CAPI query aborted: CQC (Arena) detected" = "Запит CAPI скасований: Арена Близького бою (CQC) виявлена"; + /* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */ "Fetching data..." = "Отримання даних..."; +/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ +"CAPI fleetcarrier disabled by killswitch" = "Запит CAPI корабля-носія скасований функцією аварійного відключення"; + + +/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No fleetcarrier data returned" = "CAPI: Немає даних корабля-носія"; + +/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleetcarrier data incomplete" = "CAPI: Дані корабля-носія неповні"; /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI: Не отримано данних пілота"; @@ -118,6 +195,12 @@ /* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1259; stats.py:349; */ "What are you flying?!" = "На чому летимо?!"; +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */ +"Frontier CAPI server error" = "Frontier CAPI: cерверна помилка"; + +/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */ +"CAPI: Refreshing access token..." = "CAPI: Оновлюємо токен доступу..."; + /* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */ "Last updated at %H:%M:%S" = "Останнє оновлення було %H:%M:%S"; @@ -148,6 +231,24 @@ /* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Плагіни без підтримки Python 3.x!"; +/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */ +"EDMC: Broken Plugins" = "EDMC: Поломані плагіни"; + +/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */ +"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Не вдалося завантажити один або декілька увімкнених плагінів. Перегляньте список на вкладці '{PLUGINS}' в '{FILE}' > '{SETTINGS}'. Ця помилка могла бути спричинена неправильною структурою папок. Файл load.py повинен знаходитися у папці plugins/PLUGIN_NAME/load.py\n\nВи можете відключити плагін, перейменувавши його папку додавши '{DISABLED}' в кінці назви."; + +/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */ +"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Один або декілька ваших провайдерів URL були хибні та скинуті:\n"; + +/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */ +"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} мав значення {OLDPROV}, та був скинутий до {NEWPROV}\n"; + +/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */ +"EDMC: Default Providers Reset" = "EDMC: Провайдери за замовчуванням були скинуті"; + +/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */ +"Awaiting Full CMDR Login" = "Очікуємо повного входу КМДР"; + /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */ "Journal directory already locked" = "Каталог журналу вже заблокований"; @@ -163,9 +264,45 @@ /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */ "Default" = "Стандартне налаштування"; +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */ +"Auto" = "Авто"; + +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */ +"Normal" = "Нормальний"; + +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */ +"Beta" = "Бета"; + +/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */ +"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" = "Вставте URL для використання із корабельними збірками coriolis.io. Зауважте що '/import?data=' ПОВИННО бути в кінці!"; + +/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */ +"Normal URL" = "Нормальний URL"; + +/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */ +"Reset" = "Скинути"; + +/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */ +"Beta URL" = "Бета URL"; + +/* coriolis.py: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL; In files: coriolis.py:116; */ +"Override Beta/Normal Selection" = "Перевизначити вибір Бета/Нормальний"; + +/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: coriolis.py:156; */ +"Invalid Coriolis override mode!" = "Неправильний режим перевизначення Coriolis!"; + /* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:458; eddn.py:2413; eddn.py:2451; eddn.py:2519; */ "Error: Can't connect to EDDN" = "Помилка: Немає зв'язку з EDDN!"; +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:576; */ +"EDDN Error: EDMC is too old for EDDN. Please update." = "Помилка EDDN: версія EDMC є занадто старою для EDDN. Будь-ласка, оновіть."; + +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:582; */ +"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "Помилка EDDN: Перевірка провалена (EDMC занадно старий?). Див. Лог"; + +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:587; */ +"EDDN Error: Returned {STATUS} status code" = "Помилка EDDN: Код {STATUS} було повернуто."; + /* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:2041; */ "Send station data to the Elite Dangerous Data Network" = "Надсилати дані станцій до Elite Dangerous Data Network"; @@ -175,6 +312,9 @@ /* eddn.py: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this; In files: eddn.py:2063; */ "Delay sending until docked" = "Відкласти відправку даних до стикування"; +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:2178; */ +"EDDN journal handler disabled. See Log." = "Журнал EDDN відключено. Див. Лог"; + /* eddn.py: Status text shown while attempting to send data; In files: eddn.py:2507; */ "Sending data to EDDN..." = "Відправка даних до EDDN..."; @@ -190,6 +330,12 @@ /* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:394; prefs.py:527; prefs.py:1157; prefs.py:1190; stats.py:154; stats.py:173; stats.py:192; stats.py:209; */ "None" = "Нічого"; +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:516; */ +"EDSM Handler disabled. See Log." = "Обробник EDSM відключено. Див. Лог"; + +/* edsm.py: EDSM - Only Live data; In files: edsm.py:632; */ +"EDSM only accepts Live galaxy data" = "EDSM приймає дані тільки Live версії галактики"; + /* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:916; edsm.py:1048; */ "Error: EDSM {MSG}" = "Помилка: EDSM {MSG}!"; @@ -202,6 +348,12 @@ /* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */ "Inara credentials" = "Обліковий запис Inara"; +/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; inara.py: Inara - Only Live data; In files: inara.py:384; inara.py:386; */ +"Inara only accepts Live galaxy data" = "Inara приймає дані тільки Live версії галактики"; + +/* inara.py: INARA support disabled via killswitch; In files: inara.py:395; */ +"Inara disabled. See Log." = "Inara відключено. Див. Лог"; + /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */ "Error: Inara {MSG}" = "Помилка: Inara {MSG}"; @@ -232,6 +384,12 @@ /* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */ "E:D journal file location" = "Розташування файлу-журналу E:D"; +/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ +"CAPI Settings" = "Налаштування CAPI"; + + +/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ +"Enable Fleetcarrier CAPI Queries" = "Ввімкнути запити CAPI кораблів-носіїв"; /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "Гаряча клавіша"; @@ -259,6 +417,15 @@ /* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:681; */ "Configuration" = "Конфігурація"; +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:690; */ +"Main UI privacy options" = "Налаштування приватності основного вікна."; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:695; */ +"Hide private group name in UI" = "Приховати назву приватної групи у вікні"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:699; */ +"Hide multi-crew captain name" = "Приховати ім'я капітана мультиекіпажу"; + /* prefs.py: Preferences privacy tab title; In files: prefs.py:703; */ "Privacy" = "Конфіденційність"; @@ -316,6 +483,9 @@ /* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */ "Information on migrating plugins" = "Інформація про міграцію плагінів"; +/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */ +"Broken Plugins" = "Поломані плагіни"; + /* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */ "Disabled Plugins" = "Вимкнені плагіни"; @@ -328,6 +498,21 @@ /* stats.py: Top rank; In files: stats.py:63; */ "Elite" = "Еліта"; +/* stats.py: Top rank +1; In files: stats.py:64; */ +"Elite I" = "Еліта І"; + +/* stats.py: Top rank +2; In files: stats.py:65; */ +"Elite II" = "Еліта ІІ"; + +/* stats.py: Top rank +3; In files: stats.py:66; */ +"Elite III" = "Еліта ІІІ"; + +/* stats.py: Top rank +4; In files: stats.py:67; */ +"Elite IV" = "Еліта IV"; + +/* stats.py: Top rank +5; In files: stats.py:68; */ +"Elite V" = "Еліта V"; + /* stats.py: Ranking; In files: stats.py:74; */ "Combat" = "Бойовий"; @@ -337,6 +522,12 @@ /* stats.py: Ranking; In files: stats.py:76; */ "Explorer" = "Дослідницький"; +/* stats.py: Ranking; In files: stats.py:77; */ +"Mercenary" = "Найманець"; + +/* stats.py: Ranking; In files: stats.py:78; */ +"Exobiologist" = "Екзобіолог"; + /* stats.py: Ranking; In files: stats.py:79; */ "CQC" = "Близький бій (CQC)"; @@ -421,6 +612,51 @@ /* stats.py: Explorer rank; In files: stats.py:118; */ "Pioneer" = "Піонер"; +/* stats.py: Mercenary rank; In files: stats.py:122; */ +"Defenceless" = "Беззахисний"; + +/* stats.py: Mercenary rank; In files: stats.py:123; */ +"Mostly Defenceless" = "Здебільшого беззахисний"; + +/* stats.py: Mercenary rank; In files: stats.py:124; */ +"Rookie" = "Новачок"; + +/* stats.py: Mercenary rank; In files: stats.py:125; */ +"Soldier" = "Солдат"; + +/* stats.py: Mercenary rank; In files: stats.py:126; stats.py:128; */ +"Gunslinger" = "Стрілець"; + +/* stats.py: Mercenary rank; In files: stats.py:127; */ +"Warrior" = "Воїн"; + +/* stats.py: Mercenary rank; In files: stats.py:129; */ +"Deadeye" = "Снайпер"; + +/* stats.py: Exobiologist rank; In files: stats.py:132; */ +"Directionless" = "Безнапрямний"; + +/* stats.py: Exobiologist rank; In files: stats.py:133; */ +"Mostly Directionless" = "Здебільшого безнапрямний"; + +/* stats.py: Exobiologist rank; In files: stats.py:134; */ +"Compiler" = "Упорядник"; + +/* stats.py: Exobiologist rank; In files: stats.py:135; */ +"Collector" = "Колектор"; + +/* stats.py: Exobiologist rank; In files: stats.py:136; */ +"Cataloguer" = "Каталогіст"; + +/* stats.py: Exobiologist rank; In files: stats.py:137; */ +"Taxonomist" = "Систематик"; + +/* stats.py: Exobiologist rank; In files: stats.py:138; */ +"Ecologist" = "Еколог"; + +/* stats.py: Exobiologist rank; In files: stats.py:139; */ +"Geneticist" = "Генетик"; + /* stats.py: CQC rank; In files: stats.py:142; */ "Helpless" = "Безпорадний"; @@ -541,9 +777,36 @@ /* stats.py: Power rank; In files: stats.py:197; */ "Rating 5" = "Рейтинг 5"; +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:315; */ +"Status: Don't yet know your Commander name" = "Статус: Поки ще не знаємо ім'я вашого КМДР"; + +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:323; */ +"Status: No CAPI data yet" = "Статус: Поки ще немає даних CAPI"; + /* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:409; */ "Value" = "Вартість"; /* stats.py: Status dialog title; In files: stats.py:418; */ "Ships" = "Кораблі"; +/* update.py: Update Available Text; In files: update.py:229; */ +"{NEWVER} is available" = "{NEWVER} доступна"; + +/* prefs.py: Stable Version of EDMC; */ +"Stable" = "Стабільний"; + +/* prefs.py: Select the Update Track (Beta, Stable); */ +"Update Track" = "Шлях оновлень"; + +/* EDMarketConnector.py: Inform the user the Update Track has changed; */ +"Update Track Changed to {TRACK}" = "Шлях оновлень змінено на {TRACK}"; + + +/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */ +"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Шлях оновлень змінено з \"Бета\" на \"Стабільний\". Ви більше не будете отримувати бета оновлень. Ви залишитеся на версії бета до наступного стабільного релізу.\n\nВи можете вручну вернутися до останньої стабільної версії. Щоб це зробити, вам необхідно завантажити останню стабільну версію вручну. Зауважте що заниження версії між релізами з багатьма змінами може спричинити помилки чи повністю унеможливити роботу програми.\n\nБажаєте відкрити сторінку GitHub для завантаження останнього релізу?"; + +/* myNotebook.py: Can't Paste Images or Files in Text; */ +"Cannot paste non-text content." = "Неможливо вставити нетекстовий вміст буфера обміну."; + +/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */ +"Open in {URL}" = "Відкрити у {URL}"; diff --git a/L10n/zh-Hans.strings b/L10n/zh-Hans.strings index 2cc13543f..0b13a5a1f 100644 --- a/L10n/zh-Hans.strings +++ b/L10n/zh-Hans.strings @@ -151,12 +151,12 @@ /* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */ "CAPI fleetcarrier disabled by killswitch" = "CAPI 舰队母舰 (fleet carrier) 被 killswitch 禁用"; + /* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ "CAPI: No fleetcarrier data returned" = "CAPI:无舰队母舰 (fleet carrier) 数据"; /* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */ "CAPI: Fleetcarrier data incomplete" = "CAPI:舰队母舰 (fleet carrier) 数据不完整"; - /* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */ "CAPI: No commander data returned" = "CAPI:没有指挥官数据"; @@ -343,9 +343,9 @@ /* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */ "CAPI Settings" = "CAPI 设置"; + /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ "Enable Fleetcarrier CAPI Queries" = "开启舰队母舰 (fleet carrier) CAPI 访问"; - /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */ "Hotkey" = "快捷键"; diff --git a/companion.py b/companion.py index 1b9c693a1..934fc8bb9 100644 --- a/companion.py +++ b/companion.py @@ -1063,7 +1063,7 @@ def fleetcarrier( play_sound: bool = False, auto_update: bool = False ) -> None: """ - Perform CAPI query for fleetcarrier data. + Perform CAPI query for Fleet Carrier data. :param query_time: When this query was initiated. :param tk_response_event: Name of tk event to generate when response queued. @@ -1074,8 +1074,8 @@ def fleetcarrier( if not capi_host: return - # Ask the thread worker to perform a fleetcarrier query - logger.trace_if('capi.worker', 'Enqueueing fleetcarrier request') + # Ask the thread worker to perform a Fleet Carrier query + logger.trace_if('capi.worker', 'Enqueueing Fleet Carrier request') self.capi_request_queue.put( EDMCCAPIRequest( capi_host=capi_host, diff --git a/plug.py b/plug.py index bd98f2bbd..9c203f252 100644 --- a/plug.py +++ b/plug.py @@ -418,7 +418,7 @@ def notify_capidata(data: companion.CAPIData, is_beta: bool) -> str | None: def notify_capi_fleetcarrierdata(data: companion.CAPIData) -> str | None: """ - Send the latest CAPI Fleetcarrier data from the FD servers to each plugin. + Send the latest CAPI Fleet Carrier data from the FD servers to each plugin. :param data: The CAPIData returned in the CAPI response :returns: Error message from the first plugin that returns one (if any) @@ -433,7 +433,7 @@ def notify_capi_fleetcarrierdata(data: companion.CAPIData) -> str | None: error = error if error else newerror except Exception: - logger.exception(f'Plugin "{plugin.name}" failed on receiving Fleetcarrier data') + logger.exception(f'Plugin "{plugin.name}" failed on receiving Fleet Carrier data') return error diff --git a/prefs.py b/prefs.py index 298d8cb31..7f217ac88 100644 --- a/prefs.py +++ b/prefs.py @@ -476,7 +476,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 nb.Checkbutton( config_frame, # LANG: Configuration - Enable or disable the Fleet Carrier CAPI calls - text=tr.tl('Enable Fleetcarrier CAPI Queries'), + text=tr.tl('Enable Fleet Carrier CAPI Queries'), variable=self.capi_fleetcarrier ).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) From 40b5785697f17bb77e60dac18260f68548a75253 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 4 Jun 2024 16:50:53 -0400 Subject: [PATCH 243/261] [Lang] Update Translations --- L10n/de.strings | 12 ++++++++++++ L10n/pt-BR.strings | 12 ++++++++++++ L10n/sr-Latn-BA.strings | 12 ++++++++++++ L10n/tr.strings | 12 ++++++++++++ L10n/uk.strings | 12 ++++++++++++ 5 files changed, 60 insertions(+) diff --git a/L10n/de.strings b/L10n/de.strings index 8631da0dc..a5da5f817 100644 --- a/L10n/de.strings +++ b/L10n/de.strings @@ -1,3 +1,15 @@ +/* prefs.py: Catch & Record Profiler Errors; */ +"Error in System Profiler" = "Fehler im System Profiler"; + +/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleet Carrier data incomplete" = "CAPI: Carrier-Daten unvollständig"; + +/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No Fleet Carrier data returned" = "CAPI: Keine Carrier-Daten erhalten"; + +/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ +"Enable Fleet Carrier CAPI Queries" = "Carrier-CAPI-Anfragen aktivieren"; + /* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ "Send flight log and CMDR status to EDSM" = "Sende Fluglog und CMDR-Status an EDSM"; diff --git a/L10n/pt-BR.strings b/L10n/pt-BR.strings index 9bbb2c6c6..791a582a6 100644 --- a/L10n/pt-BR.strings +++ b/L10n/pt-BR.strings @@ -1,3 +1,15 @@ +/* prefs.py: Catch & Record Profiler Errors; */ +"Error in System Profiler" = "Erro no Perfil de Sistema"; + +/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleet Carrier data incomplete" = "CAPI: Dados de porta-frotas incompletos"; + +/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No Fleet Carrier data returned" = "CAPI: Nenhum dado de porta-frotas retornado"; + +/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ +"Enable Fleet Carrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas"; + /* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ "Send flight log and CMDR status to EDSM" = "Enviar registro de voo e status do CMDT para o EDSM"; diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings index 00b6e0534..c861be2af 100644 --- a/L10n/sr-Latn-BA.strings +++ b/L10n/sr-Latn-BA.strings @@ -1,3 +1,15 @@ +/* prefs.py: Catch & Record Profiler Errors; */ +"Error in System Profiler" = "Greša u System Profileru"; + +/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleet Carrier data incomplete" = "CAPI: Fleet Carrier podaci nisu kompletni"; + +/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No Fleet Carrier data returned" = "CAPI: Fleet Carrier nisu dobijeni"; + +/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ +"Enable Fleet Carrier CAPI Queries" = "Omogući Fleet Carrier CAPI upite"; + /* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ "Send flight log and CMDR status to EDSM" = "Pošalji log leta i CMDR status na EDSM"; diff --git a/L10n/tr.strings b/L10n/tr.strings index e35c00a2e..d99bb562d 100644 --- a/L10n/tr.strings +++ b/L10n/tr.strings @@ -1,3 +1,15 @@ +/* prefs.py: Catch & Record Profiler Errors; */ +"Error in System Profiler" = "Sistem Profilcisinde Hata oluştu"; + +/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleet Carrier data incomplete" = "CAPI: Filo Taşıyıcısı verileri eksik"; + +/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No Fleet Carrier data returned" = "CAPI: Filo Taşıyıcısı verisi bulunamadı"; + +/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ +"Enable Fleet Carrier CAPI Queries" = "Filo Taşıyıcı CAPI Sorgularını Etkinleştir"; + /* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ "Send flight log and CMDR status to EDSM" = "Uçuş günlüğünü ve CMDR durumunu EDSM'e gönder"; diff --git a/L10n/uk.strings b/L10n/uk.strings index b4760202f..3729f7c0b 100644 --- a/L10n/uk.strings +++ b/L10n/uk.strings @@ -1,3 +1,15 @@ +/* prefs.py: Catch & Record Profiler Errors; */ +"Error in System Profiler" = "Помилка у Профайлері Систем"; + +/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */ +"CAPI: Fleet Carrier data incomplete" = "CAPI: Дані корабля-носія неповні"; + +/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */ +"CAPI: No Fleet Carrier data returned" = "CAPI: Немає даних корабля-носія"; + +/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */ +"Enable Fleet Carrier CAPI Queries" = "Ввімкнути запити CAPI кораблів-носіїв"; + /* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */ "Send flight log and CMDR status to EDSM" = "Відправляти дані бортового журналу до EDSM"; From 4e74b0474bd4394abea2d2e044e26c62a0155473 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 4 Jun 2024 16:57:47 -0400 Subject: [PATCH 244/261] [RELEASE] 5.11.0 --- ChangeLog.md | 19 +++++-------------- config/__init__.py | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 5d480fb8e..1f4defc85 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,20 +6,8 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- -Pre-Release 5.11.0-rc2 +Release 5.11.0 === -This is a release candidate for 5.11.0. - -This release is identical to 5.11.0-rc1, with a few additions: - -**Changes and Enhancements** -* Adds Additional Error Processing to the System Profiler when launched from EDMC -* Adds the ability to resize the Settings window to larger than the initial default size -* Tweaked a few list length checks that could just be boolean to be bool - -Pre-Release 5.11.0-rc1 -=== -This is a release candidate for 5.11.0. This release includes a number of new features and improvements, including a new Beta Update Track for testing future updates, enhanced context menus for text entry fields and UI elements, a revamp to the existing translation system and logging capabilities, and more. This release includes the Python Image Library (PIL) into our core bundle, adds a number of stability and configuration checks to the tool, and adds new schemas and configuration values to senders. @@ -41,9 +29,12 @@ This release also includes a number of bug fixes, performance enhancements, and * Added a new updater for the FDevID Files to keep the dependency up to date without requiring a new patch version push * Added a System Profiler Utility to assist with gathering system and environment information for bug report purposes * Added a new security policy for responsible disclosure of identified security issues +* Adds Additional Error Processing to the System Profiler when launched from EDMC +* Adds the ability to resize the Settings window to larger than the initial default size * Enabled security code scanning on the GitHub repository +* Tweaked a few list length checks that could just be boolean to be bool * Updates the look and feel of the "Already Running" popup to reduce overhead and improve the look of the popup -* Updated translations to latest versions +* Updated translations to latest versions, including a new language: Ukranian! * Updated documentation to reflect certain changes to the code * Updated the GitHub Bug Report template * Updated the GitHub Pull Request template diff --git a/config/__init__.py b/config/__init__.py index ab0bdd0a3..18c77c060 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.0-rc2' +_static_appversion = '5.11.0' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 9ddd0ffacc0d77206ea70b18d7a44148eadee349 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Wed, 5 Jun 2024 17:22:52 -0400 Subject: [PATCH 245/261] [Emergency] Revert Setuptools Update --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7c8e1ef5f..19e55b667 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ wheel # We can't rely on just picking this up from either the base (not venv), # or venv-init-time version. Specify here so that dependabot will prod us # about new versions. -setuptools==70.0.0 +setuptools==69.2.0 # Static analysis tools flake8==7.0.0 From 3209b4e1fbbd00846062e8868fd9bf4fd55cb663 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 17:25:51 -0400 Subject: [PATCH 246/261] [2251] ResPath Update --- build.py | 2 +- collate.py | 6 +++++- companion.py | 4 ++-- update.py | 18 +++++++++++++++--- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/build.py b/build.py index 2483a4e05..c5236c5a1 100644 --- a/build.py +++ b/build.py @@ -208,5 +208,5 @@ def build() -> None: if __name__ == "__main__": - check_for_fdev_updates() + check_for_fdev_updates(local=True) build() diff --git a/collate.py b/collate.py index caf5994f7..380cb8ba4 100755 --- a/collate.py +++ b/collate.py @@ -22,6 +22,7 @@ import companion import outfitting +from config import config from edmc_data import companion_category_map, ship_name_map @@ -50,7 +51,10 @@ def addcommodities(data) -> None: # noqa: CCR001 if not data['lastStarport'].get('commodities'): return - commodityfile = pathlib.Path('FDevIDs/commodity.csv') + try: + commodityfile = pathlib.Path(config.app_dir_path / 'FDevIDs' / 'commodity.csv') + except FileNotFoundError: + commodityfile = pathlib.Path('FDevIDs/commodity.csv') commodities = {} # slurp existing diff --git a/companion.py b/companion.py index 934fc8bb9..775da2951 100644 --- a/companion.py +++ b/companion.py @@ -1203,10 +1203,10 @@ def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully if not commodity_map: # Lazily populate for f in ('commodity.csv', 'rare_commodity.csv'): - if not os.path.isfile(config.respath_path / 'FDevIDs/' / f): + if not os.path.isfile(config.app_dir_path / 'FDevIDs/' / f): logger.warning(f'FDevID file {f} not found! Generating output without these commodity name rewrites.') continue - with open(config.respath_path / 'FDevIDs' / f, 'r') as csvfile: + with open(config.app_dir_path / 'FDevIDs' / f, 'r') as csvfile: reader = csv.DictReader(csvfile) for row in reader: diff --git a/update.py b/update.py index d6364c2d0..9dd6229ad 100644 --- a/update.py +++ b/update.py @@ -8,6 +8,7 @@ from __future__ import annotations import pathlib +import shutil import sys import threading from traceback import print_exc @@ -26,21 +27,32 @@ logger = get_main_logger() -def check_for_fdev_updates(silent: bool = False) -> None: # noqa: CCR001 +def check_for_fdev_updates(silent: bool = False, local: bool = False) -> None: # noqa: CCR001 """Check for and download FDEV ID file updates.""" + if local: + pathway = config.app_dir_path + else: + pathway = config.respath_path + files_urls = [ ('commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/commodity.csv'), ('rare_commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/rare_commodity.csv') ] for file, url in files_urls: - fdevid_file = pathlib.Path(config.respath_path / 'FDevIDs' / file) + fdevid_file = pathlib.Path(pathway / 'FDevIDs' / file) fdevid_file.parent.mkdir(parents=True, exist_ok=True) try: with open(fdevid_file, newline='', encoding='utf-8') as f: local_content = f.read() except FileNotFoundError: - local_content = None + logger.info(f'File {file} not found. Writing from bundle...') + for localfile in files_urls: + filepath = f"FDevIDs/{localfile[0]}" + shutil.copy(filepath, pathway / 'FDevIDs') + fdevid_file = pathlib.Path(pathway / 'FDevIDs' / file) + with open(fdevid_file, newline='', encoding='utf-8') as f: + local_content = f.read() response = requests.get(url) if response.status_code != 200: From 76c94bfdb99187016a51052b0949a9a4a9d8c488 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 17:56:19 -0400 Subject: [PATCH 247/261] [2251] Update FDEVID Timeout --- update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.py b/update.py index 9dd6229ad..49a99a1b6 100644 --- a/update.py +++ b/update.py @@ -54,7 +54,7 @@ def check_for_fdev_updates(silent: bool = False, local: bool = False) -> None: with open(fdevid_file, newline='', encoding='utf-8') as f: local_content = f.read() - response = requests.get(url) + response = requests.get(url, timeout=20) if response.status_code != 200: if not silent: logger.error(f'Failed to download {file}! Unable to continue.') From 1f92c0cdfb5c68ff192fe6197dd3989aefd37184 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 18:08:03 -0400 Subject: [PATCH 248/261] [2251] Remember the File May Not Exist --- update.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/update.py b/update.py index 49a99a1b6..c9b80fb07 100644 --- a/update.py +++ b/update.py @@ -30,9 +30,9 @@ def check_for_fdev_updates(silent: bool = False, local: bool = False) -> None: # noqa: CCR001 """Check for and download FDEV ID file updates.""" if local: - pathway = config.app_dir_path - else: pathway = config.respath_path + else: + pathway = config.app_dir_path files_urls = [ ('commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/commodity.csv'), @@ -47,12 +47,15 @@ def check_for_fdev_updates(silent: bool = False, local: bool = False) -> None: local_content = f.read() except FileNotFoundError: logger.info(f'File {file} not found. Writing from bundle...') - for localfile in files_urls: - filepath = f"FDevIDs/{localfile[0]}" - shutil.copy(filepath, pathway / 'FDevIDs') - fdevid_file = pathlib.Path(pathway / 'FDevIDs' / file) - with open(fdevid_file, newline='', encoding='utf-8') as f: - local_content = f.read() + try: + for localfile in files_urls: + filepath = f"FDevIDs/{localfile[0]}" + shutil.copy(filepath, pathway / 'FDevIDs') + fdevid_file = pathlib.Path(pathway / 'FDevIDs' / file) + with open(fdevid_file, newline='', encoding='utf-8') as f: + local_content = f.read() + except FileNotFoundError: + local_content = None response = requests.get(url, timeout=20) if response.status_code != 200: From 79bb416fe19d32f723063f74bc9ef6290b4825c6 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 18:16:27 -0400 Subject: [PATCH 249/261] [2251] No Ouroboros CSVs please --- update.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/update.py b/update.py index c9b80fb07..35cc31e9b 100644 --- a/update.py +++ b/update.py @@ -50,7 +50,10 @@ def check_for_fdev_updates(silent: bool = False, local: bool = False) -> None: try: for localfile in files_urls: filepath = f"FDevIDs/{localfile[0]}" - shutil.copy(filepath, pathway / 'FDevIDs') + try: + shutil.copy(filepath, pathway / 'FDevIDs') + except shutil.SameFileError: + logger.info("Not replacing same file...") fdevid_file = pathlib.Path(pathway / 'FDevIDs' / file) with open(fdevid_file, newline='', encoding='utf-8') as f: local_content = f.read() From bdf4f659b99ac339aa6639f4d1ec9295a5057926 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 18:25:46 -0400 Subject: [PATCH 250/261] [2251] More Update Checks --- EDMarketConnector.py | 2 ++ config/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index f512ccf19..beefa3092 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -25,6 +25,7 @@ from time import localtime, strftime, time from typing import TYPE_CHECKING, Any, Literal from constants import applongname, appname, protocolhandler_redirect +from update import check_for_fdev_updates # Have this as early as possible for people running EDMarketConnector.exe # from cmd.exe or a bat file or similar. Else they might not be in the correct @@ -2323,6 +2324,7 @@ def messagebox_not_py3(): root.after(2, show_killswitch_poppup, root) # Start the main event loop try: + check_for_fdev_updates() root.mainloop() except KeyboardInterrupt: logger.info("Ctrl+C Detected, Attempting Clean Shutdown") diff --git a/config/__init__.py b/config/__init__.py index 18c77c060..7682c7e32 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.0' +_static_appversion = '5.11.1-alpha2' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From 19309308545e35296b8ae93c71f4ba29ddac5c13 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 18:34:03 -0400 Subject: [PATCH 251/261] [2251] Add Temp Local Check --- config/__init__.py | 2 +- update.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index 7682c7e32..395adc9ed 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.1-alpha2' +_static_appversion = '5.11.1-alpha3' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' diff --git a/update.py b/update.py index 35cc31e9b..14360a4b1 100644 --- a/update.py +++ b/update.py @@ -187,6 +187,12 @@ def check_for_updates(self) -> None: self.updater.win_sparkle_check_update_with_ui() check_for_fdev_updates() + # TEMP: Only include until 6.0 + try: + check_for_fdev_updates(local=True) + except Exception as e: + logger.info('Tried to update local FDEV files but failed.') + logger.info(e) def check_appcast(self) -> EDMCVersion | None: """ From dd8b18cbae94dd6298b0bbb76e6a796d72cee825 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 18:49:12 -0400 Subject: [PATCH 252/261] [2251] Clarify Bundle Update Error Msg --- update.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/update.py b/update.py index 14360a4b1..6e1564f95 100644 --- a/update.py +++ b/update.py @@ -191,8 +191,9 @@ def check_for_updates(self) -> None: try: check_for_fdev_updates(local=True) except Exception as e: - logger.info('Tried to update local FDEV files but failed.') - logger.info(e) + logger.info("Tried to update bundle FDEV files but failed. Don't worry, " + "this likely isn't important and can be ignored unless" + f" you run into other issues. If you're curious: {e}") def check_appcast(self) -> EDMCVersion | None: """ From 2f6e9fa4c2af01182f30b1add013420c7c004d0e Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 19:41:10 -0400 Subject: [PATCH 253/261] [2251] Use Pathlib --- update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.py b/update.py index 6e1564f95..adf118e4e 100644 --- a/update.py +++ b/update.py @@ -49,7 +49,7 @@ def check_for_fdev_updates(silent: bool = False, local: bool = False) -> None: logger.info(f'File {file} not found. Writing from bundle...') try: for localfile in files_urls: - filepath = f"FDevIDs/{localfile[0]}" + filepath = pathlib.Path(f"FDevIDs/{localfile[0]}") try: shutil.copy(filepath, pathway / 'FDevIDs') except shutil.SameFileError: From 424c509c44239e2ee4ad3eab4fce988c5964693c Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Thu, 6 Jun 2024 19:48:39 -0400 Subject: [PATCH 254/261] [2251] Reset Update Import Location --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index beefa3092..e1524b006 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -25,7 +25,6 @@ from time import localtime, strftime, time from typing import TYPE_CHECKING, Any, Literal from constants import applongname, appname, protocolhandler_redirect -from update import check_for_fdev_updates # Have this as early as possible for people running EDMarketConnector.exe # from cmd.exe or a bat file or similar. Else they might not be in the correct @@ -66,6 +65,7 @@ from EDMCLogging import edmclogger, logger, logging from journal_lock import JournalLock, JournalLockResult +from update import check_for_fdev_updates if __name__ == '__main__': # noqa: C901 # Command-line arguments From 2c60499451aff68df1b2b1387c68df6b0109f0cc Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 7 Jun 2024 17:35:29 -0400 Subject: [PATCH 255/261] [RELEASE] 5.11.1-beta0 --- ChangeLog.md | 23 +++++++++++++++++++++++ config/__init__.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 1f4defc85..c4fd2f2b3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,29 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- +Pre-Release 5.11.1-beta0 +=== + +This is a release candidate for 5.11.1. + +This release fixes a bug regarding FDevID files when running from Source in a non-writable location. + +**Changes and Enhancements** +* Added a check on Git Pushes to check for updated translation strings for developers + +**Bug Fixes** +* Fixed a bug that could result in the program not updating or writing FDevID files when running from source in a location where the running user can't write to + +**Plugin Developers** +* nb.Entry is deprecated, and is slated for removal in 6.0 or later. Please migrate to nb.EntryMenu +* nb.ColoredButton is deprecated, and is slated for removal in 6.0 or later. Please migrate to tk.Button +* Calling internal translations with `_()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to importing `translations` and calling `translations.translate` or `translations.tl` directly +* `Translations` as the translate system singleton is deprecated, and is slated for removal in 6.0 or later. Please migrate to the `translations` singleton +* `help_open_log_folder()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to open_folder() +* `update_feed` is deprecated, and is slated for removal in 6.0 or later. Please migrate to `get_update_feed()`. +* FDevID files (`commodity.csv` and `rare_commodity.csv`) have moved their preferred location to the app dir (same location as default Plugins folder). Please migrate to use `config.app_dir_path`. + + Release 5.11.0 === diff --git a/config/__init__.py b/config/__init__.py index 395adc9ed..fe2584752 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.1-alpha3' +_static_appversion = '5.11.1-beta0' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' From b0e682f66c0fa157a5f8cab54b78966a2e2d0b3b Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 18:40:03 +0100 Subject: [PATCH 256/261] Enabled DeprecationWarning by default and fixed references --- EDMCLogging.py | 3 +++ config/__init__.py | 21 +++++++++++---------- l10n.py | 10 +++++----- myNotebook.py | 5 +++-- prefs.py | 7 +++---- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 74b439f9b..b6d4b3534 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -42,6 +42,7 @@ import os import pathlib import tempfile +import warnings from contextlib import suppress from fnmatch import fnmatch # So that any warning about accessing a protected member is only in one place. @@ -99,6 +100,8 @@ # MAGIC-CONT: See MAGIC tagged comment in Logger.__init__() logging.Formatter.converter = gmtime +warnings.simplefilter('default', DeprecationWarning) + def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None: if any(fnmatch(condition, p) for p in config_mod.trace_on): diff --git a/config/__init__.py b/config/__init__.py index fe2584752..9636d49a5 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -41,7 +41,6 @@ import re import subprocess import sys -import traceback import warnings from abc import abstractmethod from typing import Any, Callable, Type, TypeVar @@ -329,8 +328,7 @@ def get( :raises OSError: On Windows, if a Registry error occurs. :return: The data or the default. """ - warnings.warn(DeprecationWarning('get is Deprecated. use the specific getter for your type')) - logger.debug('Attempt to use Deprecated get() method\n' + ''.join(traceback.format_stack())) + warnings.warn('get is Deprecated. use the specific getter for your type', DeprecationWarning, stacklevel=2) if (a_list := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None: return a_list @@ -388,8 +386,7 @@ def getint(self, key: str, *, default: int = 0) -> int: See get_int for its replacement. :raises OSError: On Windows, if a Registry error occurs. """ - warnings.warn(DeprecationWarning('getint is Deprecated. Use get_int instead')) - logger.debug('Attempt to use Deprecated getint() method\n' + ''.join(traceback.format_stack())) + warnings.warn('getint is Deprecated. Use get_int instead', DeprecationWarning, stacklevel=2) return self.get_int(key, default=default) @@ -448,15 +445,15 @@ def close(self) -> None: def get_password(self, account: str) -> None: """Legacy password retrieval.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning) + warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2) def set_password(self, account: str, password: str) -> None: """Legacy password setting.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning) + warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2) def delete_password(self, account: str) -> None: """Legacy password deletion.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning) + warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2) def get_config(*args, **kwargs) -> AbstractConfig: @@ -489,5 +486,9 @@ def get_update_feed() -> str: return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' -# WARNING: update_feed is deprecated, and will be removed in 6.0 or later. Please migrate to get_update_feed() -update_feed = get_update_feed() +def __getattr__(name: str): + if name == 'update_feed': + warnings.warn('update_feed is deprecated, and will be removed in 6.0 or later. ' + 'Please migrate to get_update_feed()', DeprecationWarning, stacklevel=2) + return get_update_feed() + raise AttributeError(name=name) diff --git a/l10n.py b/l10n.py index 27cd95c50..79bab04ad 100755 --- a/l10n.py +++ b/l10n.py @@ -263,15 +263,15 @@ class _Locale: """Locale holds a few utility methods to convert data to and from localized versions.""" def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802 - warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.')) + warnings.warn('use _Locale.string_from_number instead.', DeprecationWarning, stacklevel=2) return self.string_from_number(number, decimals) # type: ignore def numberFromString(self, string: str) -> int | float | None: # noqa: N802 - warnings.warn(DeprecationWarning('use _Locale.number_from_string instead.')) + warnings.warn('use _Locale.number_from_string instead.', DeprecationWarning, stacklevel=2) return self.number_from_string(string) def preferredLanguages(self) -> Iterable[str]: # noqa: N802 - warnings.warn(DeprecationWarning('use _Locale.preferred_languages instead.')) + warnings.warn('use _Locale.preferred_languages instead.', DeprecationWarning, stacklevel=2) return self.preferred_languages() def string_from_number(self, number: float | int, decimals: int = 5) -> str: @@ -367,8 +367,8 @@ def preferred_languages(self) -> Iterable[str]: # Begin Deprecation Zone class _Translations(Translations): def __init__(self): - logger.warning(DeprecationWarning('Translations and _Translations() are deprecated. ' - 'Please use translations and Translations() instead.')) + warnings.warn('Translations and _Translations() are deprecated. ' + 'Please use translations and Translations() instead.', DeprecationWarning, stacklevel=2) super().__init__() diff --git a/myNotebook.py b/myNotebook.py index 0b083c237..d8b2b41b6 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -11,6 +11,7 @@ import sys import tkinter as tk +import warnings from tkinter import ttk, messagebox from PIL import ImageGrab from l10n import translations as tr @@ -124,8 +125,8 @@ def paste(self) -> None: class Entry(EntryMenu): """Custom ttk.Entry class to fix some display issues.""" - # DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): + warnings.warn('Migrate to EntryMenu. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) EntryMenu.__init__(self, master, **kw) @@ -142,8 +143,8 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class ColoredButton(tk.Button): """Custom tk.Button class to fix some display issues.""" - # DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): + warnings.warn('Migrate to tk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) tk.Button.__init__(self, master, **kw) diff --git a/prefs.py b/prefs.py index 7f217ac88..136b52de2 100644 --- a/prefs.py +++ b/prefs.py @@ -9,6 +9,7 @@ import sys import tempfile import tkinter as tk +import warnings from os import system from os.path import expanduser, expandvars, join, normpath from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812 @@ -40,10 +41,8 @@ def help_open_log_folder() -> None: """Open the folder logs are stored in.""" - logger.warning( - DeprecationWarning("This function is deprecated, use open_log_folder instead. " - "This function will be removed in 6.0 or later") - ) + warnings.warn('prefs.help_open_log_folder is deprecated, use open_log_folder instead. ' + 'This function will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) open_folder(pathlib.Path(tempfile.gettempdir()) / appname) From 192ba5f8f27f12b5b5365f031eb176c893432446 Mon Sep 17 00:00:00 2001 From: Bruno Marques Date: Sun, 9 Jun 2024 20:07:13 +0100 Subject: [PATCH 257/261] Adding pkg_resources exception to DeprecationWarning --- EDMCLogging.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EDMCLogging.py b/EDMCLogging.py index b6d4b3534..ab53dbec6 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -101,6 +101,8 @@ logging.Formatter.converter = gmtime warnings.simplefilter('default', DeprecationWarning) +# TODO remove after infi.systray drops pkg_resources +warnings.filterwarnings('ignore', '.*pkg_resources', DeprecationWarning) def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None: From 13e74f2c575ae56731e2e4a1e8ff68dba4536786 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Jun 2024 15:28:27 -0400 Subject: [PATCH 258/261] [2255] Replace Infi Systray with simplesystray --- EDMCLogging.py | 2 -- EDMarketConnector.py | 4 ++-- requirements-dev.txt | 2 +- requirements.txt | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index ab53dbec6..b6d4b3534 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -101,8 +101,6 @@ logging.Formatter.converter = gmtime warnings.simplefilter('default', DeprecationWarning) -# TODO remove after infi.systray drops pkg_resources -warnings.filterwarnings('ignore', '.*pkg_resources', DeprecationWarning) def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None: diff --git a/EDMarketConnector.py b/EDMarketConnector.py index e1524b006..cd76ccc07 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -396,7 +396,7 @@ def already_running_popup(): from logging import TRACE # type: ignore # noqa: F401 # Needed to update mypy if sys.platform == 'win32': - from infi.systray import SysTrayIcon + from simplesystray import SysTrayIcon # isort: on @@ -452,7 +452,7 @@ def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly f self.prefsdialog = None if sys.platform == 'win32': - from infi.systray import SysTrayIcon + from simplesystray import SysTrayIcon def open_window(systray: 'SysTrayIcon') -> None: self.w.deiconify() diff --git a/requirements-dev.txt b/requirements-dev.txt index 19e55b667..7c8e1ef5f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ wheel # We can't rely on just picking this up from either the base (not venv), # or venv-init-time version. Specify here so that dependabot will prod us # about new versions. -setuptools==69.2.0 +setuptools==70.0.0 # Static analysis tools flake8==7.0.0 diff --git a/requirements.txt b/requirements.txt index 83fa9c0d9..22f0a360f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ requests==2.32.3 pillow==10.3.0 watchdog==4.0.0 -infi.systray==0.1.12; sys_platform == 'win32' +simplesystray==0.1.0; sys_platform == 'win32' semantic-version==2.10.0 From 7d5fdb2b84de93900c5732672762763eec2ee8e8 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Jun 2024 15:52:44 -0400 Subject: [PATCH 259/261] [2255] Update Dep Comment Format --- config/__init__.py | 5 +++++ l10n.py | 15 ++++++++------- myNotebook.py | 2 ++ prefs.py | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index 9636d49a5..b4dec93ae 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -328,6 +328,7 @@ def get( :raises OSError: On Windows, if a Registry error occurs. :return: The data or the default. """ + # DEPRECATED: Migrate to specific type getters. Will remove in 6.0 or later. warnings.warn('get is Deprecated. use the specific getter for your type', DeprecationWarning, stacklevel=2) if (a_list := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None: @@ -386,6 +387,7 @@ def getint(self, key: str, *, default: int = 0) -> int: See get_int for its replacement. :raises OSError: On Windows, if a Registry error occurs. """ + # DEPRECATED: Migrate to get_int. Will remove in 6.0 or later. warnings.warn('getint is Deprecated. Use get_int instead', DeprecationWarning, stacklevel=2) return self.get_int(key, default=default) @@ -443,6 +445,7 @@ def close(self) -> None: """Close this config and release any associated resources.""" raise NotImplementedError +# DEPRECATED: Password system doesn't do anything. Will remove in 6.0 or later. def get_password(self, account: str) -> None: """Legacy password retrieval.""" warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2) @@ -454,6 +457,7 @@ def set_password(self, account: str, password: str) -> None: def delete_password(self, account: str) -> None: """Legacy password deletion.""" warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2) +# End Dep Zone def get_config(*args, **kwargs) -> AbstractConfig: @@ -486,6 +490,7 @@ def get_update_feed() -> str: return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' +# DEPRECATED: Migrate to get_update_feed(). Will remove in 6.0 or later. def __getattr__(name: str): if name == 'update_feed': warnings.warn('update_feed is deprecated, and will be removed in 6.0 or later. ' diff --git a/l10n.py b/l10n.py index 79bab04ad..5798a1aff 100755 --- a/l10n.py +++ b/l10n.py @@ -86,8 +86,7 @@ def install_dummy(self) -> None: Use when translation is not desired or not available """ self.translations = {None: {}} - # WARNING: '_' is Deprecated. Will be removed in 6.0 or later. - # Migrate to calling translations.translate or tr.tl directly. + # DEPRECATED: Migrate to translations.translate or tr.tl. Will remove in 6.0 or later. builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n') def install(self, lang: str | None = None) -> None: # noqa: CCR001 @@ -131,8 +130,7 @@ def install(self, lang: str | None = None) -> None: # noqa: CCR001 except Exception: logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}') - # WARNING: '_' is Deprecated. Will be removed in 6.0 or later. - # Migrate to calling translations.translate or tr.tl directly. + # DEPRECATED: Migrate to translations.translate or tr.tl. Will remove in 6.0 or later. builtins.__dict__['_'] = self.translate def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: @@ -262,14 +260,17 @@ def file(self, lang: str, plugin_path: str | None = None) -> TextIO | None: class _Locale: """Locale holds a few utility methods to convert data to and from localized versions.""" - def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802 + # DEPRECATED: Migrate to _Locale.string_from_number. Will remove in 6.0 or later. + def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: warnings.warn('use _Locale.string_from_number instead.', DeprecationWarning, stacklevel=2) return self.string_from_number(number, decimals) # type: ignore + # DEPRECATED: Migrate to _Locale.number_from_string. Will remove in 6.0 or later. def numberFromString(self, string: str) -> int | float | None: # noqa: N802 warnings.warn('use _Locale.number_from_string instead.', DeprecationWarning, stacklevel=2) return self.number_from_string(string) + # DEPRECATED: Migrate to _Locale.preferred_languages. Will remove in 6.0 or later. def preferredLanguages(self) -> Iterable[str]: # noqa: N802 warnings.warn('use _Locale.preferred_languages instead.', DeprecationWarning, stacklevel=2) return self.preferred_languages() @@ -362,8 +363,8 @@ def preferred_languages(self) -> Iterable[str]: translations = Translations() -# WARNING: 'Translations' singleton is deprecated. Will be removed in 6.0 or later. -# Migrate to importing 'translations'. +# DEPRECATED: Migrate to `translations`. Will be removed in 6.0 or later. +# 'Translations' singleton is deprecated. # Begin Deprecation Zone class _Translations(Translations): def __init__(self): diff --git a/myNotebook.py b/myNotebook.py index d8b2b41b6..c57a6deb4 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -125,6 +125,7 @@ def paste(self) -> None: class Entry(EntryMenu): """Custom ttk.Entry class to fix some display issues.""" + # DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): warnings.warn('Migrate to EntryMenu. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) EntryMenu.__init__(self, master, **kw) @@ -143,6 +144,7 @@ def __init__(self, master: ttk.Frame | None = None, **kw): class ColoredButton(tk.Button): """Custom tk.Button class to fix some display issues.""" + # DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later. def __init__(self, master: ttk.Frame | None = None, **kw): warnings.warn('Migrate to tk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) tk.Button.__init__(self, master, **kw) diff --git a/prefs.py b/prefs.py index 136b52de2..9f2062f40 100644 --- a/prefs.py +++ b/prefs.py @@ -38,7 +38,7 @@ # May be imported by plugins - +# DEPRECATED: Migrate to open_log_folder. Will remove in 6.0 or later. def help_open_log_folder() -> None: """Open the folder logs are stored in.""" warnings.warn('prefs.help_open_log_folder is deprecated, use open_log_folder instead. ' From f56302e5077c213b63d5f584ce584651f4689c39 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Jun 2024 15:56:44 -0400 Subject: [PATCH 260/261] [2255] Fix Removed Comment --- l10n.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n.py b/l10n.py index 5798a1aff..8613244fa 100755 --- a/l10n.py +++ b/l10n.py @@ -261,7 +261,7 @@ class _Locale: """Locale holds a few utility methods to convert data to and from localized versions.""" # DEPRECATED: Migrate to _Locale.string_from_number. Will remove in 6.0 or later. - def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: + def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802 warnings.warn('use _Locale.string_from_number instead.', DeprecationWarning, stacklevel=2) return self.string_from_number(number, decimals) # type: ignore From 5d433884393a45514bc587e78fc7f92ba8bd1a26 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Jun 2024 17:26:19 -0400 Subject: [PATCH 261/261] [RELEASE] 5.11.1 --- ChangeLog.md | 10 ++++++---- config/__init__.py | 3 +-- update.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index c4fd2f2b3..664f95f8f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,15 +6,17 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (not distributed with the Windows installer) for the currently used version. --- -Pre-Release 5.11.1-beta0 +Release 5.11.1 === -This is a release candidate for 5.11.1. - -This release fixes a bug regarding FDevID files when running from Source in a non-writable location. +This release fixes a bug regarding FDevID files when running from Source in a non-writable location. Additionally, +Deprecation Warnings are now more visible to aid in plugin development. **Changes and Enhancements** * Added a check on Git Pushes to check for updated translation strings for developers +* Enabled deprecation warnings to pass to plugins and logs +* Updated Dependencies +* Replaced infi.systray with drop-in replacement simplesystray **Bug Fixes** * Fixed a bug that could result in the program not updating or writing FDevID files when running from source in a location where the running user can't write to diff --git a/config/__init__.py b/config/__init__.py index b4dec93ae..8dfcf46b3 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -30,7 +30,6 @@ 'AbstractConfig', 'config', 'get_update_feed', - 'update_feed' ] import abc @@ -53,7 +52,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.11.1-beta0' +_static_appversion = '5.11.1' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' diff --git a/update.py b/update.py index adf118e4e..adef9a808 100644 --- a/update.py +++ b/update.py @@ -197,7 +197,7 @@ def check_for_updates(self) -> None: def check_appcast(self) -> EDMCVersion | None: """ - Manually (no Sparkle or WinSparkle) check the update_feed appcast file. + Manually (no Sparkle or WinSparkle) check the get_update_feed() appcast file. Checks if any listed version is semantically greater than the current running version.