parent
3fdadb51b7
commit
7c572fd218
4 changed files with 135 additions and 29 deletions
|
@ -2,7 +2,7 @@ import logging
|
|||
import json
|
||||
import hashlib
|
||||
|
||||
from flask import redirect, Blueprint, abort, send_file, make_response
|
||||
from flask import redirect, Blueprint, abort, send_file, make_response, request
|
||||
|
||||
import features
|
||||
|
||||
|
@ -121,6 +121,69 @@ def _write_synthetic_image_to_storage(verb, linked_storage_uuid, linked_location
|
|||
done_uploading.save()
|
||||
|
||||
|
||||
def _torrent_for_storage(storage_ref, is_public):
|
||||
""" Returns a response containing the torrent file contents for the given storage. May abort
|
||||
with an error if the state is not valid (e.g. non-public, non-user request).
|
||||
"""
|
||||
# Make sure the storage has a size.
|
||||
if not storage_ref.image_size:
|
||||
abort(404)
|
||||
|
||||
# Lookup the torrent information for the storage.
|
||||
try:
|
||||
torrent_info = model.storage.get_torrent_info(storage_ref)
|
||||
except model.TorrentInfoDoesNotExist:
|
||||
abort(404)
|
||||
|
||||
# Lookup the webseed path for the storage.
|
||||
path = model.storage.get_layer_path(storage_ref)
|
||||
webseed = storage.get_direct_download_url(storage_ref.locations, path)
|
||||
if webseed is None:
|
||||
# We cannot support webseeds for storages that cannot provide direct downloads.
|
||||
abort(make_response('Storage engine does not support seeding.', 501))
|
||||
|
||||
# Build the filename for the torrent.
|
||||
if is_public:
|
||||
name = public_torrent_filename(storage_ref.uuid)
|
||||
else:
|
||||
user = get_authenticated_user()
|
||||
if not user:
|
||||
abort(403)
|
||||
|
||||
name = per_user_torrent_filename(user.uuid, storage_ref.uuid)
|
||||
|
||||
# Return the torrent file.
|
||||
torrent_file = make_torrent(name, webseed, storage_ref.image_size,
|
||||
torrent_info.piece_length, torrent_info.pieces)
|
||||
|
||||
headers = {'Content-Type': 'application/x-bittorrent',
|
||||
'Content-Disposition': 'attachment; filename={0}.torrent'.format(name)}
|
||||
|
||||
return make_response(torrent_file, 200, headers)
|
||||
|
||||
|
||||
def _torrent_repo_verb(repo_image, tag, verb, **kwargs):
|
||||
""" Handles returning a torrent for the given verb on the given image and tag. """
|
||||
if not features.BITTORRENT:
|
||||
# Torrent feature is not enabled.
|
||||
abort(406)
|
||||
|
||||
# Lookup an *existing* derived storage for the verb. If the verb's image storage doesn't exist,
|
||||
# we cannot create it here, so we 404.
|
||||
derived = model.image.find_derived_storage_for_image(repo_image, verb)
|
||||
if not derived:
|
||||
abort(406)
|
||||
|
||||
# Return the torrent.
|
||||
public_repo = model.repository.is_repository_public(repo_image.repository)
|
||||
torrent = _torrent_for_storage(derived, public_repo)
|
||||
|
||||
# Log the action.
|
||||
track_and_log('repo_verb', repo_image.repository, tag=tag, verb=verb, torrent=True, **kwargs)
|
||||
|
||||
return torrent
|
||||
|
||||
|
||||
def _verify_repo_verb(store, namespace, repository, tag, verb, checker=None):
|
||||
permission = ReadRepositoryPermission(namespace, repository)
|
||||
|
||||
|
@ -179,6 +242,11 @@ def _repo_verb(namespace, repository, tag, verb, formatter, sign=False, checker=
|
|||
result = _verify_repo_verb(storage, namespace, repository, tag, verb, checker)
|
||||
(repo_image, tag_image, image_json) = result
|
||||
|
||||
# Check for torrent. If found, we return a torrent for the repo verb image (if the derived
|
||||
# image already exists).
|
||||
if request.accept_mimetypes.best == 'application/x-bittorrent':
|
||||
return _torrent_repo_verb(repo_image, tag, verb, **kwargs)
|
||||
|
||||
# Log the action.
|
||||
track_and_log('repo_verb', repo_image.repository, tag=tag, verb=verb, **kwargs)
|
||||
|
||||
|
@ -215,10 +283,13 @@ def _repo_verb(namespace, repository, tag, verb, formatter, sign=False, checker=
|
|||
database.close_db_filter(None)
|
||||
|
||||
hasher = PieceHasher(app.config['TORRENT_PIECE_SIZE'])
|
||||
|
||||
def _store_metadata_and_cleanup():
|
||||
with database.UseThenDisconnect(app.config):
|
||||
model.storage.save_torrent_info(derived, app.config['TORRENT_PIECE_SIZE'],
|
||||
hasher.final_piece_hashes())
|
||||
derived.image_size = hasher.hashed_bytes
|
||||
derived.save()
|
||||
|
||||
# Create a queue process to generate the data. The queue files will read from the process
|
||||
# and send the results to the client and storage.
|
||||
|
@ -321,26 +392,4 @@ def get_tag_torrent(namespace, repo_name, digest):
|
|||
except model.BlobDoesNotExist:
|
||||
abort(404)
|
||||
|
||||
path = model.storage.get_layer_path(blob)
|
||||
webseed = storage.get_direct_download_url(blob.locations, path)
|
||||
if webseed is None:
|
||||
# We cannot support webseeds for storages that cannot provide direct downloads.
|
||||
abort(make_response('Storage engine does not support seeding.', 501))
|
||||
|
||||
try:
|
||||
torrent_info = model.storage.get_torrent_info(blob)
|
||||
except model.TorrentInfoDoesNotExist:
|
||||
abort(404)
|
||||
|
||||
if public_repo:
|
||||
name = public_torrent_filename(blob.uuid)
|
||||
else:
|
||||
name = per_user_torrent_filename(user.uuid, blob.uuid)
|
||||
|
||||
torrent_file = make_torrent(name, webseed, blob.image_size,
|
||||
torrent_info.piece_length, torrent_info.pieces)
|
||||
|
||||
headers = {'Content-Type': 'application/x-bittorrent',
|
||||
'Content-Disposition': 'attachment; filename={0}.torrent'.format(name)}
|
||||
|
||||
return make_response(torrent_file, 200, headers)
|
||||
return _torrent_for_storage(blob, public_repo)
|
||||
|
|
Reference in a new issue