From 9745671f3b3f20caab8507560be29fd8c7b30e48 Mon Sep 17 00:00:00 2001 From: Kiooeht Date: Tue, 16 Aug 2011 00:36:04 -0700 Subject: [PATCH] Automatically download and install Pesterchum updates (zip/source only so far) --- libs/magic.py | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++ menus.py | 2 +- pesterchum.py | 11 ++- version.py | 101 +++++++++++++++++++++- 4 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 libs/magic.py diff --git a/libs/magic.py b/libs/magic.py new file mode 100644 index 0000000..6c30a0c --- /dev/null +++ b/libs/magic.py @@ -0,0 +1,233 @@ +""" +Adam Hupp (adam@hupp.org) +http://github.com/ahupp/python-magic + +magic is a wrapper around the libmagic file identification library. + +See README for more information. + +Usage: + +>>> import magic +>>> magic.from_file("testdata/test.pdf") +'PDF document, version 1.2' +>>> magic.from_file("testdata/test.pdf", mime=True) +'application/pdf' +>>> magic.from_buffer(open("testdata/test.pdf").read(1024)) +'PDF document, version 1.2' +>>> + + +""" + +import os.path +import ctypes +import ctypes.util + +from ctypes import c_char_p, c_int, c_size_t, c_void_p + +class MagicException(Exception): pass + +class Magic: + """ + Magic is a wrapper around the libmagic C library. + + """ + + def __init__(self, mime=False, magic_file=None, mime_encoding=False): + """ + Create a new libmagic wrapper. + + mime - if True, mimetypes are returned instead of textual descriptions + mime_encoding - if True, codec is returned + magic_file - use a mime database other than the system default + + """ + flags = MAGIC_NONE + if mime: + flags |= MAGIC_MIME + elif mime_encoding: + flags |= MAGIC_MIME_ENCODING + + self.cookie = magic_open(flags) + + magic_load(self.cookie, magic_file) + + + def from_buffer(self, buf): + """ + Identify the contents of `buf` + """ + return magic_buffer(self.cookie, buf) + + def from_file(self, filename): + """ + Identify the contents of file `filename` + raises IOError if the file does not exist + """ + + if not os.path.exists(filename): + raise IOError("File does not exist: " + filename) + + return magic_file(self.cookie, filename) + + def __del__(self): + if self.cookie: + magic_close(self.cookie) + self.cookie = None + +_magic_mime = None +_magic = None + +def _get_magic_mime(): + global _magic_mime + if not _magic_mime: + _magic_mime = Magic(mime=True) + return _magic_mime + +def _get_magic(): + global _magic + if not _magic: + _magic = Magic() + return _magic + +def _get_magic_type(mime): + if mime: + return _get_magic_mime() + else: + return _get_magic() + +def from_file(filename, mime=False): + m = _get_magic_type(mime) + return m.from_file(filename) + +def from_buffer(buffer, mime=False): + m = _get_magic_type(mime) + return m.from_buffer(buffer) + + + + +libmagic = None +# Let's try to find magic or magic1 +dll = ctypes.util.find_library('magic') or ctypes.util.find_library('magic1') + +# This is necessary because find_library returns None if it doesn't find the library +if dll: + libmagic = ctypes.CDLL(dll) + +if not libmagic or not libmagic._name: + import sys + platform_to_lib = {'darwin': '/opt/local/lib/libmagic.dylib', + 'win32': 'magic1.dll'} + if sys.platform in platform_to_lib: + try: + libmagic = ctypes.CDLL(platform_to_lib[sys.platform]) + except OSError: + pass + +if not libmagic or not libmagic._name: + # It is better to raise an ImportError since we are importing magic module + raise ImportError('failed to find libmagic. Check your installation') + +magic_t = ctypes.c_void_p + +def errorcheck(result, func, args): + err = magic_error(args[0]) + if err is not None: + raise MagicException(err) + else: + return result + +magic_open = libmagic.magic_open +magic_open.restype = magic_t +magic_open.argtypes = [c_int] + +magic_close = libmagic.magic_close +magic_close.restype = None +magic_close.argtypes = [magic_t] + +magic_error = libmagic.magic_error +magic_error.restype = c_char_p +magic_error.argtypes = [magic_t] + +magic_errno = libmagic.magic_errno +magic_errno.restype = c_int +magic_errno.argtypes = [magic_t] + +magic_file = libmagic.magic_file +magic_file.restype = c_char_p +magic_file.argtypes = [magic_t, c_char_p] +magic_file.errcheck = errorcheck + + +_magic_buffer = libmagic.magic_buffer +_magic_buffer.restype = c_char_p +_magic_buffer.argtypes = [magic_t, c_void_p, c_size_t] +_magic_buffer.errcheck = errorcheck + + +def magic_buffer(cookie, buf): + return _magic_buffer(cookie, buf, len(buf)) + + +magic_load = libmagic.magic_load +magic_load.restype = c_int +magic_load.argtypes = [magic_t, c_char_p] +magic_load.errcheck = errorcheck + +magic_setflags = libmagic.magic_setflags +magic_setflags.restype = c_int +magic_setflags.argtypes = [magic_t, c_int] + +magic_check = libmagic.magic_check +magic_check.restype = c_int +magic_check.argtypes = [magic_t, c_char_p] + +magic_compile = libmagic.magic_compile +magic_compile.restype = c_int +magic_compile.argtypes = [magic_t, c_char_p] + + + +MAGIC_NONE = 0x000000 # No flags + +MAGIC_DEBUG = 0x000001 # Turn on debugging + +MAGIC_SYMLINK = 0x000002 # Follow symlinks + +MAGIC_COMPRESS = 0x000004 # Check inside compressed files + +MAGIC_DEVICES = 0x000008 # Look at the contents of devices + +MAGIC_MIME = 0x000010 # Return a mime string + +MAGIC_MIME_ENCODING = 0x000400 # Return the MIME encoding + +MAGIC_CONTINUE = 0x000020 # Return all matches + +MAGIC_CHECK = 0x000040 # Print warnings to stderr + +MAGIC_PRESERVE_ATIME = 0x000080 # Restore access time on exit + +MAGIC_RAW = 0x000100 # Don't translate unprintable chars + +MAGIC_ERROR = 0x000200 # Handle ENOENT etc as real errors + +MAGIC_NO_CHECK_COMPRESS = 0x001000 # Don't check for compressed files + +MAGIC_NO_CHECK_TAR = 0x002000 # Don't check for tar files + +MAGIC_NO_CHECK_SOFT = 0x004000 # Don't check magic entries + +MAGIC_NO_CHECK_APPTYPE = 0x008000 # Don't check application type + +MAGIC_NO_CHECK_ELF = 0x010000 # Don't check for elf details + +MAGIC_NO_CHECK_ASCII = 0x020000 # Don't check for ascii files + +MAGIC_NO_CHECK_TROFF = 0x040000 # Don't check ascii/troff + +MAGIC_NO_CHECK_FORTRAN = 0x080000 # Don't check ascii/fortran + +MAGIC_NO_CHECK_TOKENS = 0x100000 # Don't check ascii/tokens diff --git a/menus.py b/menus.py index c89e328..efc282b 100644 --- a/menus.py +++ b/menus.py @@ -1513,7 +1513,7 @@ class UpdatePesterchum(QtGui.QDialog): layout_0 = QtGui.QVBoxLayout() layout_0.addWidget(self.title) - self.ok = QtGui.QPushButton("D0WNL04D N0W", self) + self.ok = QtGui.QPushButton("D0WNL04D 4ND 1N5T4LL N0W", self) self.ok.setDefault(True) self.connect(self.ok, QtCore.SIGNAL('clicked()'), self, QtCore.SLOT('accept()')) diff --git a/pesterchum.py b/pesterchum.py index 64c1277..23913ec 100644 --- a/pesterchum.py +++ b/pesterchum.py @@ -13,6 +13,7 @@ import re import socket import platform from time import strftime, time +import threading, Queue missing = [] try: @@ -1714,7 +1715,7 @@ class PesterWindow(MovingWindow): @QtCore.pyqtSlot() def updatePC(self): - QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.updatemenu.url, QtCore.QUrl.TolerantMode)) + version.updateDownload(str(self.updatemenu.url)) self.updatemenu = None @QtCore.pyqtSlot() def noUpdatePC(self): @@ -3119,11 +3120,9 @@ class MainProgram(QtCore.QObject): @QtCore.pyqtSlot() def runUpdateSlot(self): - import Queue - import threading q = Queue.Queue(1) - s = threading.Thread(target=version.updateCheck, args=(q,0)) # the 0 is to stop - w = threading.Thread(target=self.showUpdate, args=(q,0)) # stupid syntax errors + s = threading.Thread(target=version.updateCheck, args=(q,)) + w = threading.Thread(target=self.showUpdate, args=(q,)) w.start() s.start() self.widget.config.set('lastUCheck', int(time())) @@ -3248,7 +3247,7 @@ Click this message to never see this again.") self.disconnect(self.irc, QtCore.SIGNAL('finished()'), self, QtCore.SLOT('restartIRC()')) - def showUpdate(self, q,num): + def showUpdate(self, q): new_url = q.get() if new_url[0]: self.widget.pcUpdate.emit(new_url[0], new_url[1]) diff --git a/version.py b/version.py index d8a6f33..014ae0e 100644 --- a/version.py +++ b/version.py @@ -1,6 +1,8 @@ import urllib import re import time +import tarfile, zipfile +import os, sys, shutil USER_TYPE = "edge" # user - for normal people @@ -8,6 +10,17 @@ USER_TYPE = "edge" # dev - used to be for git users, now it's anyone with the 3.41 beta # edge - new git stuff. bleeding edge, do not try at home (kiooeht version) +INSTALL_TYPE = "source" + # installer - Windows/Mac installer (exe/dmg) + # zip - Windows zip (zip) + # source - Win/Linux/Mac source code (zip/tar) + +OS_TYPE = sys.platform # win32, linux, darwin +if OS_TYPE.startswith("linux"): + OS_TYPE = "linux" +elif OS_TYPE == "darwin": + OS_TYPE = "mac" + _pcMajor = "3.41" _pcMinor = "2" _pcStatus = "B" # A = alpha @@ -59,9 +72,9 @@ def verStrToNum(ver): full = ver[:ver.find(":")] return full,w.group(1),w.group(2),w.group(3),w.group(4),w.group(5) -def updateCheck(q,num): +def updateCheck(q): time.sleep(3) - data = urllib.urlencode({"type" : USER_TYPE}) + data = urllib.urlencode({"type" : USER_TYPE, "os" : OS_TYPE, "install" : INSTALL_TYPE}) try: f = urllib.urlopen("http://distantsphere.com/pesterchum.php?" + data) except: @@ -87,3 +100,87 @@ def updateCheck(q,num): return q.put((False,0)) print "A new version of Pesterchum is avaliable!" q.put((full,url)) + + +def removeCopies(path): + for f in os.listdir(path): + filePath = os.path.join(path, f) + if not os.path.isdir(filePath): + if os.path.exists(filePath[7:]): + os.remove(filePath[7:]) + else: + removeCopies(filePath) + +def copyUpdate(path): + for f in os.listdir(path): + filePath = os.path.join(path, f) + if not os.path.isdir(filePath): + shutil.copy2(filePath, filePath[7:]) + else: + if not os.path.exists(filePath[7:]): + os.mkdir(filePath[7:]) + copyUpdate(filePath) + +def updateExtract(url, extension): + if extension: + fn = "update" + extension + urllib.urlretrieve(url, fn) + else: + fn = urllib.urlretrieve(url)[0] + if tarfile.is_tarfile(fn): + extension = ".tar.gz" + elif zipfile.is_zipfile(fn): + extension = ".zip" + else: + try: + from libs import magic # :O I'M IMPORTING /MAGIC/!! HOLY SHIT! + mime = magic.from_file(fn, mime=True) + if mime == 'application/octet-stream': + extension = ".exe" + except: + pass + + print url, fn, extension + + if extension == ".exe": + pass + elif extension == ".zip" or extension.startswith(".tar"): + if extension == ".zip": + from zipfile import is_zipfile as is_updatefile, ZipFile as openupdate + print "Opening .zip" + elif extension.startswith(".tar"): + from tarfile import is_tarfile as is_updatefile, open as openupdate + print "Opening .tar" + + if is_updatefile(fn): + update = openupdate(fn, 'r') + if os.path.exists("tmp"): + shutil.rmtree("tmp") + os.mkdir("tmp") + update.extractall("tmp") + tmp = os.listdir("tmp") + if os.path.exists("update"): + shutil.rmtree("update") + if len(tmp) == 1 and \ + os.path.isdir("tmp/"+tmp[0]): + shutil.move("tmp/"+tmp[0], "update") + else: + shutil.move("tmp", "update") + os.rmdir("tmp") + os.remove(fn) + removeCopies("update") + copyUpdate("update") + shutil.rmtree("update") + +def updateDownload(url): + extensions = [".exe", ".zip", ".tar.gz", ".tar.bz2"] + found = False + for e in extensions: + if url.endswith(e): + found = True + updateExtract(url, e) + if not found: + if url.startswith("https://github.com/") and url.count('/') == 4: + updateExtract(url+"/tarball/master", None) + else: + updateExtract(url, None)