allow attachments via stream
authorCornelius Weiß <mail@corneliusweiss.de>
Tue, 15 Apr 2014 14:03:59 +0000 (16:03 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 4 Sep 2014 09:25:29 +0000 (11:25 +0200)
* allow to stream file into Filesystem
* allow to add multiple nodes with one fileObject
* ease attachment handling

Change-Id: I5b4593f16445995be6ab393455adc4117c1f8d92
Reviewed-on: http://gerrit.tine20.com/customers/524
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Tinebase/FileSystem.php
tine20/Tinebase/FileSystem/RecordAttachments.php
tine20/Tinebase/Model/Tree/Node.php

index 6961905..a033724 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  FileSystem
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2010-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2010-2014 Metaways Infosystems GmbH (http://www.metaways.de)
  * 
  * @todo 0007376: Tinebase_FileSystem / Node model refactoring: move all container related functionality to Filemanager
  */
@@ -314,28 +314,7 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
             case 'wb':
             case 'x':
             case 'xb':
-                rewind($handle);
-                
-                $ctx = hash_init('sha1');
-                hash_update_stream($ctx, $handle);
-                $hash = hash_final($ctx);
-                
-                $hashDirectory = $this->_basePath . '/' . substr($hash, 0, 3);
-                if (!file_exists($hashDirectory)) {
-                    Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' create hash directory: ' . $hashDirectory);
-                    if(mkdir($hashDirectory, 0700) === false) {
-                        throw new Tinebase_Exception_UnexpectedValue('failed to create directory');
-                    } 
-                }
-                
-                $hashFile      = $hashDirectory . '/' . substr($hash, 3);
-                if (!file_exists($hashFile)) {
-                    Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' create hash file: ' . $hashFile);
-                    rewind($handle);
-                    $hashHandle = fopen($hashFile, 'x');
-                    stream_copy_to_stream($handle, $hashHandle);
-                    fclose($hashHandle);
-                }
+                list ($hash, $hashFile) = $this->createFileBlob($handle);
                 
                 $this->_updateFileObject($options['tine20']['node']->object_id, $hash, $hashFile);
                 
@@ -365,9 +344,11 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
      * @param string $_hashFile
      * @return Tinebase_Model_Tree_FileObject
      */
