Add a getWorldInfo method to WorldEditor

This allows the WorldList to get summary info for each world without loading it and parsing its blocktypes, etc
This commit is contained in:
David Vierra 2015-09-01 10:23:06 -10:00
parent 1a11979d4a
commit 6c425d651d
5 changed files with 89 additions and 59 deletions

View File

@ -27,26 +27,11 @@ from mceditlib import worldeditor
import logging
from mceditlib.findadapter import isLevel, findAdapter
from mceditlib.util import displayName
from mceditlib.worldeditor import WorldEditor
log = logging.getLogger(__name__)
def lastPlayedTime(adapter):
try:
time = adapter.metadata.LastPlayed
dt = arrow.Arrow.fromtimestamp(time / 1000.0)
return dt
except (AttributeError, ValueError) as e: # no lastplayed, or time is before 1970
return None
def usefulFilename(adapter):
if hasattr(adapter, 'worldFolder'):
return os.path.basename(adapter.worldFolder.filename)
else:
return os.path.basename(adapter.filename)
class WorldListItemWidget(QtGui.QWidget):
doubleClicked = QtCore.Signal()
@ -81,10 +66,18 @@ class WorldListItemWidget(QtGui.QWidget):
self.setLayout(layout)
def setWorldInfo(self, (name, lastPlayedText, versionInfo)):
def setWorldInfo(self, (name, lastPlayed, versionInfo)):
self.displayNameLabel.setText(name)
self.lastPlayedLabel.setText(lastPlayedText)
self.versionInfoLabel.setText(versionInfo)
if lastPlayed:
lastPlayedText = arrow.Arrow.fromtimestamp(lastPlayed / 1000.0).humanize()
self.lastPlayedLabel.setText(lastPlayedText)
else:
self.lastPlayedLabel.setText("")
if versionInfo:
self.versionInfoLabel.setText(versionInfo)
else:
self.versionInfoLabel.setText("")
def mouseDoubleClickEvent(self, event):
self.doubleClicked.emit()
@ -93,39 +86,6 @@ class WorldListItemWidget(QtGui.QWidget):
self.sizeLabel.setText(msg)
def getWorldInfo(filename):
worldAdapter = findAdapter(filename, readonly=True)
try:
displayNameLimit = 40
name = displayName(worldAdapter.filename)
if len(name) > displayNameLimit:
name = name[:displayNameLimit] + "..."
if usefulFilename(worldAdapter) != displayName(worldAdapter.filename):
name = "%s (%s)" % (name, usefulFilename(worldAdapter))
lastPlayed = lastPlayedTime(worldAdapter)
lastPlayedText = lastPlayed.humanize() if lastPlayed else "Unknown"
version = "Unknown Version"
try:
stackVersion = worldAdapter.blocktypes.itemStackVersion
if stackVersion == VERSION_1_7:
version = "Minecraft 1.7"
if "FML" in worldAdapter.metadata.metadataTag:
version = "MinecraftForge 1.7"
if stackVersion == VERSION_1_8:
version = "Minecraft 1.8"
except Exception as e:
log.warn("Failed to get version info for %s: %r", filename, e)
return name, lastPlayedText, version
except Exception as e:
log.error("Failed getting world info for %s: %r", filename, e)
return str(e), "", ""
class WorldListItemDelegate(QtGui.QStyledItemDelegate):
def __init__(self):
super(WorldListItemDelegate, self).__init__()
@ -172,8 +132,9 @@ class WorldListModel(QtCore.QAbstractListModel):
self.worlds = []
for f in worlds:
try:
info = getWorldInfo(f)
info = WorldEditor.getWorldInfo(f)
except Exception as e:
log.warn("Error while getting world info, skipping...", exc_info=1)
continue
else:
self.worlds.append((f, info))
@ -301,7 +262,7 @@ class WorldListWidget(QtGui.QDialog):
dead.append(filename)
continue
try:
displayName, lastPlayed, versionInfo = getWorldInfo(filename)
displayName, lastPlayed, versionInfo = WorldEditor.getWorldInfo(filename)
action = self.recentWorldsMenu.addAction(displayName)
action._editWorld = _triggered(filename)
action.triggered.connect(action._editWorld)

View File

