UNPKG

5.11 kBJavaScriptView Raw
1/**
2 * {
3 * "url": "https://github.com/atom/atom/releases/download/v1.4.3/atom-windows.zip",
4 * "name": "1.4.3",
5 * "notes": "### Notable Changes\r\n\r\n* Fixed a bug that caused...",
6 * "pub_date": "2016-02-02T21:51:58Z",
7 * "version": "1.4.3",
8 * "sha1": ""
9 * }
10 */
11// process.versions.electron ? 'original-fs' :
12const fs = require('fs')
13const os = require('os')
14const path = require('path')
15const zlib = require('zlib')
16const electron = require('electron')
17const EventEmitter = require('events')
18const got = require('got')
19const semver = require('semver')
20
21const app = electron.app || electron.remote.app
22
23class Updater extends EventEmitter {
24 constructor () {
25 super()
26 this.tasks = []
27 }
28
29 _fetchJson (url) {
30 return got(url, { encoding: 'utf8', timeout: 1500, retries: 1, json: true, headers: this.headers })
31 .then(response => Promise.resolve(response.body))
32 }
33
34 _fetchFile (url, name) {
35 let onProgress = p => console.log(p)
36 let total = 0
37 let current = 0
38 let timer = null
39 const tempFile = path.resolve(this.options.cacheDirectory, name)
40 const promise = new Promise((resolve, reject) => {
41 got.stream(url, { encoding: 'utf8', timeout: 1500, retries: 1, headers: this.headers })
42 .on('request', request => { timer = setTimeout(() => request && request.abort(), 2 * 60 * 1000) })
43 .on('response', response => response.headers['content-length'] ? (total = parseInt(response.headers['content-length'], 10)) : onProgress(-1))
44 .on('data', chunk => total ? onProgress((current += chunk.length) / total) : onProgress(-1))
45 .on('end', chunk => clearTimeout(timer))
46 .on('error', (error, body, response) => reject(error))
47 .pipe(zlib.Gunzip())
48 .pipe(fs.createWriteStream(tempFile))
49 // .pipe(unzip.Extract({ path: this.folder }))
50 .on('error', (error) => reject(error))
51 .on('close', () => resolve(tempFile))
52 })
53 promise.progress = callback => {
54 onProgress = callback
55 return promise
56 }
57 return promise
58 }
59
60 init (options) {
61 const def = {
62 tmpdir: os.tmpdir(),
63 headers: {},
64 name: app.getName()
65 }
66 this.options = Object.assign({}, def, options)
67 this.options.cacheDirectory = path.resolve(this.options.tmpdir, this.options.name)
68 this.options.headers['user-agent'] = this.options.headers['user-agent'] || 'asar-updater/v0.0.1 (https://github.com/zce/asar-updater)'
69 // TODO: mkdirp
70 fs.existsSync(this.options.cacheDirectory) || fs.mkdirSync(this.options.cacheDirectory)
71 }
72
73 setFeedURL (filename, url) {
74 if (!path.isAbsolute(filename)) {
75 filename = path.resolve(app.getAppPath(), filename)
76 }
77 const name = path.basename(filename, '.asar')
78 this.tasks.push({ name, filename, url })
79 }
80
81 checkForUpdates () {
82 this.manifest = []
83 this.emit('checking-for-update')
84 Promise.all(
85 this.tasks
86 .map(t => this._local(t))
87 .map(t => this._remote(t))
88 .map(p => this._compare(p))
89 .map(p => this._download(p))
90 )
91 .then(tasks => this._allCompleted(tasks))
92 .catch(error => this.emit('error', error))
93 }
94
95 _local (task) {
96 try {
97 task.local = require(path.resolve(task.filename, 'package.json'))
98 if (!task.local.version) throw new Error('There is no version in the package.json')
99 return task
100 } catch (e) {
101 if (e.code !== 'MODULE_NOT_FOUND') throw e
102 throw new Error('There is no package.json in the ' + task.filename)
103 }
104 }
105
106 _remote (task) {
107 return this._fetchJson(task.url + '?v=' + Date.now())
108 .then(remote => {
109 task.remote = remote
110 if (!task.remote.version) return Promise.reject(new Error('There is no version in the remote'))
111 return task
112 })
113 }
114
115 _compare (promise) {
116 return promise.then(task => {
117 task.available = semver.gt(semver.clean(task.remote.version), semver.clean(task.local.version))
118 this.emit(task.available ? 'available' : 'not-available', task)
119 return task
120 })
121 }
122
123 _download (promise) {
124 return promise.then(task => {
125 if (!task.available) return task
126 return this._fetchFile(task.remote.url + '?v=' + Date.now(), task.name)
127 .progress(p => this.emit('progress', task, p))
128 .then(filename => {
129 this.manifest.push({ from: filename, to: task.filename })
130 this.emit('downloaded', task)
131 return task
132 })
133 })
134 }
135
136 _allCompleted (tasks) {
137 for (let i = tasks.length - 1; i >= 0; i--) {
138 if (!tasks[i].available) {
139 this.emit('completed', false, tasks)
140 return
141 }
142 }
143 fs.writeFile(path.resolve(this.options.cacheDirectory, 'manifest.json'), JSON.stringify(this.manifest), 'utf8', error => {
144 if (error) return fs.unlink(this.options.cacheDirectory)
145 this.emit('completed', this.manifest, tasks)
146 this.manifest = []
147 })
148 }
149
150 quitAndInstall () {
151 setTimeout(() => {
152 app.relaunch({ args: process.argv.slice(1) + ['--relaunch'] })
153 app.exit(0)
154 }, 100)
155 }
156}
157
158module.exports = new Updater()