351 lines
14 KiB
Python
Executable File
351 lines
14 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#===============================================================================
|
|
# Copyright (C) 2010 Anton Vorobyov
|
|
# Copyright (C) 2010 Diego Duclos
|
|
#
|
|
# This file is part of eos.
|
|
#
|
|
# eos is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Lesser General Public License as published by
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# eos is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
|
#===============================================================================
|
|
|
|
'''
|
|
This script pulls data out of EVE cache and makes a database dump. To get most of the data,
|
|
you need to just log into game; however, for some special data sometimes you need to dump
|
|
it by executing corresponding action in game, for example - open market tree to get data for
|
|
invmarketgroups table.
|
|
Reverence library by Entity is used, check http://wiki.github.com/ntt/reverence/ for info
|
|
As reverence uses the same Python version as EVE client (2.x series), script cannot be converted to python3
|
|
Example commands to run the script under Linux with default eve paths for getting SQLite dump:
|
|
Tranquility: python eveCacheToDb.py --eve="~/.wine/drive_c/Program Files/CCP/EVE" --cache="~/.wine/drive_c/users/"$USER"/Local Settings/Application Data/CCP/EVE/c_program_files_ccp_eve_tranquility/cache" --dump="sqlite:////home/"$USER"/Desktop/eve.db"
|
|
Singularity: python eveCacheToDb.py --eve="~/.wine/drive_c/Program Files/CCP/Singularity" --cache="~/.wine/drive_c/users/"$USER"/Local Settings/Application Data/CCP/EVE/c_program_files_ccp_singularity_singularity/cache" --sisi --dump="sqlite:////home/"$USER"/Desktop/evetest.db"
|
|
'''
|
|
|
|
import os
|
|
import sys
|
|
|
|
# Add eos root path to sys.path
|
|
path = os.path.dirname(unicode(__file__, sys.getfilesystemencoding()))
|
|
sys.path.append(os.path.realpath(os.path.join(path, "..", "..", "..")))
|
|
|
|
def get_map():
|
|
"""
|
|
Return table name - table class map
|
|
"""
|
|
return {"allianceshortnames": None,
|
|
"billtypes": None,
|
|
"certificaterelationships": None,
|
|
"certificates": None,
|
|
"corptickernames": None,
|
|
"dgmattribs": AttributeInfo,
|
|
"dgmeffects": EffectInfo,
|
|
"dgmtypeattribs": Attribute,
|
|
"dgmtypeeffects": Effect,
|
|
"evegraphics": None,
|
|
"evelocations": None,
|
|
"eveowners": None,
|
|
"eveunits": Unit,
|
|
"groupsByCategories": None,
|
|
"icons": Icon,
|
|
"invbptypes": None,
|
|
"invcategories": Category,
|
|
"invcontrabandTypesByFaction": None,
|
|
"invcontrabandTypesByType": None,
|
|
"invgroups": Group,
|
|
"invmetagroups": MetaGroup,
|
|
"invmarketgroups": MarketGroup,
|
|
"invmetatypes": MetaType,
|
|
"invmetatypesByTypeID": None,
|
|
"invreactiontypes": None,
|
|
"invtypes": Item,
|
|
"locationscenes": None,
|
|
"locationwormholeclasses": None,
|
|
"mapcelestialdescriptions": None,
|
|
"ownericons": None,
|
|
"ramactivities": None,
|
|
"ramaltypes": None,
|
|
"ramaltypesdetailpercategory": None,
|
|
"ramaltypesdetailpergroup": None,
|
|
"ramcompletedstatuses": None,
|
|
"ramtyperequirements": None,
|
|
"schematics": None,
|
|
"schematicsByPin": None,
|
|
"schematicsByType": None,
|
|
"schematicspinmap": None,
|
|
"schematicstypemap": None,
|
|
"shiptypes": None,
|
|
"sounds": None,
|
|
"typesByGroups": None,
|
|
"typesByMarketGroups": None}
|
|
|
|
def get_order():
|
|
"""
|
|
Return order for table processing
|
|
"""
|
|
return ("icons",
|
|
"invmarketgroups",
|
|
"eveunits",
|
|
"dgmattribs",
|
|
"dgmeffects",
|
|
"invcategories",
|
|
"invgroups",
|
|
"invmetagroups",
|
|
"invtypes",
|
|
"invmetatypes",
|
|
"dgmtypeattribs",
|
|
"dgmtypeeffects")
|
|
|
|
def get_customcalls():
|
|
"""
|
|
Return custom table - call to get data for it map
|
|
"""
|
|
return {"invmarketgroups": eve.RemoteSvc("marketProxy").GetMarketGroups()}
|
|
|
|
def process_table(sourcetable, tablename, tableclass):
|
|
"""
|
|
Get all data from cache and write it to database
|
|
"""
|
|
# Get data from source and process it
|
|
tabledata = get_table_data(sourcetable, tablename, get_source_headers(sourcetable))
|
|
# Insert everything into table
|
|
insert_table_values(tabledata, tableclass)
|
|
return
|
|
|
|
def get_source_headers(sourcetable):
|
|
"""
|
|
Pull list of headers from the source table
|
|
"""
|
|
sourceheaders = None
|
|
guid = getattr(sourcetable, "__guid__", "None")
|
|
# For IndexRowset and IndexedRowLists Reverence provides list of headers
|
|
if guid in ("util.IndexRowset", "util.FilterRowset"):
|
|
sourceheaders = tuple(sourcetable.header)
|
|
# For IndexedRowLists, we need to compose list ourselves
|
|
elif guid == "util.IndexedRowLists":
|
|
headerset = set()
|
|
for item in sourcetable:
|
|
for row in sourcetable[item]:
|
|
for headername in row.__header__.Keys():
|
|
headerset.add(headername)
|
|
sourceheaders = tuple(headerset)
|
|
return sourceheaders
|
|
|
|
def get_table_data(sourcetable, tablename, headers):
|
|
"""
|
|
Pull data out of source table
|
|
"""
|
|
# Each row is enclosed into dictionary, full table is list of these dictionaries
|
|
datarows = []
|
|
guid = getattr(sourcetable, "__guid__", "None")
|
|
# We have Select method for IndexRowset tables
|
|
if guid == "util.IndexRowset":
|
|
for values in sourcetable.Select(*headers):
|
|
# When Select is asked to find single value, it is returned in its raw
|
|
# form. Convert is to tuple for proper further processing
|
|
if not isinstance(values, (list, tuple, set)):
|
|
values = (values,)
|
|
headerslen = len(headers)
|
|
datarow = {}
|
|
# 1 row value should correspond to 1 header, if number or values doesn't
|
|
# correspond to number of headers then something went wrong
|
|
if headerslen != len(values):
|
|
print "Error: malformed data in source table {0}".format(tablename)
|
|
return None
|
|
# Fill row dictionary with values and append it to list
|
|
for i in xrange(headerslen):
|
|
# If we've got ASCII string, convert it to Unicode
|
|
if isinstance(values[i], str):
|
|
datarow[headers[i]] = unicode(values[i], 'ISO-8859-1')
|
|
else:
|
|
datarow[headers[i]] = values[i]
|
|
datarows.append(datarow)
|
|
# FilterRowset and IndexedRowLists are accessible almost like dictionaries
|
|
elif guid in ("util.FilterRowset", "util.IndexedRowLists"):
|
|
# Go through all source table elements
|
|
for element in sourcetable.iterkeys():
|
|
# Go through all rows of an element
|
|
for row in sourcetable[element]:
|
|
datarow = {}
|
|
# Fill row dictionary with values we need and append it to the list
|
|
for header in headers:
|
|
value = getattr(row, header, None)
|
|
# None and zero values are different, and we want to write zero
|
|
# values to database
|
|
if value or value in (0, 0.0):
|
|
datarow[header] = value
|
|
datarows.append(datarow)
|
|
|
|
return datarows
|
|
|
|
def insert_table_values(tabledata, tableclass):
|
|
"""
|
|
Insert values into tables and show progress
|
|
"""
|
|
rows = 0
|
|
rows_skipped = 0
|
|
# Go through all table rows
|
|
for row in tabledata:
|
|
instance = tableclass()
|
|
# Print dot each 1k inserted rows
|
|
if rows / 1000.0 == int(rows / 1000.0):
|
|
sys.stdout.write(".")
|
|
sys.stdout.flush()
|
|
try:
|
|
# Go through all fields of a row, process them and insert
|
|
for header in row:
|
|
setattr(instance, header, process_value(row[header], tableclass, header))
|
|
eos.db.gamedata_session.add(instance)
|
|
rows += 1
|
|
except ValueError:
|
|
rows_skipped += 1
|
|
# Print out results and actually commit results to database
|
|
print "\nInserted {0} rows. skipped {1} rows".format(rows, rows_skipped)
|
|
eos.db.gamedata_session.commit()
|
|
|
|
def process_value(value, tableclass, header):
|
|
# Get column info
|
|
info = tableclass._sa_class_manager.mapper.c.get(header)
|
|
if info is None:
|
|
return
|
|
|
|
# Null out non-existent foreign key relations
|
|
foreign_keys = info.foreign_keys
|
|
if len(foreign_keys) > 0:
|
|
for key in foreign_keys:
|
|
col = key.column
|
|
if not query_existence(col, value) and not key.deferrable:
|
|
if info.nullable:
|
|
return None
|
|
else:
|
|
raise ValueError("Integrity check failed")
|
|
else:
|
|
return value
|
|
#Turn booleans into actual booleans, don't leave them as integers
|
|
elif type(info.type) == Boolean:
|
|
return bool(value)
|
|
else:
|
|
return value
|
|
|
|
existence_cache = {}
|
|
def query_existence(col, value):
|
|
key = (col, col.table, value)
|
|
info = existence_cache.get(key)
|
|
if info is None:
|
|
info = eos.db.gamedata_session.query(col.table).filter(col == value).count() > 0
|
|
existence_cache[key] = info
|
|
|
|
return info
|
|
|
|
if __name__ == "__main__":
|
|
from ConfigParser import ConfigParser
|
|
from optparse import OptionParser
|
|
|
|
from reverence import blue
|
|
from sqlalchemy import Boolean
|
|
from sqlalchemy.orm import class_mapper, ColumnProperty
|
|
|
|
import eos.config
|
|
|
|
# Parse command line options
|
|
usage = "usage: %prog --eve=EVE --cache=CACHE --dump=DUMP [--release=RELEASE --sisi]"
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("-e", "--eve", help="path to eve folder")
|
|
parser.add_option("-c", "--cache", help="path to eve cache folder")
|
|
parser.add_option("-d", "--dump", help="the SQL Alchemy connection string of where we should place our final dump")
|
|
parser.add_option("-r", "--release", help="database release number, defaults to 1", default="1")
|
|
parser.add_option("-s", "--sisi", action="store_true", dest="singularity", help="if you're going to work with Singularity test server data, use this option", default=False)
|
|
(options, args) = parser.parse_args()
|
|
|
|
|
|
# Exit if we do not have any of required options
|
|
if not options.eve or not options.cache or not options.dump:
|
|
sys.stderr.write("You need to specify paths to eve folder, cache folder and SQL Alchemy connection string. Run script with --help option for further info.\n")
|
|
sys.exit()
|
|
|
|
# We can deal either with singularity or tranquility servers
|
|
if options.singularity: server = "singularity"
|
|
else: server = "tranquility"
|
|
|
|
# Set static variables for paths
|
|
PATH_EVE = os.path.expanduser(options.eve)
|
|
PATH_CACHE = os.path.expanduser(options.cache)
|
|
|
|
eos.config.gamedata_connectionstring = options.dump
|
|
eos.config.debug = False
|
|
|
|
from eos.gamedata import *
|
|
import eos.db
|
|
|
|
# Get version of EVE client
|
|
config = ConfigParser()
|
|
config.read(os.path.join(PATH_EVE, "common.ini"))
|
|
|
|
# Form metadata dictionary for corresponding table
|
|
metadata = {}
|
|
metadata["version"] = config.getint("main", "build")
|
|
metadata["release"] = options.release
|
|
|
|
# Initialize Reverence cache manager
|
|
eve = blue.EVE(PATH_EVE, cachepath=PATH_CACHE, server=server)
|
|
cfg = eve.getconfigmgr()
|
|
|
|
# Create all tables we need
|
|
eos.db.gamedata_meta.create_all()
|
|
|
|
# Add versioning info to the metadata table
|
|
for fieldname in metadata:
|
|
eos.db.gamedata_session.add(MetaData(fieldname, metadata[fieldname]))
|
|
|
|
eos.db.gamedata_session.commit()
|
|
|
|
# Get table map, processing order and special table data
|
|
TABLE_MAP = get_map()
|
|
TABLE_ORDER = get_order()
|
|
CUSTOM_CALLS = get_customcalls()
|
|
|
|
# Warn about various stuff
|
|
for table in cfg.tables:
|
|
if not table in TABLE_MAP:
|
|
# Warn about new tables in cache which are still not described by table map
|
|
print "Warning: unmapped table {0} found in cache".format(table)
|
|
for table in TABLE_MAP:
|
|
if not table in cfg.tables and not table in CUSTOM_CALLS:
|
|
# Warn about mapped tables which are missing in cache
|
|
print "Warning: mapped table {0} cannot be found in cache".format(table)
|
|
if not table in TABLE_ORDER and TABLE_MAP[table] is not None:
|
|
# Warn about mapped tables not specified in processing order
|
|
print "Warning: mapped table {0} is missing in processing order".format(table)
|
|
for table in TABLE_ORDER:
|
|
if not table in TABLE_MAP:
|
|
# Warn about unmapped tables in processing order
|
|
print "Warning: unmapped table {0} is specified in processing order".format(table)
|
|
|
|
# Get data from reverence and write it
|
|
for tablename in TABLE_ORDER:
|
|
tableclass = TABLE_MAP[tablename]
|
|
if tableclass is not None:
|
|
# Print currently processed table name
|
|
print "Processing: {0}".format(tablename)
|
|
# Get table object from the Reverence and process it
|
|
source_table = getattr(cfg, tablename) if tablename not in CUSTOM_CALLS else CUSTOM_CALLS[tablename]
|
|
# Gather data regarding columns for current table in cache and eos
|
|
cols_eos = set(prop.key for prop in class_mapper(TABLE_MAP[tablename]).iterate_properties if isinstance(prop, ColumnProperty))
|
|
cols_rev = set(get_source_headers(source_table))
|
|
notineos = cols_rev.difference(cols_eos)
|
|
notinrev = cols_eos.difference(cols_rev)
|
|
if notineos:
|
|
print "Warning: {0} found in cache but missing in eos definitions: {1}".format("column" if len(notineos) == 1 else "columns", ", ".join(sorted(notineos)))
|
|
if notinrev:
|
|
print "Warning: {0} found in eos definitions but missing in cache: {1}".format("column" if len(notinrev) == 1 else "columns", ", ".join(sorted(notinrev)))
|
|
process_table(source_table, tablename, tableclass)
|