|
|
|
|
|
from PyQt4.QtCore import ( |
|
QDateTime, |
|
SIGNAL, |
|
) |
|
from PyQt4.QtGui import ( |
|
QMessageBox, |
|
) |
|
from PyQt4.QtSql import ( |
|
QSqlDatabase, |
|
QSqlQuery, |
|
QSqlTableModel, |
|
QVariant, |
|
) |
|
|
|
import ConfigParser |
|
import os |
|
import shutil |
|
import sys |
|
import threading |
|
import urllib2 |
|
import zipfile |
|
|
|
from util import ( |
|
doAlert, |
|
doQuestion, |
|
) |
|
|
|
|
|
class DataModel(QSqlTableModel): |
|
defaultDbFile = os.path.join( |
|
os.path.split(os.path.realpath(__file__))[0], "models.sqlite") |
|
|
|
def __init__(self, parent=None, filename=None): |
|
self.installThreads = {} |
|
self.processes = set() |
|
if filename is None: |
|
filename = DataModel.defaultDbFile |
|
self.db = QSqlDatabase.addDatabase('QSQLITE') |
|
print >> sys.stderr, "Open database at %s" % filename |
|
self.db.setDatabaseName(filename) |
|
self.db.open() |
|
query = QSqlQuery( |
|
'SELECT COUNT(*) ' |
|
'FROM sqlite_master ' |
|
'WHERE type="table" AND tbl_name="models"', |
|
self.db) |
|
if not query.next() or query.value(0).toInt()[0] < 1: |
|
|
|
print >> sys.stderr, "Table not find, create the table" |
|
query = QSqlQuery( |
|
'CREATE TABLE models (' |
|
'ID INTEGER PRIMARY KEY AUTOINCREMENT, ' |
|
'name TEXT, ' |
|
'status TEXT, ' |
|
'srclang TEXT, ' |
|
'trglang TEXT, ' |
|
'date DATE, ' |
|
'path TEXT, ' |
|
'mosesini TEXT, ' |
|
'origin TEXT, ' |
|
'originMode TEXT, ' |
|
'deleted TEXT)', |
|
self.db) |
|
if query.next(): |
|
print >> sys.stderr, query.value(0).toString() |
|
|
|
|
|
|
|
query = QSqlQuery( |
|
'UPDATE models SET deleted="False" WHERE deleted="True"', self.db) |
|
query = QSqlQuery( |
|
'UPDATE models SET status="READY" WHERE status="ON"', self.db) |
|
super(DataModel, self).__init__(parent, self.db) |
|
self.setTable("models") |
|
self.select() |
|
self.setEditStrategy(QSqlTableModel.OnFieldChange) |
|
|
|
def destroy(self): |
|
bExit = False |
|
for i in self.installThreads: |
|
t, flag = self.installThreads[i] |
|
if t.isAlive() and flag: |
|
if not bExit: |
|
if not doQuestion( |
|
"Installing process is running in the background, " |
|
"do you want to terminate them and exit?"): |
|
return False |
|
else: |
|
bExit = True |
|
self.installThreads[i][1] = False |
|
t.join() |
|
if self.db: |
|
self.db.close() |
|
self.db = None |
|
return True |
|
|
|
def getQSqlDatabase(self): |
|
return self.db |
|
|
|
def getRowID(self, row): |
|
record = self.record(row) |
|
return record.value('ID') |
|
|
|
def delModel(self, row): |
|
record = self.record(row) |
|
if str(record.value('deleted').toString()) == 'True': |
|
self.emit( |
|
SIGNAL("messageBox(QString)"), |
|
"The model is deleting, please be patient!") |
|
return |
|
|
|
text = '''You are going to delete the selected model entry. |
|
Do you also want to delete all the model files on the disk? |
|
Click "Yes" to delete model entry and model files. |
|
Click "No" to delete model entry but keep model files. |
|
Click "Cancel" to do nothing.''' |
|
reply = QMessageBox.question( |
|
None, 'Message', text, QMessageBox.Yes, QMessageBox.No, |
|
QMessageBox.Cancel) |
|
|
|
if reply == QMessageBox.Cancel: |
|
return |
|
else: |
|
record.setValue('deleted', 'True') |
|
self.changeRecord(row, record) |
|
|
|
def delModelThread(): |
|
irowid, _ = record.value("ID").toInt() |
|
if irowid in self.installThreads: |
|
t, flag = self.installThreads[irowid] |
|
if t.isAlive() and flag: |
|
self.installThreads[irowid][1] = False |
|
t.join() |
|
if reply == QMessageBox.Yes: |
|
destDir = str(record.value("path").toString()) |
|
try: |
|
shutil.rmtree(destDir) |
|
except Exception as e: |
|
self.emit( |
|
SIGNAL("messageBox(QString)"), |
|
"Failed to remove dir: " + destDir) |
|
print >> sys.stderr, str(e) |
|
self.removeRow(row) |
|
|
|
|
|
t = threading.Thread(target=delModelThread) |
|
t.start() |
|
|
|
def newEntry(self): |
|
import random |
|
rec = self.record() |
|
for i in xrange(1, 10): |
|
rec.setValue(i, QVariant(str(random.random()))) |
|
self.insertRecord(-1, rec) |
|
doAlert(self.query().lastInsertId().toString()) |
|
|
|
def changeRecord(self, curRow, record): |
|
|
|
self.setRecord(curRow, record) |
|
|
|
|
|
def installModel(self, installParam): |
|
dest = installParam['dest'] |
|
|
|
if not os.path.exists(dest): |
|
try: |
|
os.makedirs(str(dest)) |
|
except: |
|
doAlert("Failed to create install directory: %s" % dest) |
|
return |
|
|
|
rec = self.record() |
|
rec.setValue('name', installParam['modelName']) |
|
rec.setValue('status', 'Fetching Source...') |
|
rec.setValue('path', dest) |
|
rec.setValue('origin', installParam['source']) |
|
rec.setValue('originMode', installParam['sourceMode']) |
|
rec.setValue('date', QDateTime.currentDateTime()) |
|
rec.setValue('deleted', 'False') |
|
self.insertRecord(-1, rec) |
|
rowid = self.query().lastInsertId() |
|
|
|
|
|
def installThread(irowid): |
|
|
|
|
|
def updateRecord(keyvalues): |
|
curRow = None |
|
|
|
for i in xrange(0, self.rowCount()): |
|
if self.record(i).value("ID") == rowid: |
|
curRow = i |
|
break |
|
if curRow is not None: |
|
record = self.record(curRow) |
|
for key in keyvalues: |
|
record.setValue(key, keyvalues[key]) |
|
self.changeRecord(curRow, record) |
|
return curRow |
|
|
|
def checkExit(): |
|
|
|
if irowid not in self.installThreads or not self.installThreads[irowid][1]: |
|
return True |
|
else: |
|
return False |
|
|
|
def markExit(): |
|
if irowid in self.installThreads: |
|
self.installThreads[irowid][1] = False |
|
|
|
def statusMessageLogMarkExit(status=None, message=None, |
|
exception=None): |
|
if status is not None: |
|
updateRecord({'status': status}) |
|
if message is not None: |
|
self.emit(SIGNAL("messageBox(QString)"), message) |
|
print >> sys.stderr, message |
|
if exception is not None: |
|
print >> sys.stderr, str(exception) |
|
markExit() |
|
|
|
|
|
|
|
destFile = os.path.join(str(dest), "model.zip") |
|
|
|
destDir = os.path.join(str(dest), "model") |
|
|
|
if installParam['sourceMode'] == 'Local': |
|
fin = fout = None |
|
try: |
|
inFile = str(installParam['source']) |
|
total_size = os.path.getsize(inFile) |
|
fin = open(inFile, 'rb') |
|
chunk_size = 52428800 |
|
fout = open(destFile, 'wb') |
|
content = fin.read(chunk_size) |
|
download_size = content_size = len(content) |
|
lastMsg = "" |
|
while content_size > 0: |
|
|
|
if checkExit(): |
|
return statusMessageLogMarkExit() |
|
fout.write(content) |
|
if total_size > 0: |
|
msg = 'COPY %.0f%%' % ( |
|
download_size * 100.0 / total_size) |
|
else: |
|
msg = 'COPY %d MB' % (download_size / 1048576) |
|
if msg != lastMsg: |
|
updateRecord({'status': msg}) |
|
lastMsg = msg |
|
content = fin.read(chunk_size) |
|
content_size = len(content) |
|
download_size += content_size |
|
except Exception as e: |
|
return statusMessageLogMarkExit( |
|
status=( |
|
'Failed copying from: %s' |
|
% installParam['source']), |
|
message=( |
|
"Failed copy model: %s" |
|
% installParam['modelName']), |
|
exception=e) |
|
finally: |
|
if fin: |
|
fin.close() |
|
if fout: |
|
fout.close() |
|
|
|
elif installParam['sourceMode'] == 'Internet': |
|
conn = fout = None |
|
try: |
|
conn = urllib2.urlopen(str(installParam['source'])) |
|
total_size = int(conn.headers['Content-Length']) |
|
chunk_size = 1048576 |
|
fout = open(destFile, 'wb') |
|
content = conn.read(chunk_size) |
|
download_size = content_size = len(content) |
|
lastMsg = "" |
|
while content_size > 0: |
|
|
|
if checkExit(): |
|
return statusMessageLogMarkExit() |
|
fout.write(content) |
|
if total_size > 0: |
|
msg = 'DOWNLOAD %.0f%%' % ( |
|
download_size * 100.0 / total_size) |
|
else: |
|
msg = 'DOWNLOAD %d MB' % (download_size / 1048576) |
|
if msg != lastMsg: |
|
updateRecord({'status': msg}) |
|
lastMsg = msg |
|
content = conn.read(chunk_size) |
|
content_size = len(content) |
|
download_size += content_size |
|
except Exception as e: |
|
return statusMessageLogMarkExit( |
|
status=( |
|
'Failed downloading from: %s' |
|
% installParam['source']), |
|
message=( |
|
"Failed download model: %s" |
|
% installParam['modelName']), |
|
exception=e) |
|
finally: |
|
if conn: |
|
conn.close() |
|
if fout: |
|
fout.close() |
|
else: |
|
return statusMessageLogMarkExit( |
|
status='Unsupported source mode: %s' |
|
% installParam['sourceMode']) |
|
|
|
|
|
zfile = fout = None |
|
try: |
|
zfile = zipfile.ZipFile(destFile) |
|
|
|
if "model.ini" not in zfile.namelist(): |
|
return statusMessageLogMarkExit( |
|
status=( |
|
'Missing model.ini in model file: %s' |
|
% installParam['sourceMode']), |
|
message=( |
|
"Invalid modle file format because model.ini " |
|
"is missing in the zipped model file, exit " |
|
"installation for model %s" |
|
% installParam['modelName'])) |
|
chunk_size = 52428800 |
|
|
|
total_size = 0 |
|
for name in zfile.namelist(): |
|
total_size += zfile.getinfo(name).file_size |
|
download_size = 0 |
|
lastMsg = "" |
|
for i, name in enumerate(zfile.namelist()): |
|
(dirname, filename) = os.path.split(name) |
|
outDir = os.path.join(destDir, dirname) |
|
if not os.path.exists(outDir): |
|
os.makedirs(outDir) |
|
if filename: |
|
fin = zfile.open(name, 'r') |
|
outFile = os.path.join(destDir, name) |
|
fout = open(outFile, 'wb') |
|
content = fin.read(chunk_size) |
|
content_size = len(content) |
|
download_size += content_size |
|
while content_size > 0: |
|
|
|
if checkExit(): |
|
return statusMessageLogMarkExit() |
|
fout.write(content) |
|
if total_size > 0: |
|
msg = 'UNZIP %.0f%%' % ( |
|
download_size * 100.0 / total_size) |
|
else: |
|
msg = 'UNZIP %d MB' % ( |
|
download_size / 1048576) |
|
if msg != lastMsg: |
|
updateRecord({'status': msg}) |
|
lastMsg = msg |
|
content = fin.read(chunk_size) |
|
content_size = len(content) |
|
download_size += content_size |
|
fin.close() |
|
fout.close() |
|
except Exception as e: |
|
return statusMessageLogMarkExit( |
|
status=( |
|
'Failed unzipping from: %s' % installParam['source']), |
|
message=( |
|
"Failed unzip model: %s" % installParam['modelName']), |
|
exception=e) |
|
finally: |
|
if zfile: |
|
zfile.close() |
|
if fin: |
|
fin.close() |
|
if fout: |
|
fout.close() |
|
|
|
|
|
try: |
|
modelini = os.path.join(destDir, "model.ini") |
|
cp = ConfigParser.RawConfigParser() |
|
cp.read(modelini) |
|
mosesini = os.path.join(destDir, 'moses.ini') |
|
if not os.path.exists(mosesini): |
|
raise Exception("Moses ini doesn't exist") |
|
updateRecord({ |
|
'srclang': cp.get("Language", 'Source Language').upper(), |
|
'trglang': cp.get("Language", 'Target Language').upper(), |
|
'mosesini': mosesini}, |
|
) |
|
except Exception as e: |
|
return statusMessageLogMarkExit( |
|
status='ERROR model format %s' % installParam['source'], |
|
message=( |
|
"Unspported model format: %s" |
|
% installParam['modelName']), |
|
exception=e) |
|
|
|
statusMessageLogMarkExit( |
|
status='READY', |
|
message="Model %s Installed!" % installParam['modelName']) |
|
|
|
self.emit(SIGNAL("modelInstalled()")) |
|
return |
|
|
|
|
|
|
|
irowid, _ = rowid.toInt() |
|
t = threading.Thread(target=installThread, args=(irowid, )) |
|
if irowid in self.installThreads: |
|
print >> sys.stderr, ( |
|
"table rowid %d already has a thread running, stop it" |
|
% irowid) |
|
self.installThreads[irowid][1] = False |
|
self.installThreads[irowid] = [t, True] |
|
t.start() |
|
|