protected function handlePatch() : HttpResponse
    {
        $uploadKey = $this->request->key();

        if ( ! $meta = $this->cache->get($uploadKey)) {
            return $this->response->send(null, HttpResponse::HTTP_GONE);
        }

        $status = $this->verifyPatchRequest($meta);

        if (HttpResponse::HTTP_OK !== $status) {
            return $this->response->send(null, $status);
        }

        $file     = $this->buildFile($meta);
        $checksum = $meta['checksum'];

        try {
            $fileSize = $file->getFileSize();
            $offset   = $file->setKey($uploadKey)->setChecksum($checksum)->upload($fileSize);

            // If upload is done, verify checksum.
            if ($offset === $fileSize) {
                if ( ! $this->verifyChecksum($checksum, $meta['file_path'])) {
                    return $this->response->send(null, self::HTTP_CHECKSUM_MISMATCH);
                }

                $this->event()->dispatch(
                    UploadComplete::NAME,
                    new UploadComplete($file, $this->getRequest(), $this->getResponse())
                );
            } else {
                $this->event()->dispatch(
                    UploadProgress::NAME,
                    new UploadProgress($file, $this->getRequest(), $this->getResponse())
                );
            }
        } catch (FileException $e) {
            return $this->response->send($e->getMessage(), HttpResponse::HTTP_UNPROCESSABLE_ENTITY);
        } catch (OutOfRangeException $e) {
            return $this->response->send(null, HttpResponse::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE);
        } catch (ConnectionException $e) {
            return $this->response->send(null, HttpResponse::HTTP_CONTINUE);
        }

        return $this->response->send(null, HttpResponse::HTTP_NO_CONTENT, [
            'Content-Type' => self::HEADER_CONTENT_TYPE,
            'Upload-Expires' => $this->cache->get($uploadKey)['expires_at'],
            'Upload-Offset' => $offset,
        ]);
    }