Skip to content

Commit

Permalink
Fix upgrades of c++ library
Browse files Browse the repository at this point in the history
Now both on windows and linux it only creates a marker for extraction
after restart - while on windows this is due to inability to delete DLL
that is loaded, on linux it leads to crashes later when already loaded DLL
is replaced.
  • Loading branch information
wonder-sk committed Apr 14, 2016
1 parent f6c619a commit 0eccbca
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 47 deletions.
54 changes: 47 additions & 7 deletions crayfish/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import ConfigParser
import ctypes
import os
import platform
Expand All @@ -36,6 +37,9 @@

lib = None # initialized on demand

libname = "crayfish.dll" if platform.system() == "Windows" else "libcrayfish.so.1"
libpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), libname)

class Err(object):
NoError, NotEnoughMemory, \
FileNotFound, UnknownFormat, \
Expand All @@ -61,20 +65,30 @@ def __repr__(self):
return "<Element ID %d type: %d pts: %s>" % (self.id, self.type, str(list(self.p)))


class VersionError(Exception):
""" Exception to be thrown on mismatch of C++/Python code versions """
def __init__(self, library_ver, plugin_ver):
self.library_ver = library_ver
self.plugin_ver = plugin_ver


def load_library():
""" Load the supporting Crayfish C++ library.
Does nothing if the library has been loaded already.
Raises an exception if loading fails. """

global lib
global lib, libpath

if lib is not None:
return # already loaded

this_dir = os.path.dirname(os.path.realpath(__file__))
libname = "crayfish.dll" if platform.system() == "Windows" else "libcrayfish.so.1"
lib = ctypes.cdll.LoadLibrary(libpath)

lib = ctypes.cdll.LoadLibrary(os.path.join(this_dir, libname))
library_ver = library_version()
plugin_ver = plugin_version()
if library_ver != plugin_ver:
lib = None
raise VersionError(library_ver, plugin_ver)

lib.CF_Mesh_nodeAt.restype = ctypes.POINTER(Node)
lib.CF_Mesh_elementAt.restype = ctypes.POINTER(Element)
Expand Down Expand Up @@ -347,7 +361,7 @@ def values_vector(self):
else:
count = self.dataset().mesh().element_count()

for index in xrange(node_count):
for index in xrange(count):
yield self.value_vector(index)

def status(self, index):
Expand Down Expand Up @@ -627,6 +641,32 @@ def last_load_status():
return lib.CF_LastLoadError(), lib.CF_LastLoadWarning()

def library_version():
""" Return version of the underlying C++ library (as a number) """
load_library() # make sure the library is loaded
""" Return version of the underlying C++ library as a number.
It should be the same as what plugin_version() returns.
Format: 0xXYZ for version X.Y.Z """
if lib is None:
return None
return lib.CF_Version()

def plugin_version_str():
""" Return version of Python plugin from metadata as a string """
cfg = ConfigParser.ConfigParser()
cfg.read(os.path.join(os.path.dirname(__file__), 'metadata.txt'))
return cfg.get('general', 'version')

def plugin_version():
""" Return version of Python plugin from metadata as a number.
It should be the same as what library_version() returns.
Format: 0xXYZ for version X.Y.Z """
ver_lst = plugin_version_str().split('.')
ver = 0
if len(ver_lst) >= 1:
ver_major = int(ver_lst[0])
ver |= ver_major << 16
if len(ver_lst) >= 2:
ver_minor = int(ver_lst[1])
ver |= ver_minor << 8
if len(ver_lst) == 3:
ver_bugfix = int(ver_lst[2])
ver |= ver_bugfix
return ver
2 changes: 1 addition & 1 deletion crayfish/gui/about_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from PyQt4.QtWebKit import *
from qgis.core import *

from .install_helper import plugin_version_str
from ..core import plugin_version_str
from .utils import load_ui

uiDialog, qtBaseClass = load_ui('crayfish_about_dialog_widget')
Expand Down
68 changes: 29 additions & 39 deletions crayfish/gui/install_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import ConfigParser
import os
import platform
import time
Expand All @@ -34,7 +33,7 @@
from PyQt4.QtCore import QSettings, Qt
from PyQt4.QtGui import QCursor, QMessageBox, qApp

from ..core import load_library, library_version
from ..core import load_library, VersionError, plugin_version_str, libpath