@ -26,7 +26,7 @@ from mceditlib.selection import BoundingBox
from mceditlib import nbtattr
from mceditlib.exceptions import PlayerNotFound, ChunkNotPresent, LevelFormatError
from mceditlib.revisionhistory import RevisionHistory
from mceditlib.util import exhaust
from mceditlib.util import exhaust, displayName, WorldInfo
log = logging.getLogger(__name__)
@ -464,11 +464,65 @@ class AnvilWorldAdapter(object):
else:
self.loadMetadata()
def __repr__(self):
return "AnvilWorldAdapter(%r)" % self.filename
# --- Summary info ---
@classmethod
def getWorldInfo(cls, filename, displayNameLimit=40):
try:
if os.path.isdir(filename):
folderName = os.path.basename(filename)
levelDat = os.path.join(filename, "level.dat")
else:
folderName = os.path.basename(os.path.dirname(filename))
levelDat = filename
levelTag = nbt.load(levelDat)
try:
displayName = levelTag['Data']['LevelName'].value
if len(displayName) > displayNameLimit:
displayName = displayName[:displayNameLimit] + "..."
if len(folderName) > displayNameLimit:
folderName = folderName[:displayNameLimit] + "..."
if folderName != displayName:
displayName = "%s (%s)" % (displayName, folderName)
except Exception as e:
log.warn("Failed to get display name for level.", exc_info=1)
displayName = folderName
try:
lastPlayedTime = levelTag['Data']['LastPlayed'].value
except Exception as e:
log.warn("Failed to get last-played time for level.", exc_info=1)
lastPlayedTime = 0
version = "Unknown Version"
try:
metadata = AnvilWorldMetadata(levelTag)
stackVersion = VERSION_1_8 if metadata.is1_8World() else VERSION_1_7
if stackVersion == VERSION_1_7:
version = "Minecraft 1.7"
if "FML" in metadata.metadataTag:
version = "MinecraftForge 1.7"
if stackVersion == VERSION_1_8:
version = "Minecraft 1.8"
except Exception as e:
log.warn("Failed to get version info for %s: %r", filename, e, exc_info=1)
return WorldInfo(displayName, lastPlayedTime, version)
except Exception as e:
log.error("Failed getting world info for %s: %r", filename, e)
return WorldInfo(str(e), 0, "")
# --- Create, save, close ---
def loadMetadata(self):

View File

@ -21,7 +21,7 @@ log = logging.getLogger(__name__)
# return WorldEditor(filename)
def findAdapter(filename, readonly=False, resume=None):
def findAdapter(filename, readonly=False, resume=None, getInfo=False):
"""
Try to identify the given file and find an adapter that will open it.
Returns an Adapter object if a class is found, and raises ValueError if
@ -30,6 +30,9 @@ def findAdapter(filename, readonly=False, resume=None):
Knows about ZipSchematic, PocketWorldAdapter, WorldEditor, JavaLevel,
IndevLevel, SchematicFile, and INVEditChest
If getInfo is True, returns a WorldInfo object that contains at least
the world's readable name and Minecraft version.
:param filename: The file to open
:type filename: string
"""
@ -49,7 +52,10 @@ def findAdapter(filename, readonly=False, resume=None):
log.debug("%s: Attempting", cls.__name__)
if isLevel(cls, filename):
log.debug("%s: Opening", cls.__name__)
level = cls(filename=filename, readonly=readonly, resume=resume)
if getInfo:
level = cls.getWorldInfo(filename)
else:
level = cls(filename=filename, readonly=readonly, resume=resume)
log.debug("%s: Opened%s", cls.__name__, " read-only" if readonly else "")
return level

View File

@ -2,11 +2,14 @@
util.py
"""
from __future__ import absolute_import
import collections
from contextlib import contextmanager
from math import floor
import os
import sys
WorldInfo = collections.namedtuple("WorldInfo", "displayName lastPlayedTime versionInfo")
@contextmanager
def notclosing(f):
yield f

View File

@ -247,6 +247,12 @@ class WorldEditor(object):
def __repr__(self):
return "WorldEditor(adapter=%r)" % self.adapter
# --- Summary Info ---
@classmethod
def getWorldInfo(cls, filename):
worldInfo = findAdapter(filename, readonly=True, getInfo=True)
return worldInfo
# --- Debug ---