Support downloading .zip of arbitrary photos, clipboard.

Now that creating zips of any photo set is easier, we can
let the user download whatever is on their clipboard.
This commit is contained in:
voussoir 2018-08-14 23:02:06 -07:00
parent bc6a0aa907
commit bea9f905bd
3 changed files with 98 additions and 1 deletions

View file

@ -232,6 +232,18 @@ def hash_file(filepath, hasher):
def hash_file_md5(filepath):
return hash_file(filepath, hasher=hashlib.md5())
def hash_photoset(photos):
'''
Given some photos, return a fingerprint string for that particular set.
'''
hasher = hashlib.md5()
photo_ids = sorted(set(p.id for p in photos))
for photo_id in photo_ids:
hasher.update(photo_id.encode('utf-8'))
return hasher.hexdigest()
def hyphen_range(s):
'''
Given a string like '1-3', return numbers (1, 3) representing lower

View file

@ -5,12 +5,15 @@ import urllib.parse
import etiquette
from voussoirkit import cacheclass
from .. import common
from .. import decorators
from .. import jsonify
site = common.site
session_manager = common.session_manager
photo_download_zip_tokens = cacheclass.Cache(maxlen=100)
# Individual photos ################################################################################
@ -226,6 +229,59 @@ def post_batch_photos_photo_cards():
response = jsonify.make_json_response(divs)
return response
# Zipping ##########################################################################################
@site.route('/batch/photos/download_zip/<zip_token>', methods=['GET'])
def get_batch_photos_download_zip(zip_token):
'''
After the user has generated their zip token, they can retrieve
that zip file.
'''
zip_token = zip_token.split('.')[0]
try:
photo_ids = photo_download_zip_tokens[zip_token]
except KeyError:
flask.abort(404)
# Let's re-validate those IDs just in case anything has changed.
photos = list(common.P_photos(photo_ids, response_type='json'))
if not photos:
flask.abort(400)
streamed_zip = etiquette.helpers.zip_photos(photos)
download_as = zip_token + '.zip'
download_as = urllib.parse.quote(download_as)
outgoing_headers = {
'Content-Type': 'application/octet-stream',
'Content-Disposition': f'attachment; filename*=UTF-8\'\'{download_as}',
}
return flask.Response(streamed_zip, headers=outgoing_headers)
@site.route('/batch/photos/download_zip', methods=['POST'])
@decorators.required_fields(['photo_ids'], forbid_whitespace=True)
def post_batch_photos_download_zip():
'''
Initiating file downloads via POST requests is a bit clunky and unreliable,
so the way this works is we generate a token representing the photoset
that they want, and then they can retrieve the zip itself via GET.
'''
photo_ids = request.form['photo_ids']
photo_ids = etiquette.helpers.comma_space_split(photo_ids)
photos = list(common.P_photos(photo_ids, response_type='json'))
if not photos:
flask.abort(400)
photo_ids = [p.id for p in photos]
zip_token = etiquette.helpers.hash_photoset(photos)
photo_download_zip_tokens[zip_token] = photo_ids
response = {'zip_token': zip_token}
response = jsonify.make_json_response(response)
return response
# Search ###########################################################################################
def get_search_core():

View file

@ -43,12 +43,13 @@ body
grid-area: right;
display: grid;
grid-template-rows: 75px 75px 75px 75px auto;
grid-template-rows: 75px 75px 75px 75px 75px auto;
grid-template-areas:
"add_tag_area"
"remove_tag_area"
"refresh_metadata_area"
"searchhidden_area"
"download_zip_area"
"message_area";
background-color: rgba(0, 0, 0, 0.1);
@ -73,6 +74,11 @@ body
grid-area: searchhidden_area;
margin: auto;
}
#download_zip_area
{
grid-area: download_zip_area;
margin: auto;
}
#message_area
{
grid-area: message_area;
@ -112,6 +118,10 @@ body
</span>
</div>
<div id="download_zip_area">
<button class="yellow_button" id="download_zip_button" onclick="submit_download_zip(download_zip_callback)">Download .zip</button>
</div>
<div id="message_area">
</div>
</div>
@ -262,6 +272,25 @@ function add_remove_callback(response)
common.create_message_bubble(message_area, message_positivity, message_text, 8000);
}
function submit_download_zip(callback)
{
if (photo_clipboard.clipboard.size == 0)
{return;}
var url = "/batch/photos/download_zip";
var photo_ids = Array.from(photo_clipboard.clipboard).join(",");
var data = new FormData();
data.append("photo_ids", photo_ids);
common.post(url, data, callback);
}
function download_zip_callback(response)
{
var zip_token = response["data"]["zip_token"];
var url = `/batch/photos/download_zip/${zip_token}.zip`;
window.location.href = url;
}
var refresh_in_progress = false;
function submit_refresh_metadata(callback)
{