BLUE-13: improved the design and handling of the analysis screen. Also fixed bulk uploads to work since the storage changes in 1.1

This commit is contained in:
Andy Heathershaw 2016-10-30 18:36:34 +00:00
parent e3d3d4d8be
commit 5b915f911e
10 changed files with 186 additions and 66 deletions

View File

@ -3,7 +3,7 @@
<component name="WebServers"> <component name="WebServers">
<option name="servers"> <option name="servers">
<webServer id="b14a34b0-0127-4886-964a-7be75a2281ac" name="Development" url="http://blue-twilight-dev.andys.eu"> <webServer id="b14a34b0-0127-4886-964a-7be75a2281ac" name="Development" url="http://blue-twilight-dev.andys.eu">
<fileTransfer host="scar.andys.eu" port="22" privateKey="C:\Users\aheathershaw\.ssh\id_rsa" rootFolder="/srv/www/blue-twilight-dev" accessType="SFTP" keyPair="true"> <fileTransfer host="scar.andys.eu" port="22" privateKey="$USER_HOME$/.ssh/id_rsa" rootFolder="/srv/www/blue-twilight-dev" accessType="SFTP" keyPair="true">
<advancedOptions> <advancedOptions>
<advancedOptions dataProtectionLevel="Private" /> <advancedOptions dataProtectionLevel="Private" />
</advancedOptions> </advancedOptions>

View File

