distromanager.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  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. getVersionlessID(){
  214. return this.getGroup() + ':' + this.getID()
  215. }
  216. /**
  217. * @returns {string} The version of this module's artifact.
  218. */
  219. getVersion(){
  220. return this.artifactVersion
  221. }
  222. /**
  223. * @returns {string} The extension of this module's artifact.
  224. */
  225. getExtension(){
  226. return this.artifactExt
  227. }
  228. /**
  229. * @returns {boolean} Whether or not this module has sub modules.
  230. */
  231. hasSubModules(){
  232. return this.subModules != null
  233. }
  234. /**
  235. * @returns {Array.<Module>} An array of sub modules.
  236. */
  237. getSubModules(){
  238. return this.subModules
  239. }
  240. /**
  241. * @returns {string} The type of the module.
  242. */
  243. getType(){
  244. return this.type
  245. }
  246. }
  247. exports.Module
  248. /**
  249. * Represents a server configuration.
  250. */
  251. class Server {
  252. /**
  253. * Parse a JSON object into a Server.
  254. *
  255. * @param {Object} json A JSON object representing a Server.
  256. *
  257. * @returns {Server} The parsed Server object.
  258. */
  259. static fromJSON(json){
  260. const mdls = json.modules
  261. json.modules = []
  262. const serv = Object.assign(new Server(), json)
  263. serv._resolveModules(mdls)
  264. return serv
  265. }
  266. _resolveModules(json){
  267. const arr = []
  268. for(let m of json){
  269. arr.push(Module.fromJSON(m, this.getID()))
  270. }
  271. this.modules = arr
  272. }
  273. /**
  274. * @returns {string} The ID of the server.
  275. */
  276. getID(){
  277. return this.id
  278. }
  279. /**
  280. * @returns {string} The name of the server.
  281. */
  282. getName(){
  283. return this.name
  284. }
  285. /**
  286. * @returns {string} The description of the server.
  287. */
  288. getDescription(){
  289. return this.description
  290. }
  291. /**
  292. * @returns {string} The URL of the server's icon.
  293. */
  294. getIcon(){
  295. return this.icon
  296. }
  297. /**
  298. * @returns {string} The version of the server configuration.
  299. */
  300. getVersion(){
  301. return this.version
  302. }
  303. /**
  304. * @returns {string} The IP address of the server.
  305. */
  306. getAddress(){
  307. return this.address
  308. }
  309. /**
  310. * @returns {string} The minecraft version of the server.
  311. */
  312. getMinecraftVersion(){
  313. return this.minecraftVersion
  314. }
  315. /**
  316. * @returns {boolean} Whether or not this server is the main
  317. * server. The main server is selected by the launcher when
  318. * no valid server is selected.
  319. */
  320. isMainServer(){
  321. return this.mainServer
  322. }
  323. /**
  324. * @returns {boolean} Whether or not the server is autoconnect.
  325. * by default.
  326. */
  327. isAutoConnect(){
  328. return this.autoconnect
  329. }
  330. /**
  331. * @returns {Array.<Module>} An array of modules for this server.
  332. */
  333. getModules(){
  334. return this.modules
  335. }
  336. }
  337. exports.Server
  338. /**
  339. * Represents the Distribution Index.
  340. */
  341. class DistroIndex {
  342. /**
  343. * Parse a JSON object into a DistroIndex.
  344. *
  345. * @param {Object} json A JSON object representing a DistroIndex.
  346. *
  347. * @returns {DistroIndex} The parsed Server object.
  348. */
  349. static fromJSON(json){
  350. const servers = json.servers
  351. json.servers = []
  352. const distro = Object.assign(new DistroIndex(), json)
  353. distro._resolveServers(servers)
  354. distro._resolveMainServer()
  355. return distro
  356. }
  357. _resolveServers(json){
  358. const arr = []
  359. for(let s of json){
  360. arr.push(Server.fromJSON(s))
  361. }
  362. this.servers = arr
  363. }
  364. _resolveMainServer(){
  365. for(let serv of this.servers){
  366. if(serv.mainServer){
  367. this.mainServer = serv.id
  368. return
  369. }
  370. }
  371. // If no server declares default_selected, default to the first one declared.
  372. this.mainServer = (this.servers.length > 0) ? this.servers[0].getID() : null
  373. }
  374. /**
  375. * @returns {string} The version of the distribution index.
  376. */
  377. getVersion(){
  378. return this.version
  379. }
  380. /**
  381. * @returns {string} The URL to the news RSS feed.
  382. */
  383. getRSS(){
  384. return this.rss
  385. }
  386. /**
  387. * @returns {Array.<Server>} An array of declared server configurations.
  388. */
  389. getServers(){
  390. return this.servers
  391. }
  392. /**
  393. * Get a server configuration by its ID. If it does not
  394. * exist, null will be returned.
  395. *
  396. * @param {string} id The ID of the server.
  397. *
  398. * @returns {Server} The server configuration with the given ID or null.
  399. */
  400. getServer(id){
  401. for(let serv of this.servers){
  402. if(serv.id === id){
  403. return serv
  404. }
  405. }
  406. return null
  407. }
  408. /**
  409. * Get the main server.
  410. *
  411. * @returns {Server} The main server.
  412. */
  413. getMainServer(){
  414. return getServer(this.mainServer)
  415. }
  416. }
  417. exports.DistroIndex
  418. exports.Types = {
  419. Library: 'Library',
  420. ForgeHosted: 'ForgeHosted',
  421. Forge: 'Forge', // Unimplemented
  422. LiteLoader: 'LiteLoader',
  423. ForgeMod: 'ForgeMod',
  424. LiteMod: 'LiteMod',
  425. File: 'File'
  426. }
  427. let DEV_MODE = false
  428. const DISTRO_PATH = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
  429. const DEV_PATH = path.join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json')
  430. let data = null
  431. /**
  432. * @returns {Promise.<DistroIndex>}
  433. */
  434. exports.pullRemote = function(){
  435. if(DEV_MODE){
  436. return exports.pullLocal()
  437. }
  438. return new Promise((resolve, reject) => {
  439. const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
  440. //const distroURL = 'https://gist.githubusercontent.com/dscalzi/53b1ba7a11d26a5c353f9d5ae484b71b/raw/'
  441. const opts = {
  442. url: distroURL,
  443. timeout: 2500
  444. }
  445. const distroDest = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
  446. request(opts, (error, resp, body) => {
  447. if(!error){
  448. data = DistroIndex.fromJSON(JSON.parse(body))
  449. fs.writeFile(distroDest, body, 'utf-8', (err) => {
  450. if(!err){
  451. resolve(data)
  452. } else {
  453. reject(err)
  454. }
  455. })
  456. } else {
  457. reject(error)
  458. }
  459. })
  460. })
  461. }
  462. /**
  463. * @returns {Promise.<DistroIndex>}
  464. */
  465. exports.pullLocal = function(){
  466. return new Promise((resolve, reject) => {
  467. fs.readFile(DEV_MODE ? DEV_PATH : DISTRO_PATH, 'utf-8', (err, d) => {
  468. if(!err){
  469. data = DistroIndex.fromJSON(JSON.parse(d))
  470. resolve(data)
  471. } else {
  472. reject(err)
  473. }
  474. })
  475. })
  476. }
  477. exports.setDevMode = function(value){
  478. if(value){
  479. console.log('%c[DistroManager]', 'color: #a02d2a; font-weight: bold', 'Developer mode enabled.')
  480. console.log('%c[DistroManager]', 'color: #a02d2a; font-weight: bold', 'If you don\'t know what that means, revert immediately.')
  481. } else {
  482. console.log('%c[DistroManager]', 'color: #a02d2a; font-weight: bold', 'Developer mode disabled.')
  483. }
  484. DEV_MODE = value
  485. }
  486. exports.isDevMode = function(){
  487. return DEV_MODE
  488. }
  489. /**
  490. * @returns {DistroIndex}
  491. */
  492. exports.getDistribution = function(){
  493. return data
  494. }
  495. /*async function debug(){
  496. const d = await exports.pullRemote()
  497. console.log(d)
  498. }
  499. debug()*/
  500. //console.log(DistroIndex.fromJSON(JSON.parse(require('fs').readFileSync('../distribution.json', 'utf-8'))))