@@ -11,14 +11,18 @@ var cacheFile = require('npm-cache-filename')
11
11
var getCacheStat = require ( './get-stat.js' )
12
12
var mapToRegistry = require ( '../utils/map-to-registry.js' )
13
13
var pulseTillDone = require ( '../utils/pulse-till-done.js' )
14
- var parseJSON = require ( '../utils/parse-json.js' )
14
+ var jsonstream = require ( 'JSONStream' )
15
+ var asyncMap = require ( 'slide' ) . asyncMap
16
+ var writeStreamAtomic = require ( 'fs-write-stream-atomic' )
17
+ var once = require ( 'once' )
15
18
16
19
/* /-/all is special.
17
20
* It uses timestamp-based caching and partial updates,
18
21
* because it is a monster.
19
22
*/
20
- function updateIndex ( staleness , cb ) {
21
- assert ( typeof cb === 'function' , 'must pass callback to updateIndex' )
23
+ function updateIndex ( staleness , args , notArgs , filter , cb ) {
24
+ assert ( typeof filter === 'function' , 'must pass filter callback to updateIndex' )
25
+ assert ( typeof cb === 'function' , 'must pass final callback to updateIndex' )
22
26
23
27
mapToRegistry ( '-/all' , npm . config , function ( er , uri , auth ) {
24
28
if ( er ) return cb ( er )
@@ -27,79 +31,163 @@ function updateIndex (staleness, cb) {
27
31
timeout : staleness ,
28
32
follow : true ,
29
33
staleOk : true ,
30
- auth : auth
34
+ auth : auth ,
35
+ streaming : true
31
36
}
32
- var cacheBase = cacheFile ( npm . config . get ( 'cache' ) ) ( uri )
33
- var cachePath = path . join ( cacheBase , '.cache.json' )
34
- log . info ( 'updateIndex' , cachePath )
37
+ var cacheBase = path . join ( cacheFile ( npm . config . get ( 'cache' ) ) ( uri ) , '_search' )
38
+ log . info ( 'updateIndex' , cacheBase )
35
39
36
40
getCacheStat ( function ( er , st ) {
37
41
if ( er ) return cb ( er )
38
42
39
43
mkdir ( cacheBase , function ( er , made ) {
40
44
if ( er ) return cb ( er )
41
45
42
- fs . readFile ( cachePath , function ( er , data ) {
43
- if ( er ) {
44
- log . warn ( '' , 'Building the local index for the first time, please be patient' )
45
- return updateIndex_ ( uri , params , { } , cachePath , cb )
46
- }
46
+ chownr ( made || cacheBase , st . uid , st . gid , function ( er ) {
47
+ if ( er ) return cb ( er )
47
48
48
- chownr ( made || cachePath , st . uid , st . gid , function ( er ) {
49
+ fs . readdir ( cacheBase , function ( er , cacheFiles ) {
49
50
if ( er ) return cb ( er )
50
51
51
- data = parseJSON . noExceptions ( data )
52
- if ( ! data ) {
53
- fs . writeFile ( cachePath , '{}' , function ( er ) {
54
- if ( er ) return cb ( new Error ( 'Broken cache.' ) )
55
-
56
- log . warn ( '' , 'Building the local index for the first time, please be patient' )
57
- return updateIndex_ ( uri , params , { } , cachePath , cb )
58
- } )
59
- }
60
-
61
- var t = + data . _updated || 0
62
- // use the cache and update in the background if it's not too old
63
- if ( Date . now ( ) - t < 60000 ) {
64
- cb ( null , data )
65
- cb = function ( ) { }
66
- }
67
-
68
- if ( t === 0 ) {
69
- log . warn ( '' , 'Building the local index for the first time, please be patient' )
70
- } else {
71
- log . verbose ( 'updateIndex' , 'Cached search data present with timestamp' , t )
72
- uri += '/since?stale=update_after&startkey=' + t
73
- }
74
- updateIndex_ ( uri , params , data , cachePath , cb )
52
+ cacheFiles . sort ( )
53
+
54
+ var latest = 0
55
+ asyncMap ( cacheFiles , function ( file , cb ) {
56
+ log . silly ( 'search' , 'reading cache ' + file )
57
+ cb = once ( cb )
58
+ var m = / ^ ( \d + ) - ( \d + ) [ . ] j s o n / . exec ( file )
59
+ if ( m ) {
60
+ latest = Number ( m [ 2 ] )
61
+ var cacheFile = path . join ( cacheBase , file )
62
+
63
+ fs . stat ( cacheFile , function ( er , stat ) {
64
+ if ( er ) return cb ( er )
65
+ var r = fs . createReadStream ( cacheFile ) . pipe ( log . newStream ( 'readCache' , stat . size ) )
66
+ var f = r . pipe ( collectResults ( filter , args , notArgs , cb ) )
67
+ f . once ( 'error' , cb )
68
+ } )
69
+ } else {
70
+ cb ( null , { } )
71
+ }
72
+ } , function ( err , data ) {
73
+ if ( err ) return cb ( err )
74
+
75
+ data = data . reduce ( function ( a , e ) {
76
+ Object . keys ( e ) . forEach ( function ( k ) {
77
+ a [ k ] = e [ k ]
78
+ } )
79
+ return a
80
+ } , { } )
81
+
82
+ // use the cache and make no request if it's not too old
83
+ if ( Date . now ( ) - latest < 60000 ) {
84
+ finish ( data , cb )
85
+ } else {
86
+ if ( latest === 0 ) {
87
+ log . warn ( '' , 'Building the local index for the first time, please be patient' )
88
+ } else {
89
+ log . verbose ( 'updateIndex' , 'Cached search data present with timestamp' , latest )
90
+ uri += '/since?stale=update_after&startkey=' + latest
91
+ }
92
+
93
+ updateIndex_ ( uri , params , latest , filter , args , notArgs , cacheBase , function ( err , updated ) {
94
+ if ( err ) return cb ( err )
95
+
96
+ Object . keys ( updated ) . forEach ( function ( k ) {
97
+ data [ k ] = updated [ k ]
98
+ } )
99
+
100
+ finish ( data , cb )
101
+ } )
102
+ }
103
+ } )
75
104
} )
76
105
} )
77
106
} )
78
107
} )
79
108
} )
80
109
}
81
110
82
- function updateIndex_ ( all , params , data , cachePath , cb ) {
83
- log . silly ( 'update-index' , 'fetching' , all )
84
- npm . registry . request ( all , params , pulseTillDone ( 'updateIndex' , function ( er , updates , _ , res ) {
85
- if ( er ) return cb ( er , data )
111
+ function finish ( data , cb ) {
112
+ var keys = Object . keys ( data )
113
+ keys . sort ( )
114
+ var results = keys . map ( function ( k ) {
115
+ return data [ k ]
116
+ } )
86
117
87
- var headers = res . headers
88
- var updated = updates . _updated || Date . parse ( headers . date )
118
+ cb ( null , results )
119
+ }
89
120
90
- Object . keys ( updates ) . forEach ( function ( p ) { data [ p ] = updates [ p ] } )
121
+ function updateIndex_ ( all , params , latest , filter , args , notArgs , cacheBase , cb ) {
122
+ cb = once ( cb )
123
+ log . silly ( 'update-index' , 'fetching' , all )
124
+ npm . registry . request ( all , params , pulseTillDone ( 'updateIndex' , function ( er , res ) {
125
+ if ( er ) return cb ( er )
91
126
92
- data . _updated = updated
93
- getCacheStat ( function ( er , st ) {
94
- if ( er ) return cb ( er )
127
+ var results = null
128
+ var updated = null
129
+ var wroteUpdate = false
130
+
131
+ var trackerStream = log . newStream ( 'updateIndex' )
132
+
133
+ var tmpName = path . join ( cacheBase , latest + '-next.json' )
134
+ var writeStream = writeStreamAtomic ( tmpName )
135
+ res . setMaxListeners ( 20 ) // node 0.8 has a lower margin
136
+ res . pipe ( writeStream )
137
+ res . pipe ( trackerStream )
138
+ writeStream . once ( 'error' , cb )
139
+ writeStream . once ( 'close' , function ( ) {
140
+ wroteUpdate = true
141
+ maybeFinishUpdateIndex ( )
142
+ } )
95
143
96
- fs . writeFile ( cachePath , JSON . stringify ( data ) , function ( er ) {
97
- delete data . _updated
98
- if ( er ) return cb ( er )
99
- chownr ( cachePath , st . uid , st . gid , function ( er ) {
100
- cb ( er , data )
144
+ res . pipe ( collectResults ( filter , args , notArgs , function ( err , results_ , updated_ ) {
145
+ if ( err ) return cb ( err )
146
+ results = results_
147
+ updated = updated_
148
+ maybeFinishUpdateIndex ( )
149
+ } ) )
150
+
151
+ function maybeFinishUpdateIndex ( ) {
152
+ if ( results && wroteUpdate ) {
153
+ var finalName = path . join ( cacheBase , latest + '-' + updated + '.json' )
154
+ log . silly ( 'update-index' , 'moving final cache file into place' , finalName )
155
+ fs . rename ( tmpName , finalName , function ( err ) {
156
+ if ( err ) return cb ( err )
157
+ cb ( null , results )
101
158
} )
102
- } )
103
- } )
159
+ }
160
+ }
104
161
} ) )
105
162
}
163
+
164
+ function collectResults ( filter , args , notArgs , cb ) {
165
+ cb = once ( cb )
166
+
167
+ var results = { }
168
+ var updated = null
169
+ var stream = jsonstream . parse ( '*' , function ( pkg , key ) {
170
+ if ( key [ 0 ] === '_updated' ) {
171
+ updated = pkg
172
+ return
173
+ }
174
+ if ( key [ 0 ] [ 0 ] !== '_' ) {
175
+ if ( filter ( pkg , args , notArgs ) ) {
176
+ log . verbose ( 'search' , 'matched ' + pkg . name )
177
+ results [ pkg . name ] = pkg
178
+ } else {
179
+ log . silly ( 'search' , 'not matched ' + pkg . name )
180
+ }
181
+ } else {
182
+ log . silly ( 'search' , 'skipping ' + key )
183
+ }
184
+ } )
185
+
186
+ stream . once ( 'error' , cb )
187
+
188
+ stream . once ( 'end' , function ( ) {
189
+ cb ( null , results , updated )
190
+ } )
191
+
192
+ return stream
193
+ }
0 commit comments