55use MongoDB \BSON \Binary ;
66use MongoDB \BSON \ObjectId ;
77use MongoDB \BSON \UTCDateTime ;
8+ use MongoDB \Driver \Exception \RuntimeException as DriverRuntimeException ;
89use MongoDB \Exception \InvalidArgumentException ;
10+ use MongoDB \Exception \RuntimeException ;
911
1012/**
1113 * WritableStream abstracts the process of writing a GridFS file.
@@ -16,8 +18,7 @@ class WritableStream
1618{
1719 private static $ defaultChunkSizeBytes = 261120 ;
1820
19- private $ buffer ;
20- private $ bufferLength = 0 ;
21+ private $ buffer = '' ;
2122 private $ chunkOffset = 0 ;
2223 private $ chunkSize ;
2324 private $ collectionWrapper ;
@@ -66,6 +67,10 @@ public function __construct(CollectionWrapper $collectionWrapper, $filename, arr
6667 throw InvalidArgumentException::invalidType ('"chunkSizeBytes" option ' , $ options ['chunkSizeBytes ' ], 'integer ' );
6768 }
6869
70+ if (isset ($ options ['chunkSizeBytes ' ]) && $ options ['chunkSizeBytes ' ] < 1 ) {
71+ throw new InvalidArgumentException (sprintf ('Expected "chunkSizeBytes" option to be >= 1, %d given ' , $ options ['chunkSizeBytes ' ]));
72+ }
73+
6974 if (isset ($ options ['contentType ' ]) && ! is_string ($ options ['contentType ' ])) {
7075 throw InvalidArgumentException::invalidType ('"contentType" option ' , $ options ['contentType ' ], 'string ' );
7176 }
@@ -76,15 +81,13 @@ public function __construct(CollectionWrapper $collectionWrapper, $filename, arr
7681
7782 $ this ->chunkSize = $ options ['chunkSizeBytes ' ];
7883 $ this ->collectionWrapper = $ collectionWrapper ;
79- $ this ->buffer = fopen ('php://memory ' , 'w+b ' );
8084 $ this ->ctx = hash_init ('md5 ' );
8185
8286 $ this ->file = [
8387 '_id ' => $ options ['_id ' ],
8488 'chunkSize ' => $ this ->chunkSize ,
8589 'filename ' => (string ) $ filename ,
86- // TODO: This is necessary until PHPC-536 is implemented
87- 'uploadDate ' => new UTCDateTime ((int ) floor (microtime (true ) * 1000 )),
90+ 'uploadDate ' => new UTCDateTime ,
8891 ] + array_intersect_key ($ options , ['aliases ' => 1 , 'contentType ' => 1 , 'metadata ' => 1 ]);
8992 }
9093
@@ -113,14 +116,10 @@ public function close()
113116 return ;
114117 }
115118
116- rewind ($ this ->buffer );
117- $ cached = stream_get_contents ($ this ->buffer );
118-
119- if (strlen ($ cached ) > 0 ) {
120- $ this ->insertChunk ($ cached );
119+ if (strlen ($ this ->buffer ) > 0 ) {
120+ $ this ->insertChunkFromBuffer ();
121121 }
122122
123- fclose ($ this ->buffer );
124123 $ this ->fileCollectionInsert ();
125124 $ this ->isClosed = true ;
126125 }
@@ -151,77 +150,87 @@ public function getSize()
151150 * Inserts binary data into GridFS via chunks.
152151 *
153152 * Data will be buffered internally until chunkSizeBytes are accumulated, at
154- * which point a chunk's worth of data will be inserted and the buffer
155- * reset.
153+ * which point a chunk document will be inserted and the buffer reset.
156154 *
157- * @param string $toWrite Binary data to write
155+ * @param string $data Binary data to write
158156 * @return integer
159157 */
160- public function insertChunks ( $ toWrite )
158+ public function writeBytes ( $ data )
161159 {
162160 if ($ this ->isClosed ) {
163161 // TODO: Should this be an error condition? e.g. BadMethodCallException
164162 return ;
165163 }
166164
167- $ readBytes = 0 ;
165+ $ bytesRead = 0 ;
168166
169- while ($ readBytes != strlen ($ toWrite )) {
170- $ addToBuffer = substr ($ toWrite , $ readBytes , $ this ->chunkSize - $ this ->bufferLength );
171- fwrite ($ this ->buffer , $ addToBuffer );
172- $ readBytes += strlen ($ addToBuffer );
173- $ this ->bufferLength += strlen ($ addToBuffer );
167+ while ($ bytesRead != strlen ($ data )) {
168+ $ initialBufferLength = strlen ($ this ->buffer );
169+ $ this ->buffer .= substr ($ data , $ bytesRead , $ this ->chunkSize - $ initialBufferLength );
170+ $ bytesRead += strlen ($ this ->buffer ) - $ initialBufferLength ;
174171
175- if ($ this ->bufferLength == $ this ->chunkSize ) {
176- rewind ($ this ->buffer );
177- $ this ->insertChunk (stream_get_contents ($ this ->buffer ));
178- ftruncate ($ this ->buffer , 0 );
179- $ this ->bufferLength = 0 ;
172+ if (strlen ($ this ->buffer ) == $ this ->chunkSize ) {
173+ $ this ->insertChunkFromBuffer ();
180174 }
181175 }
182176
183- return $ readBytes ;
177+ return $ bytesRead ;
184178 }
185179
186180 private function abort ()
187181 {
188- $ this ->collectionWrapper ->deleteChunksByFilesId ($ this ->file ['_id ' ]);
182+ try {
183+ $ this ->collectionWrapper ->deleteChunksByFilesId ($ this ->file ['_id ' ]);
184+ } catch (DriverRuntimeException $ e ) {
185+ // We are already handling an error if abort() is called, so suppress this
186+ }
187+
189188 $ this ->isClosed = true ;
190189 }
191190
192191 private function fileCollectionInsert ()
193192 {
194- if ($ this ->isClosed ) {
195- // TODO: Should this be an error condition? e.g. BadMethodCallException
196- return ;
197- }
198-
199193 $ md5 = hash_final ($ this ->ctx );
200194
201195 $ this ->file ['length ' ] = $ this ->length ;
202196 $ this ->file ['md5 ' ] = $ md5 ;
203197
204- $ this ->collectionWrapper ->insertFile ($ this ->file );
198+ try {
199+ $ this ->collectionWrapper ->insertFile ($ this ->file );
200+ } catch (DriverRuntimeException $ e ) {
201+ $ this ->abort ();
202+
203+ throw $ e ;
204+ }
205205
206206 return $ this ->file ['_id ' ];
207207 }
208208
209- private function insertChunk ( $ data )
209+ private function insertChunkFromBuffer ( )
210210 {
211- if ($ this ->isClosed ) {
212- // TODO: Should this be an error condition? e.g. BadMethodCallException
211+ if (strlen ($ this ->buffer ) == 0 ) {
213212 return ;
214213 }
215214
216- $ toUpload = [
215+ $ data = $ this ->buffer ;
216+ $ this ->buffer = '' ;
217+
218+ $ chunk = [
217219 'files_id ' => $ this ->file ['_id ' ],
218220 'n ' => $ this ->chunkOffset ,
219221 'data ' => new Binary ($ data , Binary::TYPE_GENERIC ),
220222 ];
221223
222224 hash_update ($ this ->ctx , $ data );
223225
224- $ this ->collectionWrapper ->insertChunk ($ toUpload );
226+ try {
227+ $ this ->collectionWrapper ->insertChunk ($ chunk );
228+ } catch (DriverRuntimeException $ e ) {
229+ $ this ->abort ();
230+
231+ throw $ e ;
232+ }
233+
225234 $ this ->length += strlen ($ data );
226235 $ this ->chunkOffset ++;
227236 }
0 commit comments