public function uploadPartFromFile(
        $bucketName,
        $key,
        $uploadId,
        $partNumber,
        $filename,
        $offset = 0,
        $length = -1,
        $options = array()
    ) {
        if (!is_int($offset) && !is_long($offset)) {
            throw new \InvalidArgumentException(
                '$offset should be int or long.'
            );
        }
        if (!is_int($length) && !is_long($length)) {
            throw new \InvalidArgumentException(
                '$length should be int or long.'
            );
        }

        $fp = fopen($filename, 'rb');
        try {
            if ($length < 0) {
                fseek($fp, 0, SEEK_END);
                $length = ftell($fp) - $offset;
            }
            $contentMd5 = base64_encode(HashUtils::md5FromStream($fp, $offset, $length));
            fseek($fp, $offset, SEEK_SET);
            $response = $this->uploadPart(
                $bucketName,
                $key,
                $uploadId,
                $partNumber,
                $length,
                $contentMd5,
                $fp,
                $options
            );
            //guzzle will close fp
            if (is_resource($fp)) {
                fclose($fp);
            }
            return $response;
        } catch (\Exception $e) {
            if(is_resource($fp)) {
                fclose($fp);
            }
            throw $e;
        }
    }