# Base URL for downloading of prepared binaries
downloadBaseUrl = 'http://www.lutraconsulting.co.uk/'
Expand All @@ -43,26 +42,6 @@
destFolder = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))


def plugin_version_str():
cfg = ConfigParser.ConfigParser()
cfg.read(os.path.join(os.path.dirname(__file__), '..', 'metadata.txt'))
return cfg.get('general', 'version')

def plugin_version():
ver_lst = plugin_version_str().split('.')
ver = 0
if len(ver_lst) >= 1:
ver_major = int(ver_lst[0])
ver |= ver_major << 16
if len(ver_lst) >= 2:
ver_minor = int(ver_lst[1])
ver |= ver_minor << 8
if len(ver_lst) == 3:
ver_bugfix = int(ver_lst[2])
ver |= ver_bugfix
return ver


crayfish_zipfile = 'crayfish-lib-%s.zip' % plugin_version_str()


Expand All @@ -72,8 +51,7 @@ def ensure_library_installed(parent_widget=None):
restartRequired = False

platformVersion = platform.system()
if platformVersion == 'Windows':
while not extractBinPackageAfterRestart():
while not extractBinPackageAfterRestart():
reply = QMessageBox.critical(parent_widget,
'Crayfish Installation Issue',
"Crayfish plugin is unable to replace previous version of library. "
Expand All @@ -83,12 +61,15 @@ def ensure_library_installed(parent_widget=None):
if reply != QMessageBox.Retry:
return False

version_error = False # to see if there is already existing incompatible version

try:
load_library()
assert library_version() == plugin_version()
return True # everything's good - we are done here!
except (OSError, AssertionError):
pass # ok we have a problem (no library or an old one)
except VersionError:
version_error = True # we have a problem - incompatible version
except OSError:
pass # ok we have a problem (no library or it failed to load - e.g. 32/64bit mismatch)

# The crayfishviewer binary cannot be found
reply = QMessageBox.question(parent_widget,
Expand All @@ -115,7 +96,7 @@ def ensure_library_installed(parent_widget=None):

# Download it
try:
filename = os.path.join(os.path.dirname(__file__), crayfish_zipfile)
filename = os.path.join(destFolder, crayfish_zipfile)
downloadBinPackage(packageUrl, filename)
except IOError, err:
QMessageBox.critical(parent_widget,
Expand All @@ -131,17 +112,25 @@ def ensure_library_installed(parent_widget=None):
if platformVersion == 'Windows':
downloadExtraLibs(parent_widget)

success_msg = "Download and installation successful."
restart_msg = "QGIS needs to be restarted in order to complete " + \
"an update to the Crayfish Library. Please restart QGIS."

# do not rewrite the library - it is already loaded and it would cause havoc
if version_error:
addExtractLibraryMarker()
QMessageBox.information(parent_widget, 'Download complete', success_msg + "\n\n" + restart_msg)
return False

# try to extract the downloaded file - may require a restart if the files exist already
if not extractBinPackage(filename):
QMessageBox.information(parent_widget,
'Restart Required',
"QGIS needs to be restarted in order to complete an update to the Crayfish "
"Library. Please restart QGIS.")
QMessageBox.information(parent_widget, 'Download complete', success_msg + "\n\n" + restart_msg)
return False

QMessageBox.information(parent_widget, 'Download complete', success_msg)

# now try again
load_library()
QMessageBox.information(parent_widget, 'Succeeded', "Download and installation successful." )
return True


Expand Down Expand Up @@ -181,11 +170,13 @@ def extractBinPackage(destinationFileName):
z.close()
return True
except IOError:
tmpF = open( os.path.join(destFolder, 'EXTRACT_DLL'), 'w' )
tmpF.write(' ')
tmpF.close()
addExtractLibraryMarker()
return False

def addExtractLibraryMarker():
tmpF = open( os.path.join(destFolder, 'EXTRACT_DLL'), 'w' )
tmpF.write(' ')
tmpF.close()

def extractBinPackageAfterRestart():
# Windows users may have opted to download a pre-compiled lib
Expand All @@ -197,13 +188,12 @@ def extractBinPackageAfterRestart():
return True

stillExists = True
dllFileName = os.path.join(destFolder, 'crayfish.dll')
for retryCount in range(3):
try:
os.unlink( dllFileName )
os.unlink(libpath)
stillExists = False
break
except WindowsError:
except OSError:
time.sleep(3)

if stillExists:
Expand Down

0 comments on commit 0eccbca

Please sign in to comment.