1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-18 09:57:40 +03:00

Point to journal location in SavedGames folder

This commit is contained in:
Jonathan Harris 2016-08-30 17:00:48 +01:00
parent 78e001343c
commit 91b5d0a6ea
5 changed files with 50 additions and 147 deletions

View File

@ -109,8 +109,8 @@
/* Empire rank. [stats.py] */ /* Empire rank. [stats.py] */
"Duke" = "Duke"; "Duke" = "Duke";
/* Configuration setting. [prefs.py] */ /* Location of the new Journal file in E:D 2.2. [prefs.py] */
"E:D log file location" = "E:D log file location"; "E:D journal file location" = "E:D journal file location";
/* Empire rank. [stats.py] */ /* Empire rank. [stats.py] */
"Earl" = "Earl"; "Earl" = "Earl";

View File

@ -69,7 +69,7 @@ This app can save a variety of data in a variety of formats:
By default these files will be placed in your Documents folder. Since this app will create a lot of files if you use it for a while you may wish to create a separate folder for the files and tell the app to place them there. By default these files will be placed in your Documents folder. Since this app will create a lot of files if you use it for a while you may wish to create a separate folder for the files and tell the app to place them there.
Some options work by reading the Elite: Dangerous game's log files. Normally this app will find the log files but if you find some options greyed-out then adjust the “E:D log file location” setting described [below](#doesnt-track-systems-visited). Some options work by reading the Elite: Dangerous game's “journal” files. If you're running this app on a different machine from the Elite: Dangerous game then adjust the “E:D journal file location” setting on the Configuration tab to point to the game's journal files.
### EDDN ### EDDN
@ -126,13 +126,7 @@ Ensure that you visit the in-game Commodity Market at a station where you intend
This problem is tracked as [Issue #92](https://github.com/Marginal/EDMarketConnector/issues/92). This problem is tracked as [Issue #92](https://github.com/Marginal/EDMarketConnector/issues/92).
### Doesn't track Systems visited ### Doesn't track Systems visited
This app uses Elite: Dangerous' log files to track the systems and stations that you visit. When looking for the log files, this app assumes: This app uses Elite: Dangerous' “journal” files to track the systems and stations that you visit. If you're running this app on a different machine from the Elite: Dangerous game, or if you find that this app isn't automatically tracking the systems that you visit and/or isn't automatically “updating” on docking (if you have that option selected), then adjust the “E:D journal file location” setting on the Configuration tab to point to the game's journal files.
- That you're running this app and Elite: Dangerous on the same machine.
- That you're running Elite: Dangerous from Steam, if you have both Steam and non-Steam versions installed.
- That you're running “Horizons” 64bit, if you have both Horizons and Season 1 installed.
If you find that this app isn't automatically tracking the systems that you visit and/or isn't automatically “updating” on docking (if you have that option selected), or if you're running this app on a different machine from the Elite: Dangerous game then adjust the “E:D log file location” setting on the Configuration tab to point to the game's log files using [this info](https://support.frontier.co.uk/kb/faq.php?id=108) as a guide.
Running from source Running from source
-------- --------

View File

@ -1,7 +1,7 @@
import numbers import numbers
import sys import sys
from os import getenv, makedirs, mkdir, pardir from os import getenv, makedirs, mkdir, pardir
from os.path import expanduser, dirname, isdir, join, normpath from os.path import expanduser, dirname, exists, isdir, join, normpath
from sys import platform from sys import platform
@ -19,14 +19,21 @@ if platform=='darwin':
elif platform=='win32': elif platform=='win32':
import ctypes import ctypes
from ctypes.wintypes import *
import uuid
CSIDL_PERSONAL = 0x0005 FOLDERID_Documents = uuid.UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}')
CSIDL_LOCAL_APPDATA = 0x001C FOLDERID_LocalAppData = uuid.UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}')
CSIDL_PROFILE = 0x0028 FOLDERID_Profile = uuid.UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}')
FOLDERID_SavedGames = uuid.UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}')
SHGetKnownFolderPath = ctypes.windll.shell32.SHGetKnownFolderPath
SHGetKnownFolderPath.argtypes = [ctypes.c_char_p, DWORD, HANDLE, ctypes.POINTER(ctypes.c_wchar_p)]
CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree
CoTaskMemFree.argtypes = [ctypes.c_void_p]
# _winreg that ships with Python 2 doesn't support unicode, so do this instead # _winreg that ships with Python 2 doesn't support unicode, so do this instead
from ctypes.wintypes import *
HKEY_CURRENT_USER = 0x80000001 HKEY_CURRENT_USER = 0x80000001
KEY_ALL_ACCESS = 0x000F003F KEY_ALL_ACCESS = 0x000F003F
REG_CREATED_NEW_KEY = 0x00000001 REG_CREATED_NEW_KEY = 0x00000001
@ -100,6 +107,8 @@ class Config:
if not isdir(self.plugin_dir): if not isdir(self.plugin_dir):
mkdir(self.plugin_dir) mkdir(self.plugin_dir)
self.default_journal_dir = join(NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], 'Frontier Developments', 'Elite Dangerous', 'Logs') # FIXME: check this
self.home = expanduser('~') self.home = expanduser('~')
self.respath = getattr(sys, 'frozen', False) and normpath(join(dirname(sys.executable), pardir, 'Resources')) or dirname(__file__) self.respath = getattr(sys, 'frozen', False) and normpath(join(dirname(sys.executable), pardir, 'Resources')) or dirname(__file__)
@ -148,19 +157,25 @@ class Config:
def __init__(self): def __init__(self):
buf = ctypes.create_unicode_buffer(MAX_PATH) buf = ctypes.c_wchar_p()
ctypes.windll.shell32.SHGetSpecialFolderPathW(0, buf, CSIDL_LOCAL_APPDATA, 0) SHGetKnownFolderPath(ctypes.create_string_buffer(FOLDERID_LocalAppData.bytes_le), 0, 0, ctypes.byref(buf))
self.app_dir = join(buf.value, appname) self.app_dir = join(buf.value, appname)
if not isdir(self.app_dir): if not isdir(self.app_dir):
mkdir(self.app_dir) mkdir(self.app_dir)
CoTaskMemFree(buf)
self.plugin_dir = join(self.app_dir, 'plugins') self.plugin_dir = join(self.app_dir, 'plugins')
if not isdir(self.plugin_dir): if not isdir(self.plugin_dir):
mkdir(self.plugin_dir) mkdir(self.plugin_dir)
# expanduser in Python 2 on Windows doesn't handle non-ASCII - http://bugs.python.org/issue13207 # expanduser in Python 2 on Windows doesn't handle non-ASCII - http://bugs.python.org/issue13207
ctypes.windll.shell32.SHGetSpecialFolderPathW(0, buf, CSIDL_PROFILE, 0) SHGetKnownFolderPath(ctypes.create_string_buffer(FOLDERID_Profile.bytes_le), 0, 0, ctypes.byref(buf))
self.home = buf.value self.home = buf.value
CoTaskMemFree(buf)
SHGetKnownFolderPath(ctypes.create_string_buffer(FOLDERID_SavedGames.bytes_le), 0, 0, ctypes.byref(buf))
self.default_journal_dir = buf.value
CoTaskMemFree(buf)
self.respath = dirname(getattr(sys, 'frozen', False) and sys.executable or __file__) self.respath = dirname(getattr(sys, 'frozen', False) and sys.executable or __file__)
@ -190,8 +205,9 @@ class Config:
if not self.get('outdir') or not isdir(self.get('outdir')): if not self.get('outdir') or not isdir(self.get('outdir')):
buf = ctypes.create_unicode_buffer(MAX_PATH) buf = ctypes.create_unicode_buffer(MAX_PATH)
ctypes.windll.shell32.SHGetSpecialFolderPathW(0, buf, CSIDL_PERSONAL, 0) SHGetKnownFolderPath(ctypes.create_string_buffer(FOLDERID_Documents.bytes_le), 0, 0, ctypes.byref(buf))
self.set('outdir', buf.value) self.set('outdir', buf.value)
CoTaskMemFree(buf)
def get(self, key): def get(self, key):
typ = DWORD() typ = DWORD()
@ -253,6 +269,8 @@ class Config:
if not isdir(self.plugin_dir): if not isdir(self.plugin_dir):
mkdir(self.plugin_dir) mkdir(self.plugin_dir)
self.default_journal_dir = None
self.home = expanduser('~') self.home = expanduser('~')
self.respath = dirname(__file__) self.respath = dirname(__file__)

