distromanager.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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.artifactVersion = m1[2] || '???'
  144. this.artifactID = m1[1] || '???'
  145. this.artifactGroup = m1[0] || '???'
  146. } catch (err) {
  147. // Improper identifier
  148. logger.error('Improper ID for module', this.identifier, err)
  149. }
  150. }
  151. _resolveArtifactPath(artifactPath, serverid){
  152. const pth = artifactPath == null ? path.join(...this.getGroup().split('.'), this.getID(), this.getVersion(), `${this.getID()}-${this.getVersion()}.${this.getExtension()}`) : artifactPath
  153. switch (this.type){
  154. case exports.Types.Library:
  155. case exports.Types.ForgeHosted:
  156. case exports.Types.LiteLoader:
  157. this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'libraries', pth)
  158. break
  159. case exports.Types.ForgeMod:
  160. case exports.Types.LiteMod:
  161. this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'modstore', pth)
  162. break
  163. case exports.Types.File:
  164. default:
  165. this.artifact.path = path.join(ConfigManager.getInstanceDirectory(), serverid, pth)
  166. break
  167. }
  168. }
  169. _resolveSubModules(json, serverid){
  170. const arr = []
  171. if(json != null){
  172. for(let sm of json){
  173. arr.push(Module.fromJSON(sm, serverid))
  174. }
  175. }
  176. this.subModules = arr.length > 0 ? arr : null
  177. }
  178. /**
  179. * @returns {string} The full, unparsed module identifier.
  180. */
  181. getIdentifier(){
  182. return this.identifier
  183. }
  184. /**
  185. * @returns {string} The name of the module.
  186. */
  187. getName(){
  188. return this.name
  189. }
  190. /**
  191. * @returns {Required} The required object declared by this module.
  192. */
  193. getRequired(){
  194. return this.required
  195. }
  196. /**
  197. * @returns {Artifact} The artifact declared by this module.
  198. */
  199. getArtifact(){
  200. return this.artifact
  201. }
  202. /**
  203. * @returns {string} The maven identifier of this module's artifact.
  204. */
  205. getID(){
  206. return this.artifactID
  207. }
  208. /**
  209. * @returns {string} The maven group of this module's artifact.
  210. */
  211. getGroup(){
  212. return this.artifactGroup
  213. }
  214. /**
  215. * @returns {string} The identifier without he version or extension.
  216. */
  217. getVersionlessID(){
  218. return this.getGroup() + ':' + this.getID()
  219. }
  220. /**
  221. * @returns {string} The identifier without the extension.
  222. */
  223. getExtensionlessID(){
  224. return this.getIdentifier().split('@')[0]
  225. }
  226. /**
  227. * @returns {string} The version of this module's artifact.
  228. */
  229. getVersion(){
  230. return this.artifactVersion
  231. }
  232. /**
  233. * @returns {string} The extension of this module's artifact.
  234. */
  235. getExtension(){
  236. return this.artifactExt
  237. }
  238. /**
  239. * @returns {boolean} Whether or not this module has sub modules.
  240. */
  241. hasSubModules(){
  242. return this.subModules != null
  243. }
  244. /**
  245. * @returns {Array.<Module>} An array of sub modules.
  246. */
  247. getSubModules(){
  248. return this.subModules
  249. }
  250. /**
  251. * @returns {string} The type of the module.
  252. */
  253. getType(){
  254. return this.type
  255. }
  256. }
  257. exports.Module
  258. /**
  259. * Represents a server configuration.
  260. */
  261. class Server {
  262. /**
  263. * Parse a JSON object into a Server.
  264. *
  265. * @param {Object} json A JSON object representing a Server.
  266. *
  267. * @returns {Server} The parsed Server object.
  268. */
  269. static fromJSON(json){
  270. const mdls = json.modules
  271. json.modules = []
  272. const serv = Object.assign(new Server(), json)
  273. serv._resolveModules(mdls)
  274. return serv
  275. }
  276. _resolveModules(json){
  277. const arr = []
  278. for(let m of json){
  279. arr.push(Module.fromJSON(m, this.getID()))
  280. }
  281. this.modules = arr
  282. }
  283. /**
  284. * @returns {string} The ID of the server.
  285. */
  286. getID(){
  287. return this.id
  288. }
  289. /**
  290. * @returns {string} The name of the server.
  291. */
  292. getName(){
  293. return this.name
  294. }
  295. /**
  296. * @returns {string} The description of the server.
  297. */
  298. getDescription(){
  299. return this.description
  300. }
  301. /**
  302. * @returns {string} The URL of the server's icon.
  303. */
  304. getIcon(){
  305. return this.icon
  306. }
  307. /**
  308. * @returns {string} The version of the server configuration.
  309. */
  310. getVersion(){
  311. return this.version
  312. }
  313. /**
  314. * @returns {string} The IP address of the server.
  315. */
  316. getAddress(){
  317. return this.address
  318. }
  319. /**
  320. * @returns {string} The minecraft version of the server.
  321. */
  322. getMinecraftVersion(){
  323. return this.minecraftVersion
  324. }
  325. /**
  326. * @returns {boolean} Whether or not this server is the main
  327. * server. The main server is selected by the launcher when
  328. * no valid server is selected.
  329. */
  330. isMainServer(){
  331. return this.mainServer
  332. }
  333. /**
  334. * @returns {boolean} Whether or not the server is autoconnect.
  335. * by default.
  336. */
  337. isAutoConnect(){
  338. return this.autoconnect
  339. }
  340. /**
  341. * @returns {Array.<Module>} An array of modules for this server.
  342. */
  343. getModules(){
  344. return this.modules
  345. }
  346. }
  347. exports.Server
  348. /**
  349. * Represents the Distribution Index.
  350. */
  351. class DistroIndex {
  352. /**
  353. * Parse a JSON object into a DistroIndex.
  354. *
  355. * @param {Object} json A JSON object representing a DistroIndex.
  356. *
  357. * @returns {DistroIndex} The parsed Server object.
  358. */
  359. static fromJSON(json){
  360. const servers = json.servers
  361. json.servers = []
  362. const distro = Object.assign(new DistroIndex(), json)
  363. distro._resolveServers(servers)
  364. distro._resolveMainServer()
  365. return distro
  366. }
  367. _resolveServers(json){
  368. const arr = []
  369. for(let s of json){
  370. arr.push(Server.fromJSON(s))
  371. }
  372. this.servers = arr
  373. }
  374. _resolveMainServer(){
  375. for(let serv of this.servers){
  376. if(serv.mainServer){
  377. this.mainServer = serv.id
  378. return
  379. }
  380. }
  381. // If no server declares default_selected, default to the first one declared.
  382. this.mainServer = (this.servers.length > 0) ? this.servers[0].getID() : null
  383. }
  384. /**
  385. * @returns {string} The version of the distribution index.
  386. */
  387. getVersion(){
  388. return this.version
  389. }
  390. /**
  391. * @returns {string} The URL to the news RSS feed.
  392. */
  393. getRSS(){
  394. return this.rss
  395. }
  396. /**
  397. * @returns {Array.<Server>} An array of declared server configurations.
  398. */
  399. getServers(){
  400. return this.servers
  401. }
  402. /**
  403. * Get a server configuration by its ID. If it does not
  404. * exist, null will be returned.
  405. *
  406. * @param {string} id The ID of the server.
  407. *
  408. * @returns {Server} The server configuration with the given ID or null.
  409. */
  410. getServer(id){
  411. for(let serv of this.servers){
  412. if(serv.id === id){
  413. return serv
  414. }
  415. }
  416. return null
  417. }
  418. /**
  419. * Get the main server.
  420. *
  421. * @returns {Server} The main server.
  422. */
  423. getMainServer(){
  424. return this.mainServer != null ? this.getServer(this.mainServer) : null
  425. }
  426. }
  427. exports.DistroIndex
  428. exports.Types = {
  429. Library: 'Library',
  430. ForgeHosted: 'ForgeHosted',
  431. Forge: 'Forge', // Unimplemented
  432. LiteLoader: 'LiteLoader',
  433. ForgeMod: 'ForgeMod',
  434. LiteMod: 'LiteMod',
  435. File: 'File'
  436. }
  437. let DEV_MODE = false
  438. const DISTRO_PATH = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
  439. const DEV_PATH = path.join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json')
  440. let data = null
  441. /**
  442. * @returns {Promise.<DistroIndex>}
  443. */
  444. exports.pullRemote = function(){
  445. if(DEV_MODE){
  446. return exports.pullLocal()
  447. }
  448. return new Promise((resolve, reject) => {
  449. const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
  450. //const distroURL = 'https://gist.githubusercontent.com/dscalzi/53b1ba7a11d26a5c353f9d5ae484b71b/raw/'
  451. const opts = {
  452. url: distroURL,
  453. timeout: 2500
  454. }
  455. const distroDest = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
  456. request(opts, (error, resp, body) => {
  457. if(!error){
  458. try {
  459. data = DistroIndex.fromJSON(JSON.parse(body))
  460. } catch (e) {
  461. reject(e)
  462. }
  463. fs.writeFile(distroDest, body, 'utf-8', (err) => {
  464. if(!err){
  465. resolve(data)
  466. } else {
  467. reject(err)
  468. }
  469. })
  470. } else {
  471. reject(error)
  472. }
  473. })
  474. })
  475. }
  476. /**
  477. * @returns {Promise.<DistroIndex>}
  478. */
  479. exports.pullLocal = function(){
  480. return new Promise((resolve, reject) => {
  481. fs.readFile(DEV_MODE ? DEV_PATH : DISTRO_PATH, 'utf-8', (err, d) => {
  482. if(!err){
  483. data = DistroIndex.fromJSON(JSON.parse(d))
  484. resolve(data)
  485. } else {
  486. reject(err)
  487. }
  488. })
  489. })
  490. }
  491. exports.setDevMode = function(value){
  492. if(value){
  493. logger.log('Developer mode enabled.')
  494. logger.log('If you don\'t know what that means, revert immediately.')
  495. } else {
  496. logger.log('Developer mode disabled.')
  497. }
  498. DEV_MODE = value
  499. }
  500. exports.isDevMode = function(){
  501. return DEV_MODE
  502. }
  503. /**
  504. * @returns {DistroIndex}
  505. */
  506. exports.getDistribution = function(){
  507. return data
  508. }