public function putObjectFromFile(
        $bucketName,
        $key,
        $filename,
        $options = array()
    ) {
        if (!isset($options[BosOptions::CONTENT_TYPE])) {
            $options[BosOptions::CONTENT_TYPE] = MimeTypes::guessMimeType(
                $filename
            );
        }

        list($contentLength, $contentMd5) = $this->parseOptionsIgnoreExtra(
            $options,
            BosOptions::CONTENT_LENGTH,
            BosOptions::CONTENT_MD5
        );

        if ($contentLength === null) {
            $contentLength = filesize($filename);
        } else {
            if (!is_int($contentLength) && !is_long($contentLength)) {
                throw new \InvalidArgumentException(
                    '$contentLength should be int or long.'
                );
            }
            unset($options[BosOptions::CONTENT_LENGTH]);
        }

        $fp = fopen($filename, 'rb');
        if ($contentMd5 === null) {
            $contentMd5 = base64_encode(HashUtils::md5FromStream($fp, 0, $contentLength));
        } else {
            unset($options[BosOptions::CONTENT_MD5]);
        }

        try {
            $response = $this->putObject(
                $bucketName,
                $key,
                $fp,
                $contentLength,
                $contentMd5,
                $options);
            //streams are close in the destructor of stream object in guzzle
            if (is_resource($fp)) {
                fclose($fp);
            }
            return $response;
        } catch (\Exception $e) {
            if (is_resource($fp)) {
                fclose($fp);
            }
            throw $e;
        }
    }