From 05acb6e87a0b7131b498f32b8cadcc2eca0e888b Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Thu, 20 Mar 2014 01:55:48 +0100 Subject: [PATCH] Mostly done refactoring Photo controller --- appinfo/routes.php | 2 +- js/app.js | 55 ++----- lib/contact.php | 15 ++ lib/controller/contactphotocontroller.php | 137 ++++------------ lib/utils/temporaryphoto.php | 180 ++++++++++++++++++++++ lib/utils/temporaryphoto/contact.php | 61 ++++++++ lib/utils/temporaryphoto/filesystem.php | 63 ++++++++ lib/utils/temporaryphoto/uploaded.php | 64 ++++++++ templates/contacts.php | 2 +- 9 files changed, 432 insertions(+), 147 deletions(-) create mode 100644 lib/utils/temporaryphoto.php create mode 100644 lib/utils/temporaryphoto/contact.php create mode 100644 lib/utils/temporaryphoto/filesystem.php create mode 100644 lib/utils/temporaryphoto/uploaded.php diff --git a/appinfo/routes.php b/appinfo/routes.php index f9f21e73..2fa1a6d4 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -243,7 +243,7 @@ $this->create('contacts_contact_photo', 'addressbook/{backend}/{addressBookId}/c ->requirements(array('backend', 'addressbook', 'contactId')); $this->create('contacts_upload_contact_photo', 'addressbook/{backend}/{addressBookId}/contact/{contactId}/photo') - ->post() + ->put() ->action( function($params) { session_write_close(); diff --git a/js/app.js b/js/app.js index 2621c129..39b59323 100644 --- a/js/app.js +++ b/js/app.js @@ -765,29 +765,26 @@ OC.Contacts = OC.Contacts || { } });*/ $('#contactphoto_fileupload').on('click', function(event, metadata) { - var form = $('#file_upload_form'); var url = OC.generateUrl( 'apps/contacts/addressbook/{backend}/{addressBookId}/contact/{contactId}/photo', {backend: metadata.backend, addressBookId: metadata.addressBookId, contactId: metadata.contactId} ); - form.attr('action', url); - }).on('change', function() { - console.log('#contactphoto_fileupload, change'); - self.uploadPhoto(this.files); - }); - - var target = $('#file_upload_target'); - target.load(function() { - var response = $.parseJSON(target.contents().text()); - if(response && response.status === 'success') { - console.log('response', response); + $(this).fileupload('option', 'url', url); + }).fileupload({ + singleFileUploads: true, + multipart: false, + dataType: 'json', + type: 'PUT', + done: function (e, data) { + console.log('Upload done:', data); self.editPhoto( - response.data.metadata, - response.data.tmp + data.result.metadata, + data.result.tmp ); - //alert('File: ' + file.tmp + ' ' + file.name + ' ' + file.mime); - } else if(response) { - $(document).trigger('status.contacts.error', response); + }, + fail: function(e, data) { + console.log('fail', data); + OC.notify({message:data.errorThrown + ': ' + data.textStatus}); } }); @@ -1491,34 +1488,14 @@ OC.Contacts = OC.Contacts || { update: function() { console.log('update'); }, - uploadPhoto:function(filelist) { - console.log('uploadPhoto'); - if(!filelist) { - $(document).trigger('status.contacts.error', {message:t('contacts','No files selected for upload.')}); - return; - } - var file = filelist[0]; - var form = $('#file_upload_form'); - - if(file.size > $('#max_upload').val()) { - $(document).trigger('status.contacts.error', { - message:t( - 'contacts', - 'The file you are trying to upload exceed the maximum size for file uploads on this server.') - }); - return; - } else { - form.submit(); - } - }, cloudPhotoSelected:function(metadata, path) { var self = this; console.log('cloudPhotoSelected', metadata); var url = OC.generateUrl( 'apps/contacts/addressbook/{backend}/{addressBookId}/contact/{contactId}/photo/cacheFS', - {backend: metadata.backend, addressBookId: metadata.addressBookId, contactId: metadata.contactId, path: path} + {backend: metadata.backend, addressBookId: metadata.addressBookId, contactId: metadata.contactId} ); - var jqXHR = $.getJSON(url, function(response) { + var jqXHR = $.getJSON(url, {path: path}, function(response) { console.log('response', response); response = self.storage.formatResponse(response, jqXHR); if(!response.error) { diff --git a/lib/contact.php b/lib/contact.php index 39f7529b..eaee77c6 100644 --- a/lib/contact.php +++ b/lib/contact.php @@ -385,6 +385,21 @@ class Contact extends VObject\VCard implements IPIMObject { return true; } + /** + * Get the PHOTO or LOGO + * + * @return \OCP\Image|null + */ + public function getPhoto() { + $image = new \OCP\Image(); + + if (isset($this->PHOTO) && $image->loadFromBase64((string)$this->PHOTO)) { + return $image; + } elseif (isset($this->LOGO) && $image->loadFromBase64((string)$this->LOGO)) { + return $image; + } + } + /** * Get a property index in the contact by the checksum of its serialized value * diff --git a/lib/controller/contactphotocontroller.php b/lib/controller/contactphotocontroller.php index 1aaafa0e..348473df 100644 --- a/lib/controller/contactphotocontroller.php +++ b/lib/controller/contactphotocontroller.php @@ -13,6 +13,7 @@ use OCA\Contacts\App, OCA\Contacts\JSONResponse, OCA\Contacts\ImageResponse, OCA\Contacts\Utils\Properties, + OCA\Contacts\Utils\TemporaryPhoto, OCA\Contacts\Controller; /** @@ -79,73 +80,24 @@ class ContactPhotoController extends Controller { */ public function uploadPhoto() { $params = $this->request->urlParams; - $maxSize = isset($this->request->post['maxSize']) ? $this->request->post['maxSize'] : 400; + + + $tempPhoto = TemporaryPhoto::get( + $this->server, + TemporaryPhoto::PHOTO_UPLOADED, + $this->request + ); $response = new JSONResponse(); - if (!isset($this->request->files['imagefile'])) { - $response->bailOut(App::$l10n->t('No file was uploaded. Unknown error')); - return $response; - } - - $file = $this->request->files['imagefile']; - $error = $file['error']; - if($error !== UPLOAD_ERR_OK) { - $errors = array( - 0=>App::$l10n->t("There is no error, the file uploaded with success"), - 1=>App::$l10n->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'), - 2=>App::$l10n->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"), - 3=>App::$l10n->t("The uploaded file was only partially uploaded"), - 4=>App::$l10n->t("No file was uploaded"), - 6=>App::$l10n->t("Missing a temporary folder") - ); - $response->bailOut($errors[$error]); - return $response; - } - - if(!file_exists($file['tmp_name'])) { - $response->bailOut('Temporary file: \''.$file['tmp_name'].'\' has gone AWOL?'); - return $response; - } - - $tmpkey = 'contact-photo-'.md5(basename($file['tmp_name'])); - $image = new \OCP\Image(); - - if(!$image->loadFromFile($file['tmp_name'])) { - $response->bailOut(App::$l10n->t('Couldn\'t load temporary image: ').$file['tmp_name']); - return $response; - } - - if(!$image->fixOrientation()) { // No fatal error so we don't bail out. - $response->debug('Couldn\'t save correct image orientation: '.$tmpkey); - } - - if($image->valid()) { - if($image->height() > $maxSize || $image->width() > $maxSize) { - $image->resize($maxSize); - } - } else { - $response->bailOut(App::$l10n->t('Uploaded image is invalid')); - } - - if(!$this->server->getCache()->set($tmpkey, $image->data(), 600)) { - $response->bailOut(App::$l10n->t('Couldn\'t save temporary image: ').$tmpkey); - return $response; - } - - $response->setData(array( - 'status' => 'success', - 'data' => array( - 'tmp'=>$tmpkey, - 'metadata' => array( - 'contactId'=> $params['contactId'], - 'addressBookId'=> $params['addressBookId'], - 'backend'=> $params['backend'], - ), - ) + return $response->setParams(array( + 'tmp'=>$tempPhoto->getKey(), + 'metadata' => array( + 'contactId'=> $params['contactId'], + 'addressBookId'=> $params['addressBookId'], + 'backend'=> $params['backend'], + ), )); - - return $response; } /** @@ -158,32 +110,24 @@ class ContactPhotoController extends Controller { public function cacheCurrentPhoto() { $params = $this->request->urlParams; $response = new JSONResponse(); - $maxSize = isset($this->request->get['maxSize']) ? $this->request->get['maxSize'] : 400; - $photoResponse = $this->getPhoto($maxSize); + $addressBook = $this->app->getAddressBook($params['backend'], $params['addressBookId']); + $contact = $addressBook->getChild($params['contactId']); - if(!$photoResponse instanceof ImageResponse) { - return $photoResponse; - } + $tempPhoto = TemporaryPhoto::get( + $this->server, + TemporaryPhoto::PHOTO_CURRENT, + $contact + ); - $data = $photoResponse->render(); - $tmpkey = 'contact-photo-' . $params['contactId']; - if(!$this->server->getCache()->set($tmpkey, $data, 600)) { - $response->bailOut(App::$l10n->t('Couldn\'t save temporary image: ').$tmpkey); - return $response; - } - - $response->setParams(array( - 'tmp'=>$tmpkey, + return $response->setParams(array( + 'tmp'=>$tempPhoto->getKey(), 'metadata' => array( 'contactId'=> $params['contactId'], 'addressBookId'=> $params['addressBookId'], 'backend'=> $params['backend'], ), )); - - return $response; - } /** @@ -202,39 +146,20 @@ class ContactPhotoController extends Controller { $response->bailOut(App::$l10n->t('No photo path was submitted.')); } - $localpath = \OC\Files\Filesystem::getLocalFile($this->request->get['path']); - $tmpkey = 'contact-photo-' . $params['contactId']; + $tempPhoto = TemporaryPhoto::get( + $this->server, + TemporaryPhoto::PHOTO_FILESYSTEM, + $this->request->get['path'] + ); - if(!file_exists($localpath)) { - return $response->bailOut(App::$l10n->t('File doesn\'t exist:').$localpath); - } - - $image = new \OCP\Image(); - if(!$image) { - return $response->bailOut(App::$l10n->t('Error loading image.')); - } - if(!$image->loadFromFile($localpath)) { - return $response->bailOut(App::$l10n->t('Error loading image.')); - } - if($image->width() > $maxSize || $image->height() > $maxSize) { - $image->resize($maxSize); // Prettier resizing than with browser and saves bandwidth. - } - if(!$image->fixOrientation()) { // No fatal error so we don't bail out. - $response->debug('Couldn\'t save correct image orientation: '.$localpath); - } - if(!$this->server->getCache()->set($tmpkey, $image->data(), 600)) { - return $response->bailOut('Couldn\'t save temporary image: '.$tmpkey); - } - - return $response->setData(array( - 'tmp'=>$tmpkey, + return $response->setParams(array( + 'tmp'=>$tempPhoto->getKey(), 'metadata' => array( 'contactId'=> $params['contactId'], 'addressBookId'=> $params['addressBookId'], 'backend'=> $params['backend'], ), )); - } /** diff --git a/lib/utils/temporaryphoto.php b/lib/utils/temporaryphoto.php new file mode 100644 index 00000000..122f9d32 --- /dev/null +++ b/lib/utils/temporaryphoto.php @@ -0,0 +1,180 @@ +. + * + */ + +namespace OCA\Contacts\Utils; + +/** + * This class is used for getting a contact photo for cropping. + */ +abstract class TemporaryPhoto { + + const MAX_SIZE = 400; + + const PHOTO_CURRENT = 0; + const PHOTO_FILESYSTEM = 1; + const PHOTO_UPLOADED = 2; + + /** + * @var \OCP\IServerContainer + */ + protected $server; + + /** + * @var OCP\Image + */ + protected $image; + + /** + * Cache key for temporary storage. + * + * @var string + */ + protected $key; + + + /** + * Whether normalizePhoto() has already been called. + * + * @var bool + */ + protected $normalized; + + /** + * Whether the photo is cached. + * + * @var bool + */ + protected $cached; + + /** + * Photo loader classes. + * + * @var array + */ + static public $classMap = array( + 'OCA\\Contacts\\Utils\\TemporaryPhoto\\Contact', + 'OCA\\Contacts\\Utils\\TemporaryPhoto\\FileSystem', + 'OCA\\Contacts\\Utils\\TemporaryPhoto\\Uploaded', + ); + + /** + * Always call parents ctor: + * parent::__construct($server); + */ + public function __construct(\OCP\IServerContainer $server) { + $this->server = $server; + } + + /** + * Returns an instance of a subclass of this class + * + * @param \OCP\IServerContainer $server + * @param int $type One of the pre-defined types. + * @param mixed $data Whatever data is needed to load the photo. + */ + public static function get(\OCP\IServerContainer $server, $type, $data) { + if (isset(self::$classMap[$type])) { + return new self::$classMap[$type]($server, $data); + } else { + // TODO: Return a "null object" + return new self($data); + } + } + + /** + * Do what's needed to get the image from storage + * depending on the type. + * After this method is called $this->image must hold an + * instance of \OCP\Image. + */ + protected abstract function processImage(); + + /** + * Whether this image is valied + * + * @return bool + */ + public function isValid() { + return (($this->image instanceof \OCP\Image) && $this->image->valid()); + } + + /** + * Get the key to the cache image. + * + * @return string + */ + public function getKey() { + $this->cachePhoto(); + return $this->key; + } + + /** + * Get normalized image. + * + * @return \OCP\Image + */ + public function getPhoto() { + $this->normalizePhoto(); + return $this->image; + } + + /** + * Save image data to cache and return the key + * + * @return string + */ + private function cachePhoto() { + if ($this->cached) { + return; + } + + if (!$this->image instanceof \OCP\Image) { + $this->processImage(); + } + $this->normalizePhoto(); + + $data = $this->image->data(); + $this->key = uniqid('photo-'); + $this->server->getCache()->set($this->key, $data, 600); + } + + /** + * Resize and rotate the image if needed. + */ + private function normalizePhoto() { + + if($this->normalized) { + return; + } + + $this->image->fixOrientation(); + + if ($this->image->height() > self::MAX_SIZE + || $this->image->width() > self::MAX_SIZE + ) { + $this->image->resize(self::MAX_SIZE); + } + + $this->normalized = true; + } + +} \ No newline at end of file diff --git a/lib/utils/temporaryphoto/contact.php b/lib/utils/temporaryphoto/contact.php new file mode 100644 index 00000000..42fb7e9b --- /dev/null +++ b/lib/utils/temporaryphoto/contact.php @@ -0,0 +1,61 @@ +. + * + */ + +namespace OCA\Contacts\Utils\TemporaryPhoto; + +use OCA\Contacts\Contact as ContactObject, + OCA\Contacts\Utils\TemporaryPhoto as AbstractTemporaryPhoto; + +/** + * This class loads the PHOTO or LOGO property from a contact. + */ +class Contact extends AbstractTemporaryPhoto { + + /** + * The Contact object to load the image from + * + * @var OCA\Contacts\Contact + */ + protected $contact; + + public function __construct(\OCP\IServerContainer $server, $contact) { + if (!$contact instanceof ContactObject) { + throw new \Exception( + __METHOD__ + . ' Second argument must be an instance of OCA\\Contacts\\Contact' + ); + } + + parent::__construct($server); + $this->contact = $contact; + $this->processImage(); + } + + /** + * Do what's needed to get the image from storage + * depending on the type. + */ + protected function processImage() { + $this->image = $this->contact->getPhoto(); + } + +} \ No newline at end of file diff --git a/lib/utils/temporaryphoto/filesystem.php b/lib/utils/temporaryphoto/filesystem.php new file mode 100644 index 00000000..3e6aa937 --- /dev/null +++ b/lib/utils/temporaryphoto/filesystem.php @@ -0,0 +1,63 @@ +. + * + */ + +namespace OCA\Contacts\Utils\TemporaryPhoto; + +use OCA\Contacts\Contact as ContactObject, + OCA\Contacts\Utils\TemporaryPhoto as AbstractTemporaryPhoto; + +/** + * This class loads an image from the virtual file system. + */ +class FileSystem extends AbstractTemporaryPhoto { + + /** + * The virtual file system path to load the image from + * + * @var string + */ + protected $path; + + public function __construct(\OCP\IServerContainer $server, $path) { + \OCP\Util::writeLog('contacts', __METHOD__.' path: ' . $path, \OCP\Util::DEBUG); + if (!is_string($path)) { + throw new \Exception( + __METHOD__ . ' Second argument must a string' + ); + } + + parent::__construct($server); + $this->path = $path; + $this->processImage(); + } + + /** + * Load the image. + */ + protected function processImage() { + $localpath = \OC\Files\Filesystem::getLocalFile($this->path); + + $this->image = new \OCP\Image(); + $this->image->loadFromFile($localpath); + } + +} \ No newline at end of file diff --git a/lib/utils/temporaryphoto/uploaded.php b/lib/utils/temporaryphoto/uploaded.php new file mode 100644 index 00000000..f8e7dc27 --- /dev/null +++ b/lib/utils/temporaryphoto/uploaded.php @@ -0,0 +1,64 @@ +. + * + */ + +namespace OCA\Contacts\Utils\TemporaryPhoto; + +use OCA\Contacts\Contact as ContactObject, + OCA\Contacts\Utils\TemporaryPhoto as AbstractTemporaryPhoto; + +/** + * This class loads an image from the virtual file system. + */ +class Uploaded extends AbstractTemporaryPhoto { + + /** + * The request to read the data from + * + * @var \OCP\IRequest + */ + protected $input; + + public function __construct(\OCP\IServerContainer $server, \OCP\IRequest $request) { + \OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG); + if (!$request instanceOf \OCP\IRequest) { + throw new \Exception( + __METHOD__ . ' Second argument must be an instance of \\OCP\\IRequest' + ); + } + + parent::__construct($server); + $this->request = $request; + $this->processImage(); + } + + /** + * Load the image. + */ + protected function processImage() { + $this->image = new \OCP\Image(); + \OCP\Util::writeLog('contacts', __METHOD__ . ', Content-Type: ' . $this->request->getHeader('Content-Type'), \OCP\Util::DEBUG); + \OCP\Util::writeLog('contacts', __METHOD__ . ', Content-Length: ' . $this->request->getHeader('Content-Length'), \OCP\Util::DEBUG); + + $this->image->loadFromFileHandle($this->request->put); + } + +} \ No newline at end of file diff --git a/templates/contacts.php b/templates/contacts.php index 68a45163..b005abf5 100644 --- a/templates/contacts.php +++ b/templates/contacts.php @@ -117,7 +117,7 @@ -
+