This is the code path for when something ignores the ddeexec registry
key and instead invokes EDMarketConnector.exe with the edmc://...
argument directly.
Our response is to try ShellExecute() to invoke the DDE path instead.
If the DDE path isn't set up (registry key
`Computer\HKEY_CLASSES_ROOT\edmc\shell\open\ddeexec` not present and
correct), then this will cause a loop of repeatedly invoking
EDMarketConnector.exe.
That key is inserted by the installer. The only reasons for it to not
be there would be something blocking the installer from creating it or
something removing it after install.
This way we can tell the difference between:
1. This process obtained the lock.
2. Another process has the lock.
3. We couldn't get the lock due to not being able to open the lock file
read-write.
Case 3 is currently also returned if the configured journal directory
doesn't exist. This will be the case on any MacOS system that never had
the game running. Likely given the OS hasn't been supported for the
game in years now.
I was testing the new Steam or Epic CAPI auth. My EGS account hasn't
yet been used, so has no commander attached. EDMC thinks the auth has
succeeded in this case, but hitting 'Update' causes it to error because
the returned data is empty.
So, add some checks for lack of 'commander' key and a specific message
"CAPI: No commander data returned" for status line.
Without this there's a KeyError exception thrown, causing the status
line to just get 'commander' in it, which isn't helpful.
The actual duplicate process checks *used* to be done in this function,
but no longer are. Instead it's now specifically performing the "is
this an edmc:// auth callback?" check, including the foreground of the
extant process if needs be.
* Implement JournalLock.release_lock().
* Renamed other methods to remove journaldir_ prefix.
* Slightly tweak the code flow for obtaining the lock.
* Implement JournalLock.update_lock(), called from AppWindow.postprefs().
Without this we only truncate to where the file was when we did the
stdout/err redirect.
What I observed was it truncated out the "journal locking" output, but
not anything from a prior run of the program.
* The "truncate the stdout redirect" code was erroneously inside the
else of a try.
* Added some comments about exceptions found on Linux when unable to
open the journals_dir lock file
* Changed 'assuming another process running' logging to not split lines.
* "Couldn't open <journal lock file>" isn't inside no_other_instances()
so made the message more obvious.
* We're not going to refactor those 'complex' sections any time soon, so
clean up flake8 output for now.
* No-one still uses "send SIGTERM to see a stack trace".
* In `develop` config import will set up logging, so let's head that off
at the pass and put the redirect right at the top.
* Also moved the EDMCLogging import to right after the config import's
new position, to emulate `develop` behaviour.
* We *append* on the initial open of the redirect log file. Then once
we're sure we're the only process we truncate this.
The edmc://auth catch/forward code relies on the old "is there another
window with this handle already?" check, so need to reinstate that code
*after* the lock check in order to forward the message.
* The end of the win32/else conditional was identical, so only do that
write to the successfully locked file in one place.
* No need to go out of `__main__` only to go straight back in.
* It *should* only be a PermissionError, although the docs actually say
the 'higher' OSError. This way we will always catch whatever it is.
* The print uses {e!r} so as to make it explicit what the type of the
exception is.
* The variable is journal_dir_lockfile not lockfile.
* Typehint on journal_dir (likely not needed in `develop` as config.py
is cleaned up there).
* Tweak the per-platform prints
1. We need the args available at the top.
2. In `develop` we'd renamed `enforce_single_instance()` to
`no_other_instance_running()` so that it's obvious it's a function
returning a boolean, rather than the function that does the
enforcement.
1. So now they're only defined in one place.
2. config.py does an import of them, and `from config import ...` then
chains through, so no need to update other users.
3. No need to ' # noqa E402' the killswitch/config imports now.
1. The updater is in a thread/DLL and we don't want that firing part way
through shutdown, so it gets asked to stop first.
2. Then plugins, so they're out of the way and we won't have already
stopped something they might rely on.
3. Hotkey after that to head off the user triggering something via them.
4. Now stop the programmatic input from Journal files (including
Status.json).
5. Frontier auth handling.
6. And then anything else.
monitor.thread should only be none when there's a <<JournalEvent>> to
process if we're in shutdown, in which case we do *not* want to be
processing journal events.
This is some best effort support for using logging in properties.
This works by using the (as suggested by reporter) inspect
`getattr_static` method, and failing that (as it can possibly fail),
wrapping a `getattr` in a try/catch for a RecursionError--don't want to
catch other things, probably best if that explodes on its own.
From there as the `property` object will not have location information,
we rebuild as best we can to an approximation of what the path would be.
With a healthy dash of defensive programming "Just in case".
I don't think that this will have any adverse effects to other logging
methods, as all the new code should only be touched if we hit a property
object.
Closes#808