/** * Repacks exported document from Google.Drive, which has wrong order files in archive to show preview * in Google.Viewer. In Google.Viewer document should have file '[Content_Types].xml' on first position in archive. * @param FileData $fileData * @return FileData * @throws IO\FileNotFoundException * @throws IO\InvalidPathException * @internal */ public function repackDocument(FileData $fileData) { if (!extension_loaded('zip')) { return null; } if (!TypeFile::isDocument($fileData->getName())) { return null; } $file = new IO\File($fileData->getSrc()); if (!$file->isExists() || $file->getSize() > 15 * 1024 * 1024) { return null; } unset($file); $ds = DIRECTORY_SEPARATOR; $targetDir = \CTempFile::getDirectoryName(2, 'disk_repack' . $ds . md5(uniqid('di', true))); checkDirPath($targetDir); $targetDir = IO\Path::normalize($targetDir) . $ds; $zipOrigin = new \ZipArchive(); if (!$zipOrigin->open($fileData->getSrc())) { return null; } if ($zipOrigin->getNameIndex(0) === '[Content_Types].xml') { $zipOrigin->close(); return null; } if (!$zipOrigin->extractTo($targetDir)) { $zipOrigin->close(); return null; } $zipOrigin->close(); unset($zipOrigin); if (is_dir($targetDir) !== true) { return null; } $newName = md5(uniqid('di', true)); $newFilepath = $targetDir . '..' . $ds . $newName; $repackedZip = new \ZipArchive(); if (!$repackedZip->open($newFilepath, \ZipArchive::CREATE)) { return null; } $source = realpath($targetDir); $repackedZip->addFile($source . $ds . '[Content_Types].xml', '[Content_Types].xml'); $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST); foreach ($files as $file) { if ($file->getBasename() === '[Content_Types].xml') { continue; } $file = str_replace('\\', '/', $file); $file = realpath($file); if (is_dir($file) === true) { $repackedZip->addEmptyDir(str_replace('\\', '/', str_replace($source . $ds, '', $file . $ds))); } elseif (is_file($file) === true) { $repackedZip->addFile($file, str_replace('\\', '/', str_replace($source . $ds, '', $file))); } } $repackedZip->close(); $newFileData = new FileData(); $newFileData->setSrc($newFilepath); return $newFileData; }
/** * Download part of file from cloud service by FileData::id, put contents in FileData::src * @param FileData $fileData * @param $startRange * @param $chunkSize * @return FileData|null */ public function downloadPartFile(FileData $fileData, $startRange, $chunkSize) { if (!$this->checkRequiredInputParams($fileData->toArray(), array('id', 'src'))) { return null; } $accessToken = $this->getAccessToken(); @set_time_limit(0); $http = new HttpClient(array('socketTimeout' => 10, 'streamTimeout' => 30, 'version' => HttpClient::HTTP_1_1)); $http->setHeader('Authorization', "Bearer {$accessToken}"); $endRange = $startRange + $chunkSize - 1; $http->setHeader('Range', "bytes={$startRange}-{$endRange}"); if ($http->download(self::API_URL_V2 . "/files/{$fileData->getId()}/content", $fileData->getSrc()) === false) { $errorString = implode('; ', array_keys($http->getError())); $this->errorCollection->add(array(new Error($errorString, self::ERROR_HTTP_DOWNLOAD_FILE))); return null; } return $fileData; }
protected function processActionPublish() { $onlineSession = $this->getOnlineEditSessionForFile(); if ($onlineSession) { $forkSession = $onlineSession; if ($onlineSession->getOwnerId() != $this->getUser()->getId()) { $forkSession = $this->forkEditSessionForCurrentUser($onlineSession); } $this->sendJsonSuccessResponse(array('editSessionId' => $forkSession->getId(), 'id' => $onlineSession->getServiceFileId(), 'link' => $onlineSession->getServiceFileLink())); } $src = $this->getFileSrcToPublish(); if (!$src) { $this->errorCollection->add(array(new Error(Loc::getMessage('DISK_DOC_CONTROLLER_ERROR_COULD_NOT_GET_FILE'), self::ERROR_COULD_NOT_GET_FILE))); $this->sendJsonErrorResponse(); } $fileData = new FileData(); $fileData->setName($this->file->getName()); $fileData->setMimeType(TypeFile::getMimeTypeByFilename($this->file->getName())); $fileData->setSrc($src); if (!$fileData->getSize()) { $fileData->setSize(filesize($fileData->getSrc())); } if ($fileData->getSize() === 0) { $fileData = $this->documentHandler->createBlankFile($fileData); } else { $fileData = $this->documentHandler->createFile($fileData); } if (!$fileData) { if ($this->documentHandler->isRequiredAuthorization()) { $this->sendNeedAuth(); } $this->errorCollection->add($this->documentHandler->getErrors()); $this->sendJsonErrorResponse(); } //if somebody publish to google similar document $onlineSession = $this->getOnlineEditSessionForFile(); if ($onlineSession) { $this->documentHandler->deleteFile($fileData); $forkSession = $onlineSession; if ($onlineSession->getOwnerId() != $this->getUser()->getId()) { $forkSession = $this->forkEditSessionForCurrentUser($onlineSession); } $this->sendJsonSuccessResponse(array('editSessionId' => $forkSession->getId(), 'id' => $onlineSession->getServiceFileId(), 'link' => $onlineSession->getServiceFileLink())); } $session = $this->addFileEditSessionByCurrentUser($fileData); $this->sendJsonSuccessResponse(array('editSessionId' => $session->getId(), 'id' => $fileData->getId(), 'link' => $fileData->getLinkInService())); }
/** * Fix for Google. It does not get in metadata real size of empty file. * @param Entry $entry * @param Document\FileData $fileData * @return bool * @throws IO\FileNotFoundException * @throws \Bitrix\Main\SystemException */ protected function uploadEmptyFileFromGoogle(Entry $entry, Document\FileData $fileData) { $tmpFile = $fileData->getSrc(); $downloadedContentSize = $entry->getDownloadedContentSize(); $startRange = $downloadedContentSize; //fix for Google. It doesn't get in metadata real size of empty file. if (!$this->documentHandler->downloadFile($fileData)) { $this->errorCollection->add($this->documentHandler->getErrors()); return false; } $realFile = new IO\File($tmpFile); $contentSize = $realFile->getSize(); $entry->setContentSize($contentSize); $chunkSize = $contentSize - $downloadedContentSize; $uploadFileManager = new Bitrix24Disk\UploadFileManager(); $uploadFileManager->setTmpFileClass(TmpFile::className())->setUser($this->documentHandler->getUserId())->setFileSize($contentSize)->setContentRange(array($startRange, $startRange + $chunkSize - 1)); $tmpFileArray = \CFile::makeFileArray($tmpFile); if (!$uploadFileManager->upload($fileData->getId(), $tmpFileArray)) { $this->errorCollection->add($uploadFileManager->getErrors()); return false; } $entry->linkTmpFile(TmpFile::load(array('=TOKEN' => $uploadFileManager->getToken()))); return $entry->increaseDownloadedContentSize($chunkSize); }
/** * @param FileData $fileData * @param string $lastStatus * @param array $fileMetadata * @return FileData|null */ protected function createByResumableUpload(FileData $fileData, &$lastStatus, &$fileMetadata) { if (!$this->checkRequiredInputParams($fileData->toArray(), array('src', 'mimeType'))) { return null; } $accessToken = $this->getAccessToken(); if (!$fileData->getSize()) { $fileData->setSize(filesize($fileData->getSrc())); } $chunkSize = 40 * 256 * 1024; // Chunk size restriction: All chunks must be a multiple of 256 KB (256 x 1024 bytes) in size except for the final chunk that completes the upload $locationForUpload = $this->getLocationForResumableUpload($fileData); if (!$locationForUpload) { return null; } $lastResponseCode = false; $fileMetadata = null; $lastRange = false; $transactionCounter = 0; $doExponentialBackoff = false; $exponentialBackoffCounter = 0; $response = array(); while ($lastResponseCode === false || $lastResponseCode == '308') { $transactionCounter++; if ($doExponentialBackoff) { $sleepFor = pow(2, $exponentialBackoffCounter); sleep($sleepFor); usleep(rand(0, 1000)); $exponentialBackoffCounter++; if ($exponentialBackoffCounter > 5) { $lastStatus = $response['code']; $this->errorCollection->add(array(new Error("Could not upload part (Exponential back off) ({$lastStatus})", self::ERROR_HTTP_RESUMABLE_UPLOAD))); return null; } } // determining what range is next $rangeStart = 0; $rangeEnd = min($chunkSize, $fileData->getSize() - 1); if ($lastRange !== false) { $lastRange = explode('-', $lastRange); $rangeStart = (int) $lastRange[1] + 1; $rangeEnd = min($rangeStart + $chunkSize, $fileData->getSize() - 1); } $http = new HttpClient(array('socketTimeout' => 10, 'streamTimeout' => 30, 'version' => HttpClient::HTTP_1_1)); $http->setHeader('Authorization', "Bearer {$accessToken}"); $http->setHeader('Content-Length', (string) ($rangeEnd - $rangeStart + 1)); $http->setHeader('Content-Type', $fileData->getMimeType()); $http->setHeader('Content-Range', "bytes {$rangeStart}-{$rangeEnd}/{$fileData->getSize()}"); $toSendContent = file_get_contents($fileData->getSrc(), false, null, $rangeStart, $rangeEnd - $rangeStart + 1); if ($http->query('PUT', $locationForUpload, $toSendContent)) { $response['code'] = $http->getStatus(); $response['headers']['range'] = $http->getHeaders()->get('Range'); } $doExponentialBackoff = false; if (isset($response['code'])) { // checking for expired credentials if ($response['code'] == "401") { // todo: make sure that we also got an invalid credential response //$access_token = get_access_token(true); $lastResponseCode = false; } else { if ($response['code'] == "308") { $lastResponseCode = $response['code']; $lastRange = $response['headers']['range']; // todo: verify x-range-md5 header to be sure $exponentialBackoffCounter = 0; } else { if ($response['code'] == "503") { // Google's letting us know we should retry $doExponentialBackoff = true; $lastResponseCode = false; } else { if ($response['code'] == "200") { // we are done! $lastResponseCode = $response['code']; } else { $lastStatus = $response['code']; $this->errorCollection->add(array(new Error("Could not upload part ({$lastStatus})", self::ERROR_HTTP_RESUMABLE_UPLOAD))); return null; } } } } } else { $doExponentialBackoff = true; $lastResponseCode = false; } } if ($lastResponseCode != "200") { $lastStatus = $response['code']; $this->errorCollection->add(array(new Error("Could not upload final part ({$lastStatus})", self::ERROR_HTTP_RESUMABLE_UPLOAD))); return null; } $fileMetadata = null; if (isset($http)) { $fileMetadata = Json::decode($http->getResult()); } if ($fileMetadata === null) { $this->errorCollection->add(array(new Error('Could not decode response as json', self::ERROR_BAD_JSON))); return null; } $fileData->setLinkInService($fileMetadata['alternateLink']); $fileData->setId($fileMetadata['id']); return $fileData; }