View File

@ -65,7 +65,6 @@ class EDLogs(FileSystemEventHandler):
def __init__(self): def __init__(self):
FileSystemEventHandler.__init__(self) # futureproofing - not need for current version of watchdog FileSystemEventHandler.__init__(self) # futureproofing - not need for current version of watchdog
self.root = None self.root = None
self.logdir = self._logdir() # E:D client's default Logs directory, or None if not found
self.currentdir = None # The actual logdir that we're monitoring self.currentdir = None # The actual logdir that we're monitoring
self.logfile = None self.logfile = None
self.observer = None self.observer = None
@ -88,8 +87,8 @@ class EDLogs(FileSystemEventHandler):
def start(self, root): def start(self, root):
self.root = root self.root = root
logdir = config.get('logdir') or self.logdir logdir = config.get('journaldir') or config.default_journal_dir
if not self.is_valid_logdir(logdir): if not logdir or not exists(logdir):
self.stop() self.stop()
return False return False
@ -101,7 +100,7 @@ class EDLogs(FileSystemEventHandler):
# File system events are unreliable/non-existent over network drives on Linux. # File system events are unreliable/non-existent over network drives on Linux.
# We can't easily tell whether a path points to a network drive, so assume # We can't easily tell whether a path points to a network drive, so assume
# any non-standard logdir might be on a network drive and poll instead. # any non-standard logdir might be on a network drive and poll instead.
polling = bool(config.get('logdir')) and platform != 'win32' polling = bool(config.get('journaldir')) and platform != 'win32'
if not polling and not self.observer: if not polling and not self.observer:
self.observer = Observer() self.observer = Observer()
self.observer.daemon = True self.observer.daemon = True
@ -239,114 +238,6 @@ class EDLogs(FileSystemEventHandler):
else: else:
return self.parse_entry(self.event_queue.pop(0)) return self.parse_entry(self.event_queue.pop(0))
def is_valid_logdir(self, path):
return self._is_valid_logdir(path)
if platform=='darwin':
def _logdir(self):
# https://support.frontier.co.uk/kb/faq.php?id=97
paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)
if len(paths) and self._is_valid_logdir(join(paths[0], 'Frontier Developments', 'Elite Dangerous', 'Logs')):
return join(paths[0], 'Frontier Developments', 'Elite Dangerous', 'Logs')
else:
return None
def _is_valid_logdir(self, path):
# Apple's SMB implementation is too flaky so assume target machine is OSX
return path and isdir(path) and isfile(join(path, pardir, 'AppNetCfg.xml'))
elif platform=='win32':
def _logdir(self):
# Try locations described in https://support.elitedangerous.com/kb/faq.php?id=108, in reverse order of age
candidates = []
# Steam and Steam libraries
key = HKEY()
if not RegOpenKeyEx(HKEY_CURRENT_USER, r'Software\Valve\Steam', 0, KEY_READ, ctypes.byref(key)):
valtype = DWORD()
valsize = DWORD()
if not RegQueryValueEx(key, 'SteamPath', 0, ctypes.byref(valtype), None, ctypes.byref(valsize)) and valtype.value == REG_SZ:
buf = ctypes.create_unicode_buffer(valsize.value / 2)
if not RegQueryValueEx(key, 'SteamPath', 0, ctypes.byref(valtype), buf, ctypes.byref(valsize)):
steampath = buf.value.replace('/', '\\') # For some reason uses POSIX seperators
steamlibs = [steampath]
try:
# Simple-minded Valve VDF parser
with open(join(steampath, 'config', 'config.vdf'), 'rU') as h:
for line in h:
vals = line.split()
if vals and vals[0].startswith('"BaseInstallFolder_'):
steamlibs.append(vals[1].strip('"').replace('\\\\', '\\'))
except:
pass
for lib in steamlibs:
candidates.append(join(lib, 'steamapps', 'common', 'Elite Dangerous', 'Products'))
RegCloseKey(key)
# Next try custom installation under the Launcher
if not RegOpenKeyEx(HKEY_LOCAL_MACHINE,
machine().endswith('64') and
r'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall' or # Assumes that the launcher is a 32bit process
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
0, KEY_READ, ctypes.byref(key)):
buf = ctypes.create_unicode_buffer(MAX_PATH)
i = 0
while True:
size = DWORD(MAX_PATH)
if RegEnumKeyEx(key, i, buf, ctypes.byref(size), None, None, None, None):
break
subkey = HKEY()
if not RegOpenKeyEx(key, buf, 0, KEY_READ, ctypes.byref(subkey)):
valtype = DWORD()
valsize = DWORD((len('Frontier Developments')+1)*2)
valbuf = ctypes.create_unicode_buffer(valsize.value / 2)
if not RegQueryValueEx(subkey, 'Publisher', 0, ctypes.byref(valtype), valbuf, ctypes.byref(valsize)) and valtype.value == REG_SZ and valbuf.value == 'Frontier Developments':
if not RegQueryValueEx(subkey, 'InstallLocation', 0, ctypes.byref(valtype), None, ctypes.byref(valsize)) and valtype.value == REG_SZ:
valbuf = ctypes.create_unicode_buffer(valsize.value / 2)
if not RegQueryValueEx(subkey, 'InstallLocation', 0, ctypes.byref(valtype), valbuf, ctypes.byref(valsize)):
candidates.append(join(valbuf.value, 'Products'))
RegCloseKey(subkey)
i += 1
RegCloseKey(key)
# Standard non-Steam locations
programs = ctypes.create_unicode_buffer(MAX_PATH)
ctypes.windll.shell32.SHGetSpecialFolderPathW(0, programs, CSIDL_PROGRAM_FILESX86, 0)
candidates.append(join(programs.value, 'Frontier', 'Products')),
applocal = ctypes.create_unicode_buffer(MAX_PATH)
ctypes.windll.shell32.SHGetSpecialFolderPathW(0, applocal, CSIDL_LOCAL_APPDATA, 0)
candidates.append(join(applocal.value, 'Frontier_Developments', 'Products'))
for game in ['elite-dangerous-64', 'FORC-FDEV-D-1']: # Look for Horizons in all candidate places first
for base in candidates:
if isdir(base):
for d in listdir(base):
if d.startswith(game) and self._is_valid_logdir(join(base, d, 'Logs')):
return join(base, d, 'Logs')
return None
def _is_valid_logdir(self, path):
# Assume target machine is Windows
return path and isdir(path) and isfile(join(path, pardir, 'AppConfig.xml'))
elif platform=='linux2':
def _logdir(self):
return None
def _is_valid_logdir(self, path):
# Assume target machine is Windows
return path and isdir(path) and isfile(join(path, pardir, 'AppConfig.xml'))
# singleton # singleton
monitor = EDLogs() monitor = EDLogs()

