10
10
*/
11
11
12
12
import BlockingQueue from "./blocking-queue.js" ;
13
+ import * as promise from "./promise.js" ;
13
14
import { promisify } from "./promise.js" ;
14
15
import map from "./map.js" ;
15
16
@@ -31,86 +32,147 @@ export let lstat = promisify(fs.lstat);
31
32
export let chmod = promisify ( fs . chmod ) ;
32
33
33
34
let fsSymlink = promisify ( fs . symlink ) ;
35
+ let invariant = require ( "invariant" ) ;
34
36
let stripBOM = require ( "strip-bom" ) ;
35
37
36
- export async function copy ( src : string , dest : string ) : Promise < boolean > {
37
- let srcStat = await lstat ( src ) ;
38
- let destFiles = [ ] ;
39
- let srcFiles = [ ] ;
38
+ type CopyQueue = Array < {
39
+ src : string ,
40
+ dest : string ,
41
+ onFresh ?: ?( ) => void
42
+ } > ;
43
+
44
+ type CopyActions = Array < {
45
+ src : string ,
46
+ dest : string ,
47
+ atime : number ,
48
+ mtime : number ,
49
+ mode : number
50
+ } > ;
51
+
52
+ async function buildActionsForCopy ( queue : CopyQueue ) : Promise < CopyActions > {
53
+ let actions : CopyActions = [ ] ;
54
+ await init ( ) ;
55
+ return actions ;
56
+
57
+ // custom concurrency logic as we're always executing stacks of 4 queue items
58
+ // at a time due to the requirement to push items onto the queue
59
+ async function init ( ) {
60
+ let items = queue . splice ( 0 , 4 ) ;
61
+ if ( ! items . length ) return ;
62
+
63
+ await Promise . all ( items . map ( build ) ) ;
64
+ return init ( ) ;
65
+ }
40
66
41
- if ( await exists ( dest ) ) {
42
- let destStat = await lstat ( dest ) ;
67
+ //
68
+ async function build ( data ) {
69
+ let { src, dest, onFresh } = data ;
70
+ let srcStat = await lstat ( src ) ;
71
+ let srcFiles ;
43
72
44
- if ( srcStat . mode !== destStat . mode ) {
45
- // different types
46
- await access ( dest , srcStat . mode ) ;
73
+ if ( srcStat . isDirectory ( ) ) {
74
+ srcFiles = await readdir ( src ) ;
47
75
}
48
76
49
- if ( srcStat . isFile ( ) && destStat . isFile ( ) &&
50
- srcStat . size === destStat . size && + srcStat . mtime === + destStat . mtime ) {
51
- // we can safely assume this is the same file
52
- return false ;
53
- }
77
+ if ( await exists ( dest ) ) {
78
+ let destStat = await lstat ( dest ) ;
54
79
55
- if ( srcStat . isDirectory ( ) && destStat . isDirectory ( ) ) {
56
- // remove files that aren't in source
57
- [ destFiles , srcFiles ] = await Promise . all ( [
58
- readdir ( dest ) ,
59
- readdir ( src )
60
- ] ) ;
80
+ let bothFiles = srcStat . isFile ( ) && destStat . isFile ( ) ;
81
+ let bothFolders = ! bothFiles && srcStat . isDirectory ( ) && destStat . isDirectory ( ) ;
61
82
62
- let promises = destFiles . map ( async ( file ) => {
63
- if ( file !== "node_modules" && srcFiles . indexOf ( file ) < 0 ) {
64
- await unlink ( path . join ( dest , file ) ) ;
83
+ if ( srcStat . mode !== destStat . mode ) {
84
+ if ( bothFiles ) {
85
+ await access ( dest , srcStat . mode ) ;
86
+ } else {
87
+ await unlink ( dest ) ;
88
+ return build ( data ) ;
65
89
}
66
- } ) ;
90
+ }
67
91
68
- await Promise . all ( promises ) ;
69
- }
70
- }
92
+ if ( bothFiles && srcStat . size === destStat . size && + srcStat . mtime === + destStat . mtime ) {
93
+ // we can safely assume this is the same file
94
+ return ;
95
+ }
71
96
72
- if ( srcStat . isDirectory ( ) ) {
73
- let anyFresh = false ;
97
+ if ( bothFolders ) {
98
+ // remove files that aren't in source
99
+ let destFiles = await readdir ( dest ) ;
100
+ invariant ( srcFiles , "src files not initialised" ) ;
74
101
75
- // create dest directory
76
- await mkdirp ( dest ) ;
102
+ for ( let file of destFiles ) {
103
+ if ( file === "node_modules" ) continue ;
77
104
78
- // copy all files from source to dest
79
- let promises = srcFiles . map ( ( file ) => {
80
- return copy ( path . join ( src , file ) , path . join ( dest , file ) ) . then ( function ( fresh ) {
81
- if ( fresh ) anyFresh = true ;
82
- return fresh ;
105
+ if ( srcFiles . indexOf ( file ) < 0 ) {
106
+ await unlink ( path . join ( dest , file ) ) ;
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ if ( srcStat . isDirectory ( ) ) {
113
+ await mkdirp ( dest ) ;
114
+
115
+ // push all files to queue
116
+ invariant ( srcFiles , "src files not initialised" ) ;
117
+ for ( let file of srcFiles ) {
118
+ queue . push ( {
119
+ onFresh,
120
+ src : path . join ( src , file ) ,
121
+ dest : path . join ( dest , file )
122
+ } ) ;
123
+ }
124
+ } else if ( srcStat . isFile ( ) ) {
125
+ if ( onFresh ) onFresh ( ) ;
126
+ actions . push ( {
127
+ src,
128
+ dest,
129
+ atime : srcStat . atime ,
130
+ mtime : srcStat . mtime ,
131
+ mode : srcStat . mode
83
132
} ) ;
84
- } ) ;
133
+ } else {
134
+ throw new Error ( "unsure how to copy this?" ) ;
135
+ }
136
+ }
137
+ }
85
138
86
- await Promise . all ( promises ) ;
139
+ export function copy ( src : string , dest : string ) : Promise < void > {
140
+ return copyBulk ( [ { src, dest } ] ) ;
141
+ }
87
142
88
- return anyFresh ;
89
- } else if ( srcStat . isFile ( ) ) {
90
- return new Promise ( ( resolve , reject ) => {
91
- let readStream = fs . createReadStream ( src ) ;
92
- let writeStream = fs . createWriteStream ( dest , { mode : srcStat . mode } ) ;
143
+ export async function copyBulk (
144
+ queue : CopyQueue ,
145
+ events ?: {
146
+ onProgress : ( dest : string ) => void ,
147
+ onStart : ( num : number ) => void
148
+ }
149
+ ) : Promise < void > {
150
+ let actions : CopyActions = await buildActionsForCopy ( queue ) ;
93
151
94
- readStream . on ( "error" , reject ) ;
95
- writeStream . on ( "error" , reject ) ;
152
+ if ( events ) events . onStart ( actions . length ) ;
96
153
97
- writeStream . on ( "open" , function ( ) {
98
- readStream . pipe ( writeStream ) ;
99
- } ) ;
154
+ await promise . queue ( actions , ( data ) => new Promise ( ( resolve , reject ) => {
155
+ let readStream = fs . createReadStream ( data . src ) ;
156
+ let writeStream = fs . createWriteStream ( data . dest , { mode : data . mode } ) ;
100
157
101
- writeStream . once ( "finish" , function ( ) {
102
- fs . utimes ( dest , srcStat . atime , srcStat . mtime , function ( err ) {
103
- if ( err ) {
104
- reject ( err ) ;
105
- } else {
106
- resolve ( true ) ;
107
- }
108
- } ) ;
158
+ readStream . on ( "error" , reject ) ;
159
+ writeStream . on ( "error" , reject ) ;
160
+
161
+ writeStream . on ( "open" , function ( ) {
162
+ readStream . pipe ( writeStream ) ;
163
+ } ) ;
164
+
165
+ writeStream . once ( "finish" , function ( ) {
166
+ fs . utimes ( data . dest , data . atime , data . mtime , function ( err ) {
167
+ if ( err ) {
168
+ reject ( err ) ;
169
+ } else {
170
+ if ( events ) events . onProgress ( data . dest ) ;
171
+ resolve ( ) ;
172
+ }
109
173
} ) ;
110
174
} ) ;
111
- } else {
112
- throw new Error ( "unsure how to copy this?" ) ;
113
- }
175
+ } ) , 4 ) ;
114
176
}
115
177
116
178
export async function readFile ( loc : string ) : Promise < string > {
0 commit comments