Added preliminary support for versions.

This commit is contained in:
Joey Payne 2014-04-05 20:42:29 -06:00
commit dd1074803f
9 changed files with 199 additions and 56 deletions

Binary file not shown.

Binary file not shown.

28
files/nw-versions.txt Normal file
View file

@ -0,0 +1,28 @@
0.9.2
0.9.1
0.9.0
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.4.2
0.4.1
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

227
main.py
View file

@ -1,5 +1,6 @@
from utils import zip_files, join_files, log, get_temp_dir from utils import zip_files, join_files, log, get_temp_dir
import sys, os, glob, json, re, shutil, stat, tarfile, zipfile, traceback, platform import sys, os, glob, json, re, shutil, stat, tarfile
import zipfile, traceback, platform, filecmp
from PySide import QtGui, QtCore from PySide import QtGui, QtCore
from PySide.QtGui import QApplication from PySide.QtGui import QApplication
from PySide.QtNetwork import QHttp from PySide.QtNetwork import QHttp
@ -62,17 +63,36 @@ class Setting(object):
self.extract_class = TarFile.open self.extract_class = TarFile.open
self.extract_args = ('r:gz',) self.extract_args = ('r:gz',)
def save_file_path(self, version):
if self.full_file_path:
return self.full_file_path.format(version)
return ''
def extract_file_path(self, version):
if self.extract_file:
return self.extract_file.format(version)
return ''
def set_extra_attributes_from_keyword_args(self, kwargs): def set_extra_attributes_from_keyword_args(self, kwargs):
for undefined_key, undefined_value in kwargs.items(): for undefined_key, undefined_value in kwargs.items():
setattr(self, undefined_key, undefined_value) setattr(self, undefined_key, undefined_value)
def get_file_bytes(self): def get_file_bytes(self, version):
fbytes = None fbytes = []
file = self.extract_class(self.full_file_path, *self.extract_args) file = self.extract_class(self.save_file_path(version), *self.extract_args)
if self.file_ext == '.gz': for extract_path, dest_path in zip(self.extract_files, self.dest_files):
fbytes = file.extractfile(self.extract_file).read() new_bytes = None
elif self.file_ext == '.zip': try:
fbytes = file.read(self.extract_file) if self.file_ext == '.gz':
new_bytes = file.extractfile(extract_path.format(version)).read()
elif self.file_ext == '.zip':
new_bytes = file.read(extract_path.format(version))
except KeyError as e:
log(e)
if new_bytes is not None:
fbytes.append((dest_path, new_bytes))
return fbytes return fbytes
def __repr__(self): def __repr__(self):
@ -81,20 +101,20 @@ class Setting(object):
class MainWindow(QtGui.QWidget): class MainWindow(QtGui.QWidget):
base_url = 'http://node-webkit.s3-website-us-east-1.amazonaws.com/v0.9.2/' base_url = 'http://node-webkit.s3-website-us-east-1.amazonaws.com/v{}/'
app_settings = {'main': Setting(name='main', display_name='Main file', required=True, type='file', file_types='*.html *.php *.htm'), app_settings = {'main': Setting(name='main', display_name='Main file', required=True, type='file', file_types='*.html *.php *.htm'),
'name': Setting(name='name', display_name='App Name', required=True, type='string'), 'name': Setting(name='name', display_name='App Name', required=True, type='string'),
'description': Setting(name='description', default_default_value='', type='string'), 'description': Setting(name='description', default_default_value='', type='string'),
'version': Setting(name='version', default_value='0.1.0', type='string'), 'version': Setting(name='version', default_value='0.1.0', type='string'),
'keywords':Setting(name='keywords', default_value='', type='string'), 'keywords':Setting(name='keywords', default_value='', type='string'),
'nodejs': Setting('nodejs', 'Include Nodejs', default_value=True, type='check'), 'nodejs': Setting('nodejs', 'Include Nodejs', default_value=True, type='check'),
'node-main': Setting('node-main', 'Custom Nodejs Path', default_value='', type='file', file_types='*.js'), 'node-main': Setting('node-main', 'Alt. Nodejs', default_value='', type='file', file_types='*.js'),
'single-instance': Setting('single-instance', 'Single Instance', default_value=True, type='check')} 'single-instance': Setting('single-instance', 'Single Instance', default_value=True, type='check')}
webkit_settings = {'plugin': Setting('plugin', 'Load plugins', default_value=False, type='check'), webkit_settings = {'plugin': Setting('plugin', 'Load plugins', default_value=False, type='check'),
'java': Setting('java', 'Load Java', default_value=False, type='check'), 'java': Setting('java', 'Load Java', default_value=False, type='check'),
'page-cache': Setting('page-cache', 'Enable Page Cache', default_value=False, type='check')} 'page-cache': Setting('page-cache', 'Page Cache', default_value=False, type='check')}
window_settings = {'title': Setting(name='title', default_value='', type='string'), window_settings = {'title': Setting(name='title', default_value='', type='string'),
'icon': Setting('icon', 'Window Icon', default_value='', type='file', file_types='*.png *.jpg *.jpeg'), 'icon': Setting('icon', 'Window Icon', default_value='', type='file', file_types='*.png *.jpg *.jpeg'),
@ -106,32 +126,44 @@ class MainWindow(QtGui.QWidget):
'max_height': Setting('max_height', default_value=None, type='string'), 'max_height': Setting('max_height', default_value=None, type='string'),
'toolbar': Setting('toolbar', 'Show Toolbar', default_value=False, type='check'), 'toolbar': Setting('toolbar', 'Show Toolbar', default_value=False, type='check'),
'always-on-top': Setting('always-on-top', 'Always on top', default_value=False, type='check'), 'always-on-top': Setting('always-on-top', 'Always on top', default_value=False, type='check'),
'frame': Setting('frame', 'Show Window Frame', default_value=True, type='check'), 'frame': Setting('frame', 'Window Frame', default_value=True, type='check'),
'show_in_taskbar': Setting('show_in_taskbar', 'Show In Taskbar', default_value=True, type='check'), 'show_in_taskbar': Setting('show_in_taskbar', 'Taskbar', default_value=True, type='check'),
'visible': Setting('visible', default_value=True, type='check'), 'visible': Setting('visible', default_value=True, type='check'),
'resizable': Setting('resizable', default_value=False, type='check'), 'resizable': Setting('resizable', default_value=False, type='check'),
'fullscreen': Setting('fullscreen', default_value=False, type='check')} 'fullscreen': Setting('fullscreen', default_value=False, type='check')}
export_settings = {'windows': Setting('windows', default_value=False, type='check', export_settings = {'windows': Setting('windows', default_value=False, type='check',
url=base_url+'node-webkit-v0.9.2-win-ia32.zip', url=base_url+'node-webkit-v{}-win-ia32.zip',
extract_file='nw.exe', extract_files=['nw.exe', 'nw.pak', 'icudt.dll', 'libEGL.dll', 'libGLESv2.dll'],
dest_file='nw.exe'), dest_files=['nw.exe', 'nw.pak', 'icudt.dll', 'libEGL.dll', 'libGLESv2.dll']),
'mac': Setting('mac', default_value=False, type='check', 'mac': Setting('mac', default_value=False, type='check',
url=base_url+'node-webkit-v0.9.2-osx-ia32.zip', url=base_url+'node-webkit-v{}-osx-ia32.zip',
extract_file='node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework', extract_file='node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework',
dest_file=os.path.join('node-webkit.app','Contents', extract_files=['node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework',
'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/nw.pak'],
dest_files=[os.path.join('node-webkit.app','Contents',
'Frameworks','node-webkit Framework.framework', 'Frameworks','node-webkit Framework.framework',
'node-webkit Framework')), 'node-webkit Framework'),
os.path.join('node-webkit.app','Contents',
'Frameworks','node-webkit Framework.framework',
'Resources', 'nw.pak')]),
'linux-x64': Setting('linux-x64', default_value=False, type='check', 'linux-x64': Setting('linux-x64', default_value=False, type='check',
url=base_url+'node-webkit-v0.9.2-linux-x64.tar.gz', url=base_url+'node-webkit-v{}-linux-x64.tar.gz',
extract_file='node-webkit-v0.9.2-linux-x64/nw', extract_file='node-webkit-v{}-linux-x64/nw',
dest_file='nw'), extract_files=['node-webkit-v{}-linux-x64/nw',
'node-webkit-v{}-linux-x64/nw.pak'],
dest_files=['nw', 'nw.pak']),
'linux-x32': Setting('linux-x32', default_value=False, type='check', 'linux-x32': Setting('linux-x32', default_value=False, type='check',
url=base_url+'node-webkit-v0.9.2-linux-ia32.tar.gz', url=base_url+'node-webkit-v{}-linux-ia32.tar.gz',
extract_file='node-webkit-v0.9.2-linux-ia32/nw', extract_file='node-webkit-v{}-linux-ia32/nw',
dest_file='nw')} extract_files=['node-webkit-v{}-linux-ia32/nw',
'node-webkit-v{}-linux-ia32/nw.pak'],
dest_files=['nw', 'nw.pak'])}
_setting_groups = [app_settings, webkit_settings, window_settings, export_settings] download_settings = {'nw_version':Setting('nw_version', 'Node-webkit version', default_value='0.9.2', type='list'),
'force_download': Setting('force_download', default_value=False, type='check')}
_setting_groups = [app_settings, webkit_settings, window_settings, export_settings, download_settings]
application_setting_order = ['main', 'node-main', 'name', 'description', 'version', 'keywords', application_setting_order = ['main', 'node-main', 'name', 'description', 'version', 'keywords',
'nodejs', 'single-instance', 'plugin', 'nodejs', 'single-instance', 'plugin',
@ -143,6 +175,8 @@ class MainWindow(QtGui.QWidget):
export_setting_order = ['windows', 'linux-x64', 'mac', 'linux-x32'] export_setting_order = ['windows', 'linux-x64', 'mac', 'linux-x32']
download_setting_order = ['nw_version', 'force_download']
def __init__(self, width, height, parent=None): def __init__(self, width, height, parent=None):
super(MainWindow, self).__init__(parent) super(MainWindow, self).__init__(parent)
@ -175,6 +209,7 @@ class MainWindow(QtGui.QWidget):
self.app_settings_widget = self.createApplicationSettings() self.app_settings_widget = self.createApplicationSettings()
self.win_settings_widget = self.createWindowSettings() self.win_settings_widget = self.createWindowSettings()
self.ex_settings_widget = self.createExportSettings() self.ex_settings_widget = self.createExportSettings()
self.dl_settings_widget = self.createDownloadSettings()
self.directory_chooser_widget = self.createDirectoryChoose() self.directory_chooser_widget = self.createDirectoryChoose()
def add_widgets_to_main_layout(self): def add_widgets_to_main_layout(self):
@ -182,6 +217,7 @@ class MainWindow(QtGui.QWidget):
self.main_layout.addWidget(self.app_settings_widget) self.main_layout.addWidget(self.app_settings_widget)
self.main_layout.addWidget(self.win_settings_widget) self.main_layout.addWidget(self.win_settings_widget)
self.main_layout.addWidget(self.ex_settings_widget) self.main_layout.addWidget(self.ex_settings_widget)
self.main_layout.addWidget(self.dl_settings_widget)
self.main_layout.addLayout(self.download_bar_widget) self.main_layout.addLayout(self.download_bar_widget)
def option_settings_enabled(self, is_enabled): def option_settings_enabled(self, is_enabled):
@ -189,11 +225,23 @@ class MainWindow(QtGui.QWidget):
self.app_settings_widget.setEnabled(is_enabled) self.app_settings_widget.setEnabled(is_enabled)
self.win_settings_widget.setEnabled(is_enabled) self.win_settings_widget.setEnabled(is_enabled)
self.ex_settings_widget.setEnabled(is_enabled) self.ex_settings_widget.setEnabled(is_enabled)
self.dl_settings_widget.setEnabled(is_enabled)
def export(self, export_button, cancel_button): def export(self, export_button, cancel_button):
#self.delete_files_if_forced()
self.get_files_to_download() self.get_files_to_download()
self.try_to_download_files() self.try_to_download_files()
def delete_files_if_forced(self):
forced = self.getSetting('force_download').value
if forced:
for ex_setting in self.export_settings.values():
for dest_file in ex_setting.dest_files:
f_path = os.path.join('files', ex_setting.name, dest_file)
if os.path.exists(f_path):
os.remove(f_path)
def get_files_to_download(self): def get_files_to_download(self):
self.files_to_download = [] self.files_to_download = []
for setting_name, setting in self.export_settings.items(): for setting_name, setting in self.export_settings.items():
@ -212,16 +260,27 @@ class MainWindow(QtGui.QWidget):
#But in the weird event that this does happen, we are prepared! #But in the weird event that this does happen, we are prepared!
QtGui.QMessageBox.information(self, 'Export Options Empty!', 'Please choose one of the export options!') QtGui.QMessageBox.information(self, 'Export Options Empty!', 'Please choose one of the export options!')
def selected_version(self):
print self.getSetting('nw_version').value
return self.getSetting('nw_version').value
def download_file_with_error_handling(self): def download_file_with_error_handling(self):
setting = self.files_to_download.pop() setting = self.files_to_download.pop()
try: try:
self.downloadFile(setting.url, setting) self.downloadFile(setting.url.format(self.selected_version(), self.selected_version()), setting)
except Exception as e: except Exception as e:
if os.path.exists(setting.full_file_path): if os.path.exists(setting.save_file_path(self.selected_version())):
os.remove(setting.full_file_path) os.remove(setting.save_file_path(self.selected_version()))
error = ''.join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])) error = ''.join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
self.show_error(error) self.show_error(error)
self.enable_ui_after_error()
def enable_ui_after_error(self):
self.enableUI()
self.progress_text = ''
self.progress_bar.setVisible(False)
self.cancel_button.setEnabled(False)
def show_error(self, exception): def show_error(self, exception):
QtGui.QMessageBox.information(self, 'Error!', str(exception)) QtGui.QMessageBox.information(self, 'Error!', str(exception))
@ -319,6 +378,7 @@ class MainWindow(QtGui.QWidget):
self.show_error('Download failed: {}.'.format(response_header.reasonPhrase())) self.show_error('Download failed: {}.'.format(response_header.reasonPhrase()))
self.httpRequestAborted = True self.httpRequestAborted = True
self.http.abort() self.http.abort()
self.enable_ui_after_error()
def httpRequestFinished(self, requestId, error): def httpRequestFinished(self, requestId, error):
if requestId != self.httpGetId: if requestId != self.httpGetId:
@ -336,13 +396,17 @@ class MainWindow(QtGui.QWidget):
if error: if error:
self.outFile.remove() self.outFile.remove()
self.show_error('Download failed: {}.'.format(self.http.errorString())) self.show_error('Download failed: {}.'.format(self.http.errorString()))
self.enable_ui_after_error()
self.continueDownloadingOrExtract() self.continueDownloadingOrExtract()
def continueDownloadingOrExtract(self): def continueDownloadingOrExtract(self):
if self.files_to_download: if self.files_to_download:
setting = self.files_to_download.pop() self.progress_bar.setVisible(True)
self.downloadFile(setting.url, setting) self.cancel_button.setEnabled(True)
self.disableUIWhileWorking()
self.download_file_with_error_handling()
else: else:
self.progress_text = 'Done.' self.progress_text = 'Done.'
self.cancel_button.setEnabled(False) self.cancel_button.setEnabled(False)
@ -365,15 +429,15 @@ class MainWindow(QtGui.QWidget):
def makeOutputFilesInBackground(self): def makeOutputFilesInBackground(self):
self.ex_button.setEnabled(False) self.ex_button.setEnabled(False)
self.runInBackground('makeOutputDirs', self.doneMakingFiles) self.runInBackground('makeOutputDirs', self.doneMakingFiles)
def doneMakingFiles(self): def doneMakingFiles(self):
self.ex_button.setEnabled(True) self.ex_button.setEnabled(self.requiredSettingsFilled())
self.progress_text = 'Done Exporting.' self.progress_text = 'Done Exporting.'
self.enableUI() self.enableUI()
if self.output_err: if self.output_err:
self.show_error(self.output_err) self.show_error(self.output_err)
self.enable_ui_after_error()
def extractFilesInBackground(self): def extractFilesInBackground(self):
self.progress_text = 'Extracting.' self.progress_text = 'Extracting.'
@ -384,33 +448,38 @@ class MainWindow(QtGui.QWidget):
def extractFiles(self): def extractFiles(self):
self.extract_error = None self.extract_error = None
for setting_name, setting in self.export_settings.items(): for setting_name, setting in self.export_settings.items():
save_file_path = setting.save_file_path(self.selected_version())
try: try:
if setting.value: if setting.value:
extract_path = os.path.join('files', setting.name) extract_path = os.path.join('files', setting.name)
if not os.path.exists(os.path.join(extract_path, setting.dest_file)): if os.path.exists(save_file_path):
fbytes = setting.get_file_bytes() for dest_file, fbytes in setting.get_file_bytes(self.selected_version()):
with open(os.path.join(extract_path, dest_file), 'wb+') as d:
d.write(fbytes)
self.progress_text += '.'
with open(os.path.join(extract_path, setting.dest_file), 'wb+') as d: if os.path.exists(save_file_path):
d.write(fbytes) os.remove(save_file_path) #remove the zip/tar since we don't need it anymore
if os.path.exists(setting.full_file_path):
os.remove(setting.full_file_path) #remove the zip/tar since we don't need it anymore
self.progress_text += '.' self.progress_text += '.'
except (tarfile.ReadError, zipfile.BadZipfile) as e: except (tarfile.ReadError, zipfile.BadZipfile) as e:
if os.path.exists(setting.full_file_path): if os.path.exists(save_file_path):
os.remove(setting.full_file_path) os.remove(save_file_path)
self.extract_error = e self.extract_error = e
#cannot use GUI in thread to notify user. Save it for later #cannot use GUI in thread to notify user. Save it for later
def doneExtracting(self): def doneExtracting(self):
self.ex_button.setEnabled(True) self.ex_button.setEnabled(self.requiredSettingsFilled())
if self.extract_error: if self.extract_error:
self.progress_text = 'Error extracting.' self.progress_text = 'Error extracting.'
self.show_error('There were one or more errors with your zip/tar files. They were deleted. Please try to export again.') self.show_error('There were one or more errors with your zip/tar files. They were deleted. Please try to export again.')
self.enable_ui_after_error()
else: else:
self.progress_text = 'Done extracting.' self.progress_text = 'Done extracting.'
self.makeOutputFilesInBackground() self.makeOutputFilesInBackground()
@ -429,13 +498,23 @@ class MainWindow(QtGui.QWidget):
self.progress_bar.setValue(bytesRead) self.progress_bar.setValue(bytesRead)
def downloadFile(self, path, setting): def downloadFile(self, path, setting):
self.progress_text = 'Downloading {}'.format(path.replace(self.base_url,'')) self.progress_text = 'Downloading {}'.format(path.replace(self.base_url.format(self.selected_version()),''))
url = QUrl(path) url = QUrl(path)
fileInfo = QFileInfo(url.path()) fileInfo = QFileInfo(url.path())
fileName = setting.full_file_path fileName = setting.save_file_path(self.selected_version())
if QFile.exists(fileName) or QFile.exists(os.path.join('files', setting.name, setting.dest_file)): archive_exists = QFile.exists(fileName)
dest_files_exist = True
for dest_file in setting.dest_files:
dest_file_path = os.path.join('files', setting.name, dest_file)
dest_files_exist &= QFile.exists(dest_file_path)
forced = self.getSetting('force_download').value
if (archive_exists or dest_files_exist) and not forced:
self.continueDownloadingOrExtract() self.continueDownloadingOrExtract()
return #QFile.remove(fileName) return #QFile.remove(fileName)
@ -571,6 +650,8 @@ class MainWindow(QtGui.QWidget):
return self.createTextInputWithFileSetting(name) return self.createTextInputWithFileSetting(name)
elif setting.type == 'check': elif setting.type == 'check':
return self.createCheckSetting(name) return self.createCheckSetting(name)
elif setting.type == 'list':
return self.createListSetting(name)
def createWindowSettings(self): def createWindowSettings(self):
@ -587,7 +668,15 @@ class MainWindow(QtGui.QWidget):
groupBox.setLayout(vlayout) groupBox.setLayout(vlayout)
return groupBox return groupBox
def createLayout(self, settings, cols=2):
def createDownloadSettings(self):
groupBox = QtGui.QGroupBox("Download Settings")
vlayout = self.createLayout(self.download_setting_order)
groupBox.setLayout(vlayout)
return groupBox
def createLayout(self, settings, cols=3):
hlayout = QtGui.QHBoxLayout() hlayout = QtGui.QHBoxLayout()
layouts = [] layouts = []
@ -662,16 +751,22 @@ class MainWindow(QtGui.QWidget):
for sgroup in self._setting_groups: for sgroup in self._setting_groups:
for setting in sgroup.values(): for setting in sgroup.values():
widget = self.findChildByName(setting.name) widget = self.findChildByName(setting.name)
if setting.type == 'string' or setting.type == 'file': if setting.type == 'string' or setting.type == 'file':
old_val = '' old_val = ''
if setting.default_value is not None: if setting.default_value is not None:
old_val = setting.default_value old_val = setting.default_value
setting.value = old_val setting.value = old_val
widget.setText(str(old_val)) widget.setText(str(old_val))
elif setting.type == 'check': elif setting.type == 'check':
old_val = False old_val = False
if setting.default_value is not None: if setting.default_value is not None:
old_val = setting.default_value old_val = setting.default_value
setting.value = old_val setting.value = old_val
widget.setChecked(old_val) widget.setChecked(old_val)
@ -681,6 +776,8 @@ class MainWindow(QtGui.QWidget):
setting.value = obj.text() setting.value = obj.text()
elif setting.type == 'check': elif setting.type == 'check':
setting.value = obj.isChecked() setting.value = obj.isChecked()
elif setting.type == 'list':
setting.value = obj.currentText()
self.ex_button.setEnabled(self.requiredSettingsFilled()) self.ex_button.setEnabled(self.requiredSettingsFilled())
@ -692,9 +789,7 @@ class MainWindow(QtGui.QWidget):
if os.path.exists(self.projectDir()): if os.path.exists(self.projectDir()):
dirs_filled_out = True dirs_filled_out = True
self.app_settings_widget.setEnabled(dirs_filled_out) self.option_settings_enabled(dirs_filled_out)
self.win_settings_widget.setEnabled(dirs_filled_out)
self.ex_settings_widget.setEnabled(dirs_filled_out)
def getSetting(self, name): def getSetting(self, name):
for setting_group in self._setting_groups: for setting_group in self._setting_groups:
@ -719,6 +814,26 @@ class MainWindow(QtGui.QWidget):
return hlayout return hlayout
def createListSetting(self, name):
hlayout = QtGui.QHBoxLayout()
setting = self.getSetting(name)
combo = QtGui.QComboBox()
combo.setObjectName(setting.name)
combo.currentIndexChanged.connect(self.callWithObject('settingChanged', combo, setting))
combo.editTextChanged.connect(self.callWithObject('settingChanged', combo, setting))
if combo.findData(setting.value) == -1:
combo.addItem(setting.value)
combo.addItem('0.8.5')
hlayout.addWidget(combo)
return hlayout
def generate_json(self): def generate_json(self):
if 'webkit' not in self.original_packagejson: if 'webkit' not in self.original_packagejson:
self.original_packagejson['webkit'] = {} self.original_packagejson['webkit'] = {}
@ -856,7 +971,7 @@ class MainWindow(QtGui.QWidget):
if ex_setting.name == 'windows': if ex_setting.name == 'windows':
ext = '.exe' ext = '.exe'
nw_path = os.path.join(export_dest, ex_setting.dest_file) nw_path = os.path.join(export_dest, ex_setting.dest_files[0])
dest_binary_path = os.path.join(export_dest, self.projectName()+ext) dest_binary_path = os.path.join(export_dest, self.projectName()+ext)
join_files(os.path.join(export_dest, self.projectName()+ext), nw_path, zip_file) join_files(os.path.join(export_dest, self.projectName()+ext), nw_path, zip_file)