View File

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from os.path import dirname, expanduser, isdir, join, sep from os.path import dirname, expanduser, exists, isdir, join, sep
from sys import platform from sys import platform
import Tkinter as tk import Tkinter as tk
@ -191,7 +191,7 @@ class PreferencesDialog(tk.Toplevel):
configframe.columnconfigure(1, weight=1) configframe.columnconfigure(1, weight=1)
self.logdir = nb.Entry(configframe, takefocus=False) self.logdir = nb.Entry(configframe, takefocus=False)
logdir = config.get('logdir') or monitor.logdir logdir = config.get('journaldir') or config.default_journal_dir
if not logdir: if not logdir:
pass pass
elif logdir.startswith(config.home): elif logdir.startswith(config.home):
@ -202,14 +202,14 @@ class PreferencesDialog(tk.Toplevel):
if platform != 'darwin': if platform != 'darwin':
# Apple's SMB implementation is way too flaky - no filesystem events and bogus NULLs # Apple's SMB implementation is way too flaky - no filesystem events and bogus NULLs
nb.Label(configframe, text = _('E:D log file location')+':').grid(columnspan=3, padx=PADX, sticky=tk.W) # Configuration setting nb.Label(configframe, text = _('E:D journal file location')+':').grid(columnspan=3, padx=PADX, sticky=tk.W) # Location of the new Journal file in E:D 2.2
self.logdir.grid(row=10, columnspan=2, padx=(PADX,0), sticky=tk.EW) self.logdir.grid(row=10, columnspan=2, padx=(PADX,0), sticky=tk.EW)
self.logbutton = nb.Button(configframe, text=(platform=='darwin' and _('Change...') or # Folder selection button on OSX self.logbutton = nb.Button(configframe, text=(platform=='darwin' and _('Change...') or # Folder selection button on OSX
_('Browse...')), # Folder selection button on Windows _('Browse...')), # Folder selection button on Windows
command = lambda:self.filebrowse(_('E:D log file location'), self.logdir)) command = lambda:self.filebrowse(_('E:D journal file location'), self.logdir))
self.logbutton.grid(row=10, column=2, padx=PADX, sticky=tk.EW) self.logbutton.grid(row=10, column=2, padx=PADX, sticky=tk.EW)
if monitor.logdir: if config.default_journal_dir:
nb.Button(configframe, text=_('Default'), command=self.logdir_reset, state = monitor.logdir and tk.NORMAL or tk.DISABLED).grid(column=2, padx=PADX, pady=(5,0), sticky=tk.EW) # Appearance theme and language setting nb.Button(configframe, text=_('Default'), command=self.logdir_reset, state = config.get('journaldir') and tk.NORMAL or tk.DISABLED).grid(column=2, padx=PADX, pady=(5,0), sticky=tk.EW) # Appearance theme and language setting
if platform == 'win32': if platform == 'win32':
ttk.Separator(configframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY*8, sticky=tk.EW) ttk.Separator(configframe, orient=tk.HORIZONTAL).grid(columnspan=3, padx=PADX, pady=PADY*8, sticky=tk.EW)
@ -308,7 +308,7 @@ class PreferencesDialog(tk.Toplevel):
def outvarchanged(self): def outvarchanged(self):
logdir = self.logdir.get().startswith('~') and join(config.home, self.logdir.get()[2:]) or self.logdir.get() logdir = self.logdir.get().startswith('~') and join(config.home, self.logdir.get()[2:]) or self.logdir.get()
logvalid = monitor.is_valid_logdir(logdir) logvalid = logdir and exists(logdir)
local = self.out_bpc.get() or self.out_td.get() or self.out_csv.get() or self.out_ship_eds.get() or self.out_ship_coriolis.get() local = self.out_bpc.get() or self.out_td.get() or self.out_csv.get() or self.out_ship_eds.get() or self.out_ship_coriolis.get()
self.out_auto_button['state'] = local and logvalid and tk.NORMAL or tk.DISABLED self.out_auto_button['state'] = local and logvalid and tk.NORMAL or tk.DISABLED
@ -366,12 +366,12 @@ class PreferencesDialog(tk.Toplevel):
def logdir_reset(self): def logdir_reset(self):
self.logdir['state'] = tk.NORMAL # must be writable to update self.logdir['state'] = tk.NORMAL # must be writable to update
self.logdir.delete(0, tk.END) self.logdir.delete(0, tk.END)
if not monitor.logdir: if not config.default_journal_dir:
pass pass # Can't reset
elif monitor.logdir.startswith(config.home): elif config.default_journal_dir.startswith(config.home):
self.logdir.insert(0, '~' + monitor.logdir[len(config.home):]) self.logdir.insert(0, '~' + config.default_journal_dir[len(config.home):])
else: else:
self.logdir.insert(0, monitor.logdir) self.logdir.insert(0, config.default_journal_dir)
self.logdir['state'] = 'readonly' self.logdir['state'] = 'readonly'
self.outvarchanged() self.outvarchanged()
@ -456,10 +456,10 @@ class PreferencesDialog(tk.Toplevel):
config.set('edsm_apikey', self.edsm_apikey.get().strip()) config.set('edsm_apikey', self.edsm_apikey.get().strip())
logdir = self.logdir.get().startswith('~') and join(config.home, self.logdir.get()[2:]) or self.logdir.get() logdir = self.logdir.get().startswith('~') and join(config.home, self.logdir.get()[2:]) or self.logdir.get()
if monitor.logdir and logdir.lower() == monitor.logdir.lower(): if config.default_journal_dir and logdir.lower() == config.default_journal_dir.lower():
config.set('logdir', '') # default location config.set('journaldir', '') # default location
else: else:
config.set('logdir', logdir) config.set('journaldir', logdir)
if platform in ['darwin','win32']: if platform in ['darwin','win32']:
config.set('hotkey_code', self.hotkey_code) config.set('hotkey_code', self.hotkey_code)
config.set('hotkey_mods', self.hotkey_mods) config.set('hotkey_mods', self.hotkey_mods)