public function localize($file_id) { $contentClient = new \Guzzle\Service\Client('http://' . Config::get('app.content_host') . '/'); $attachment = Attachment::find($file_id); $mailbox = Mailbox::find($attachment->mailbox_id); $tmpfname = tempnam(sys_get_temp_dir(), $file_id); $response = $this->contextIO->getFileContent($mailbox->mailbox_id, ['file_id' => $attachment->attachment_id], $tmpfname); if (!$response) { return ['success' => false, 'error_message' => 'Failed to retrieve context.io file contents']; } $sha1 = sha1_file($tmpfname); if (strrpos(basename($attachment->original_path), '.')) { $extension = substr(basename($attachment->original_path), strrpos(basename($attachment->original_path), '.')); } else { $extension = ".extensionless"; } $save_path = 'dropbox/' . $sha1 . $extension; // check the byte size of the cached file if it exists on the content server $request = $contentClient->head($save_path); try { $response = $request->send(); $contentLength = $response->getContentLength(); } catch (Guzzle\Http\Exception\BadResponseException $e) { $clientResponseException = $e->getResponse(); $statusCode = $clientResponseException->getStatusCode(); if ($statusCode !== 404) { return ['success' => false, 'error_message' => 'HEAD request from content server path ' . $save_path . ' raised http exception ' . $statusCode . "\n" . print_r($clientResponseException, true)]; } $contentLength = false; } // Raise an exception if we are about to localize this file in place of another file with the same content SHA if ($contentLength && $contentLength > 0 && $contentLength != filesize($tmpfname)) { return ['success' => false, 'error_message' => "File exists on content server with same SHA but different byte count!"]; } // Upload the file if it does not already exist on the content server if (!$contentLength) { $request = $contentClient->put($save_path); $request->getCurlOptions()->set(CURLOPT_SSL_VERIFYHOST, false); // for testing ONLY! $request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false); // for testing ONLY! // TODO: Determine which approach is best of these two: // Having a second pointer on same file, while first pointer is still open // (Temp files are deleted as soon as fclose called) $request->setBody(fopen($tmpfname, 'r')); // Or should we use.... // rewind($fd); // $request->setBody($fd); // I'm not sure if guzzle would support that, // nor if w+ mode supports rewind // (Will it read from start without erasing after a rewind in w+?) $response = $request->send(); } // Update the database with the file size information $attachment->file_sha = $sha1; $attachment->etag = sha1($sha1 . $attachment->bytes . $attachment->service_updated_at . $attachment->client_updated_at); $attachment->save(); // TODO: Error handling // TODO: Process abstraction from controller to classes return ['success' => true, 'local_url' => 'http://' . Config::get('app.content_host') . '/' . $save_path]; }