-    protected function _updateFileObject($_id, $_hash, $_hashFile)
+    protected function _updateFileObject($_id, $_hash, $_hashFile = null)
     {
-        $currentFileObject = $this->_fileObjectBackend->get($_id);
+        $currentFileObject = $_id instanceof Tinebase_Record_Abstract ? $_id : $this->_fileObjectBackend->get($_id);
+        
+        $_hashFile = $_hashFile ?: ($this->_basePath . '/' . substr($_hash, 0, 3) . '/' . substr($_hash, 3));
         
         $updatedFileObject = clone($currentFileObject);
         $updatedFileObject->hash = $_hash;
@@ -876,6 +857,45 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
     }
     
     /**
+     * places contents into a file blob
+     * 
+     * @param  stream|string|tempFile $contents
+     * @return string hash
+     */
+    public function createFileBlob($contents)
+    {
+        if (! is_resource($contents)) {
+            throw new Tinebase_Exception_NotImplemented('please implement me!');
+        }
+        
+        $handle = $contents;
+        rewind($handle);
+        
+        $ctx = hash_init('sha1');
+        hash_update_stream($ctx, $handle);
+        $hash = hash_final($ctx);
+        
+        $hashDirectory = $this->_basePath . '/' . substr($hash, 0, 3);
+        if (!file_exists($hashDirectory)) {
+            Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' create hash directory: ' . $hashDirectory);
+            if(mkdir($hashDirectory, 0700) === false) {
+                throw new Tinebase_Exception_UnexpectedValue('failed to create directory');
+            }
+        }
+        
+        $hashFile      = $hashDirectory . '/' . substr($hash, 3);
+        if (!file_exists($hashFile)) {
+            Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' create hash file: ' . $hashFile);
+            rewind($handle);
+            $hashHandle = fopen($hashFile, 'x');
+            stream_copy_to_stream($handle, $hashHandle);
+            fclose($hashHandle);
+        }
+        
+        return array($hash, $hashFile);
+    }
+    
+    /**
      * get tree node children
      * 
      * @param string|Tinebase_Model_Tree_Node|Tinebase_Record_RecordSet  $nodeId
@@ -1016,7 +1036,8 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
         // update file object
         $fileObject = $this->_fileObjectBackend->get($currentNodeObject->object_id);
         $fileObject->description = $_node->description;
-        $this->_fileObjectBackend->update($fileObject);
+        
+        $this->_updateFileObject($fileObject, $_node->hash);
         
         return $this->_treeNodeBackend->update($_node);
     }
@@ -1175,29 +1196,71 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
     /**
      * copy tempfile data to file path
      * 
-     * @param  string|Tinebase_Model_TempFile  $tempFile
-     * @param  string                          $path
+     * @param  mixed   $tempFile
+         Tinebase_Model_Tree_Node     with property tempfile or stream
+         Tinebase_Model_Tempfile      tempfile
+         string                       with tempFile id
+         array                        with [id] => tempFile id (this is odd IMHO)
+         stream                       stream ressource
+         NULL                         create empty file
+     * @param  string  $path
      * @throws Tinebase_Exception_AccessDenied
      */
     public function copyTempfile($tempFile, $path)
     {
+        if ($tempFile === NULL) {
+            $tempStream = fopen('php://memory', 'r');
+        }
+        
+        else if (is_resource($tempFile)) {
+            $tempStream = $tempFile;
+        }
+        
+        else if (is_string($tempFile) || is_array($tempFile)) {
+            $tempFile = Tinebase_TempFile::getInstance()->getTempFile($tempFile);
+            return $this->copyTempfile($tempFile, $path);
+        }
+        
+        else if ($tempFile instanceof Tinebase_Model_Tree_Node) {
+            if (is_resource($tempFile->stream)) {
+                $tempStream = $tempFile->stream;
+            } else {
+                return $this->copyTempfile($tempFile->tempFile, $path);
+            }
+        }
+        
+        else if ($tempFile instanceof Tinebase_Model_TempFile) {
+            $tempStream = fopen($tempFile->path, 'r');
+        }
+        
+        else {
+            throw new Tasks_Exception_UnexpectedValue('unexpected tempfile value');
+        }
+        
+        return $this->copyStream($tempStream, $path);
+    }
+    
+    /**
+     * copy stream data to file path
+     *
+     * @param  stream  $in
+     * @param  string  $path
+     * @throws Tinebase_Exception_AccessDenied
+     * @throws Tinebase_Exception_UnexpectedValue
+     */
+    public function copyStream($in, $path)
+    {
         if (! $handle = $this->fopen($path, 'w')) {
             throw new Tinebase_Exception_AccessDenied('Permission denied to create file (filename ' . $path . ')');
         }
         
-        if ($tempFile !== NULL) {
-            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
-                ' Reading data from tempfile ...');
+        if (! is_resource($in)) {
+            throw new Tinebase_Exception_UnexpectedValue('source needs to be of type stream');
+        }
         
-            $tempFile = ($tempFile instanceof Tinebase_Model_TempFile) ? $tempFile : Tinebase_TempFile::getInstance()->getTempFile($tempFile);
-            $tempData = fopen($tempFile->path, 'r');
-            if (!$tempData) {
-                throw new Tinebase_Exception_AccessDenied('Could not read tempfile ' . $tempFile->path);
-            }
-            
-            stream_copy_to_stream($tempData, $handle);
-            
-            fclose($tempData);
+        if (is_resource($in) !== NULL) {
+            rewind($in);
+            stream_copy_to_stream($in, $handle);
             
             $this->clearStatCache($path);
         }
index 0b3fec9..b90ef97 100644 (file)
@@ -139,19 +139,14 @@ class Tinebase_FileSystem_RecordAttachments
         $attachmentDiff = $currentAttachments->diff($attachmentsToSet);
         
         foreach ($attachmentDiff->added as $added) {
-            if (isset($added->tempFile)) {
-                $tempFile = ($added->tempFile instanceof Tinebase_Model_TempFile) 
-                    ? $added->tempFile : new Tinebase_Model_TempFile($added->tempFile, TRUE);
-                try {
-                    $this->_addAttachmentFromTempfile($record, $tempFile);
-                } catch (Tinebase_Exception_NotFound $tenf) {
-                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
+            try {
+                $this->addRecordAttachment($record, $added->name, $added);
+            } catch (Tinebase_Exception_NotFound $tenf) {
+                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
                         ' Record: ' . print_r($record->toArray(), TRUE));
-                    if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ .
+                if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ .
                         ' Could not add new attachment to record: ' . $tenf);
-                }
             }
-            
         }
         
         foreach ($attachmentDiff->removed as $removed) {
@@ -164,27 +159,38 @@ class Tinebase_FileSystem_RecordAttachments
     }
     
     /**
-     * add attachment from tempfile
+     * add attachement to record
      * 
-     * @param Tinebase_Record_Abstract $record
-     * @param Tinebase_Model_TempFile $tempFile
-     * @throws Tinebase_Exception_InvalidArgument
+     * @param  Tinebase_Record_Abstract $record
+     * @param  string $name
+     * @param  mixed $attachment
+         @see Tinebase_FileSystem::copyTempfile
+     * @return Tinebase_Model_Tree_Node
      */
-    protected function _addAttachmentFromTempfile(Tinebase_Record_Abstract $record, $tempFile)
+    public function addRecordAttachment(Tinebase_Record_Abstract $record, $name, $attachment)
     {
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
-            ' Creating new record attachment from tempfile');
+                ' Creating new record attachment');
+        
+        // only occurs via unittests
+        if (!$name && isset($attachment->tempFile)) {
+            $attachment = Tinebase_TempFile::getInstance()->getTempFile($attachment->tempFile);
+            $name = $attachment->name;
+        }
         
-        $tempFile = Tinebase_TempFile::getInstance()->get($tempFile->getId());
         $attachmentsDir = $this->getRecordAttachmentPath($record, TRUE);
-        $attachmentPath = $attachmentsDir . '/' . $tempFile->name;
+        $attachmentPath = $attachmentsDir . '/' . $name;
         if ($this->_fsController->fileExists($attachmentPath)) {
             throw new Tinebase_Exception_InvalidArgument('file already exists');
         }
         
-        $this->_fsController->copyTempfile($tempFile, $attachmentPath);
+        $this->_fsController->copyTempfile($attachment, $attachmentPath);
+        
+        $node = $this->_fsController->stat($attachmentPath);
+        return $node;
     }
     
+    
     /**
      * delete attachments of record
      * 
index 484bfb8..54cf93f 100644 (file)
@@ -112,6 +112,7 @@ class Tinebase_Model_Tree_Node extends Tinebase_Record_Abstract
         'path'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'account_grants' => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'tempFile'       => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'stream'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
     );
     
     /**