1 : /******************************************************************************
2 : * $Id: cpl_vsil_curl_streaming.cpp 25311 2012-12-15 12:48:14Z rouault $
3 : *
4 : * Project: CPL - Common Portability Library
5 : * Purpose: Implement VSI large file api for HTTP/FTP files in streaming mode
6 : * Author: Even Rouault <even dot rouault at mines dash paris.org>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2012, Even Rouault <even dot rouault at mines dash paris.org>
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "cpl_vsi_virtual.h"
31 : #include "cpl_string.h"
32 : #include "cpl_multiproc.h"
33 : #include "cpl_hash_set.h"
34 : #include "cpl_time.h"
35 :
36 : CPL_CVSID("$Id: cpl_vsil_curl_streaming.cpp 25311 2012-12-15 12:48:14Z rouault $");
37 :
38 : #if !defined(HAVE_CURL) || defined(CPL_MULTIPROC_STUB)
39 :
40 : void VSIInstallCurlStreamingFileHandler(void)
41 : {
42 : /* not supported */
43 : }
44 :
45 : #else
46 :
47 : #include <curl/curl.h>
48 :
49 : #include <map>
50 :
51 : #define ENABLE_DEBUG 0
52 :
53 : #define N_MAX_REGIONS 10
54 :
55 : #define BKGND_BUFFER_SIZE (1024 * 1024)
56 :
57 :
58 : /************************************************************************/
59 : /* RingBuffer */
60 : /************************************************************************/
61 :
62 : class RingBuffer
63 : {
64 : GByte* pabyBuffer;
65 : size_t nCapacity;
66 : size_t nOffset;
67 : size_t nLength;
68 :
69 : public:
70 : RingBuffer(size_t nCapacity = BKGND_BUFFER_SIZE);
71 : ~RingBuffer();
72 :
73 4820 : size_t GetCapacity() const { return nCapacity; }
74 26566 : size_t GetSize() const { return nLength; }
75 :
76 : void Reset();
77 : void Write(void* pBuffer, size_t nSize);
78 : void Read(void* pBuffer, size_t nSize);
79 : };
80 :
81 41 : RingBuffer::RingBuffer(size_t nCapacityIn)
82 : {
83 41 : pabyBuffer = (GByte*)CPLMalloc(nCapacityIn);
84 41 : nCapacity = nCapacityIn;
85 41 : nOffset = 0;
86 41 : nLength = 0;
87 41 : }
88 :
89 41 : RingBuffer::~RingBuffer()
90 : {
91 41 : CPLFree(pabyBuffer);
92 41 : }
93 :
94 68 : void RingBuffer::Reset()
95 : {
96 68 : nOffset = 0;
97 68 : nLength = 0;
98 68 : }
99 :
100 4820 : void RingBuffer::Write(void* pBuffer, size_t nSize)
101 : {
102 4820 : CPLAssert(nLength + nSize <= nCapacity);
103 :
104 4820 : size_t nEndOffset = (nOffset + nLength) % nCapacity;
105 4820 : size_t nSz = MIN(nSize, nCapacity - nEndOffset);
106 4820 : memcpy(pabyBuffer + nEndOffset, pBuffer, nSz);
107 4820 : if (nSz < nSize)
108 4 : memcpy(pabyBuffer, (GByte*)pBuffer + nSz, nSize - nSz);
109 :
110 4820 : nLength += nSize;
111 4820 : }
112 :
113 7070 : void RingBuffer::Read(void* pBuffer, size_t nSize)
114 : {
115 7070 : CPLAssert(nSize <= nLength);
116 :
117 7070 : if (pBuffer)
118 : {
119 7070 : size_t nSz = MIN(nSize, nCapacity - nOffset);
120 7070 : memcpy(pBuffer, pabyBuffer + nOffset, nSz);
121 7070 : if (nSz < nSize)
122 2 : memcpy((GByte*)pBuffer + nSz, pabyBuffer, nSize - nSz);
123 : }
124 :
125 7070 : nOffset = (nOffset + nSize) % nCapacity;
126 7070 : nLength -= nSize;
127 7070 : }
128 :
129 : /************************************************************************/
130 :
131 : typedef enum
132 : {
133 : EXIST_UNKNOWN = -1,
134 : EXIST_NO,
135 : EXIST_YES,
136 : } ExistStatus;
137 :
138 : typedef struct
139 : {
140 : ExistStatus eExists;
141 : int bHastComputedFileSize;
142 : vsi_l_offset fileSize;
143 : int bIsDirectory;
144 : } CachedFileProp;
145 :
146 : typedef struct
147 : {
148 : unsigned long pszURLHash;
149 : size_t nSize;
150 : GByte *pData;
151 : } CachedRegion;
152 :
153 : /************************************************************************/
154 : /* VSICurlStreamingFSHandler */
155 : /************************************************************************/
156 :
157 : class VSICurlStreamingFSHandler : public VSIFilesystemHandler
158 : {
159 : void *hMutex;
160 :
161 : CachedRegion **papsRegions;
162 : int nRegions;
163 :
164 : std::map<CPLString, CachedFileProp*> cacheFileSize;
165 :
166 : public:
167 : VSICurlStreamingFSHandler();
168 : ~VSICurlStreamingFSHandler();
169 :
170 : virtual VSIVirtualHandle *Open( const char *pszFilename,
171 : const char *pszAccess);
172 : virtual int Stat( const char *pszFilename, VSIStatBufL *pStatBuf, int nFlags );
173 :
174 : void AcquireMutex();
175 : void ReleaseMutex();
176 :
177 : CachedRegion* GetRegion(const char* pszURL);
178 :
179 : void AddRegion(const char* pszURL,
180 : vsi_l_offset nFileOffsetStart,
181 : size_t nSize,
182 : GByte *pData);
183 :
184 : CachedFileProp* GetCachedFileProp(const char* pszURL);
185 : };
186 :
187 : /************************************************************************/
188 : /* VSICurlStreamingHandle */
189 : /************************************************************************/
190 :
191 : class VSICurlStreamingHandle : public VSIVirtualHandle
192 : {
193 : private:
194 : VSICurlStreamingFSHandler* poFS;
195 :
196 : char* pszURL;
197 :
198 : vsi_l_offset curOffset;
199 : vsi_l_offset fileSize;
200 : int bHastComputedFileSize;
201 : ExistStatus eExists;
202 : int bIsDirectory;
203 :
204 : vsi_l_offset lastDownloadedOffset;
205 : int nBlocksToDownload;
206 : int bEOF;
207 :
208 : CURL* hCurlHandle;
209 :
210 : volatile int bDownloadInProgress;
211 : volatile int bDownloadStopped;
212 : volatile int bAskDownloadEnd;
213 : vsi_l_offset nRingBufferFileOffset;
214 : void *hThread;
215 : void *hRingBufferMutex;
216 : void *hCondProducer;
217 : void *hCondConsumer;
218 : RingBuffer oRingBuffer;
219 : void StartDownload();
220 : void StopDownload();
221 : void PutRingBufferInCache();
222 :
223 : GByte *pabyHeaderData;
224 : size_t nHeaderSize;
225 : vsi_l_offset nBodySize;
226 : int nHTTPCode;
227 :
228 : void AcquireMutex();
229 : void ReleaseMutex();
230 :
231 : public:
232 :
233 : VSICurlStreamingHandle(VSICurlStreamingFSHandler* poFS, const char* pszURL);
234 : ~VSICurlStreamingHandle();
235 :
236 : virtual int Seek( vsi_l_offset nOffset, int nWhence );
237 : virtual vsi_l_offset Tell();
238 : virtual size_t Read( void *pBuffer, size_t nSize, size_t nMemb );
239 : virtual size_t Write( const void *pBuffer, size_t nSize, size_t nMemb );
240 : virtual int Eof();
241 : virtual int Flush();
242 : virtual int Close();
243 :
244 : void DownloadInThread();
245 : int ReceivedBytes(GByte *buffer, size_t count, size_t nmemb);
246 : int ReceivedBytesHeader(GByte *buffer, size_t count, size_t nmemb);
247 :
248 0 : int IsKnownFileSize() const { return bHastComputedFileSize; }
249 : vsi_l_offset GetFileSize();
250 : int Exists();
251 0 : int IsDirectory() const { return bIsDirectory; }
252 : };
253 :
254 : /************************************************************************/
255 : /* VSICurlStreamingHandle() */
256 : /************************************************************************/
257 :
258 41 : VSICurlStreamingHandle::VSICurlStreamingHandle(VSICurlStreamingFSHandler* poFS, const char* pszURL)
259 : {
260 41 : this->poFS = poFS;
261 41 : this->pszURL = CPLStrdup(pszURL);
262 :
263 41 : curOffset = 0;
264 :
265 41 : poFS->AcquireMutex();
266 41 : CachedFileProp* cachedFileProp = poFS->GetCachedFileProp(pszURL);
267 41 : eExists = cachedFileProp->eExists;
268 41 : fileSize = cachedFileProp->fileSize;
269 41 : bHastComputedFileSize = cachedFileProp->bHastComputedFileSize;
270 41 : bIsDirectory = cachedFileProp->bIsDirectory;
271 41 : poFS->ReleaseMutex();
272 :
273 41 : lastDownloadedOffset = -1;
274 41 : nBlocksToDownload = 1;
275 41 : bEOF = FALSE;
276 :
277 41 : hCurlHandle = NULL;
278 :
279 41 : hThread = NULL;
280 41 : hRingBufferMutex = CPLCreateMutex();
281 41 : ReleaseMutex();
282 41 : hCondProducer = CPLCreateCond();
283 41 : hCondConsumer = CPLCreateCond();
284 :
285 41 : bDownloadInProgress = FALSE;
286 41 : bDownloadStopped = FALSE;
287 41 : bAskDownloadEnd = FALSE;
288 41 : nRingBufferFileOffset = 0;
289 :
290 41 : pabyHeaderData = NULL;
291 41 : nHeaderSize = 0;
292 41 : nBodySize = 0;
293 41 : nHTTPCode = 0;
294 41 : }
295 :
296 : /************************************************************************/
297 : /* ~VSICurlStreamingHandle() */
298 : /************************************************************************/
299 :
300 41 : VSICurlStreamingHandle::~VSICurlStreamingHandle()
301 : {
302 41 : StopDownload();
303 :
304 41 : CPLFree(pszURL);
305 41 : if (hCurlHandle != NULL)
306 0 : curl_easy_cleanup(hCurlHandle);
307 :
308 41 : CPLFree(pabyHeaderData);
309 :
310 41 : CPLDestroyMutex( hRingBufferMutex );
311 41 : CPLDestroyCond( hCondProducer );
312 41 : CPLDestroyCond( hCondConsumer );
313 41 : }
314 :
315 : /************************************************************************/
316 : /* AcquireMutex() */
317 : /************************************************************************/
318 :
319 15955 : void VSICurlStreamingHandle::AcquireMutex()
320 : {
321 15955 : CPLAcquireMutex(hRingBufferMutex, 1000.0);
322 15955 : }
323 :
324 : /************************************************************************/
325 : /* ReleaseMutex() */
326 : /************************************************************************/
327 :
328 15996 : void VSICurlStreamingHandle::ReleaseMutex()
329 : {
330 15996 : CPLReleaseMutex(hRingBufferMutex);
331 15995 : }
332 :
333 : /************************************************************************/
334 : /* Seek() */
335 : /************************************************************************/
336 :
337 124 : int VSICurlStreamingHandle::Seek( vsi_l_offset nOffset, int nWhence )
338 : {
339 124 : if (nWhence == SEEK_SET)
340 : {
341 123 : curOffset = nOffset;
342 : }
343 1 : else if (nWhence == SEEK_CUR)
344 : {
345 0 : curOffset = curOffset + nOffset;
346 : }
347 : else
348 : {
349 1 : curOffset = GetFileSize() + nOffset;
350 : }
351 124 : bEOF = FALSE;
352 124 : return 0;
353 : }
354 :
355 : /************************************************************************/
356 : /* VSICurlSetOptions() */
357 : /************************************************************************/
358 :
359 26 : static void VSICurlSetOptions(CURL* hCurlHandle, const char* pszURL)
360 : {
361 26 : curl_easy_setopt(hCurlHandle, CURLOPT_URL, pszURL);
362 26 : if (CSLTestBoolean(CPLGetConfigOption("CPL_CURL_VERBOSE", "NO")))
363 0 : curl_easy_setopt(hCurlHandle, CURLOPT_VERBOSE, 1);
364 :
365 : /* Set Proxy parameters */
366 26 : const char* pszProxy = CPLGetConfigOption("GDAL_HTTP_PROXY", NULL);
367 26 : if (pszProxy)
368 0 : curl_easy_setopt(hCurlHandle,CURLOPT_PROXY,pszProxy);
369 :
370 26 : const char* pszProxyUserPwd = CPLGetConfigOption("GDAL_HTTP_PROXYUSERPWD", NULL);
371 26 : if (pszProxyUserPwd)
372 0 : curl_easy_setopt(hCurlHandle,CURLOPT_PROXYUSERPWD,pszProxyUserPwd);
373 :
374 : /* Enable following redirections. Requires libcurl 7.10.1 at least */
375 26 : curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 1);
376 26 : curl_easy_setopt(hCurlHandle, CURLOPT_MAXREDIRS, 10);
377 :
378 : /* 7.16 */
379 : #if LIBCURL_VERSION_NUM >= 0x071000
380 26 : long option = CURLFTPMETHOD_SINGLECWD;
381 26 : curl_easy_setopt(hCurlHandle, CURLOPT_FTP_FILEMETHOD, option);
382 : #endif
383 :
384 : /* 7.12.3 */
385 : #if LIBCURL_VERSION_NUM > 0x070C03
386 : /* ftp://ftp2.cits.rncan.gc.ca/pub/cantopo/250k_tif/ doesn't like EPSV command */
387 26 : curl_easy_setopt(hCurlHandle, CURLOPT_FTP_USE_EPSV, 0);
388 : #endif
389 :
390 : /* NOSIGNAL should be set to true for timeout to work in multithread
391 : environments on Unix, requires libcurl 7.10 or more recent.
392 : (this force avoiding the use of sgnal handlers) */
393 :
394 : /* 7.10 */
395 : #if LIBCURL_VERSION_NUM >= 0x070A00
396 26 : curl_easy_setopt(hCurlHandle, CURLOPT_NOSIGNAL, 1);
397 : #endif
398 :
399 26 : curl_easy_setopt(hCurlHandle, CURLOPT_NOBODY, 0);
400 26 : curl_easy_setopt(hCurlHandle, CURLOPT_HTTPGET, 1);
401 26 : curl_easy_setopt(hCurlHandle, CURLOPT_HEADER, 0);
402 :
403 : /* 7.16.4 */
404 : #if LIBCURL_VERSION_NUM <= 0x071004
405 : curl_easy_setopt(hCurlHandle, CURLOPT_FTPLISTONLY, 0);
406 : #elif LIBCURL_VERSION_NUM > 0x071004
407 26 : curl_easy_setopt(hCurlHandle, CURLOPT_DIRLISTONLY, 0);
408 : #endif
409 :
410 26 : curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, NULL);
411 26 : curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, NULL);
412 26 : }
413 :
414 :
415 : typedef struct
416 : {
417 : char* pBuffer;
418 : size_t nSize;
419 : int bIsHTTP;
420 : int bIsInHeader;
421 : int nHTTPCode;
422 : int bDownloadHeaderOnly;
423 : } WriteFuncStruct;
424 :
425 : /************************************************************************/
426 : /* VSICURLStreamingInitWriteFuncStruct() */
427 : /************************************************************************/
428 :
429 0 : static void VSICURLStreamingInitWriteFuncStruct(WriteFuncStruct *psStruct)
430 : {
431 0 : psStruct->pBuffer = NULL;
432 0 : psStruct->nSize = 0;
433 0 : psStruct->bIsHTTP = FALSE;
434 0 : psStruct->bIsInHeader = TRUE;
435 0 : psStruct->nHTTPCode = 0;
436 0 : psStruct->bDownloadHeaderOnly = FALSE;
437 0 : }
438 :
439 : /************************************************************************/
440 : /* VSICurlStreamingHandleWriteFuncForHeader() */
441 : /************************************************************************/
442 :
443 0 : static int VSICurlStreamingHandleWriteFuncForHeader(void *buffer, size_t count, size_t nmemb, void *req)
444 : {
445 0 : WriteFuncStruct* psStruct = (WriteFuncStruct*) req;
446 0 : size_t nSize = count * nmemb;
447 :
448 : char* pNewBuffer = (char*) VSIRealloc(psStruct->pBuffer,
449 0 : psStruct->nSize + nSize + 1);
450 0 : if (pNewBuffer)
451 : {
452 0 : psStruct->pBuffer = pNewBuffer;
453 0 : memcpy(psStruct->pBuffer + psStruct->nSize, buffer, nSize);
454 0 : psStruct->pBuffer[psStruct->nSize + nSize] = '\0';
455 0 : if (psStruct->bIsHTTP && psStruct->bIsInHeader)
456 : {
457 0 : char* pszLine = psStruct->pBuffer + psStruct->nSize;
458 0 : if (EQUALN(pszLine, "HTTP/1.0 ", 9) ||
459 : EQUALN(pszLine, "HTTP/1.1 ", 9))
460 0 : psStruct->nHTTPCode = atoi(pszLine + 9);
461 :
462 0 : if (pszLine[0] == '\r' || pszLine[0] == '\n')
463 : {
464 0 : if (psStruct->bDownloadHeaderOnly)
465 : {
466 : /* If moved permanently/temporarily, go on. Otherwise stop now*/
467 0 : if (!(psStruct->nHTTPCode == 301 || psStruct->nHTTPCode == 302))
468 0 : return 0;
469 : }
470 : else
471 : {
472 0 : psStruct->bIsInHeader = FALSE;
473 : }
474 : }
475 : }
476 0 : psStruct->nSize += nSize;
477 0 : return nmemb;
478 : }
479 : else
480 : {
481 0 : return 0;
482 : }
483 : }
484 :
485 :
486 : /************************************************************************/
487 : /* GetFileSize() */
488 : /************************************************************************/
489 :
490 1 : vsi_l_offset VSICurlStreamingHandle::GetFileSize()
491 : {
492 : WriteFuncStruct sWriteFuncData;
493 : WriteFuncStruct sWriteFuncHeaderData;
494 :
495 1 : AcquireMutex();
496 1 : if (bHastComputedFileSize)
497 : {
498 1 : vsi_l_offset nRet = fileSize;
499 1 : ReleaseMutex();
500 1 : return nRet;
501 : }
502 0 : ReleaseMutex();
503 :
504 : #if LIBCURL_VERSION_NUM < 0x070B00
505 : /* Curl 7.10.X doesn't manage to unset the CURLOPT_RANGE that would have been */
506 : /* previously set, so we have to reinit the connection handle */
507 : if (hCurlHandle)
508 : {
509 : curl_easy_cleanup(hCurlHandle);
510 : hCurlHandle = curl_easy_init();
511 : }
512 : #endif
513 :
514 0 : CURL* hLocalHandle = curl_easy_init();
515 :
516 0 : VSICurlSetOptions(hLocalHandle, pszURL);
517 :
518 0 : VSICURLStreamingInitWriteFuncStruct(&sWriteFuncHeaderData);
519 :
520 : /* HACK for mbtiles driver: proper fix would be to auto-detect servers that don't accept HEAD */
521 : /* http://a.tiles.mapbox.com/v3/ doesn't accept HEAD, so let's start a GET */
522 : /* and interrupt is as soon as the header is found */
523 0 : if (strstr(pszURL, ".tiles.mapbox.com/") != NULL)
524 : {
525 0 : curl_easy_setopt(hLocalHandle, CURLOPT_HEADERDATA, &sWriteFuncHeaderData);
526 0 : curl_easy_setopt(hLocalHandle, CURLOPT_HEADERFUNCTION, VSICurlStreamingHandleWriteFuncForHeader);
527 :
528 0 : sWriteFuncHeaderData.bIsHTTP = strncmp(pszURL, "http", 4) == 0;
529 0 : sWriteFuncHeaderData.bDownloadHeaderOnly = TRUE;
530 : }
531 : else
532 : {
533 0 : curl_easy_setopt(hLocalHandle, CURLOPT_NOBODY, 1);
534 0 : curl_easy_setopt(hLocalHandle, CURLOPT_HTTPGET, 0);
535 0 : curl_easy_setopt(hLocalHandle, CURLOPT_HEADER, 1);
536 : }
537 :
538 : /* We need that otherwise OSGEO4W's libcurl issue a dummy range request */
539 : /* when doing a HEAD when recycling connections */
540 0 : curl_easy_setopt(hLocalHandle, CURLOPT_RANGE, NULL);
541 :
542 : /* Bug with older curl versions (<=7.16.4) and FTP. See http://curl.haxx.se/mail/lib-2007-08/0312.html */
543 0 : VSICURLStreamingInitWriteFuncStruct(&sWriteFuncData);
544 0 : curl_easy_setopt(hLocalHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
545 0 : curl_easy_setopt(hLocalHandle, CURLOPT_WRITEFUNCTION, VSICurlStreamingHandleWriteFuncForHeader);
546 :
547 : char szCurlErrBuf[CURL_ERROR_SIZE+1];
548 0 : szCurlErrBuf[0] = '\0';
549 0 : curl_easy_setopt(hLocalHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf );
550 :
551 0 : double dfSize = 0;
552 0 : curl_easy_perform(hLocalHandle);
553 :
554 0 : AcquireMutex();
555 :
556 0 : eExists = EXIST_UNKNOWN;
557 0 : bHastComputedFileSize = TRUE;
558 :
559 0 : if (strncmp(pszURL, "ftp", 3) == 0)
560 : {
561 0 : if (sWriteFuncData.pBuffer != NULL &&
562 : strncmp(sWriteFuncData.pBuffer, "Content-Length: ", strlen( "Content-Length: ")) == 0)
563 : {
564 0 : const char* pszBuffer = sWriteFuncData.pBuffer + strlen("Content-Length: ");
565 0 : eExists = EXIST_YES;
566 0 : fileSize = CPLScanUIntBig(pszBuffer, sWriteFuncData.nSize - strlen("Content-Length: "));
567 0 : if (ENABLE_DEBUG)
568 : CPLDebug("VSICURL", "GetFileSize(%s)=" CPL_FRMT_GUIB,
569 : pszURL, fileSize);
570 : }
571 : }
572 :
573 0 : if (eExists != EXIST_YES)
574 : {
575 0 : CURLcode code = curl_easy_getinfo(hLocalHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dfSize );
576 0 : if (code == 0)
577 : {
578 0 : eExists = EXIST_YES;
579 0 : if (dfSize < 0)
580 0 : fileSize = 0;
581 : else
582 0 : fileSize = (GUIntBig)dfSize;
583 : }
584 : else
585 : {
586 0 : eExists = EXIST_NO;
587 0 : fileSize = 0;
588 : CPLError(CE_Failure, CPLE_AppDefined,
589 0 : "VSICurlStreamingHandle::GetFileSize failed");
590 : }
591 :
592 0 : long response_code = 0;
593 0 : curl_easy_getinfo(hLocalHandle, CURLINFO_HTTP_CODE, &response_code);
594 0 : if (response_code != 200)
595 : {
596 0 : eExists = EXIST_NO;
597 0 : fileSize = 0;
598 : }
599 :
600 : /* Try to guess if this is a directory. Generally if this is a directory, */
601 : /* curl will retry with an URL with slash added */
602 0 : char *pszEffectiveURL = NULL;
603 0 : curl_easy_getinfo(hLocalHandle, CURLINFO_EFFECTIVE_URL, &pszEffectiveURL);
604 0 : if (pszEffectiveURL != NULL &&
605 : strncmp(pszURL, pszEffectiveURL, strlen(pszURL)) == 0 &&
606 0 : pszEffectiveURL[strlen(pszURL)] == '/')
607 : {
608 0 : eExists = EXIST_YES;
609 0 : fileSize = 0;
610 0 : bIsDirectory = TRUE;
611 : }
612 :
613 0 : if (ENABLE_DEBUG)
614 : CPLDebug("VSICURL", "GetFileSize(%s)=" CPL_FRMT_GUIB " response_code=%d",
615 : pszURL, fileSize, (int)response_code);
616 : }
617 :
618 0 : CPLFree(sWriteFuncData.pBuffer);
619 0 : CPLFree(sWriteFuncHeaderData.pBuffer);
620 :
621 0 : poFS->AcquireMutex();
622 0 : CachedFileProp* cachedFileProp = poFS->GetCachedFileProp(pszURL);
623 0 : cachedFileProp->bHastComputedFileSize = TRUE;
624 0 : cachedFileProp->fileSize = fileSize;
625 0 : cachedFileProp->eExists = eExists;
626 0 : cachedFileProp->bIsDirectory = bIsDirectory;
627 0 : poFS->ReleaseMutex();
628 :
629 0 : vsi_l_offset nRet = fileSize;
630 0 : ReleaseMutex();
631 :
632 0 : if (hCurlHandle == NULL)
633 0 : hCurlHandle = hLocalHandle;
634 : else
635 0 : curl_easy_cleanup(hLocalHandle);
636 :
637 0 : return nRet;
638 : }
639 :
640 : /************************************************************************/
641 : /* Exists() */
642 : /************************************************************************/
643 :
644 41 : int VSICurlStreamingHandle::Exists()
645 : {
646 41 : if (eExists == EXIST_UNKNOWN)
647 : {
648 : /* Consider that only the files whose extension ends up with one that is */
649 : /* listed in CPL_VSIL_CURL_ALLOWED_EXTENSIONS exist on the server */
650 : /* This can speeds up dramatically open experience, in case the server */
651 : /* cannot return a file list */
652 : /* For example : */
653 : /* gdalinfo --config CPL_VSIL_CURL_ALLOWED_EXTENSIONS ".tif" /vsicurl_streaming/http://igskmncngs506.cr.usgs.gov/gmted/Global_tiles_GMTED/075darcsec/bln/W030/30N030W_20101117_gmted_bln075.tif */
654 : const char* pszAllowedExtensions =
655 24 : CPLGetConfigOption("CPL_VSIL_CURL_ALLOWED_EXTENSIONS", NULL);
656 24 : if (pszAllowedExtensions)
657 : {
658 0 : char** papszExtensions = CSLTokenizeString2( pszAllowedExtensions, ", ", 0 );
659 0 : int nURLLen = strlen(pszURL);
660 0 : int bFound = FALSE;
661 0 : for(int i=0;papszExtensions[i] != NULL;i++)
662 : {
663 0 : int nExtensionLen = strlen(papszExtensions[i]);
664 0 : if (nURLLen > nExtensionLen &&
665 0 : EQUAL(pszURL + nURLLen - nExtensionLen, papszExtensions[i]))
666 : {
667 0 : bFound = TRUE;
668 0 : break;
669 : }
670 : }
671 :
672 0 : if (!bFound)
673 : {
674 0 : eExists = EXIST_NO;
675 0 : fileSize = 0;
676 :
677 0 : poFS->AcquireMutex();
678 0 : CachedFileProp* cachedFileProp = poFS->GetCachedFileProp(pszURL);
679 0 : cachedFileProp->bHastComputedFileSize = TRUE;
680 0 : cachedFileProp->fileSize = fileSize;
681 0 : cachedFileProp->eExists = eExists;
682 0 : poFS->ReleaseMutex();
683 :
684 0 : CSLDestroy(papszExtensions);
685 :
686 0 : return 0;
687 : }
688 :
689 0 : CSLDestroy(papszExtensions);
690 : }
691 :
692 : char chFirstByte;
693 24 : int bExists = (Read(&chFirstByte, 1, 1) == 1);
694 :
695 24 : AcquireMutex();
696 24 : poFS->AcquireMutex();
697 24 : CachedFileProp* cachedFileProp = poFS->GetCachedFileProp(pszURL);
698 24 : cachedFileProp->eExists = eExists = bExists ? EXIST_YES : EXIST_NO;
699 24 : poFS->ReleaseMutex();
700 24 : ReleaseMutex();
701 :
702 24 : Seek(0, SEEK_SET);
703 : }
704 :
705 41 : return eExists == EXIST_YES;
706 : }
707 :
708 : /************************************************************************/
709 : /* Tell() */
710 : /************************************************************************/
711 :
712 8 : vsi_l_offset VSICurlStreamingHandle::Tell()
713 : {
714 8 : return curOffset;
715 : }
716 :
717 : /************************************************************************/
718 : /* ReceivedBytes() */
719 : /************************************************************************/
720 :
721 4820 : int VSICurlStreamingHandle::ReceivedBytes(GByte *buffer, size_t count, size_t nmemb)
722 : {
723 4820 : size_t nSize = count * nmemb;
724 4820 : nBodySize += nSize;
725 :
726 : if (ENABLE_DEBUG)
727 : CPLDebug("VSICURL", "Receiving %d bytes...", (int)nSize);
728 :
729 4820 : AcquireMutex();
730 4820 : if (eExists == EXIST_UNKNOWN)
731 : {
732 0 : poFS->AcquireMutex();
733 0 : CachedFileProp* cachedFileProp = poFS->GetCachedFileProp(pszURL);
734 0 : cachedFileProp->eExists = eExists = EXIST_YES;
735 0 : poFS->ReleaseMutex();
736 : }
737 4820 : else if (eExists == EXIST_NO)
738 : {
739 0 : ReleaseMutex();
740 0 : return 0;
741 : }
742 :
743 0 : while(TRUE)
744 : {
745 4820 : size_t nFree = oRingBuffer.GetCapacity() - oRingBuffer.GetSize();
746 4820 : if (nSize <= nFree)
747 : {
748 4820 : oRingBuffer.Write(buffer, nSize);
749 :
750 : /* Signal to the consumer that we have added bytes to the buffer */
751 4820 : CPLCondSignal(hCondProducer);
752 :
753 4820 : if (bAskDownloadEnd)
754 : {
755 : if (ENABLE_DEBUG)
756 : CPLDebug("VSICURL", "Download interruption asked");
757 :
758 3 : ReleaseMutex();
759 3 : return 0;
760 : }
761 : break;
762 : }
763 : else
764 : {
765 0 : oRingBuffer.Write(buffer, nFree);
766 0 : buffer += nFree;
767 0 : nSize -= nFree;
768 :
769 : /* Signal to the consumer that we have added bytes to the buffer */
770 0 : CPLCondSignal(hCondProducer);
771 :
772 : if (ENABLE_DEBUG)
773 : CPLDebug("VSICURL", "Waiting for reader to consume some bytes...");
774 :
775 0 : while(oRingBuffer.GetSize() == oRingBuffer.GetCapacity() && !bAskDownloadEnd)
776 : {
777 0 : CPLCondWait(hCondConsumer, hRingBufferMutex);
778 : }
779 :
780 0 : if (bAskDownloadEnd)
781 : {
782 : if (ENABLE_DEBUG)
783 : CPLDebug("VSICURL", "Download interruption asked");
784 :
785 0 : ReleaseMutex();
786 0 : return 0;
787 : }
788 : }
789 : }
790 :
791 4817 : ReleaseMutex();
792 :
793 4817 : return nmemb;
794 : }
795 :
796 : /************************************************************************/
797 : /* VSICurlStreamingHandleReceivedBytes() */
798 : /************************************************************************/
799 :
800 4820 : static int VSICurlStreamingHandleReceivedBytes(void *buffer, size_t count, size_t nmemb, void *req)
801 : {
802 4820 : return ((VSICurlStreamingHandle*)req)->ReceivedBytes((GByte*)buffer, count, nmemb);
803 : }
804 :
805 :
806 : /************************************************************************/
807 : /* VSICurlStreamingHandleReceivedBytesHeader() */
808 : /************************************************************************/
809 :
810 : #define HEADER_SIZE 32768
811 :
812 192 : int VSICurlStreamingHandle::ReceivedBytesHeader(GByte *buffer, size_t count, size_t nmemb)
813 : {
814 192 : size_t nSize = count * nmemb;
815 : if (ENABLE_DEBUG)
816 : CPLDebug("VSICURL", "Receiving %d bytes for header...", (int)nSize);
817 :
818 : /* Reset buffer if we have followed link after a redirect */
819 192 : if (nSize >=9 && (nHTTPCode == 301 || nHTTPCode == 302) &&
820 : (EQUALN((const char*)buffer, "HTTP/1.0 ", 9) ||
821 : EQUALN((const char*)buffer, "HTTP/1.1 ", 9)))
822 : {
823 0 : nHeaderSize = 0;
824 0 : nHTTPCode = 0;
825 : }
826 :
827 192 : if (nHeaderSize < HEADER_SIZE)
828 : {
829 192 : size_t nSz = MIN(nSize, HEADER_SIZE - nHeaderSize);
830 192 : memcpy(pabyHeaderData + nHeaderSize, buffer, nSz);
831 192 : pabyHeaderData[nHeaderSize + nSz] = '\0';
832 192 : nHeaderSize += nSz;
833 :
834 : //CPLDebug("VSICURL", "Header : %s", pabyHeaderData);
835 :
836 192 : AcquireMutex();
837 :
838 192 : if (eExists == EXIST_UNKNOWN && nHTTPCode == 0 &&
839 : strchr((const char*)pabyHeaderData, '\n') != NULL &&
840 : (EQUALN((const char*)pabyHeaderData, "HTTP/1.0 ", 9) ||
841 : EQUALN((const char*)pabyHeaderData, "HTTP/1.1 ", 9)))
842 : {
843 24 : nHTTPCode = atoi((const char*)pabyHeaderData + 9);
844 : if (ENABLE_DEBUG)
845 : CPLDebug("VSICURL", "HTTP code = %d", nHTTPCode);
846 :
847 : /* If moved permanently/temporarily, go on */
848 24 : if( !(nHTTPCode == 301 || nHTTPCode == 302) )
849 : {
850 24 : poFS->AcquireMutex();
851 24 : CachedFileProp* cachedFileProp = poFS->GetCachedFileProp(pszURL);
852 24 : cachedFileProp->eExists = eExists = (nHTTPCode == 200) ? EXIST_YES : EXIST_NO;
853 24 : poFS->ReleaseMutex();
854 : }
855 : }
856 :
857 192 : if ( !(nHTTPCode == 301 || nHTTPCode == 302) && !bHastComputedFileSize)
858 : {
859 144 : const char* pszContentLength = strstr((const char*)pabyHeaderData, "Content-Length: ");
860 144 : const char* pszEndOfLine = pszContentLength ? strchr(pszContentLength, '\n') : NULL;
861 144 : if (pszEndOfLine != NULL)
862 : {
863 16 : poFS->AcquireMutex();
864 16 : CachedFileProp* cachedFileProp = poFS->GetCachedFileProp(pszURL);
865 : cachedFileProp->fileSize = fileSize = CPLScanUIntBig(pszContentLength + 16,
866 16 : pszEndOfLine - (pszContentLength + 16));
867 16 : cachedFileProp->bHastComputedFileSize = bHastComputedFileSize = TRUE;
868 : if (ENABLE_DEBUG)
869 : CPLDebug("VSICURL", "File size = " CPL_FRMT_GUIB, fileSize);
870 16 : poFS->ReleaseMutex();
871 : }
872 : }
873 :
874 192 : ReleaseMutex();
875 : }
876 :
877 192 : return nmemb;
878 : }
879 :
880 : /************************************************************************/
881 : /* VSICurlStreamingHandleReceivedBytesHeader() */
882 : /************************************************************************/
883 :
884 192 : static int VSICurlStreamingHandleReceivedBytesHeader(void *buffer, size_t count, size_t nmemb, void *req)
885 : {
886 192 : return ((VSICurlStreamingHandle*)req)->ReceivedBytesHeader((GByte*)buffer, count, nmemb);
887 : }
888 : /************************************************************************/
889 : /* DownloadInThread() */
890 : /************************************************************************/
891 :
892 26 : void VSICurlStreamingHandle::DownloadInThread()
893 : {
894 26 : VSICurlSetOptions(hCurlHandle, pszURL);
895 :
896 26 : if (pabyHeaderData == NULL)
897 25 : pabyHeaderData = (GByte*) CPLMalloc(HEADER_SIZE + 1);
898 26 : nHeaderSize = 0;
899 26 : nBodySize = 0;
900 26 : nHTTPCode = 0;
901 :
902 26 : curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, this);
903 26 : curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, VSICurlStreamingHandleReceivedBytesHeader);
904 :
905 26 : curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, this);
906 26 : curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, VSICurlStreamingHandleReceivedBytes);
907 :
908 : char szCurlErrBuf[CURL_ERROR_SIZE+1];
909 26 : szCurlErrBuf[0] = '\0';
910 26 : curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf );
911 :
912 26 : CURLcode eRet = curl_easy_perform(hCurlHandle);
913 :
914 26 : curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, NULL);
915 26 : curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, NULL);
916 26 : curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, NULL);
917 26 : curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, NULL);
918 :
919 26 : AcquireMutex();
920 26 : if (!bAskDownloadEnd && eRet == 0 && !bHastComputedFileSize)
921 : {
922 6 : poFS->AcquireMutex();
923 6 : CachedFileProp* cachedFileProp = poFS->GetCachedFileProp(pszURL);
924 6 : cachedFileProp->fileSize = fileSize = nBodySize;
925 6 : cachedFileProp->bHastComputedFileSize = bHastComputedFileSize = TRUE;
926 : if (ENABLE_DEBUG)
927 : CPLDebug("VSICURL", "File size = " CPL_FRMT_GUIB, fileSize);
928 6 : poFS->ReleaseMutex();
929 : }
930 :
931 26 : bDownloadInProgress = FALSE;
932 26 : bDownloadStopped = TRUE;
933 :
934 : /* Signal to the consumer that the download has ended */
935 26 : CPLCondSignal(hCondProducer);
936 26 : ReleaseMutex();
937 26 : }
938 :
939 26 : static void VSICurlDownloadInThread(void* pArg)
940 : {
941 26 : ((VSICurlStreamingHandle*)pArg)->DownloadInThread();
942 26 : }
943 :
944 : /************************************************************************/
945 : /* StartDownload() */
946 : /************************************************************************/
947 :
948 86 : void VSICurlStreamingHandle::StartDownload()
949 : {
950 86 : if (bDownloadInProgress || bDownloadStopped)
951 60 : return;
952 :
953 : //if (ENABLE_DEBUG)
954 26 : CPLDebug("VSICURL", "Start download for %s", pszURL);
955 :
956 26 : if (hCurlHandle == NULL)
957 26 : hCurlHandle = curl_easy_init();
958 26 : oRingBuffer.Reset();
959 26 : bDownloadInProgress = TRUE;
960 26 : nRingBufferFileOffset = 0;
961 26 : hThread = CPLCreateJoinableThread(VSICurlDownloadInThread, this);
962 : }
963 :
964 : /************************************************************************/
965 : /* StopDownload() */
966 : /************************************************************************/
967 :
968 42 : void VSICurlStreamingHandle::StopDownload()
969 : {
970 42 : if (hThread)
971 : {
972 : //if (ENABLE_DEBUG)
973 26 : CPLDebug("VSICURL", "Stop download for %s", pszURL);
974 :
975 26 : AcquireMutex();
976 : /* Signal to the producer that we ask for download interruption */
977 26 : bAskDownloadEnd = TRUE;
978 26 : CPLCondSignal(hCondConsumer);
979 :
980 : /* Wait for the producer to have finished */
981 56 : while(bDownloadInProgress)
982 4 : CPLCondWait(hCondProducer, hRingBufferMutex);
983 :
984 26 : bAskDownloadEnd = FALSE;
985 :
986 26 : ReleaseMutex();
987 :
988 26 : CPLJoinThread(hThread);
989 26 : hThread = NULL;
990 :
991 26 : curl_easy_cleanup(hCurlHandle);
992 26 : hCurlHandle = NULL;
993 : }
994 :
995 42 : PutRingBufferInCache();
996 :
997 42 : oRingBuffer.Reset();
998 42 : bDownloadStopped = FALSE;
999 42 : }
1000 :
1001 : /************************************************************************/
1002 : /* PutRingBufferInCache() */
1003 : /************************************************************************/
1004 :
1005 135 : void VSICurlStreamingHandle::PutRingBufferInCache()
1006 : {
1007 135 : if (nRingBufferFileOffset >= BKGND_BUFFER_SIZE)
1008 17 : return;
1009 :
1010 118 : AcquireMutex();
1011 :
1012 : /* Cache any remaining bytes available in the ring buffer */
1013 118 : size_t nBufSize = oRingBuffer.GetSize();
1014 118 : if ( nBufSize > 0 )
1015 : {
1016 42 : if (nRingBufferFileOffset + nBufSize > BKGND_BUFFER_SIZE)
1017 0 : nBufSize = (size_t) (BKGND_BUFFER_SIZE - nRingBufferFileOffset);
1018 42 : GByte* pabyTmp = (GByte*) CPLMalloc(nBufSize);
1019 42 : oRingBuffer.Read(pabyTmp, nBufSize);
1020 :
1021 : /* Signal to the producer that we have ingested some bytes */
1022 42 : CPLCondSignal(hCondConsumer);
1023 :
1024 : poFS->AddRegion(pszURL, nRingBufferFileOffset,
1025 42 : nBufSize, pabyTmp);
1026 42 : nRingBufferFileOffset += nBufSize;
1027 42 : CPLFree(pabyTmp);
1028 : }
1029 :
1030 118 : ReleaseMutex();
1031 : }
1032 :
1033 : /************************************************************************/
1034 : /* Read() */
1035 : /************************************************************************/
1036 :
1037 215 : size_t VSICurlStreamingHandle::Read( void *pBuffer, size_t nSize, size_t nMemb )
1038 : {
1039 215 : GByte* pabyBuffer = (GByte*)pBuffer;
1040 215 : size_t nBufferRequestSize = nSize * nMemb;
1041 215 : if (nBufferRequestSize == 0)
1042 0 : return 0;
1043 215 : size_t nRemaining = nBufferRequestSize;
1044 :
1045 215 : AcquireMutex();
1046 215 : int bHastComputedFileSizeLocal = bHastComputedFileSize;
1047 215 : vsi_l_offset fileSizeLocal = fileSize;
1048 215 : ReleaseMutex();
1049 :
1050 215 : if (bHastComputedFileSizeLocal && curOffset >= fileSizeLocal)
1051 1 : bEOF = TRUE;
1052 215 : if (bEOF)
1053 1 : return 0;
1054 :
1055 214 : if (curOffset < nRingBufferFileOffset)
1056 93 : PutRingBufferInCache();
1057 :
1058 : if (ENABLE_DEBUG)
1059 : CPLDebug("VSICURL", "Read [" CPL_FRMT_GUIB ", " CPL_FRMT_GUIB "[ in %s",
1060 : curOffset, curOffset + nBufferRequestSize, pszURL);
1061 :
1062 : /* Can we use the cache ? */
1063 214 : poFS->AcquireMutex();
1064 214 : CachedRegion* psRegion = poFS->GetRegion(pszURL);
1065 214 : if( psRegion != NULL && curOffset < psRegion->nSize )
1066 : {
1067 162 : size_t nSz = MIN(nRemaining, (size_t)(psRegion->nSize - curOffset));
1068 : if (ENABLE_DEBUG)
1069 : CPLDebug("VSICURL", "Using cache for [%d, %d[ in %s",
1070 : (int)curOffset, (int)(curOffset + nSz), pszURL);
1071 162 : memcpy(pabyBuffer, psRegion->pData + curOffset, nSz);
1072 162 : pabyBuffer += nSz;
1073 162 : curOffset += nSz;
1074 162 : nRemaining -= nSz;
1075 : }
1076 :
1077 : /* Is the request partially covered by the cache and going beyond file size ? */
1078 214 : if ( psRegion != NULL && bHastComputedFileSizeLocal &&
1079 : curOffset <= psRegion->nSize &&
1080 : curOffset + nRemaining > fileSizeLocal &&
1081 : fileSize == psRegion->nSize )
1082 : {
1083 88 : size_t nSz = (size_t) (psRegion->nSize - curOffset);
1084 : if (ENABLE_DEBUG && nSz != 0)
1085 : CPLDebug("VSICURL", "Using cache for [%d, %d[ in %s",
1086 : (int)curOffset, (int)(curOffset + nSz), pszURL);
1087 88 : memcpy(pabyBuffer, psRegion->pData + curOffset, nSz);
1088 88 : pabyBuffer += nSz;
1089 88 : curOffset += nSz;
1090 88 : nRemaining -= nSz;
1091 88 : bEOF = TRUE;
1092 : }
1093 214 : poFS->ReleaseMutex();
1094 :
1095 : /* Has a Seek() being done since the last Read() ? */
1096 214 : if (!bEOF && nRemaining > 0 && curOffset != nRingBufferFileOffset)
1097 : {
1098 : /* Backward seek : we need to restart the download from the start */
1099 4 : if (curOffset < nRingBufferFileOffset)
1100 1 : StopDownload();
1101 :
1102 4 : StartDownload();
1103 :
1104 : #define SKIP_BUFFER_SIZE 32768
1105 4 : GByte* pabyTmp = (GByte*)CPLMalloc(SKIP_BUFFER_SIZE);
1106 :
1107 4 : CPLAssert(curOffset >= nRingBufferFileOffset);
1108 4 : vsi_l_offset nBytesToSkip = curOffset - nRingBufferFileOffset;
1109 4109 : while(nBytesToSkip > 0)
1110 : {
1111 4101 : vsi_l_offset nBytesToRead = nBytesToSkip;
1112 :
1113 4101 : AcquireMutex();
1114 4101 : if (nBytesToRead > oRingBuffer.GetSize())
1115 4097 : nBytesToRead = oRingBuffer.GetSize();
1116 4101 : if (nBytesToRead > SKIP_BUFFER_SIZE)
1117 0 : nBytesToRead = SKIP_BUFFER_SIZE;
1118 4101 : oRingBuffer.Read(pabyTmp, (size_t)nBytesToRead);
1119 :
1120 : /* Signal to the producer that we have ingested some bytes */
1121 4101 : CPLCondSignal(hCondConsumer);
1122 4101 : ReleaseMutex();
1123 :
1124 4101 : if (nBytesToRead)
1125 2051 : poFS->AddRegion(pszURL, nRingBufferFileOffset, (size_t)nBytesToRead, pabyTmp);
1126 :
1127 4101 : nBytesToSkip -= nBytesToRead;
1128 4101 : nRingBufferFileOffset += nBytesToRead;
1129 :
1130 4101 : if (nBytesToRead == 0 && nBytesToSkip != 0)
1131 : {
1132 : if (ENABLE_DEBUG)
1133 : CPLDebug("VSICURL", "Waiting for writer to produce some bytes...");
1134 :
1135 2050 : AcquireMutex();
1136 6150 : while(oRingBuffer.GetSize() == 0 && bDownloadInProgress)
1137 2050 : CPLCondWait(hCondProducer, hRingBufferMutex);
1138 2050 : int bBufferEmpty = (oRingBuffer.GetSize() == 0);
1139 2050 : ReleaseMutex();
1140 :
1141 2050 : if (bBufferEmpty && !bDownloadInProgress)
1142 0 : break;
1143 : }
1144 : }
1145 :
1146 4 : CPLFree(pabyTmp);
1147 :
1148 4 : if (nBytesToSkip != 0)
1149 : {
1150 0 : bEOF = TRUE;
1151 0 : return 0;
1152 : }
1153 : }
1154 :
1155 214 : if (!bEOF && nRemaining > 0)
1156 : {
1157 82 : StartDownload();
1158 82 : CPLAssert(curOffset == nRingBufferFileOffset);
1159 : }
1160 :
1161 : /* Fill the destination buffer from the ring buffer */
1162 3342 : while(!bEOF && nRemaining > 0)
1163 : {
1164 2927 : AcquireMutex();
1165 2927 : size_t nToRead = oRingBuffer.GetSize();
1166 2927 : if (nToRead > nRemaining)
1167 69 : nToRead = nRemaining;
1168 2927 : oRingBuffer.Read(pabyBuffer, nToRead);
1169 :
1170 : /* Signal to the producer that we have ingested some bytes */
1171 2927 : CPLCondSignal(hCondConsumer);
1172 2927 : ReleaseMutex();
1173 :
1174 2927 : if (nToRead)
1175 1472 : poFS->AddRegion(pszURL, curOffset, nToRead, pabyBuffer);
1176 :
1177 2927 : nRemaining -= nToRead;
1178 2927 : pabyBuffer += nToRead;
1179 2927 : curOffset += nToRead;
1180 2927 : nRingBufferFileOffset += nToRead;
1181 :
1182 2927 : if (nToRead == 0 && nRemaining != 0)
1183 : {
1184 : if (ENABLE_DEBUG)
1185 : CPLDebug("VSICURL", "Waiting for writer to produce some bytes...");
1186 :
1187 1455 : AcquireMutex();
1188 4353 : while(oRingBuffer.GetSize() == 0 && bDownloadInProgress)
1189 1443 : CPLCondWait(hCondProducer, hRingBufferMutex);
1190 1455 : int bBufferEmpty = (oRingBuffer.GetSize() == 0);
1191 1455 : ReleaseMutex();
1192 :
1193 1455 : if (bBufferEmpty && !bDownloadInProgress)
1194 13 : break;
1195 : }
1196 : }
1197 :
1198 : if (ENABLE_DEBUG)
1199 : CPLDebug("VSICURL", "Read(%d) = %d",
1200 : (int)nBufferRequestSize, (int)(nBufferRequestSize - nRemaining));
1201 214 : size_t nRet = (nBufferRequestSize - nRemaining) / nSize;
1202 214 : if (nRet < nMemb)
1203 101 : bEOF = TRUE;
1204 214 : return nRet;
1205 : }
1206 :
1207 : /************************************************************************/
1208 : /* Write() */
1209 : /************************************************************************/
1210 :
1211 0 : size_t VSICurlStreamingHandle::Write( const void *pBuffer, size_t nSize, size_t nMemb )
1212 : {
1213 0 : return 0;
1214 : }
1215 :
1216 : /************************************************************************/
1217 : /* Eof() */
1218 : /************************************************************************/
1219 :
1220 :
1221 244 : int VSICurlStreamingHandle::Eof()
1222 : {
1223 244 : return bEOF;
1224 : }
1225 :
1226 : /************************************************************************/
1227 : /* Flush() */
1228 : /************************************************************************/
1229 :
1230 0 : int VSICurlStreamingHandle::Flush()
1231 : {
1232 0 : return 0;
1233 : }
1234 :
1235 : /************************************************************************/
1236 : /* Close() */
1237 : /************************************************************************/
1238 :
1239 41 : int VSICurlStreamingHandle::Close()
1240 : {
1241 41 : return 0;
1242 : }
1243 :
1244 :
1245 : /************************************************************************/
1246 : /* VSICurlStreamingFSHandler() */
1247 : /************************************************************************/
1248 :
1249 712 : VSICurlStreamingFSHandler::VSICurlStreamingFSHandler()
1250 : {
1251 712 : hMutex = CPLCreateMutex();
1252 712 : CPLReleaseMutex(hMutex);
1253 712 : papsRegions = NULL;
1254 712 : nRegions = 0;
1255 712 : }
1256 :
1257 : /************************************************************************/
1258 : /* ~VSICurlStreamingFSHandler() */
1259 : /************************************************************************/
1260 :
1261 687 : VSICurlStreamingFSHandler::~VSICurlStreamingFSHandler()
1262 : {
1263 : int i;
1264 705 : for(i=0;i<nRegions;i++)
1265 : {
1266 18 : CPLFree(papsRegions[i]->pData);
1267 18 : CPLFree(papsRegions[i]);
1268 : }
1269 687 : CPLFree(papsRegions);
1270 :
1271 687 : std::map<CPLString, CachedFileProp*>::const_iterator iterCacheFileSize;
1272 :
1273 711 : for( iterCacheFileSize = cacheFileSize.begin();
1274 : iterCacheFileSize != cacheFileSize.end();
1275 : iterCacheFileSize++ )
1276 : {
1277 24 : CPLFree(iterCacheFileSize->second);
1278 : }
1279 :
1280 687 : CPLDestroyMutex( hMutex );
1281 687 : hMutex = NULL;
1282 687 : }
1283 :
1284 : /************************************************************************/
1285 : /* GetRegion() */
1286 : /************************************************************************/
1287 :
1288 3229 : void VSICurlStreamingFSHandler::AcquireMutex()
1289 : {
1290 3229 : CPLAcquireMutex(hMutex, 1000.0);
1291 3229 : }
1292 :
1293 : /************************************************************************/
1294 : /* GetRegion() */
1295 : /************************************************************************/
1296 :
1297 3229 : void VSICurlStreamingFSHandler::ReleaseMutex()
1298 : {
1299 3229 : CPLReleaseMutex(hMutex);
1300 3229 : }
1301 :
1302 : /************************************************************************/
1303 : /* GetRegion() */
1304 : /************************************************************************/
1305 :
1306 : /* Should be called undef the FS Lock */
1307 :
1308 3118 : CachedRegion* VSICurlStreamingFSHandler::GetRegion(const char* pszURL)
1309 : {
1310 3118 : unsigned long pszURLHash = CPLHashSetHashStr(pszURL);
1311 :
1312 : int i;
1313 3359 : for(i=0;i<nRegions;i++)
1314 : {
1315 3311 : CachedRegion* psRegion = papsRegions[i];
1316 3311 : if (psRegion->pszURLHash == pszURLHash)
1317 : {
1318 3070 : memmove(papsRegions + 1, papsRegions, i * sizeof(CachedRegion*));
1319 3070 : papsRegions[0] = psRegion;
1320 3070 : return psRegion;
1321 : }
1322 : }
1323 48 : return NULL;
1324 : }
1325 :
1326 : /************************************************************************/
1327 : /* AddRegion() */
1328 : /************************************************************************/
1329 :
1330 3565 : void VSICurlStreamingFSHandler::AddRegion( const char* pszURL,
1331 : vsi_l_offset nFileOffsetStart,
1332 : size_t nSize,
1333 : GByte *pData )
1334 : {
1335 3565 : if (nFileOffsetStart >= BKGND_BUFFER_SIZE)
1336 661 : return;
1337 :
1338 2904 : AcquireMutex();
1339 :
1340 2904 : unsigned long pszURLHash = CPLHashSetHashStr(pszURL);
1341 :
1342 2904 : CachedRegion* psRegion = GetRegion(pszURL);
1343 2904 : if (psRegion == NULL)
1344 : {
1345 24 : if (nRegions == N_MAX_REGIONS)
1346 : {
1347 6 : psRegion = papsRegions[N_MAX_REGIONS-1];
1348 : memmove(papsRegions + 1, papsRegions,
1349 6 : (N_MAX_REGIONS-1) * sizeof(CachedRegion*));
1350 6 : papsRegions[0] = psRegion;
1351 : }
1352 : else
1353 : {
1354 : papsRegions = (CachedRegion**)
1355 18 : CPLRealloc(papsRegions, (nRegions + 1) * sizeof(CachedRegion*));
1356 18 : if (nRegions)
1357 15 : memmove(papsRegions + 1, papsRegions, nRegions * sizeof(CachedRegion*));
1358 18 : nRegions ++;
1359 18 : papsRegions[0] = psRegion =
1360 18 : (CachedRegion*) CPLCalloc(1, sizeof(CachedRegion));
1361 : }
1362 :
1363 24 : psRegion->pszURLHash = pszURLHash;
1364 24 : psRegion->nSize = 0;
1365 24 : if (psRegion->pData == NULL)
1366 18 : psRegion->pData = (GByte*) CPLMalloc(BKGND_BUFFER_SIZE);
1367 : }
1368 :
1369 2904 : if (nFileOffsetStart <= psRegion->nSize &&
1370 : nFileOffsetStart + nSize > psRegion->nSize)
1371 : {
1372 1557 : size_t nSz = MIN(nSize, (size_t) (BKGND_BUFFER_SIZE - nFileOffsetStart));
1373 : if (ENABLE_DEBUG)
1374 : CPLDebug("VSICURL", "Writing [%d, %d[ in cache for %s",
1375 : (int)nFileOffsetStart, (int)(nFileOffsetStart + nSz), pszURL);
1376 1557 : memcpy(psRegion->pData + nFileOffsetStart, pData, nSz);
1377 1557 : psRegion->nSize = (size_t) (nFileOffsetStart + nSz);
1378 : }
1379 :
1380 2904 : ReleaseMutex();
1381 : }
1382 :
1383 : /************************************************************************/
1384 : /* GetCachedFileProp() */
1385 : /************************************************************************/
1386 :
1387 : /* Should be called undef the FS Lock */
1388 :
1389 111 : CachedFileProp* VSICurlStreamingFSHandler::GetCachedFileProp(const char* pszURL)
1390 : {
1391 111 : CachedFileProp* cachedFileProp = cacheFileSize[pszURL];
1392 111 : if (cachedFileProp == NULL)
1393 : {
1394 24 : cachedFileProp = (CachedFileProp*) CPLMalloc(sizeof(CachedFileProp));
1395 24 : cachedFileProp->eExists = EXIST_UNKNOWN;
1396 24 : cachedFileProp->bHastComputedFileSize = FALSE;
1397 24 : cachedFileProp->fileSize = 0;
1398 24 : cachedFileProp->bIsDirectory = FALSE;
1399 24 : cacheFileSize[pszURL] = cachedFileProp;
1400 : }
1401 :
1402 111 : return cachedFileProp;
1403 : }
1404 :
1405 : /************************************************************************/
1406 : /* Open() */
1407 : /************************************************************************/
1408 :
1409 41 : VSIVirtualHandle* VSICurlStreamingFSHandler::Open( const char *pszFilename,
1410 : const char *pszAccess )
1411 : {
1412 41 : if (strchr(pszAccess, 'w') != NULL ||
1413 : strchr(pszAccess, '+') != NULL)
1414 : {
1415 : CPLError(CE_Failure, CPLE_AppDefined,
1416 0 : "Only read-only mode is supported for /vsicurl_streaming");
1417 0 : return NULL;
1418 : }
1419 :
1420 : VSICurlStreamingHandle* poHandle = new VSICurlStreamingHandle(
1421 41 : this, pszFilename + strlen("/vsicurl_streaming/"));
1422 : /* If we didn't get a filelist, check that the file really exists */
1423 41 : if (!poHandle->Exists())
1424 : {
1425 0 : delete poHandle;
1426 0 : poHandle = NULL;
1427 : }
1428 41 : return poHandle;
1429 : }
1430 :
1431 : /************************************************************************/
1432 : /* Stat() */
1433 : /************************************************************************/
1434 :
1435 0 : int VSICurlStreamingFSHandler::Stat( const char *pszFilename,
1436 : VSIStatBufL *pStatBuf,
1437 : int nFlags )
1438 : {
1439 0 : CPLString osFilename(pszFilename);
1440 :
1441 0 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
1442 :
1443 0 : VSICurlStreamingHandle oHandle( this, osFilename + strlen("/vsicurl_streaming/"));
1444 :
1445 0 : if ( oHandle.IsKnownFileSize() ||
1446 : ((nFlags & VSI_STAT_SIZE_FLAG) && !oHandle.IsDirectory() &&
1447 : CSLTestBoolean(CPLGetConfigOption("CPL_VSIL_CURL_SLOW_GET_SIZE", "YES"))) )
1448 0 : pStatBuf->st_size = oHandle.GetFileSize();
1449 :
1450 0 : int nRet = (oHandle.Exists()) ? 0 : -1;
1451 0 : pStatBuf->st_mode = oHandle.IsDirectory() ? S_IFDIR : S_IFREG;
1452 0 : return nRet;
1453 : }
1454 :
1455 : /************************************************************************/
1456 : /* VSIInstallCurlFileHandler() */
1457 : /************************************************************************/
1458 :
1459 : /**
1460 : * \brief Install /vsicurl_streaming/ HTTP/FTP file system handler (requires libcurl)
1461 : *
1462 : * A special file handler is installed that allows on-the-fly reading of files
1463 : * streamed through HTTP/FTP web protocols (typically dynamically generated files),
1464 : * without downloading the entire file.
1465 : *
1466 : * Although this file handler is able seek to random offsets in the file, this will not
1467 : * be efficient. If you need efficient random access and that the server supports range
1468 : * dowloading, you should use the /vsicurl/ file system handler instead.
1469 : *
1470 : * Recognized filenames are of the form /vsicurl_streaming/http://path/to/remote/ressource or
1471 : * /vsicurl_streaming/ftp://path/to/remote/ressource where path/to/remote/ressource is the
1472 : * URL of a remote ressource.
1473 : *
1474 : * The GDAL_HTTP_PROXY and GDAL_HTTP_PROXYUSERPWD configuration options can be
1475 : * used to define a proxy server. The syntax to use is the one of Curl CURLOPT_PROXY
1476 : * and CURLOPT_PROXYUSERPWD options.
1477 : *
1478 : * VSIStatL() will return the size in st_size member and file
1479 : * nature- file or directory - in st_mode member (the later only reliable with FTP
1480 : * resources for now).
1481 : *
1482 : * @since GDAL 1.10
1483 : */
1484 712 : void VSIInstallCurlStreamingFileHandler(void)
1485 : {
1486 712 : VSIFileManager::InstallHandler( "/vsicurl_streaming/", new VSICurlStreamingFSHandler );
1487 712 : }
1488 :
1489 : #ifdef AS_PLUGIN
1490 : CPL_C_START
1491 : void CPL_DLL GDALRegisterMe();
1492 : void GDALRegisterMe()
1493 : {
1494 : VSIInstallCurlStreamingFileHandler();
1495 : }
1496 : CPL_C_END
1497 : #endif
1498 :
1499 : #endif /* !defined(HAVE_CURL) || defined(CPL_MULTIPROC_STUB) */
|