@ -2,11 +2,38 @@
namespace App\Helpers; namespace App\Helpers;
use Illuminate\Http\File;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Symfony\Component\HttpFoundation\File\File;
class FileHelper class FileHelper
{ {
public static function deleteIfEmpty($folderPath)
{
// Another request may have got here first!
if (!is_dir($folderPath))
{
return;
}
$queueIterator = new \DirectoryIterator($folderPath);
$files = 0;
foreach ($queueIterator as $item)
{
if ($item->isDot())
{
continue;
}
$files++;
}
if ($files == 0)
{
@rmdir($folderPath);
}
}
public static function getQueuePath($queueUid) public static function getQueuePath($queueUid)
{ {
$path = join(DIRECTORY_SEPARATOR, [ $path = join(DIRECTORY_SEPARATOR, [
@ -25,6 +52,27 @@ class FileHelper
return $path; return $path;
} }
public static function saveExtractedFile(File $extractedFile, $destinationPath, $overrideFilename = null)
{
$tempFilename = join(DIRECTORY_SEPARATOR, [
$destinationPath,
is_null($overrideFilename) ? MiscHelper::randomString(20) : basename($overrideFilename)
]);
// Only add an extension if an override filename was not given, assume this is present
if (is_null($overrideFilename))
{
$extension = $extractedFile->guessExtension();
if (!is_null($extension))
{
$tempFilename .= '.' . $extension;
}
}
$extractedFile->move(dirname($tempFilename), basename($tempFilename));
return new File($tempFilename);
}
public static function saveUploadedFile(UploadedFile $uploadedFile, $destinationPath, $overrideFilename = null) public static function saveUploadedFile(UploadedFile $uploadedFile, $destinationPath, $overrideFilename = null)
{ {
$tempFilename = join(DIRECTORY_SEPARATOR, [ $tempFilename = join(DIRECTORY_SEPARATOR, [

View File

@ -65,7 +65,7 @@ class ImageHelper
} }
// TODO make the /tmp folder configurable // TODO make the /tmp folder configurable
$tempName = tempnam('/tmp', 'btw_thumb_'); $tempName = tempnam(sys_get_temp_dir(), 'btw_thumb_');
$tempNameWithExtension = ($tempName . '.jpg'); $tempNameWithExtension = ($tempName . '.jpg');
rename($tempName, $tempNameWithExtension); rename($tempName, $tempNameWithExtension);

View File

@ -37,6 +37,11 @@ class AlbumController extends Controller
->orderBy('created_at') ->orderBy('created_at')
->get(); ->get();
if (count($photos) == 0)
{
return redirect(route('albums.show', ['id' => $album->id]));
}
return Theme::render('admin.analyse_album', ['album' => $album, 'photos' => $photos, 'queue_token' => $queue_token]); return Theme::render('admin.analyse_album', ['album' => $album, 'photos' => $photos, 'queue_token' => $queue_token]);
} }

View File

@ -306,6 +306,7 @@ class PhotoController extends Controller
$zip->open($archiveFile->getPathname()); $zip->open($archiveFile->getPathname());
$zip->extractTo($queueFolder); $zip->extractTo($queueFolder);
$zip->close(); $zip->close();
@unlink($archiveFile->getPathname());
break; break;
default: default:
@ -329,6 +330,13 @@ class PhotoController extends Controller
continue; continue;
} }
if (substr($fileInfo->getFilename(), 0, 1) == '.')
{
// Temporary/hidden file - skip
@unlink($fileInfo->getPathname());
continue;
}
$result = getimagesize($fileInfo->getPathname()); $result = getimagesize($fileInfo->getPathname());
if ($result === false) if ($result === false)
{ {
@ -340,7 +348,7 @@ class PhotoController extends Controller
$photoFile = new File($fileInfo->getPathname()); $photoFile = new File($fileInfo->getPathname());
/** @var File $savedFile */ /** @var File $savedFile */
$savedFile = $album->getAlbumSource()->saveUploadedPhoto($photoFile); $savedFile = FileHelper::saveExtractedFile($photoFile, $queueFolder);
$photo = new Photo(); $photo = new Photo();
$photo->album_id = $album->id; $photo->album_id = $album->id;
@ -354,8 +362,6 @@ class PhotoController extends Controller
$photo->save(); $photo->save();
} }
@rmdir($queueFolder);
return redirect(route('albums.analyse', [ return redirect(route('albums.analyse', [
'id' => $album->id, 'id' => $album->id,
'queue_token' => $queueUid 'queue_token' => $queueUid

View File

@ -51,11 +51,11 @@ class PhotoService
public function analyse($queueToken) public function analyse($queueToken)
{ {
$photoFile = join(DIRECTORY_SEPARATOR, [ $queuePath = FileHelper::getQueuePath($queueToken);
FileHelper::getQueuePath($queueToken), $photoFile = join(DIRECTORY_SEPARATOR, [$queuePath, $this->photo->storage_file_name]);
$this->photo->storage_file_name
]);
try
{
$imageInfo = null; $imageInfo = null;
$originalPhotoResource = $this->imageHelper->openImage($photoFile, $imageInfo); $originalPhotoResource = $this->imageHelper->openImage($photoFile, $imageInfo);
if ($originalPhotoResource === false) if ($originalPhotoResource === false)
@ -121,6 +121,18 @@ class PhotoService
$this->regenerateThumbnails($originalPhotoResource); $this->regenerateThumbnails($originalPhotoResource);
} }
catch (\Exception $ex)
{
throw $ex;
}
finally
{
@unlink($photoFile);
// If the queue directory is now empty, get rid of it
FileHelper::deleteIfEmpty($queuePath);
}
}
public function changeAlbum(Album $newAlbum) public function changeAlbum(Album $newAlbum)
{ {
@ -213,6 +225,7 @@ class PhotoService
{ {
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $this->photo, $thumbnail); $generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $this->photo, $thumbnail);
$this->albumSource->saveThumbnail($this->photo, $generatedThumbnailPath, $thumbnail['name']); $this->albumSource->saveThumbnail($this->photo, $generatedThumbnailPath, $thumbnail['name']);
@unlink($generatedThumbnailPath);
} }
if (is_null($originalPhotoResource) && !is_null($photoPath)) if (is_null($originalPhotoResource) && !is_null($photoPath))

View File

@ -3,6 +3,8 @@ function AnalyseAlbumViewModel() {
self.imagesFailed = ko.observableArray(); self.imagesFailed = ko.observableArray();
self.imagesToAnalyse = ko.observableArray(); self.imagesToAnalyse = ko.observableArray();
self.imagesInProgress = ko.observableArray();
self.imagesRecentlyCompleted = ko.observableArray();
self.numberSuccessful = ko.observable(0); self.numberSuccessful = ko.observable(0);
self.numberFailed = ko.observable(0); self.numberFailed = ko.observable(0);
@ -23,11 +25,20 @@ function AnalyseAlbumViewModel() {
// When an image is added to the array, automatically issue it for analysis // When an image is added to the array, automatically issue it for analysis
self.imagesToAnalyse.subscribe(function (changes) { self.imagesToAnalyse.subscribe(function (changes) {
// We only care about additions
if (changes[0].status !== 'added')
{
return;
}
// changes[0].value is an instance of AnalyseImageViewModel // changes[0].value is an instance of AnalyseImageViewModel
var item = changes[0].value; var item = changes[0].value;
$.ajax( $.ajax(
item.url(), item.url(),
{ {
beforeSend: function() {
self.imagesInProgress.push(item);
},
dataType: 'json', dataType: 'json',
error: function (xhr, textStatus, errorThrown) { error: function (xhr, textStatus, errorThrown) {
self.numberFailed(self.numberFailed() + 1); self.numberFailed(self.numberFailed() + 1);
@ -44,6 +55,15 @@ function AnalyseAlbumViewModel() {
self.numberSuccessful(self.numberSuccessful() + 1); self.numberSuccessful(self.numberSuccessful() + 1);
item.isSuccessful(true); item.isSuccessful(true);
item.isPending(false); item.isPending(false);
// Push into our "recently completed" array
self.imagesRecentlyCompleted.push(item);
self.imagesInProgress.remove(item);
// Remove it again after a few seconds
window.setTimeout(function() {
self.imagesRecentlyCompleted.remove(item);
}, 2000);
} }
else { else {
self.numberFailed(self.numberFailed() + 1); self.numberFailed(self.numberFailed() + 1);
@ -70,9 +90,12 @@ function AnalyseImageViewModel(image_info) {
self.url = ko.observable(image_info.url); self.url = ko.observable(image_info.url);
self.iconClass = ko.computed(function () { self.iconClass = ko.computed(function () {
var string = 'fa fa-fw '; var string = 'fa fa-fw fa-';
if (!self.isPending()) { if (self.isPending()) {
string += 'refresh';
}
else {
string += (self.isSuccessful() ? 'check text-success' : 'times text-danger') string += (self.isSuccessful() ? 'check text-success' : 'times text-danger')
} }

View File

@ -17,6 +17,10 @@ return [
'album_security_intro' => 'The settings below affect the visibility of this album to other users.', 'album_security_intro' => 'The settings below affect the visibility of this album to other users.',
'album_settings_tab' => 'Settings', 'album_settings_tab' => 'Settings',
'album_upload_tab' => 'Upload', 'album_upload_tab' => 'Upload',
'analyse_and_more' => [
'and' => '... and ',
'others' => ' others'
],
'analyse_photos_failed' => 'The following items could not be analysed and were removed:', 'analyse_photos_failed' => 'The following items could not be analysed and were removed:',
'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.', 'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.',
'cannot_delete_own_user_account' => 'It is not possible to delete your own user account. Please ask another administrator to delete it for you.', 'cannot_delete_own_user_account' => 'It is not possible to delete your own user account. Please ask another administrator to delete it for you.',

View File

@ -19,8 +19,17 @@
</div> </div>
</div> </div>
<div data-bind="foreach: imagesToAnalyse" style="margin-top: 20px;"> {{-- We display a queue of 3 recently completed items, and 5 up-next items, as well as a counter saying "... and XYZ more" --}}
<p><span data-bind="text: name"></span> ... <i data-bind="css: iconClass"></i></p> {{-- That's what the 3's and 5's are in the next couple of blocks --}}
<div data-bind="foreach: imagesRecentlyCompleted.slice((imagesRecentlyCompleted().length - 3 < 0) ? 0 : imagesRecentlyCompleted().length - 3, 3)" style="margin-top: 20px;">
<p class="text-success"><span data-bind="text: name"></span> ... <i class="fa fa-fw fa-check"></i></p>
</div>
<div data-bind="foreach: imagesInProgress.slice(0, 5)" style="margin-top: 20px;">
<p><span data-bind="text: name"></span> ... <i data-bind="css: iconClass()"></i></p>
</div>
<div data-bind="visible: imagesInProgress().length > 5">
<p>@lang('admin.analyse_and_more.and') <span data-bind="text: imagesInProgress().length - 5"></span> @lang('admin.analyse_and_more.others')</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -324,7 +324,19 @@
$('#bulk-upload-form').submit(function(event) { $('#bulk-upload-form').submit(function(event) {
// Set the in progress flag - no need to unset it as this is a synchronous process so the browser // Set the in progress flag - no need to unset it as this is a synchronous process so the browser
// will reload the page in some way after completion // will reload the page in some way after completion
if (!viewModel.isBulkUploadInProgress())
{
viewModel.isBulkUploadInProgress(true); viewModel.isBulkUploadInProgress(true);
// Wait a minute to give KnockoutJS chance to update the UI
window.setTimeout(function() {
$('#bulk-upload-form').submit();
}, 1000);
return false;
}
// Already set the flag, let the upload commence
return true; return true;
}); });
} }