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:
parent
bc6a0aa907
commit
bea9f905bd
3 changed files with 98 additions and 1 deletions
|
@ -232,6 +232,18 @@ def hash_file(filepath, hasher):
|
||||||
def hash_file_md5(filepath):
|
def hash_file_md5(filepath):
|
||||||
return hash_file(filepath, hasher=hashlib.md5())
|
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):
|
def hyphen_range(s):
|
||||||
'''
|
'''
|
||||||
Given a string like '1-3', return numbers (1, 3) representing lower
|
Given a string like '1-3', return numbers (1, 3) representing lower
|
||||||
|
|
|
@ -5,12 +5,15 @@ import urllib.parse
|
||||||
|
|
||||||
import etiquette
|
import etiquette
|
||||||
|
|
||||||
|
from voussoirkit import cacheclass
|
||||||
|
|
||||||
from .. import common
|
from .. import common
|
||||||
from .. import decorators
|
from .. import decorators
|
||||||
from .. import jsonify
|
from .. import jsonify
|
||||||
|
|
||||||
site = common.site
|
site = common.site
|
||||||
session_manager = common.session_manager
|
session_manager = common.session_manager
|
||||||
|
photo_download_zip_tokens = cacheclass.Cache(maxlen=100)
|
||||||
|
|
||||||
|
|
||||||
# Individual photos ################################################################################
|
# Individual photos ################################################################################
|
||||||
|
@ -226,6 +229,59 @@ def post_batch_photos_photo_cards():
|
||||||
response = jsonify.make_json_response(divs)
|
response = jsonify.make_json_response(divs)
|
||||||
return response
|
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 ###########################################################################################
|
# Search ###########################################################################################
|
||||||
|
|
||||||
def get_search_core():
|
def get_search_core():
|
||||||
|
|
|
@ -43,12 +43,13 @@ body
|
||||||
grid-area: right;
|
grid-area: right;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 75px 75px 75px 75px auto;
|
grid-template-rows: 75px 75px 75px 75px 75px auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"add_tag_area"
|
"add_tag_area"
|
||||||
"remove_tag_area"
|
"remove_tag_area"
|
||||||
"refresh_metadata_area"
|
"refresh_metadata_area"
|
||||||
"searchhidden_area"
|
"searchhidden_area"
|
||||||
|
"download_zip_area"
|
||||||
"message_area";
|
"message_area";
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
@ -73,6 +74,11 @@ body
|
||||||
grid-area: searchhidden_area;
|
grid-area: searchhidden_area;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
#download_zip_area
|
||||||
|
{
|
||||||
|
grid-area: download_zip_area;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
#message_area
|
#message_area
|
||||||
{
|
{
|
||||||
grid-area: message_area;
|
grid-area: message_area;
|
||||||
|
@ -112,6 +118,10 @@ body
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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 id="message_area">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -262,6 +272,25 @@ function add_remove_callback(response)
|
||||||
common.create_message_bubble(message_area, message_positivity, message_text, 8000);
|
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;
|
var refresh_in_progress = false;
|
||||||
function submit_refresh_metadata(callback)
|
function submit_refresh_metadata(callback)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue