Skip to content

Commit

Permalink
Add JSON collection import feature
Browse files Browse the repository at this point in the history
  • Loading branch information
jtsage committed Mar 29, 2024
1 parent 8949ebd commit 004d694
Show file tree
Hide file tree
Showing 21 changed files with 944 additions and 17 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"iconfilename",
"IMAGEFILENAME",
"IMAGENAME",
"importjson",
"incomeperhour",
"ingame",
"inputbinding",
Expand Down
188 changes: 185 additions & 3 deletions lib/modAssist_func_lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,14 @@ const general = {
filters : [{ name : 'JSON', extensions : ['json'] }],
}).then(async (result) => {
if ( result.canceled ) {
jsonLog.debug('Save CSV Cancelled')
jsonLog.debug('Save JSON Cancelled')
} else {
try {
fs.writeFileSync(result.filePath, JSON.stringify({
collection_color : parseInt(serveIPC.modCollect.getSafeNote(collection, 'color')),
collection_description : serveIPC.modCollect.getSafeNote(collection, 'tagline'),
extra_downloads : [],
download_direct : [],
download_unzip : [],
force_frozen : serveIPC.modCollect.getSafeNote(collection, 'frozen'),
game_version : serveIPC.modCollect.getSafeNote(collection, 'version'),
server_downloads : serveIPC.modCollect.getSafeNote(collection, 'websiteDL'),
Expand Down Expand Up @@ -472,6 +473,186 @@ const general = {
})
},

importJSON_process : (filename) => {
const jsonLog = serveIPC.log.group('json-import')
try {
const import_json = JSON.parse(fs.readFileSync(filename))
const [isValid, errors] = general.importJSON_check(import_json)
if ( !isValid ) {
serveIPC.windowLib.doDialogBox('main', { type : 'warning', messageL10n : 'import_json_read_failed' })
const errorList = errors.map((x) => x.message).join(' -- ')
jsonLog.warning('Could not validate JSON file', errorList)
return
}
serveIPC.windowLib.createNamedWindow('importjson', {
thisImport : import_json,
})
} catch (err) {
jsonLog.warning('Could not load JSON file', err)
serveIPC.windowLib.doDialogBox('main', { type : 'warning', messageL10n : 'import_json_load_failed' })
}
},

importJSON_check : (parsed_json) => {
const Ajv = require('ajv')
const addFormats = require('ajv-formats')
const ajv = new Ajv({strict : false})
addFormats(ajv)
const import_schema = {
properties : {
collection_color : { type : 'integer', maximum : 8, minimum : 0 },
collection_description : { type : 'string', maxLength : 255 },
download_direct : {
type : 'array',
items : { type : 'string', format : 'uri' },
},
download_unzip : {
type : 'array',
items : { type : 'string', format : 'uri' },
},
force_frozen : { type : 'boolean', default : false },
game_version : {
type : 'integer',
enum : [22, 19, 17, 15, 13],
},
server_downloads : { type : 'boolean', default : false },
server_id : { type : 'string', maxLength : 255 },
server_name : { type : 'string', maxLength : 255 },
server_password : { type : 'string', maxLength : 100 },
server_website : { anyOf : [
{ type : 'string', maxLength : 255, format : 'uri' },
{ type : 'string', maxLength : 0 },
]},
},
required : [
'collection_color',
'collection_description',
'force_frozen',
'game_version',
'server_downloads',
'server_id',
'server_name',
'server_password',
'server_website'
],
type : 'object',
}
const isValid = ajv.validate(import_schema, parsed_json)
return [isValid, ajv.errors]
},

importJSON_download : (uri, unpack, collection) => {
if ( serveIPC.isDownloading ) { return }
const modDLLog = serveIPC.log.group('mod-download')

serveIPC.isDownloading = true
modDLLog.info('Downloading Collection', collection)
modDLLog.debug('Download Link', uri)

serveIPC.dlRequest = net.request(uri)

serveIPC.dlRequest.on('response', (response) => {
modDLLog.info('Got download', response.statusCode)

if ( response.statusCode < 200 || response.statusCode >= 400 ) {
serveIPC.windowLib.doDialogBox('importjson', {
type : 'error',
titleL10n : 'download_title',
message : `${serveIPC.__('download_failed')} :: ${serveIPC.modCollect.mapCollectionToName(collection)}`,
})
serveIPC.isDownloading = false
} else {
serveIPC.loadJSON.open('download', true)

serveIPC.loadJSON.total(response.headers['content-length'] || 0, true, true)
serveIPC.loadJSON.current(0, true, true)

const dlFile = new URL(uri).pathname.split('/').filter(Boolean).pop()

const dlPath = unpack ?
path.join(app.getPath('temp'), `${collection}.zip`) :
path.join(serveIPC.modCollect.mapCollectionToFolder(collection), dlFile)

if ( !unpack && fs.existsSync(dlPath) ) { fs.rmSync(dlPath) }

const writeStream = fs.createWriteStream(dlPath)

response.pipe(writeStream)
response.on('data', (chunk) => { serveIPC.loadJSON.current(chunk.length, false, true) })

writeStream.on('finish', () => {
writeStream.close()
if ( !unpack ) {
modDLLog.info('Download complete, finished!')
modDLLog.info('Unzipping complete')
serveIPC.isDownloading = false
serveIPC.loadJSON.hide()
serveIPC.refFunc.processModFolders(true)
return
}

modDLLog.info('Download complete, unzipping')
try {
let zipBytesSoFar = 0
const zipBytesTotal = fs.statSync(dlPath).size

serveIPC.loadJSON.open('zip')
serveIPC.loadJSON.total(100, true)

const zipReadStream = fs.createReadStream(dlPath)

zipReadStream.on('data', (chunk) => {
zipBytesSoFar += chunk.length
serveIPC.loadJSON.current(((zipBytesSoFar/zipBytesTotal)*100).toFixed(2), true)
})

zipReadStream.on('error', (err) => {
serveIPC.loadJSON.hide()
serveIPC.isDownloading = false
modDLLog.danger('Download unzip failed', err)
})

zipReadStream.on('end', () => {
modDLLog.info('Unzipping complete')
zipReadStream.close()
fs.unlinkSync(dlPath)
serveIPC.isDownloading = false
serveIPC.loadJSON.hide()
serveIPC.refFunc.processModFolders(true)
})
const extract = unzip.Extract({ path : serveIPC.modCollect.mapCollectionToFolder(collection) })

zipReadStream.pipe(extract)

extract.on('error', (err) => {
serveIPC.loadJSON.hide()
serveIPC.isDownloading = false
modDLLog.danger('Download unzip failed', err)
})
} catch (err) {
modDLLog.danger('Download failed', response.statusCode, err)
serveIPC.loadJSON.hide()
}
})
}
})
serveIPC.dlRequest.on('abort', () => {
modDLLog.notice('Download canceled')
serveIPC.isDownloading = false
serveIPC.loadJSON.hide()
})
serveIPC.dlRequest.on('error', (err) => {
serveIPC.windowLib.doDialogBox('importjson', {
type : 'error',
titleL10n : 'download_title',
message : `${serveIPC.__('download_failed')} :: ${err}`,
})
modDLLog.warning('Network error', err)
serveIPC.isDownloading = false
serveIPC.loadJSON.hide()
})
serveIPC.dlRequest.end()
},
}

const discord = {
Expand Down Expand Up @@ -1018,9 +1199,10 @@ const processor = {
serveIPC.modCollect.processMods()
},

addFolderTracking : (folder) => {
addFolderTracking : (folder, json_import = false) => {
const collectKey = serveIPC.modCollect.getFolderHash(folder)
if ( serveIPC.modCollect.collections.has(collectKey) ) {
if ( json_import ) { return }
serveIPC.windowLib.doDialogBox('main', {
type : 'error',
messageL10n : 'drop_folder_exists',
Expand Down
33 changes: 24 additions & 9 deletions lib/modAssist_window_lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ module.exports.windowLib = class {
'detail',
'find',
// 'gamelog',
// 'import',
'importjson',
// 'load',
'main',
// 'mini',
Expand Down Expand Up @@ -160,6 +162,13 @@ module.exports.windowLib = class {
callback : () => { return },
refocusCallback : true,
},
importjson : {
winName : 'importjson',
HTMLFile : 'importjson.html',
subWindowArgs : { preload : 'importjsonWindow' },
callback : (windowArgs) => { this.sendModList(windowArgs, 'fromMain_importData', 'importjson', false ) },
handleURLinWin : true,
},
/* eslint-enable sort-keys */
}

Expand All @@ -179,6 +188,7 @@ module.exports.windowLib = class {
find : null,
gamelog : null,
import : null,
importjson : null,
load : null,
main : null,
notes : null,
Expand Down Expand Up @@ -218,6 +228,7 @@ module.exports.windowLib = class {
constructor() {
this.loading = new loadingWindow(this)
serveIPC.loadWindow = this.loading
serveIPC.loadJSON = new loadingWindow(this, 'importjson')

this.populateContextIcons()

Expand Down Expand Up @@ -947,14 +958,17 @@ module.exports.windowLib = class {
class loadingWindow {
#win = null

#parent = null

#total = 0
#current = 0

/**
* Create loading window interface
* @param {module:modAssist_window_lib.windowLib} win
*/
constructor(win) {
constructor(win, parent = 'main') {
this.#parent = parent
this.#win = win
}

Expand Down Expand Up @@ -988,17 +1002,17 @@ class loadingWindow {
}

/** @type {boolean} */
get isReady () { return this.#win.isVisible('main') }
get isReady () { return this.#win.isVisible(this.#parent) }

#doCount (whichCount, amount, reset, inMB) {
const thisCounter = `#${whichCount}`
this[thisCounter] = ( reset ) ? amount : amount + this[thisCounter]

if ( whichCount === 'current' && this.#win.isVisible('main') ) {
if ( whichCount === 'current' && this.#win.isVisible(this.#parent) ) {
this.#win.win.main.setProgressBar(Math.max(0, Math.min(1, this.#current / this.#total)))
}

this.#win.sendToValidWindow('main', `fromMain_loading_${whichCount}`, this[thisCounter], inMB)
this.#win.sendToValidWindow(this.#parent, `fromMain_loading_${whichCount}`, this[thisCounter], inMB)
}

/**
Expand All @@ -1007,16 +1021,16 @@ class loadingWindow {
*/
hide (time = 1250) {
setTimeout(() => {
if ( this.#win.isValid('main') ) { this.#win.win.main.setProgressBar(-1)}
this.#win.sendToValidWindow('main', 'formMain_loading_hide')
if ( this.#win.isValid(this.#parent) ) { this.#win.win.main.setProgressBar(-1)}
this.#win.sendToValidWindow(this.#parent, 'formMain_loading_hide')
}, time)
}

/**
* Do not display the count in the loading window
*/
noCount () {
this.#win.sendToValidWindow('main', 'fromMain_loadingNoCount')
this.#win.sendToValidWindow(this.#parent, 'fromMain_loadingNoCount')
}

/**
Expand All @@ -1029,9 +1043,9 @@ class loadingWindow {
const winSubTitle = this.#win.l10n.syncStringLookup((l10nKey) !== 'launch' ? `loading_${l10nKey}_subtitle` : 'launch_game')
const dlCancel = this.#win.l10n.syncStringLookup('cancel_download')

this.#win.sendToValidWindow('main', 'formMain_loadingTitles', winTitle, winSubTitle, dlCancel)
this.#win.sendToValidWindow(this.#parent, 'formMain_loadingTitles', winTitle, winSubTitle, dlCancel)
if ( isDownload ) {
this.#win.sendToValidWindow('main', 'fromMain_loadingDownload')
this.#win.sendToValidWindow(this.#parent, 'fromMain_loadingDownload')
}
}
}
Expand Down Expand Up @@ -1105,6 +1119,7 @@ module.exports.defaultSettings = class {
folder : this.#winDef(800, 500),
gamelog : this.#winDef(1000, 500),
import : this.#winDef(750, 500),
importjson : this.#winDef(900, 700),
main : this.#winDef(1000, 700),
mini : this.#winDef(400, 80),
notes : this.#winDef(800, 500),
Expand Down
1 change: 1 addition & 0 deletions lib/modUtilLib.js
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ const serveIPC = {
isModCacheDisabled : false,
isProcessing : false,

loadJSON : { current : () => {} },
loadWindow : { current : () => {} },

devControls : { 13 : false, 15 : false, 17 : false, 19 : false, 22 : false },
Expand Down
Loading

0 comments on commit 004d694

Please sign in to comment.