distromanager.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. const fs = require('fs')
  2. const path = require('path')
  3. const request = require('request')
  4. const ConfigManager = require('./configmanager')
  5. const logger = require('./loggerutil')('%c[DistroManager]', 'color: #a02d2a; font-weight: bold')
  6. /**
  7. * Represents the download information
  8. * for a specific module.
  9. */
  10. class Artifact {
  11. /**
  12. * Parse a JSON object into an Artifact.
  13. *
  14. * @param {Object} json A JSON object representing an Artifact.
  15. *
  16. * @returns {Artifact} The parsed Artifact.
  17. */
  18. static fromJSON(json){
  19. return Object.assign(new Artifact(), json)
  20. }
  21. /**
  22. * Get the MD5 hash of the artifact. This value may
  23. * be undefined for artifacts which are not to be
  24. * validated and updated.
  25. *
  26. * @returns {string} The MD5 hash of the Artifact or undefined.
  27. */
  28. getHash(){
  29. return this.MD5
  30. }
  31. /**
  32. * @returns {number} The download size of the artifact.
  33. */
  34. getSize(){
  35. return this.size
  36. }
  37. /**
  38. * @returns {string} The download url of the artifact.
  39. */
  40. getURL(){
  41. return this.url
  42. }
  43. /**
  44. * @returns {string} The artifact's destination path.
  45. */
  46. getPath(){
  47. return this.path
  48. }
  49. }
  50. exports.Artifact
  51. /**
  52. * Represents a the requirement status
  53. * of a module.
  54. */
  55. class Required {
  56. /**
  57. * Parse a JSON object into a Required object.
  58. *
  59. * @param {Object} json A JSON object representing a Required object.
  60. *
  61. * @returns {Required} The parsed Required object.
  62. */
  63. static fromJSON(json){
  64. if(json == null){
  65. return new Required(true, true)
  66. } else {
  67. return new Required(json.value == null ? true : json.value, json.def == null ? true : json.def)
  68. }
  69. }
  70. constructor(value, def){
  71. this.value = value
  72. this.default = def
  73. }
  74. /**
  75. * Get the default value for a required object. If a module
  76. * is not required, this value determines whether or not
  77. * it is enabled by default.
  78. *
  79. * @returns {boolean} The default enabled value.
  80. */
  81. isDefault(){
  82. return this.default
  83. }
  84. /**
  85. * @returns {boolean} Whether or not the module is required.
  86. */
  87. isRequired(){
  88. return this.value
  89. }
  90. }
  91. exports.Required
  92. /**
  93. * Represents a module.
  94. */
  95. class Module {
  96. /**
  97. * Parse a JSON object into a Module.
  98. *
  99. * @param {Object} json A JSON object representing a Module.
  100. * @param {string} serverid The ID of the server to which this module belongs.
  101. *
  102. * @returns {Module} The parsed Module.
  103. */
  104. static fromJSON(json, serverid){
  105. return new Module(json.id, json.name, json.type, json.required, json.artifact, json.subModules, serverid)
  106. }
  107. /**
  108. * Resolve the default extension for a specific module type.
  109. *
  110. * @param {string} type The type of the module.
  111. *
  112. * @return {string} The default extension for the given type.
  113. */
  114. static _resolveDefaultExtension(type){
  115. switch (type) {
  116. case exports.Types.Library:
  117. case exports.Types.ForgeHosted:
  118. case exports.Types.LiteLoader:
  119. case exports.Types.ForgeMod:
  120. return 'jar'
  121. case exports.Types.LiteMod:
  122. return 'litemod'
  123. case exports.Types.File:
  124. default:
  125. return 'jar' // There is no default extension really.
  126. }
  127. }
  128. constructor(id, name, type, required, artifact, subModules, serverid) {
  129. this.identifier = id
  130. this.type = type
  131. this._resolveMetaData()
  132. this.name = name
  133. this.required = Required.fromJSON(required)
  134. this.artifact = Artifact.fromJSON(artifact)
  135. this._resolveArtifactPath(artifact.path, serverid)
  136. this._resolveSubModules(subModules, serverid)
  137. }
  138. _resolveMetaData(){
  139. try {
  140. const m0 = this.identifier.split('@')
  141. this.artifactExt = m0[1] || Module._resolveDefaultExtension(this.type)
  142. const m1 = m0[0].split(':')
  143. this.artifactClassifier = m1[3] || undefined
  144. this.artifactVersion = m1[2] || '???'
  145. this.artifactID = m1[1] || '???'
  146. this.artifactGroup = m1[0] || '???'
  147. } catch (err) {
  148. // Improper identifier
  149. logger.error('Improper ID for module', this.identifier, err)
  150. }
  151. }
  152. _resolveArtifactPath(artifactPath, serverid){
  153. const pth = artifactPath == null ? path.join(...this.getGroup().split('.'), this.getID(), this.getVersion(), `${this.getID()}-${this.getVersion()}${this.artifactClassifier != undefined ? `-${this.artifactClassifier}` : ''}.${this.getExtension()}`) : artifactPath
  154. switch (this.type){
  155. case exports.Types.Library:
  156. case exports.Types.ForgeHosted:
  157. case exports.Types.LiteLoader:
  158. this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'libraries', pth)
  159. break
  160. case exports.Types.ForgeMod:
  161. case exports.Types.LiteMod:
  162. this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'modstore', pth)
  163. break
  164. case exports.Types.VersionManifest:
  165. this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'versions', this.getIdentifier(), `${this.getIdentifier()}.json`)
  166. break
  167. case exports.Types.File:
  168. default:
  169. this.artifact.path = path.join(ConfigManager.getInstanceDirectory(), serverid, pth)
  170. break
  171. }
  172. }
  173. _resolveSubModules(json, serverid){
  174. const arr = []
  175. if(json != null){
  176. for(let sm of json){
  177. arr.push(Module.fromJSON(sm, serverid))
  178. }
  179. }
  180. this.subModules = arr.length > 0 ? arr : null
  181. }
  182. /**
  183. * @returns {string} The full, unparsed module identifier.
  184. */
  185. getIdentifier(){
  186. return this.identifier
  187. }
  188. /**
  189. * @returns {string} The name of the module.
  190. */
  191. getName(){
  192. return this.name
  193. }
  194. /**
  195. * @returns {Required} The required object declared by this module.
  196. */
  197. getRequired(){
  198. return this.required
  199. }
  200. /**
  201. * @returns {Artifact} The artifact declared by this module.
  202. */
  203. getArtifact(){
  204. return this.artifact
  205. }
  206. /**
  207. * @returns {string} The maven identifier of this module's artifact.
  208. */
  209. getID(){
  210. return this.artifactID
  211. }
  212. /**
  213. * @returns {string} The maven group of this module's artifact.
  214. */
  215. getGroup(){
  216. return this.artifactGroup
  217. }
  218. /**
  219. * @returns {string} The identifier without he version or extension.
  220. */
  221. getVersionlessID(){
  222. return this.getGroup() + ':' + this.getID()
  223. }
  224. /**
  225. * @returns {string} The identifier without the extension.
  226. */
  227. getExtensionlessID(){
  228. return this.getIdentifier().split('@')[0]
  229. }
  230. /**
  231. * @returns {string} The version of this module's artifact.
  232. */
  233. getVersion(){
  234. return this.artifactVersion
  235. }
  236. /**
  237. * @returns {string} The classifier of this module's artifact
  238. */
  239. getClassifier(){
  240. return this.artifactClassifier
  241. }
  242. /**
  243. * @returns {string} The extension of this module's artifact.
  244. */
  245. getExtension(){
  246. return this.artifactExt
  247. }
  248. /**
  249. * @returns {boolean} Whether or not this module has sub modules.
  250. */
  251. hasSubModules(){
  252. return this.subModules != null
  253. }
  254. /**
  255. * @returns {Array.<Module>} An array of sub modules.
  256. */
  257. getSubModules(){
  258. return this.subModules
  259. }
  260. /**
  261. * @returns {string} The type of the module.
  262. */
  263. getType(){
  264. return this.type
  265. }
  266. }
  267. exports.Module
  268. /**
  269. * Represents a server configuration.
  270. */
  271. class Server {
  272. /**
  273. * Parse a JSON object into a Server.
  274. *
  275. * @param {Object} json A JSON object representing a Server.
  276. *
  277. * @returns {Server} The parsed Server object.
  278. */
  279. static fromJSON(json){
  280. const mdls = json.modules
  281. json.modules = []
  282. const serv = Object.assign(new Server(), json)
  283. serv._resolveModules(mdls)
  284. return serv
  285. }
  286. _resolveModules(json){
  287. const arr = []
  288. for(let m of json){
  289. arr.push(Module.fromJSON(m, this.getID()))
  290. }
  291. this.modules = arr
  292. }
  293. /**
  294. * @returns {string} The ID of the server.
  295. */
  296. getID(){
  297. return this.id
  298. }
  299. /**
  300. * @returns {string} The name of the server.
  301. */
  302. getName(){
  303. return this.name
  304. }
  305. /**
  306. * @returns {string} The description of the server.
  307. */
  308. getDescription(){
  309. return this.description
  310. }
  311. /**
  312. * @returns {string} The URL of the server's icon.
  313. */
  314. getIcon(){
  315. return this.icon
  316. }
  317. /**
  318. * @returns {string} The version of the server configuration.
  319. */
  320. getVersion(){
  321. return this.version
  322. }
  323. /**
  324. * @returns {string} The IP address of the server.
  325. */
  326. getAddress(){
  327. return this.address
  328. }
  329. /**
  330. * @returns {string} The minecraft version of the server.
  331. */
  332. getMinecraftVersion(){
  333. return this.minecraftVersion
  334. }
  335. /**
  336. * @returns {boolean} Whether or not this server is the main
  337. * server. The main server is selected by the launcher when
  338. * no valid server is selected.
  339. */
  340. isMainServer(){
  341. return this.mainServer
  342. }
  343. /**
  344. * @returns {boolean} Whether or not the server is autoconnect.
  345. * by default.
  346. */
  347. isAutoConnect(){
  348. return this.autoconnect
  349. }
  350. /**
  351. * @returns {Array.<Module>} An array of modules for this server.
  352. */
  353. getModules(){
  354. return this.modules
  355. }
  356. }
  357. exports.Server
  358. /**
  359. * Represents the Distribution Index.
  360. */
  361. class DistroIndex {
  362. /**
  363. * Parse a JSON object into a DistroIndex.
  364. *
  365. * @param {Object} json A JSON object representing a DistroIndex.
  366. *
  367. * @returns {DistroIndex} The parsed Server object.
  368. */
  369. static fromJSON(json){
  370. const servers = json.servers
  371. json.servers = []
  372. const distro = Object.assign(new DistroIndex(), json)
  373. distro._resolveServers(servers)
  374. distro._resolveMainServer()
  375. return distro
  376. }
  377. _resolveServers(json){
  378. const arr = []
  379. for(let s of json){
  380. arr.push(Server.fromJSON(s))
  381. }
  382. this.servers = arr
  383. }
  384. _resolveMainServer(){
  385. for(let serv of this.servers){
  386. if(serv.mainServer){
  387. this.mainServer = serv.id
  388. return
  389. }
  390. }
  391. // If no server declares default_selected, default to the first one declared.
  392. this.mainServer = (this.servers.length > 0) ? this.servers[0].getID() : null
  393. }
  394. /**
  395. * @returns {string} The version of the distribution index.
  396. */
  397. getVersion(){
  398. return this.version
  399. }
  400. /**
  401. * @returns {string} The URL to the news RSS feed.
  402. */
  403. getRSS(){
  404. return this.rss
  405. }
  406. /**
  407. * @returns {Array.<Server>} An array of declared server configurations.
  408. */
  409. getServers(){
  410. return this.servers
  411. }
  412. /**
  413. * Get a server configuration by its ID. If it does not
  414. * exist, null will be returned.
  415. *
  416. * @param {string} id The ID of the server.
  417. *
  418. * @returns {Server} The server configuration with the given ID or null.
  419. */
  420. getServer(id){
  421. for(let serv of this.servers){
  422. if(serv.id === id){
  423. return serv
  424. }
  425. }
  426. return null
  427. }
  428. /**
  429. * Get the main server.
  430. *
  431. * @returns {Server} The main server.
  432. */
  433. getMainServer(){
  434. return this.mainServer != null ? this.getServer(this.mainServer) : null
  435. }
  436. }
  437. exports.DistroIndex
  438. exports.Types = {
  439. Library: 'Library',
  440. ForgeHosted: 'ForgeHosted',
  441. Forge: 'Forge', // Unimplemented
  442. LiteLoader: 'LiteLoader',
  443. ForgeMod: 'ForgeMod',
  444. LiteMod: 'LiteMod',
  445. File: 'File',
  446. VersionManifest: 'VersionManifest'
  447. }
  448. let DEV_MODE = false
  449. const DISTRO_PATH = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
  450. const DEV_PATH = path.join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json')
  451. let data = null
  452. /**
  453. * @returns {Promise.<DistroIndex>}
  454. */
  455. exports.pullRemote = function(){
  456. if(DEV_MODE){
  457. return exports.pullLocal()
  458. }
  459. return new Promise((resolve, reject) => {
  460. const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
  461. //const distroURL = 'https://gist.githubusercontent.com/dscalzi/53b1ba7a11d26a5c353f9d5ae484b71b/raw/'
  462. const opts = {
  463. url: distroURL,
  464. timeout: 2500
  465. }
  466. const distroDest = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
  467. request(opts, (error, resp, body) => {
  468. if(!error){
  469. try {
  470. data = DistroIndex.fromJSON(JSON.parse(body))
  471. } catch (e) {
  472. reject(e)
  473. }
  474. fs.writeFile(distroDest, body, 'utf-8', (err) => {
  475. if(!err){
  476. resolve(data)
  477. } else {
  478. reject(err)
  479. }
  480. })
  481. } else {
  482. reject(error)
  483. }
  484. })
  485. })
  486. }
  487. /**
  488. * @returns {Promise.<DistroIndex>}
  489. */
  490. exports.pullLocal = function(){
  491. return new Promise((resolve, reject) => {
  492. fs.readFile(DEV_MODE ? DEV_PATH : DISTRO_PATH, 'utf-8', (err, d) => {
  493. if(!err){
  494. data = DistroIndex.fromJSON(JSON.parse(d))
  495. resolve(data)
  496. } else {
  497. reject(err)
  498. }
  499. })
  500. })
  501. }
  502. exports.setDevMode = function(value){
  503. if(value){
  504. logger.log('Developer mode enabled.')
  505. logger.log('If you don\'t know what that means, revert immediately.')
  506. } else {
  507. logger.log('Developer mode disabled.')
  508. }
  509. DEV_MODE = value
  510. }
  511. exports.isDevMode = function(){
  512. return DEV_MODE
  513. }
  514. /**
  515. * @returns {DistroIndex}
  516. */
  517. exports.getDistribution = function(){
  518. return data
  519. }