distromanager.js 14 KB

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