Added Move, started to add delete

This commit is contained in:
Deon George 2015-05-15 14:12:10 +10:00
parent dfe10e490d
commit 9359564ea8
7 changed files with 302 additions and 71 deletions

View File

@ -1,7 +1,9 @@
<?php defined('SYSPATH') or die('No direct script access.'); <?php defined('SYSPATH') or die('No direct script access.');
// -- Environment setup -------------------------------------------------------- // -- Environment setup --------------------------------------------------------
$SERVER_NAME = 'penguin.au.ibm.com'; $SERVER_NAMES = array(
'xphoto.leenooks.vpn',
);
// Load the core Kohana class // Load the core Kohana class
require SYSPATH.'classes/Kohana/Core'.EXT; require SYSPATH.'classes/Kohana/Core'.EXT;
@ -74,7 +76,7 @@ I18n::lang('en-us');
/** /**
* Set the environment status by the domain. * Set the environment status by the domain.
*/ */
Kohana::$environment = (isset($_SERVER['SERVER_NAME']) AND ($_SERVER['SERVER_NAME'] === $SERVER_NAME)) ? Kohana::PRODUCTION : Kohana::DEVELOPMENT; Kohana::$environment = (! isset($_SERVER['SERVER_NAME']) OR in_array($_SERVER['SERVER_NAME'],$SERVER_NAMES)) ? Kohana::PRODUCTION : Kohana::DEVELOPMENT;
if (isset($_SERVER['KOHANA_ENV'])) if (isset($_SERVER['KOHANA_ENV']))
{ {

View File

@ -13,6 +13,96 @@ class Controller_Photo extends Controller_TemplateDefault {
public function action_index() { public function action_index() {
} }
public function action_delete() {
$output = '';
// Update the current posted photos.
if ($this->request->post())
foreach ($this->request->post('process') as $pid) {
if (! Arr::get($this->request->post('remove'),$pid,FALSE))
continue;
$po = ORM::factory('Photo',$pid);
// If the photo is not marked as remove, or flagged, dont do it.
if (! $po->loaded() OR $po->flag OR ! $po->remove)
continue;
if (! is_writable(dirname($po->file_path())))
$output .= sprintf('Dont have write permissions on %s',dirname($po->file_path()));
$output .= sprintf('Removing %s (%s)',$po->id,$po->file_path());
}
$p = ORM::factory('Photo');
// Review a specific photo, or the next one marked remove
if ($x=$this->request->param('id'))
$p->where('id','=',$x);
else
$p->where('remove','=',TRUE)
->where_open()
->where('flag','!=',TRUE)
->or_where('flag','is',NULL)
->where_close();
$output .= Form::open(sprintf('%s/%s',strtolower($this->request->controller()),$this->request->action()));
foreach ($p->find_all() as $po) {
$dp = $po->list_duplicate()->find_all();
$output .= Form::hidden('process[]',$po->id);
foreach ($dp as $dpo)
$output .= Form::hidden('process[]',$dpo->id);
$output .= '<table class="table table-striped table-condensed table-hover">';
foreach (array(
'ID'=>array('key'=>'id','value'=>HTML::anchor('/photo/details/%VALUE%','%VALUE%')),
'Thumbnail'=>array('key'=>'id','value'=>HTML::anchor('/photo/view/%VALUE%',HTML::image('photo/thumbnail/%VALUE%'))),
'Signature'=>array('key'=>'signature'),
'Date Taken'=>array('key'=>'date_taken()'),
'File Modified'=>array('key'=>'date_file("m",TRUE)'),
'File Created'=>array('key'=>'date_file("c",TRUE)'),
'Filename'=>array('key'=>'file_path(TRUE,FALSE)'),
'Proposed Name'=>array('key'=>'path()'),
'Width'=>array('key'=>'width'),
'Height'=>array('key'=>'height'),
'Orientation'=>array('key'=>'orientation'),
'Orientate'=>array('key'=>'rotation()'),
'Make'=>array('key'=>'make'),
'Model'=>array('key'=>'model'),
) as $k=>$v)
$output .= $this->table_duplicate_details($dp,$po,$v['key'],$k,Arr::get($v,'value','%VALUE%'));
foreach (array(
'Delete'=>array('key'=>'id','value'=>'remove'),
) as $k=>$v)
$output .= $this->table_duplicate_checkbox($dp,$po,$v['key'],$k,Arr::get($v,'value','%VALUE%'));
$output .= '</table>';
break;
}
$output .= '<div class="row">';
$output .= '<div class="col-md-offset-2">';
$output .= '<button type="submit" class="btn btn-primary">Save changes</button>';
$output .= '<button type="button" class="btn">Cancel</button>';
$output .= '</div>';
$output .= '</div>';
$output .= Form::close();
Block::factory()
->title('Delete Photo:'.$po->id)
->title_icon('icon-delete')
->body($output);
}
public function action_details() { public function action_details() {
$po = ORM::factory('Photo',$this->request->param('id')); $po = ORM::factory('Photo',$this->request->param('id'));
if (! $po->loaded()) if (! $po->loaded())
@ -32,7 +122,7 @@ class Controller_Photo extends Controller_TemplateDefault {
$po = ORM::factory('Photo',$pid); $po = ORM::factory('Photo',$pid);
$po->duplicate = Arr::get($this->request->post('duplicate'),$pid); $po->duplicate = Arr::get($this->request->post('duplicate'),$pid);
$po->delete = Arr::get($this->request->post('delete'),$pid); $po->remove = Arr::get($this->request->post('remove'),$pid);
$po->flag = Arr::get($this->request->post('flag'),$pid); $po->flag = Arr::get($this->request->post('flag'),$pid);
$po->save(); $po->save();
@ -40,20 +130,21 @@ class Controller_Photo extends Controller_TemplateDefault {
$p = ORM::factory('Photo'); $p = ORM::factory('Photo');
// Review a specific photo, or the next one marked duplicate
if ($x=$this->request->param('id')) if ($x=$this->request->param('id'))
$p->where('id','=',$x); $p->where('id','=',$x);
else else
$p->where('duplicate','=',TRUE) $p->where('duplicate','=',TRUE)
->where_open() ->where_open()
->where('delete','!=',TRUE) ->where('remove','!=',TRUE)
->or_where('delete','is',NULL) ->or_where('remove','is',NULL)
->where_close(); ->where_close();
$output .= Form::open(sprintf('%s/%s',strtolower($this->request->controller()),$this->request->action())); $output .= Form::open(sprintf('%s/%s',strtolower($this->request->controller()),$this->request->action()));
foreach ($p->find_all() as $po) { foreach ($p->find_all() as $po) {
$dp = $po->duplicate_find()->find_all(); $dp = $po->list_duplicate()->find_all();
// Check that there are still duplicates // Check that there are still duplicates
if ($dp->count() == 0) { if ($dp->count() == 0) {
@ -73,20 +164,24 @@ class Controller_Photo extends Controller_TemplateDefault {
'Thumbnail'=>array('key'=>'id','value'=>HTML::anchor('/photo/view/%VALUE%',HTML::image('photo/thumbnail/%VALUE%'))), 'Thumbnail'=>array('key'=>'id','value'=>HTML::anchor('/photo/view/%VALUE%',HTML::image('photo/thumbnail/%VALUE%'))),
'Signature'=>array('key'=>'signature'), 'Signature'=>array('key'=>'signature'),
'Date Taken'=>array('key'=>'date_taken()'), 'Date Taken'=>array('key'=>'date_taken()'),
'Filename'=>array('key'=>'filename'), 'File Modified'=>array('key'=>'date_file("m",TRUE)'),
'Proposed Name'=>array('key'=>'path()'), 'File Created'=>array('key'=>'date_file("c",TRUE)'),
'Filename'=>array('key'=>'file_path(TRUE,FALSE)'),
'Proposed Name'=>array('key'=>'file_path(TRUE,TRUE)'),
'Width'=>array('key'=>'width'), 'Width'=>array('key'=>'width'),
'Height'=>array('key'=>'height'), 'Height'=>array('key'=>'height'),
'Orientation'=>array('key'=>'orientation'), 'Orientation'=>array('key'=>'orientation'),
'Orientate'=>array('key'=>'rotation()'),
'Make'=>array('key'=>'make'), 'Make'=>array('key'=>'make'),
'Model'=>array('key'=>'model'), 'Model'=>array('key'=>'model'),
'Exif Diff'=>array('key'=>"propertydiff({$po->id})"),
) as $k=>$v) ) as $k=>$v)
$output .= $this->table_duplicate_details($dp,$po,$v['key'],$k,Arr::get($v,'value','%VALUE%')); $output .= $this->table_duplicate_details($dp,$po,$v['key'],$k,Arr::get($v,'value','%VALUE%'));
foreach (array( foreach (array(
'Flag'=>array('key'=>'id','value'=>'flag'), 'Flag'=>array('key'=>'id','value'=>'flag'),
'Duplicate'=>array('key'=>'id','value'=>'duplicate'), 'Duplicate'=>array('key'=>'id','value'=>'duplicate'),
'Delete'=>array('key'=>'id','value'=>'delete'), 'Delete'=>array('key'=>'id','value'=>'remove'),
) as $k=>$v) ) as $k=>$v)
$output .= $this->table_duplicate_checkbox($dp,$po,$v['key'],$k,Arr::get($v,'value','%VALUE%')); $output .= $this->table_duplicate_checkbox($dp,$po,$v['key'],$k,Arr::get($v,'value','%VALUE%'));
@ -96,7 +191,7 @@ class Controller_Photo extends Controller_TemplateDefault {
} }
$output .= '<div class="row">'; $output .= '<div class="row">';
$output .= '<div class="offset2">'; $output .= '<div class="col-md-offset-2">';
$output .= '<button type="submit" class="btn btn-primary">Save changes</button>'; $output .= '<button type="submit" class="btn btn-primary">Save changes</button>';
$output .= '<button type="button" class="btn">Cancel</button>'; $output .= '<button type="button" class="btn">Cancel</button>';
$output .= '</div>'; $output .= '</div>';
@ -157,24 +252,29 @@ class Controller_Photo extends Controller_TemplateDefault {
return $output; return $output;
} }
private function evaluate(Model $o,$param) {
$result = NULL;
if (preg_match('/\(/',$param) OR preg_match('/-\>/',$param))
eval("\$result = \$o->$param;");
else
$result = $o->display($param);
return $result;
}
private function table_duplicate_details(Database_MySQL_Result $dp,Model_Photo $po,$param,$title='',$content='') { private function table_duplicate_details(Database_MySQL_Result $dp,Model_Photo $po,$param,$title='',$content='') {
$output = '<tr>'; $output = '<tr>';
if (preg_match('/\(/',$param) OR preg_match('/-\>/',$param)) $v = $this->evaluate($po,$param);
eval("\$d = \$po->$param;");
else
$d = $po->display($param);
$output .= sprintf('<th>%s</th>',$title); $output .= sprintf('<th>%s</th>',$title);
$output .= sprintf('<td>%s</td>',$content ? str_replace('%VALUE%',$d,$content) : $d); $output .= sprintf('<td>%s</td>',$content ? str_replace('%VALUE%',$v,$content) : $v);
foreach ($dp as $dpo) { foreach ($dp as $dpo) {
if (preg_match('/\(/',$param) OR preg_match('/-\>/',$param)) $d = $this->evaluate($dpo,$param);
eval("\$d = \$dpo->$param;");
else
$d = $dpo->display($param);
$output .= sprintf('<td>%s</td>',$content ? str_replace('%VALUE%',$d,$content) : $d); $output .= sprintf('<td class="%s">%s</td>',($d==$v ? 'success' : 'warning'),$content ? str_replace('%VALUE%',$d,$content) : $d);
} }
$output .= '</tr>'; $output .= '</tr>';

View File

@ -0,0 +1,24 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class extends the core Kohana class by adding some core application
* specific functions, and configuration.
*
* @package Photo
* @category Helpers
* @author Deon George
* @copyright (c) 2014 Deon George
* @license http://dev.leenooks.net/license.html
*/
class File extends Kohana_File {
public static function ParentDirExist($path,$create=FALSE) {
$isDir = is_dir($path);
if ($isDir OR ! $create)
return $isDir;
if (File::ParentDirExist(dirname($path),$create))
return mkdir($path);
}
}
?>

View File

@ -10,7 +10,7 @@
* @license http://dev.leenooks.net/license.html * @license http://dev.leenooks.net/license.html
*/ */
class Model_Photo extends ORM { class Model_Photo extends ORM {
private $path = '/mnt/net/qnap/Multimedia/Photos'; private $_path = '/mnt/net/qnap/Multimedia/Photos';
private $_io = NULL; private $_io = NULL;
protected $_has_many = array( protected $_has_many = array(
@ -33,44 +33,27 @@ class Model_Photo extends ORM {
), ),
); );
public function duplicate_find() { public function date_file($type,$format=FALSE) {
$po = ORM::factory($this->_object_name); switch ($type) {
case 'a': $t = fileatime($this->file_path());
if ($this->loaded()) break;
$po->where('id','!=',$this->id); case 'c': $t = filectime($this->file_path());
break;
$po->where_open(); case 'm': $t = filemtime($this->file_path());
$po->where('delete','!=',TRUE); break;
$po->or_where('delete','is',NULL);
$po->where_close();
$po->where_open();
$po->where('signature','=',$this->signature);
if ($this->date_taken AND ($this->model OR $this->make)) {
$po->or_where_open();
$po->where('date_taken','=',$this->date_taken);
$po->where('subsectime','=',$this->subsectime);
if (! is_null($this->model))
$po->and_where('model','=',$this->model);
if (! is_null($this->make))
$po->and_where('make','=',$this->make);
$po->where_close();
} }
$po->where_close(); return $format ? Site::Datetime($t) : $t;
return $po;
} }
public function date_taken() { public function date_taken() {
return $this->display('date_taken').($this->subsectime ? '.'.$this->subsectime : ''); return $this->display('date_taken').($this->subsectime ? '.'.$this->subsectime : '');
} }
public function duplicate_get() { public function file_path($short=FALSE,$new=FALSE) {
return $this->duplicate_find()->find_all(); $file = $new ? sprintf('%s_%03s.%s',date('Y/m/d-His',$this->date_taken),$this->subsectime,$this->type()) : $this->filename;
return (($short OR preg_match('/^\//',$file)) ? '' : $this->_path.DIRECTORY_SEPARATOR).$file;
} }
public function gps(array $coordinate,$hemisphere) { public function gps(array $coordinate,$hemisphere) {
@ -100,7 +83,9 @@ class Model_Photo extends ORM {
public function image() { public function image() {
$imo = $this->io(); $imo = $this->io();
if (array_key_exists('exif',$imo->getImageProfiles()))
$imo->removeImageProfile('exif'); $imo->removeImageProfile('exif');
$this->rotate($imo); $this->rotate($imo);
return $imo->getImageBlob(); return $imo->getImageBlob();
@ -114,29 +99,45 @@ class Model_Photo extends ORM {
public function io($attr=NULL) { public function io($attr=NULL) {
if (is_nulL($this->_io)) if (is_nulL($this->_io))
$this->_io = new Imagick($this->path.'/'.$this->filename); $this->_io = new Imagick($this->_path.DIRECTORY_SEPARATOR.$this->filename);
return is_null($attr) ? $this->_io : $this->_io->getImageProperty($attr); return is_null($attr) ? $this->_io : $this->_io->getImageProperty($attr);
} }
public function path() { public function move($path='') {
$path = ''; if (! $path)
$ao = $this->album->where('primary','=',TRUE)->find(); $path = $this->file_path(FALSE,TRUE);
if ($ao->loaded()) { $po = ORM::factory('Photo',$path);
$path .= $ao->path.'/';
if ($ao->subpath_age) { // If the file already exists, we'll ignore the move.
$po = $this->people->where('primary','=',TRUE)->find(); if ($po->loaded() OR file_exists($path) OR ! File::ParentDirExist(dirname($path),TRUE))
$path .= $po->age($this->date_taken).'/'; return FALSE;
if (rename($this->file_path(FALSE,FALSE),$path)) {
$this->filename = preg_replace(":^{$this->_path}/:",'',$path);
// If the DB update failed, move it back.
if (! $this->save() AND ! rename($path,$this->file_path()))
throw new Kohana_Exception('Error: Unable to move file, ID: :id, OLD: :oldname, NEW: :newname',
array(':id'=>$this->id,':oldname'=>$this->file_path(),':newname'=>$this->file_path()));
return TRUE;
} }
} }
$path .= sprintf('%s_%03s.%s',date('Ymd-His',$this->date_taken),$this->subsectime,$this->type()); public function propertydiff($id) {
if ($id == $this->id)
return;
return $path; $po = ORM::factory($this->_object_name,$id);
if (! $po->loaded())
return;
$result = array_diff_assoc($this->info(),$po->info());
return join('|',array_keys($result));
} }
private function rotate(Imagick $imo) { private function rotate(Imagick $imo) {
switch ($this->orientation) { switch ($this->orientation) {
case 3: $imo->rotateImage(new ImagickPixel('none'),180); case 3: $imo->rotateImage(new ImagickPixel('none'),180);
@ -148,14 +149,27 @@ class Model_Photo extends ORM {
} }
} }
public static function Signaturetrim($signature) { public function rotation() {
switch ($this->orientation) {
case 1: return 'None!';
case 3: return 'Upside Down';
case 6: return 'Rotate Right';
case 8: return 'Rotate Left';
default:
return 'unknown?';
}
}
public static function SignatureTrim($signature) {
return sprintf('%s...%s',substr($signature,0,6),substr($signature,-6)); return sprintf('%s...%s',substr($signature,0,6),substr($signature,-6));
} }
public function thumbnail() { public function thumbnail($rotate=TRUE) {
$imo = new Imagick(); $imo = new Imagick();
$imo->readImageBlob($this->thumbnail); $imo->readImageBlob($this->thumbnail);
if ($rotate)
$this->rotate($imo); $this->rotate($imo);
return $imo->getImageBlob(); return $imo->getImageBlob();
@ -164,5 +178,41 @@ class Model_Photo extends ORM {
public function type($mime=FALSE) { public function type($mime=FALSE) {
return strtolower($mime ? File::mime_by_ext(pathinfo($this->filename,PATHINFO_EXTENSION)) : pathinfo($this->filename,PATHINFO_EXTENSION)); return strtolower($mime ? File::mime_by_ext(pathinfo($this->filename,PATHINFO_EXTENSION)) : pathinfo($this->filename,PATHINFO_EXTENSION));
} }
public function list_duplicate() {
$po = ORM::factory($this->_object_name);
if ($this->loaded())
$po->where('id','!=',$this->id);
// Ignore photo's pending removal.
$po->where_open();
$po->where('remove','!=',TRUE);
$po->or_where('remove','is',NULL);
$po->where_close();
// Where the signature is the same
$po->where_open();
$po->where('signature','=',$this->signature);
// Or they have the same time taken with the same camera
if ($this->date_taken AND ($this->model OR $this->make)) {
$po->or_where_open();
$po->where('date_taken','=',$this->date_taken);
$po->where('subsectime','=',$this->subsectime);
if (! is_null($this->model))
$po->and_where('model','=',$this->model);
if (! is_null($this->make))
$po->and_where('make','=',$this->make);
$po->where_close();
}
$po->where_close();
return $po;
}
} }
?> ?>

View File

@ -54,7 +54,7 @@ class Task_Photo_Import extends Minion_Task {
break; break;
} }
if ($po->duplicate_get()->count()) if ($po->list_duplicate()->find_all()->count())
$po->duplicate = '1'; $po->duplicate = '1';
if (! $po->changed()) if (! $po->changed())
@ -68,6 +68,7 @@ class Task_Photo_Import extends Minion_Task {
return sprintf("Image [%s] processed in DB: %s\n",$params['file'],$po->id); return sprintf("Image [%s] processed in DB: %s\n",$params['file'],$po->id);
} }
// Force the return of a string or NULL
private function dbcol($val,$noval=NULL) { private function dbcol($val,$noval=NULL) {
return $val ? (string)$val : $noval; return $val ? (string)$val : $noval;
} }

View File

@ -0,0 +1,54 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* Mark all accounts that have no outstanding invoices and active services as disabled.
*
* @package Photo
* @category Tasks
* @author Deon George
* @copyright (c) 2014 Deon George
* @license http://dev.leenooks.net/license.html
*/
class Task_Photo_Move extends Minion_Task {
protected $_options = array(
'file'=>NULL, // Photo File to Move
'batch'=>NULL, // Number of photos to move in a batch
);
protected function _execute(array $params) {
if ($params['file']) {
$po = ORM::factory('Photo',array('filename'=>$params['file']));
} else {
$p = ORM::factory('Photo')
->where('date_taken','is not',NULL)
->where_open()
->where('remove','!=',TRUE)
->or_where('remove','is',NULL)
->where_close()
->where_open()
->where('duplicate','!=',TRUE)
->or_where('duplicate','is',NULL)
->where_close();
}
$c = 0;
foreach ($p->find_all() as $po) {
if ($po->file_path() == $po->file_path(FALSE,TRUE))
continue;
if ($po->move())
printf("Photo [%s] moved to %s.\n",$po->id,$po->file_path());
else
printf("Photo [%s] NOT moved to %s.\n",$po->id,$po->file_path(FALSE,TRUE));
$c++;
if (! is_null($params['batch']) AND $c >= $params['batch'])
break;
}
return sprintf("Images processed [%s]\n",$c);
}
}
?>

View File

@ -20,7 +20,7 @@ return array
*/ */
'hostname' => 'mysql.leenooks.vpn', 'hostname' => 'mysql.leenooks.vpn',
'database' => 'weblnphoto', 'database' => 'weblnphoto',
'username' => 'ln-webphoto', 'username' => 'ln-photo',
'password' => 'Ph0T0!', 'password' => 'Ph0T0!',
'persistent' => TRUE, 'persistent' => TRUE,
), ),