@@ -4,9 +4,7 @@ const fs = require('fs');
44const os = require ( 'os' ) ;
55const url = require ( 'url' ) ;
66const axios = require ( 'axios' ) ;
7- const Promise = require ( 'promise' ) ;
87const shell = require ( 'shelljs' ) ;
9- const save = require ( 'save-file' ) ;
108const argsParser = require ( 'args-parser' ) ;
119
1210const AUTHOR = 1 ;
@@ -163,44 +161,13 @@ function parseInfo(repoInfo) {
163161 return info ;
164162}
165163
166- function extractFilenameAndDirectoryFrom ( path ) {
167- const components = path . split ( '/' ) ;
168- const filename = components [ components . length - 1 ] ;
169- const directory = path . substring ( 0 , path . length - filename . length ) ;
170-
171- return {
172- filename,
173- directory,
174- } ;
175- }
176-
177164const basicOptions = {
178165 method : 'get' ,
179166 responseType : 'arrayBuffer' ,
180167} ;
181168// Global variable
182169let repoInfo = { } ;
183170
184- function saveFiles ( files , requestPromises ) {
185- const rootDir = outputDirectory + repoInfo . rootDirectoryName ;
186- shell . mkdir ( '-p' , rootDir ) ;
187-
188- Promise . all ( requestPromises ) . then ( ( ) => {
189- for ( let i = 0 ; i < files . length ; i ++ ) {
190- const pathForSave = extractFilenameAndDirectoryFrom ( files [ i ] . path . substring ( decodeURI ( repoInfo . resPath ) . length + 1 ) ) ;
191- const dir = rootDir + pathForSave . directory ;
192- fs . exists ( dir , ( ( i , dir , pathForSave , exists ) => {
193- if ( ! exists ) {
194- shell . mkdir ( '-p' , dir ) ;
195- }
196- save ( files [ i ] . data , dir + pathForSave . filename , ( err ) => {
197- if ( err ) throw err ;
198- } ) ;
199- } ) . bind ( null , i , dir , pathForSave ) ) ;
200- }
201- } ) ;
202- }
203-
204171function processClientError ( error , retryCallback ) {
205172 if ( error . response . status === '401' ) {
206173 // Unauthorized
@@ -222,39 +189,61 @@ function processClientError(error, retryCallback) {
222189 }
223190}
224191
225- function fetchFile ( path , url , files ) {
226- return axios ( {
227- ...basicOptions ,
228- url,
229- ...authenticationSwitch ,
230- } ) . then ( ( file ) => {
231- console . log ( 'downloading ' , path ) ;
232- files . push ( { path, data : file . data } ) ;
233- } ) . catch ( ( error ) => {
234- processClientError ( error , fetchFile . bind ( null , path , url , files ) ) ;
235- } ) ;
192+ function extractFilenameAndDirectoryFrom ( path ) {
193+ const components = path . split ( '/' ) ;
194+ const filename = components [ components . length - 1 ] ;
195+ const directory = path . substring ( 0 , path . length - filename . length ) ;
196+
197+ return {
198+ filename,
199+ directory,
200+ } ;
201+ }
202+
203+ /*
204+ * @example
205+ * take fether --url='https://github.com/reduxjs/redux/tree/master/examples/async' for example:
206+ * all paths of files under the 'async' directory are prefixed with the so-called 'resPath', which
207+ * equals to 'example/async', and the 'rootDirectoryName' is 'async'. The 'resPath' could be very long,
208+ * and we don't need that deep path locally in fact. So we just remove the 'resPath' from the path of a file.
209+ */
210+ function removeResPathFrom ( path ) {
211+ return path . substring ( decodeURI ( repoInfo . resPath ) . length + 1 ) ;
236212}
237213
238- function downloadFile ( url ) {
239- console . log ( 'downloading ' , repoInfo . resPath ) ;
214+ function constructLocalPathname ( repoPath ) {
215+ const partialPath = extractFilenameAndDirectoryFrom ( removeResPathFrom ( repoPath ) ) ;
216+ const localRootDirectory = outputDirectory + repoInfo . rootDirectoryName ;
217+ const localDirectory = localRootDirectory + partialPath . directory ;
218+
219+ return {
220+ filename : partialPath . filename ,
221+ directory : localDirectory ,
222+ } ;
223+ }
240224
225+ function downloadFile ( url , pathname ) {
241226 axios ( {
242227 ...basicOptions ,
228+ responseType : 'stream' ,
243229 url,
244230 ...authenticationSwitch ,
245- } ) . then ( ( file ) => {
246- shell . mkdir ( '-p' , outputDirectory ) ;
247- const pathForSave = extractFilenameAndDirectoryFrom ( decodeURI ( repoInfo . resPath ) ) ;
231+ } ) . then ( ( response ) => {
232+ if ( ! fs . existsSync ( pathname . directory ) ) {
233+ shell . mkdir ( '-p' , pathname . directory ) ;
234+ }
248235
249- save ( file . data , outputDirectory + pathForSave . filename , ( err ) => {
250- if ( err ) throw err ;
251- } ) ;
236+ const localPathname = pathname . directory + pathname . filename ;
237+ response . data . pipe ( fs . createWriteStream ( localPathname ) )
238+ . on ( 'close' , ( ) => {
239+ console . log ( `${ localPathname } downloaded.` ) ;
240+ } ) ;
252241 } ) . catch ( ( error ) => {
253- processClientError ( error , downloadFile . bind ( null , url ) ) ;
242+ processClientError ( error , downloadFile . bind ( null , url , pathname ) ) ;
254243 } ) ;
255244}
256245
257- function iterateDirectory ( dirPaths , files , requestPromises ) {
246+ function iterateDirectory ( dirPaths ) {
258247 axios ( {
259248 ...basicOptions ,
260249 url : repoInfo . urlPrefix + dirPaths . pop ( ) + repoInfo . urlPostfix ,
@@ -265,59 +254,38 @@ function iterateDirectory(dirPaths, files, requestPromises) {
265254 if ( data [ i ] . type === 'dir' ) {
266255 dirPaths . push ( data [ i ] . path ) ;
267256 } else if ( data [ i ] . download_url ) {
268- const promise = fetchFile ( data [ i ] . path , data [ i ] . download_url , files ) ;
269- requestPromises . push ( promise ) ;
257+ const pathname = constructLocalPathname ( data [ i ] . path ) ;
258+ downloadFile ( data [ i ] . download_url , pathname ) ;
270259 } else {
271260 console . log ( data [ i ] ) ;
272261 }
273262 }
274263
275- // Save files after we iterate all the directories
276- if ( dirPaths . length === 0 ) {
277- saveFiles ( files , requestPromises ) ;
278- } else {
279- iterateDirectory ( dirPaths , files , requestPromises ) ;
264+ if ( dirPaths . length !== 0 ) {
265+ iterateDirectory ( dirPaths ) ;
280266 }
281267 } ) . catch ( ( error ) => {
282- processClientError ( error , iterateDirectory . bind ( null , dirPaths , files , requestPromises ) ) ;
268+ processClientError ( error , iterateDirectory . bind ( null , dirPaths ) ) ;
283269 } ) ;
284270}
285271
286272function downloadDirectory ( ) {
287273 const dirPaths = [ ] ;
288- const files = [ ] ;
289- const requestPromises = [ ] ;
290-
291274 dirPaths . push ( repoInfo . resPath ) ;
292- iterateDirectory ( dirPaths , files , requestPromises ) ;
275+ iterateDirectory ( dirPaths ) ;
293276}
294277
295- function initializeDownload ( para ) {
296- repoInfo = parseInfo ( para ) ;
278+ function initializeDownload ( paras ) {
279+ repoInfo = parseInfo ( paras ) ;
297280
298281 if ( ! repoInfo . resPath || repoInfo . resPath === '' ) {
299282 if ( ! repoInfo . branch || repoInfo . branch === '' ) {
300283 repoInfo . branch = 'master' ;
301284 }
302285
303- // Download the whole repository
286+ // Download the whole repository as a zip file
304287 const repoURL = `https://github.com/${ repoInfo . author } /${ repoInfo . repository } /archive/${ repoInfo . branch } .zip` ;
305-
306- axios ( {
307- ...basicOptions ,
308- responseType : 'stream' ,
309- url : repoURL ,
310- ...authenticationSwitch ,
311- } ) . then ( ( response ) => {
312- shell . mkdir ( '-p' , outputDirectory ) ;
313- const filename = `${ outputDirectory } ${ repoInfo . repository } .zip` ;
314- response . data . pipe ( fs . createWriteStream ( filename ) )
315- . on ( 'close' , ( ) => {
316- console . log ( `${ filename } downloaded.` ) ;
317- } ) ;
318- } ) . catch ( ( error ) => {
319- processClientError ( error , initializeDownload . bind ( null , parameters ) ) ;
320- } ) ;
288+ downloadFile ( repoURL , { directory : outputDirectory , filename : `${ repoInfo . repository } .zip` } ) ;
321289 } else {
322290 // Download part(s) of repository
323291 axios ( {
@@ -328,10 +296,11 @@ function initializeDownload(para) {
328296 if ( response . data instanceof Array ) {
329297 downloadDirectory ( ) ;
330298 } else {
331- downloadFile ( response . data . download_url ) ;
299+ const partialPath = extractFilenameAndDirectoryFrom ( decodeURI ( repoInfo . resPath ) ) ;
300+ downloadFile ( response . data . download_url , { ...partialPath , directory : outputDirectory } ) ;
332301 }
333302 } ) . catch ( ( error ) => {
334- processClientError ( error , initializeDownload . bind ( null , parameters ) ) ;
303+ processClientError ( error , initializeDownload . bind ( null , paras ) ) ;
335304 } ) ;
336305 }
337306}
0 commit comments