0013282: Tinebase_FileSystem - make it replicable
authorPaul Mehrer <p.mehrer@metaways.de>
Wed, 21 Jun 2017 15:13:08 +0000 (17:13 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Fri, 30 Jun 2017 16:04:39 +0000 (18:04 +0200)
https://forge.tine20.org/view.php?id=13282

Change-Id: I56aa4232d1323701f31e66e1abd2fb3acef4c9d5
Reviewed-on: http://gerrit.tine20.com/customers/4936
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
20 files changed:
tests/tine20/Filemanager/Frontend/JsonTests.php
tests/tine20/Tinebase/FileSystemTest.php
tests/tine20/Tinebase/Server/JsonTests.php
tests/tine20/Tinebase/Timemachine/ModificationLogTest.php
tine20/Filemanager/Controller/Node.php
tine20/Tinebase/Backend/Sql/Abstract.php
tine20/Tinebase/Controller/Record/Grants.php
tine20/Tinebase/Controller/Record/ModlogTrait.php
tine20/Tinebase/FileSystem.php
tine20/Tinebase/Frontend/Json.php
tine20/Tinebase/Model/Tree/FileObject.php
tine20/Tinebase/Model/Tree/Node.php
tine20/Tinebase/Model/Tree/Node/Filter.php
tine20/Tinebase/Record/Interface.php
tine20/Tinebase/Setup/Update/Release10.php
tine20/Tinebase/Setup/setup.xml
tine20/Tinebase/Timemachine/ModificationLog.php
tine20/Tinebase/Tree.php
tine20/Tinebase/Tree/FileObject.php
tine20/Tinebase/Tree/Node.php

index 075ccf3..6681d18 100644 (file)
@@ -69,6 +69,8 @@ class Filemanager_Frontend_JsonTests extends TestCase
      * @var array
      */
     protected $_rmDir = array();
+
+    protected $_oldModLog = null;
     
     /**
      * Sets up the fixture.
@@ -80,7 +82,10 @@ class Filemanager_Frontend_JsonTests extends TestCase
     {
         parent::setUp();
 
+        $this->_oldModLog = Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_MODLOGACTIVE};
+        Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_MODLOGACTIVE} = true;
         $this->_fsController = Tinebase_FileSystem::getInstance();
+        $this->_fsController->resetBackends();
         $this->_application = Tinebase_Application::getInstance()->getApplicationByName('Filemanager');
         $this->_rmDir = array();
 
@@ -103,7 +108,10 @@ class Filemanager_Frontend_JsonTests extends TestCase
                 $this->_getUit()->deleteNodes($dir);
             }
         }
-        
+
+        Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_MODLOGACTIVE} = $this->_oldModLog;
+
+        Tinebase_FileSystem::getInstance()->resetBackends();
         Tinebase_FileSystem::getInstance()->clearStatCache();
         Tinebase_FileSystem::getInstance()->clearDeletedFilesFromFilesystem();
         
@@ -943,13 +951,12 @@ class Filemanager_Frontend_JsonTests extends TestCase
      */
     public function testMoveFolderNodesToFolderExisting()
     {
-        sleep(1);
         $targetNode = $this->testCreateContainerNodeInPersonalFolder();
         $testPath = '/' . Tinebase_FileSystem::FOLDER_TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName . '/dir1';
-        $result = $this->_getUit()->moveNodes(array($targetNode['path']), array($testPath), false);
-        $dirs = $this->testCreateDirectoryNodesInShared();
+        $this->_getUit()->moveNodes(array($targetNode['path']), array($testPath), false);
+        $this->testCreateDirectoryNodesInShared();
         try {
-            $result = $this->_getUit()->moveNodes(array($testPath), '/shared/testcontainer', false);
+            $this->_getUit()->moveNodes(array($testPath), '/shared/testcontainer', false);
             $this->fail('Expected Filemanager_Exception_NodeExists!');
         } catch (Filemanager_Exception_NodeExists $fene) {
             $result = $this->_getUit()->moveNodes(array($testPath), '/shared/testcontainer', true);
@@ -972,7 +979,7 @@ class Filemanager_Frontend_JsonTests extends TestCase
         $createdNode = $result[0];
 
         try {
-            $result = $this->_getUit()->moveNodes(array($targetNode['path']), array($createdNode['path']), false);
+            $this->_getUit()->moveNodes(array($targetNode['path']), array($createdNode['path']), false);
             $this->fail('Expected Filemanager_Exception_NodeExists!');
         } catch (Filemanager_Exception_NodeExists $fene) {
             $result = $this->_getUit()->moveNodes(array($targetNode['path']), array($createdNode['path']), true);
@@ -1387,7 +1394,13 @@ class Filemanager_Frontend_JsonTests extends TestCase
      */
     public function testDeletedFileCleanupFromFilesystem()
     {
+        Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_MODLOGACTIVE} = false;
+        $this->_fsController->resetBackends();
+
         // remove all files with size 0 first
+        // better here than below?
+        $this->testDeleteFileNodes();
+
         $size0Nodes = Tinebase_FileSystem::getInstance()->searchNodes(new Tinebase_Model_Tree_Node_Filter(array(
             array('field' => 'type', 'operator' => 'equals', 'value' => Tinebase_Model_Tree_FileObject::TYPE_FILE),
             array('field' => 'size', 'operator' => 'equals', 'value' => 0)
@@ -1395,13 +1408,16 @@ class Filemanager_Frontend_JsonTests extends TestCase
         foreach ($size0Nodes as $node) {
             Tinebase_FileSystem::getInstance()->deleteFileNode($node);
         }
-        
-        $this->testDeleteFileNodes();
+
+        // why here?
+        //$this->testDeleteFileNodes();
         $result = Tinebase_FileSystem::getInstance()->clearDeletedFilesFromFilesystem();
         $this->assertEquals(0, $result, 'should not clean up anything as files with size 0 are not written to disk');
         $this->tearDown();
         
         Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
+        Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_MODLOGACTIVE} = false;
+        $this->_fsController->resetBackends();
         $this->testDeleteFileNodes(true);
         $result = Tinebase_FileSystem::getInstance()->clearDeletedFilesFromFilesystem();
         $this->assertEquals(1, $result, 'should cleanup one file');
index 9ae3a52..2299bd7 100644 (file)
@@ -49,6 +49,7 @@ class Tinebase_FileSystemTest extends TestCase
         $this->_rmDir = array();
         $this->_oldNotification = Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_ENABLE_NOTIFICATIONS};
         $this->_oldModLog = Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_MODLOGACTIVE};
+        Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_MODLOGACTIVE} = true;
         $this->_oldIndexContent = Tinebase_Core::getConfig()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_INDEX_CONTENT};
         $this->_oldCreatePreview = Tinebase_Config::getInstance()->{Tinebase_Config::FILESYSTEM}->{Tinebase_Config::FILESYSTEM_CREATE_PREVIEWS};
 
@@ -213,6 +214,18 @@ class Tinebase_FileSystemTest extends TestCase
     
         $this->assertTrue($result,                                    'wrong result for rmdir command');
         $this->assertFalse($this->_controller->fileExists($testPath), 'failed to delete directory');
+
+        return $testPath;
+    }
+
+    public function testRecreateDir()
+    {
+        $testPath = $this->testRmdir();
+
+        $node = $this->_controller->mkdir($testPath);
+        $this->assertTrue($this->_controller->isDir($testPath),      'path created by mkdir is not a directory');
+
+        $this->assertEquals(1, $node->revision);
     }
     
     public function testScandir()
@@ -454,6 +467,42 @@ class Tinebase_FileSystemTest extends TestCase
         
         $this->assertTrue(!in_array('phpunit.txt', $children));
     }
+
+    public function testRecreateFile()
+    {
+        $this->testDeleteFile();
+
+        $path = $this->_basePath . '/PHPUNIT/phpunit.txt';
+
+        $handle = $this->_controller->fopen($path, 'w');
+        $this->assertEquals('resource', gettype($handle), 'opening file failed');
+        $written = fwrite($handle, 'somethingNew');
+        $this->assertEquals(12, $written);
+        $this->_controller->fclose($handle);
+
+        $children = $this->_controller->scanDir($this->_basePath . '/PHPUNIT')->name;
+
+        $this->assertContains('phpunit.txt', $children);
+        $handle = $this->_controller->fopen($path, 'r');
+        $contents = stream_get_contents($handle);
+        $this->_controller->fclose($handle);
+        $this->assertEquals('somethingNew', $contents);
+    }
+
+    public function testCopyRecreateFile()
+    {
+        $destinationPath = $this->_basePath . '/TESTCOPY2/phpunit.txt';
+        $this->_controller->mkdir($this->_basePath . '/TESTCOPY2');
+        $handle = $this->_controller->fopen($destinationPath, 'w');
+        $this->assertEquals('resource', gettype($handle), 'opening file failed');
+        $written = fwrite($handle, 'somethingNew');
+        $this->assertEquals(12, $written);
+        $this->_controller->fclose($handle);
+        $node = $this->_controller->stat($destinationPath);
+        $this->_controller->deleteFileNode($node);
+
+        $this->testCopyFileToExistingDirectory();
+    }
     
     public function testGetFileSize()
     {
@@ -754,7 +803,7 @@ class Tinebase_FileSystemTest extends TestCase
         // mail foo?
         // check mail
         $messages = $mailer->getMessages();
-        $this->assertEquals(2, count($messages));
+        $this->assertEquals(1, count($messages));
         $headers = $messages[0]->getHeaders();
         $this->assertEquals('filemanager notification', $headers['Subject'][0]);
         $this->assertTrue(strpos($headers['To'][0], Tinebase_Core::getUser()->accountEmailAddress) !== false);
index f8e02e1..9ec7b0e 100644 (file)
@@ -101,7 +101,8 @@ class Tinebase_Server_JsonTests extends TestCase
 
         $smd = Tinebase_Server_Json::getServiceMap();
         $smdArray = $smd->toArray();
-        $this->assertTrue(isset($smdArray['services']['Tinebase.ping']));
+        $this->assertTrue(isset($smdArray['services']['Tinebase.ping']), 'Tinebase.ping missing from service map: '
+            . print_r($smdArray, true));
     }
 
     /**
index 93bcb05..222bfdc 100644 (file)
@@ -40,6 +40,8 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
      */
     protected $_recordIds = array();
 
+    protected $_oldFileSystemConfig = null;
+
 
     /**
      * Runs the test methods of this class.
@@ -62,6 +64,8 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
     {
         Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
 
+        $this->_oldFileSystemConfig = clone Tinebase_Config::getInstance()->{Tinebase_Config::FILESYSTEM};
+
         $now = new Tinebase_DateTime();
         $this->_modLogClass = Tinebase_Timemachine_ModificationLog::getInstance();
         $this->_persistantLogEntries = new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog');
@@ -155,6 +159,8 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
      */
     protected function tearDown()
     {
+        Tinebase_Config::getInstance()->{Tinebase_Config::FILESYSTEM} = $this->_oldFileSystemConfig;
+
         Tinebase_TransactionManager::getInstance()->rollBack();
     }
 
@@ -734,15 +740,22 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
 
     public function testFileManagerReplication()
     {
-        $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getReplicationModificationsByInstanceSeq(-1, 10000);
+        Tinebase_Config::getInstance()->{Tinebase_Config::FILESYSTEM}
+            ->{Tinebase_Config::FILESYSTEM_MODLOGACTIVE} = true;
+        $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->
+            getReplicationModificationsByInstanceSeq(-1, 10000);
         $instance_seq = $modifications->getLastRecord()->instance_seq;
 
-        $testPath = '/' . Tinebase_Model_Container::TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName . '/unittestTestPath';
+        $testPath = '/' . Tinebase_Model_Container::TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName
+            . '/unittestTestPath';
         $fmController = Filemanager_Controller_Node::getInstance();
         $filesystem = Tinebase_FileSystem::getInstance();
+        $filesystem->resetBackends();
 
         // create two folders
-        $fmController->createNodes(array($testPath, $testPath . '/subfolder'), Tinebase_Model_Tree_FileObject::TYPE_FOLDER);
+        $fmController->createNodes(array($testPath, $testPath . '/subfolder'),
+            Tinebase_Model_Tree_FileObject::TYPE_FOLDER);
+
 
         // set Grants
         $testPathNode = $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/subfolder'))->statpath);
@@ -751,16 +764,34 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
         $grantRecord->id = null;
         $testPathNode->grants = new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array($grantRecord));
         $testPathNode->acl_node = $testPathNode->getId();
-        $fmController->update($testPathNode);
+        $testNodeGrants = $fmController->update($testPathNode);
+        Tinebase_Tree_NodeGrants::getInstance()->getGrantsForRecord($testNodeGrants);
 
         // unset Grants
         $testPathNode->acl_node = null;
         $fmController->update($testPathNode);
 
         // move subfolder to new name
-        /*$newSubFolderNode = */$fmController->moveNodes(array($testPath . '/subfolder'), array($testPath . '/newsubfolder'))->getFirstRecord();
+        $fmController->moveNodes(array($testPath . '/subfolder'), array($testPath . '/newsubfolder'))->getFirstRecord();
         // copy it back to old name
-        /*$subFolderNode = */$fmController->copyNodes(array($testPath . '/newsubfolder'), array($testPath . '/subfolder'))->getFirstRecord();
+        $fmController->copyNodes(array($testPath . '/newsubfolder'), array($testPath . '/subfolder'))->getFirstRecord();
+
+        // create file
+        $tempPath = Tinebase_TempFile::getTempPath();
+        $tempFileId = Tinebase_TempFile::getInstance()->createTempFile($tempPath);
+        file_put_contents($tempPath, 'someData');
+        $fmController->createNodes(array($testPath . '/newsubfolder/testFile'),
+            Tinebase_Model_Tree_FileObject::TYPE_FILE, array($tempFileId));
+
+        // delete file
+        $fmController->deleteNodes(array($testPath . '/newsubfolder/testFile'));
+
+        // recreate file
+        $tempPath = Tinebase_TempFile::getTempPath();
+        $tempFileId = Tinebase_TempFile::getInstance()->createTempFile($tempPath);
+        file_put_contents($tempPath, 'otherData');
+        $fmController->createNodes(array($testPath . '/newsubfolder/testFile'),
+            Tinebase_Model_Tree_FileObject::TYPE_FILE, array($tempFileId));
 
         //this is not supported for folders!
         //$fmController->delete($subFolderNode->getId());
@@ -770,7 +801,10 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
 
         $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getReplicationModificationsByInstanceSeq($instance_seq);
         $fmModifications = $modifications->filter('record_type', 'Filemanager_Model_Node');
-        $this->assertEquals($modifications->count(), $fmModifications->count(), 'other changes thatn to Filemanager_Model_Node detected');
+        $fnModifications = $modifications->filter('record_type', 'Tinebase_Model_Tree_Node');
+        $foModifications = $modifications->filter('record_type', 'Tinebase_Model_Tree_FileObject');
+        $this->assertEquals($modifications->count(), $fmModifications->count() + $fnModifications->count() +
+            $foModifications->count(), 'other changes than to Tinebase_Model_Tree_Node or Filemanager_Model_Node detected');
 
         // rollback
         Tinebase_TransactionManager::getInstance()->rollBack();
@@ -787,43 +821,90 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
         $this->assertTrue($notFound, 'roll back did not work...');
 
         // create first folder
-        $mod = $fmModifications->getFirstRecord();
-        $fmModifications->removeRecord($mod);
+        // create FileObject
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        // create FileNode
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        // update hash of FileObject
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        // update acl_node of FileNode
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
         $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
         $this->assertTrue($result, 'applyReplactionModLogs failed');
         $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath))->statpath);
 
         // create second folder
-        $mod = $fmModifications->getFirstRecord();
-        $fmModifications->removeRecord($mod);
+        // create FileObject
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        // create FileNode
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
         $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
         $this->assertTrue($result, 'applyReplactionModLogs failed');
         $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/subfolder'))->statpath);
 
         // set grants
-        $mod = $fmModifications->getFirstRecord();
-        $fmModifications->removeRecord($mod);
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
         $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
         $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $filesystem->clearStatCache();
         $testPathNode = $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/subfolder'))->statpath);
         static::assertEquals($testPathNode->getId(), $testPathNode->acl_node, 'grants not set');
+        Tinebase_Tree_NodeGrants::getInstance()->getGrantsForRecord($testPathNode);
+        static::assertEquals($testNodeGrants->grants, $testPathNode->grants);
 
         // unset grants
-        $mod = $fmModifications->getFirstRecord();
-        $fmModifications->removeRecord($mod);
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
         $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
         $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $filesystem->clearStatCache();
         $testPathNode = $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/subfolder'))->statpath);
         static::assertNotEquals($testPathNode->getId(), $testPathNode->acl_node, 'grants still set');
 
         // move subfolder to new name
-        $mod = $fmModifications->getFirstRecord();
-        $fmModifications->removeRecord($mod);
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
         $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
         $this->assertTrue($result, 'applyReplactionModLogs failed');
         $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/newsubfolder'))->statpath);
         $notFound = false;
         try {
+            $filesystem->clearStatCache();
             $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/subfolder'))->statpath);
         } catch (Tinebase_Exception_NotFound $tenf) {
             $notFound = true;
@@ -831,16 +912,74 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
         $this->assertTrue($notFound, 'move did not work...');
 
         // copy it back to old name
-        $mod = $fmModifications->getFirstRecord();
-        $fmModifications->removeRecord($mod);
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
         $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
         $this->assertTrue($result, 'applyReplactionModLogs failed');
         $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/newsubfolder'))->statpath);
         $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/subfolder'))->statpath);
 
+        // create file
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $path = Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/newsubfolder/testFile'));
+        $node = $filesystem->stat($path->statpath);
+        static::assertEquals('someData', $filesystem->getNodeContents($node->getId()));
+
+        // delete file
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $path = Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/newsubfolder/testFile'));
+        static::assertFalse($filesystem->fileExists($path->statpath));
+
+        // recreate file
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $path = Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/newsubfolder/testFile'));
+        $node = $filesystem->stat($path->statpath);
+        static::assertEquals('otherData', $filesystem->getNodeContents($node->getId()));
+
         // delete new subfolder
-        $mod = $fmModifications->getFirstRecord();
-        $fmModifications->removeRecord($mod);
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
         $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
         $this->assertTrue($result, 'applyReplactionModLogs failed');
         $filesystem->stat(Tinebase_Model_Tree_Node_Path::createFromPath($fmController->addBasePath($testPath . '/subfolder'))->statpath);
@@ -853,8 +992,20 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
         $this->assertTrue($notFound, 'delete did not work...');
 
         // delete new folder
-        $mod = $fmModifications->getFirstRecord();
-        $fmModifications->removeRecord($mod);
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $mod = $modifications->getFirstRecord();
+        $modifications->removeRecord($mod);
         $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
         $this->assertTrue($result, 'applyReplactionModLogs failed');
         $notFound = false;
@@ -871,5 +1022,7 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
             $notFound = true;
         }
         $this->assertTrue($notFound, 'delete did not work...');
+        
+        $this->assertEquals(0, $modifications->count(), 'not all modifications processed');
     }
 }
index 78039e1..d874748 100644 (file)
@@ -38,15 +38,11 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
      * @var string
      */
     protected $_modelName = 'Filemanager_Model_Node';
-    
+
     /**
-     * TODO handle modlog
-     *
-     * attention, this NEEDS to be off / true for replication!
-     *
      * @var boolean
      */
-    protected $_omitModLog = true;
+    protected $_omitModLog = false;
     
     /**
      * holds the total count of the last recursive search
@@ -173,59 +169,19 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
             $_record->{Tinebase_Model_Tree_Node::XPROPS_REVISION} = $_oldRecord->{Tinebase_Model_Tree_Node::XPROPS_REVISION};
         }
 
-        $nodePath = null;
-        if (Tinebase_Model_Tree_FileObject::TYPE_FOLDER === $_record->type) {
-            $nodePath = Tinebase_Model_Tree_Node_Path::createFromStatPath($this->_backend->getPathOfNode($_record->getId(), true));
-            $modlogNode = new Filemanager_Model_Node(array(
-                'id' => $_record->getId(),
-                'path' => $nodePath->statpath,
-                'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER,
-                Tinebase_Model_Tree_Node::XPROPS_NOTIFICATION => $_record->xprops(Tinebase_Model_Tree_Node::XPROPS_NOTIFICATION),
-                // do not set acl_node, this will be calculated on the client side
-            ), true);
-            if (isset($_record->xprops(Tinebase_Model_Tree_Node::XPROPS_REVISION)[Tinebase_Model_Tree_Node::XPROPS_REVISION_NODE_ID]) &&
-                    $_record->xprops(Tinebase_Model_Tree_Node::XPROPS_REVISION)[Tinebase_Model_Tree_Node::XPROPS_REVISION_NODE_ID] === $_record->getId() &&
-                    $_record->xprops(Tinebase_Model_Tree_Node::XPROPS_REVISION) != $_oldRecord->xprops(Tinebase_Model_Tree_Node::XPROPS_REVISION)) {
-                $revisions = $_record->xprops(Tinebase_Model_Tree_Node::XPROPS_REVISION);
-                unset($revisions[Tinebase_Model_Tree_Node::XPROPS_REVISION_NODE_ID]);
-                $modlogNode->{Tinebase_Model_Tree_Node::XPROPS_REVISION} = $revisions;
-            }
-            $modlogOldNode = new Filemanager_Model_Node(array(
-                'id' => $_record->getId(),
-                'path' => $nodePath->statpath,
-                'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER,
-                Tinebase_Model_Tree_Node::XPROPS_NOTIFICATION => $_oldRecord->xprops(Tinebase_Model_Tree_Node::XPROPS_NOTIFICATION),
-            ), true);
-            $this->_omitModLog = false;
-            $this->_writeModLog($modlogNode, $modlogOldNode);
-            $this->_omitModLog = true;
-        }
-
         // update node acl
         $aclNode = $_oldRecord->acl_node;
         if (Tinebase_Model_Tree_FileObject::TYPE_FOLDER === $_record->type
             && Tinebase_Core::getUser()->hasGrant($_record, Tinebase_Model_Grants::GRANT_ADMIN, 'Tinebase_Model_Tree_Node')
         ) {
+            $nodePath = Tinebase_Model_Tree_Node_Path::createFromStatPath($this->_backend->getPathOfNode($_record->getId(), true));
             if (! $nodePath->isSystemPath()) {
 
-                $modlogOldNode = $modlogNode = null;
                 if ($_record->acl_node === null && ! $nodePath->isToplevelPath()) {
                     // acl_node === null -> remove acl
                     $node = $this->_backend->setAclFromParent($nodePath->statpath);
                     $aclNode = $node->acl_node;
 
-                    $modlogNode = new Filemanager_Model_Node(array(
-                        'id' => $_record->getId(),
-                        'path' => $nodePath->statpath,
-                        'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER,
-                        'grants' => 'unset'
-                        // do not set acl_node, this will be calculated on the client side
-                    ), true);
-                    $modlogOldNode = new Filemanager_Model_Node(array(
-                        'id' => $_record->getId(),
-                        'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER
-                    ), true);
-
                 } elseif ($_record->acl_node === $_record->getId() && isset($_record->grants)) {
                     $oldGrants = Tinebase_Tree_NodeGrants::getInstance()->getGrantsForRecord($_oldRecord);
                     if (is_array($_record->grants)) {
@@ -234,26 +190,9 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
                     $diff = $_record->grants->diff($oldGrants);
                     if (!$diff->isEmpty() || $_oldRecord->acl_node !== $_record->acl_node) {
                         $this->_backend->setGrantsForNode($_record, $_record->grants);
-                        $modlogNode = new Filemanager_Model_Node(array(
-                            'id' => $_record->getId(),
-                            'path' => $nodePath->statpath,
-                            'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER,
-                            'grants' => $_record->grants
-                            // do not set acl_node, this will be calculated on the client side
-                        ), true);
-                        $modlogOldNode = new Filemanager_Model_Node(array(
-                            'id' => $_record->getId(),
-                            'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER
-                        ), true);
                     }
                     $aclNode = $_record->acl_node;
                 }
-
-                if (null !== $modlogNode) {
-                    $this->_omitModLog = false;
-                    $this->_writeModLog($modlogNode, $modlogOldNode);
-                    $this->_omitModLog = true;
-                }
             }
         }
         // reset node acl value to prevent spoofing
@@ -744,33 +683,11 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
 
         $newNodePath = $parentPathRecord->statpath . '/' . $path->name;
         $newNode = $this->_createNodeInBackend($newNodePath, $_type, $_tempFileId);
-        $this->_writeModlogForNewNode($newNode, /*$existingNode,*/ $_type, $_path);
 
         $this->resolvePath($newNode, $parentPathRecord);
         $this->resolveGrants($newNode);
         return $newNode;
     }
-
-    /**
-     * @param Tinebase_Model_Tree_Node $_newNode
-     * @param string $_type
-     * @param string $_path
-     */
-    protected function _writeModlogForNewNode($_newNode, /*$_existingNode,*/ $_type, $_path)
-    {
-        if (Tinebase_Model_Tree_FileObject::TYPE_FOLDER === $_type && false === $this->_inCopyOrMoveNode) {
-            $modlogNode = new Filemanager_Model_Node(array(
-                'id' => $_newNode->getId(),
-                'path' => ($_path instanceof Tinebase_Model_Tree_Node_Path) ? $this->removeBasePath($_path->flatpath) : $_path,
-                'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER,
-                // no grants, no acl_node, it will all be handled on client side
-                //'grants' => Tinebase_Tree_NodeGrants::getInstance()->getGrantsForRecord($_newNode)
-            ), true);
-            $this->_omitModLog = false;
-            $this->_writeModLog($modlogNode, null);
-            $this->_omitModLog = true;
-        }
-    }
     
     /**
      * create node in backend
@@ -1015,24 +932,6 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
 
                 if ($node instanceof Tinebase_Record_Abstract) {
                     $result->addRecord($node);
-
-                    if (Tinebase_Model_Tree_FileObject::TYPE_FOLDER === $node->type) {
-                        $modlogNode = new Filemanager_Model_Node(array(
-                            'id' => $node->getId(),
-                            'path' => is_array($_destinationFilenames) ? array($_destinationFilenames[$idx]) : $_destinationFilenames,
-                            'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER,
-                            'name' => $_action,
-                            // do not set acl_node, this will be calculated on the client side
-                        ), true);
-                        $modlogOldNode = new Filemanager_Model_Node(array(
-                            'id' => $node->getId(),
-                            'path' => $_sourceFilenames[$idx],
-                        ), true);
-                        $this->_omitModLog = false;
-                        $this->_writeModLog($modlogNode, $modlogOldNode);
-                        $this->_omitModLog = true;
-                    }
-
                 } else {
                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
                         . ' Could not copy or move node to destination ' . $destinationPathRecord->flatpath);
@@ -1340,17 +1239,6 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
                 break;
             case Tinebase_Model_Tree_FileObject::TYPE_FOLDER:
                 $success = $this->_backend->rmdir($_path->statpath, TRUE);
-
-                if (FALSE !== $success && false === $this->_inCopyOrMoveNode) {
-                    $modlogNode = new Filemanager_Model_Node(array(
-                        'id' => $node->getId(),
-                        'path' => $_flatpath,
-                        'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER
-                    ), true);
-                    $this->_omitModLog = false;
-                    $this->_writeModLog(null, $modlogNode);
-                    $this->_omitModLog = true;
-                }
                 break;
         }
         
@@ -1506,36 +1394,7 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
      */
     public function applyReplicationModificationLog(Tinebase_Model_ModificationLog $modification)
     {
-        switch ($modification->change_type) {
-            case Tinebase_Timemachine_ModificationLog::CREATED:
-                $diff = new Tinebase_Record_Diff(json_decode($modification->new_value, true));
-                $this->createNodes(array($diff->diff['path']), $diff->diff['type']);
-                break;
-
-            case Tinebase_Timemachine_ModificationLog::UPDATED:
-                $diff = new Tinebase_Record_Diff(json_decode($modification->new_value, true));
-                if (isset($diff->diff['name'])) {
-                    $this->_copyOrMoveNodes(array($diff->oldData['path']), $diff->diff['path'], $diff->diff['name']);
-                } elseif(isset($diff->diff['grants'])) {
-                    $record = $this->_backend->stat($diff->diff['path']);
-                    if ('unset' === $diff->diff['grants']) {
-                        $this->_backend->removeAclFromNode($record);
-                    } else {
-                        $this->_backend->setGrantsForNode($record, $diff->diff['grants']);
-                    }
-                } else {
-                    throw new Tinebase_Exception_InvalidArgument('update modlogs need the property name containing copy or move or grants for grants update');
-                }
-                break;
-
-            case Tinebase_Timemachine_ModificationLog::DELETED:
-                $diff = new Tinebase_Record_Diff(json_decode($modification->new_value, true));
-                $this->deleteNodes(array($diff->oldData['path']));
-                break;
-
-            default:
-                throw new Tinebase_Exception('unknown Tinebase_Model_ModificationLog->old_value: ' . $modification->old_value);
-        }
+        Tinebase_Tree::getInstance()->applyReplicationModificationLog($modification);
     }
 
     /**
index e88ed5b..393c334 100644 (file)
@@ -1359,12 +1359,21 @@ abstract class Tinebase_Backend_Sql_Abstract extends Tinebase_Backend_Abstract i
         $idArray = (! is_array($_id)) ? array(Tinebase_Record_Abstract::convertId($_id, $this->_modelName)) : $_id;
         $identifier = $this->_getRecordIdentifier();
 
+        $this->_inspectBeforeSoftDelete($idArray);
+
         $where = array(
             $this->_db->quoteInto($this->_db->quoteIdentifier($identifier) . ' IN (?)', $idArray)
         );
 
         return $this->_db->update($this->_tablePrefix . $this->_tableName, array('is_deleted' => 1), $where);
     }
+
+    /**
+     * @param array $_ids
+     */
+    protected function _inspectBeforeSoftDelete(array $_ids)
+    {
+    }
     
     /**
       * Deletes entries
index 51efa4a..ef9d687 100644 (file)
@@ -251,7 +251,15 @@ abstract class Tinebase_Controller_Record_Grants extends Tinebase_Controller_Rec
         $grant->sanitizeAccountIdAndFillWithAllGrants();
         $record->grants->addRecord($grant);
     }
-    
+
+    /**
+     * @param string $recordId
+     */
+    public function deleteGrantsOfRecord($recordId)
+    {
+        $this->_grantsBackend->deleteByProperty($recordId, 'record_id');
+    }
+
     /**
      * this function creates a new record with default grants during inital setup
      * 
index 0e6bb9f..ae53fc3 100644 (file)
@@ -48,8 +48,8 @@ trait Tinebase_Controller_Record_ModlogTrait
     /**
      * write modlog
      *
-     * @param Tinebase_Record_Interface $_newRecord
-     * @param Tinebase_Record_Interface $_oldRecord
+     * @param Tinebase_Record_Interface|null $_newRecord
+     * @param Tinebase_Record_Interface|null $_oldRecord
      * @return NULL|Tinebase_Record_RecordSet
      * @throws Tinebase_Exception_InvalidArgument
      */
index 9e27034..7e158d3 100644 (file)
@@ -159,6 +159,14 @@ class Tinebase_FileSystem implements
         ));
     }
 
+    /**
+     * @return Tinebase_Tree_FileObject
+     */
+    public function getFileObjectBackend()
+    {
+        return $this->_fileObjectBackend;
+    }
+
     public function setStreamOptionForNextOperation($_key, $_value)
     {
         $this->_streamOptionsForNextOperation[$_key] = $_value;
@@ -243,12 +251,13 @@ class Tinebase_FileSystem implements
         return $node;
     }
 
-    protected function _getTreeNodeBackend()
+    public function _getTreeNodeBackend()
     {
         if ($this->_treeNodeBackend === null) {
             $this->_treeNodeBackend    = new Tinebase_Tree_Node(null, /* options */ array(
                 'modelName' => $this->_treeNodeModel,
-                Tinebase_Config::FILESYSTEM_ENABLE_NOTIFICATIONS => $this->_notificationActive
+                Tinebase_Config::FILESYSTEM_ENABLE_NOTIFICATIONS => $this->_notificationActive,
+                Tinebase_Config::FILESYSTEM_MODLOGACTIVE => $this->_modLogActive,
             ));
         }
 
@@ -375,6 +384,7 @@ class Tinebase_FileSystem implements
             $parentNode = $this->get($node->parent_id);
             $node->acl_node = $parentNode->acl_node;
             $this->update($node);
+            $this->_nodeAclController->deleteGrantsOfRecord($node->getId());
 
             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
             $transactionId = null;
@@ -467,6 +477,11 @@ class Tinebase_FileSystem implements
                 throw new Tinebase_Exception_UnexpectedValue("Source path and destination path must be different.");
             }
 
+            if (null !== ($deletedNode = $this->_getTreeNodeBackend()
+                    ->getChild($parentNode, $destinationNodeName, true, false)) && $deletedNode->is_deleted) {
+                $this->_updateDeletedNodeName($deletedNode);
+            }
+
             // set new node properties
             $destinationNode->setId(null);
             $destinationNode->parent_id = $parentNode->getId();
@@ -701,7 +716,7 @@ class Tinebase_FileSystem implements
                         . ' revision_size should not become smaller than 0: ' . $fileObject->size . ' for object id: ' . $fileObject->getId());
                     $fileObject->revision_size = 0;
                 }
-                $this->_fileObjectBackend->update($fileObject);
+                $this->_fileObjectBackend->update($fileObject, false);
             }
         }
     }
@@ -1052,6 +1067,11 @@ class Tinebase_FileSystem implements
                 $node->name = basename($newPath);
             }
 
+            try {
+                $deletedNewPathNode = $this->stat($newPath, null, true);
+                $this->_updateDeletedNodeName($deletedNewPathNode);
+            } catch (Tinebase_Exception_NotFound $tenf) {}
+
             $node = $this->_getTreeNodeBackend()->update($node, true);
 
             $transactionManager->commitTransaction($transactionId);
@@ -1069,6 +1089,21 @@ class Tinebase_FileSystem implements
             }
         }
     }
+
+    protected function _updateDeletedNodeName(Tinebase_Model_Tree_Node $_node)
+    {
+        $treeNodeBackend = $this->_getTreeNodeBackend();
+        $parentId = $_node->parent_id;
+        do {
+            $id = uniqid();
+            $name = $_node->name . $id;
+            if (($len = mb_strlen($name)) > 255) {
+                $name = mb_substr($name, $len - 255);
+            }
+        } while (null !== $treeNodeBackend->getChild($parentId, $name, true, false));
+        $_node->name = $name;
+        $this->_getTreeNodeBackend()->update($_node, true);
+    }
     
     /**
      * create directory
@@ -1156,7 +1191,7 @@ class Tinebase_FileSystem implements
                 $this->_updateFolderSizesUpToRoot(new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node', array($node)),
                     0 - (int)$node->size, 0 - (int)$node->revision_size);
             }
-            $this->_getTreeNodeBackend()->delete($node->getId());
+            $this->_getTreeNodeBackend()->softDelete($node->getId());
             $this->clearStatCache($path);
 
             // delete object only, if no other tree node refers to it
@@ -1203,10 +1238,11 @@ class Tinebase_FileSystem implements
      *
      * @param  string  $path
      * @param  int|null $revision
+     * @param  boolean $getDeleted
      * @return Tinebase_Model_Tree_Node
      * @throws Tinebase_Exception_NotFound
      */
-    public function stat($path, $revision = null)
+    public function stat($path, $revision = null, $getDeleted = false)
     {
         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
 
@@ -1252,7 +1288,11 @@ class Tinebase_FileSystem implements
             $missingPathParts = array_diff_assoc($this->_splitPath($path), $pathParts);
 
             foreach ($missingPathParts as $pathPart) {
-                $node = $this->_getTreeNodeBackend()->getChild($parentNode, $pathPart);
+                $node = $this->_getTreeNodeBackend()->getChild($parentNode, $pathPart, $getDeleted);
+
+                if ($node->is_deleted && null !== $parentNode && $parentNode->is_deleted) {
+                    throw new Tinebase_Exception_NotFound('cascading deleted nodes');
+                }
 
                 // keep track of current path position
                 array_push($pathParts, $pathPart);
@@ -1263,6 +1303,8 @@ class Tinebase_FileSystem implements
                 $parentNode = $node;
             }
 
+
+
             if (null !== $revision) {
                 try {
                     $this->_getTreeNodeBackend()->setRevision($revision);
@@ -1420,28 +1462,43 @@ class Tinebase_FileSystem implements
         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
         try {
             $parentId = $_parentId instanceof Tinebase_Model_Tree_Node ? $_parentId->getId() : $_parentId;
-            $parentNode = $_parentId instanceof Tinebase_Model_Tree_Node
-                ? $_parentId
-                : ($_parentId ? $this->get($_parentId) : null);
-
-            $directoryObject = new Tinebase_Model_Tree_FileObject(array(
-                'type'          => Tinebase_Model_Tree_FileObject::TYPE_FOLDER,
-                'contentytype'  => null,
-                'hash'          => Tinebase_Record_Abstract::generateUID(),
-                'size'          => 0
-            ));
-            Tinebase_Timemachine_ModificationLog::setRecordMetaData($directoryObject, 'create');
-            $directoryObject = $this->_fileObjectBackend->create($directoryObject);
-
-            $treeNode = new Tinebase_Model_Tree_Node(array(
-                'name'          => $name,
-                'object_id'     => $directoryObject->getId(),
-                'parent_id'     => $parentId,
-                'acl_node'      => $parentNode && !empty($parentNode->acl_node) ? $parentNode->acl_node : null,
-                Tinebase_Model_Tree_Node::XPROPS_REVISION
-                                => $parentNode && !empty($parentNode->{Tinebase_Model_Tree_Node::XPROPS_REVISION}) ? $parentNode->{Tinebase_Model_Tree_Node::XPROPS_REVISION} : null
-            ));
-            $treeNode = $this->_getTreeNodeBackend()->create($treeNode);
+
+            if (null !== ($deletedNode = $this->_getTreeNodeBackend()->getChild($parentId, $name, true, false)) &&
+                    $deletedNode->is_deleted) {
+                $deletedNode->is_deleted = 0;
+                $object = $this->_fileObjectBackend->get($deletedNode->object_id, true);
+                if ($object->is_deleted) {
+                    $object->is_deleted = 0;
+                    $this->_fileObjectBackend->update($object);
+                }
+                //we can use _treeNodeBackend as we called get further up
+                $treeNode = $this->_treeNodeBackend->update($deletedNode);
+            } else {
+
+                $parentNode = $_parentId instanceof Tinebase_Model_Tree_Node
+                    ? $_parentId
+                    : ($_parentId ? $this->get($_parentId) : null);
+
+                $directoryObject = new Tinebase_Model_Tree_FileObject(array(
+                    'type' => Tinebase_Model_Tree_FileObject::TYPE_FOLDER,
+                    'contentytype' => null,
+                    'hash' => Tinebase_Record_Abstract::generateUID(),
+                    'size' => 0
+                ));
+                Tinebase_Timemachine_ModificationLog::setRecordMetaData($directoryObject, 'create');
+                $directoryObject = $this->_fileObjectBackend->create($directoryObject);
+
+                $treeNode = new Tinebase_Model_Tree_Node(array(
+                    'name' => $name,
+                    'object_id' => $directoryObject->getId(),
+                    'parent_id' => $parentId,
+                    'acl_node' => $parentNode && !empty($parentNode->acl_node) ? $parentNode->acl_node : null,
+                    Tinebase_Model_Tree_Node::XPROPS_REVISION => $parentNode &&
+                        !empty($parentNode->{Tinebase_Model_Tree_Node::XPROPS_REVISION}) ?
+                        $parentNode->{Tinebase_Model_Tree_Node::XPROPS_REVISION} : null
+                ));
+                $treeNode = $this->_getTreeNodeBackend()->create($treeNode);
+            }
 
             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
             $transactionId = null;
@@ -1468,37 +1525,65 @@ class Tinebase_FileSystem implements
         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
         try {
             $parentId = $_parentId instanceof Tinebase_Model_Tree_Node ? $_parentId->getId() : $_parentId;
-            $parentNode = $_parentId instanceof Tinebase_Model_Tree_Node ? $_parentId : $this->get($parentId);
 
-            $fileObject = new Tinebase_Model_Tree_FileObject(array(
-                'type'          => $_fileType,
-                'contentytype'  => null,
-            ));
-            Tinebase_Timemachine_ModificationLog::setRecordMetaData($fileObject, 'create');
+            if (null !== ($deletedNode = $this->_getTreeNodeBackend()->getChild($parentId, $_name, true, false)) &&
+                    $deletedNode->is_deleted) {
+                $deletedNode->is_deleted = 0;
+                /** @var Tinebase_Model_Tree_FileObject $object */
+                $object = $this->_fileObjectBackend->get($deletedNode->object_id, true);
+                if (isset($_SERVER['HTTP_X_OC_MTIME'])) {
+                    $object->creation_time = new Tinebase_DateTime($_SERVER['HTTP_X_OC_MTIME']);
+                    $object->last_modified_time = new Tinebase_DateTime($_SERVER['HTTP_X_OC_MTIME']);
+                    Tinebase_Server_WebDAV::getResponse()->setHeader('X-OC-MTime', 'accepted');
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
+                        Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " using X-OC-MTIME: {$object->last_modified_time->format(Tinebase_Record_Abstract::ISO8601LONG)} for {$_name}");
+                    }
+                }
+                $object->hash = Tinebase_Record_Abstract::generateUID();
+                $object->size = 0;
+                $object->is_deleted = 0;
+                $object->type = $_fileType;
+                $object->preview_count = 0;
+                $this->_fileObjectBackend->update($object);
+                //we can use _treeNodeBackend as we called get further up
+                $treeNode = $this->_treeNodeBackend->update($deletedNode);
+            } else {
 
-            // quick hack for 2014.11 - will be resolved correctly in 2015.11-develop
-            if (isset($_SERVER['HTTP_X_OC_MTIME'])) {
-                $fileObject->creation_time = new Tinebase_DateTime($_SERVER['HTTP_X_OC_MTIME']);
-                $fileObject->last_modified_time = new Tinebase_DateTime($_SERVER['HTTP_X_OC_MTIME']);
-                Tinebase_Server_WebDAV::getResponse()->setHeader('X-OC-MTime', 'accepted');
-                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
-                    Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " using X-OC-MTIME: {$fileObject->last_modified_time->format(Tinebase_Record_Abstract::ISO8601LONG)} for {$_name}");
+                $parentNode = $_parentId instanceof Tinebase_Model_Tree_Node ? $_parentId : $this->get($parentId);
 
-            }
+                $fileObject = new Tinebase_Model_Tree_FileObject(array(
+                    'type' => $_fileType,
+                    'contentytype' => null,
+                ));
+                Tinebase_Timemachine_ModificationLog::setRecordMetaData($fileObject, 'create');
+
+                // quick hack for 2014.11 - will be resolved correctly in 2015.11-develop
+                if (isset($_SERVER['HTTP_X_OC_MTIME'])) {
+                    $fileObject->creation_time = new Tinebase_DateTime($_SERVER['HTTP_X_OC_MTIME']);
+                    $fileObject->last_modified_time = new Tinebase_DateTime($_SERVER['HTTP_X_OC_MTIME']);
+                    Tinebase_Server_WebDAV::getResponse()->setHeader('X-OC-MTime', 'accepted');
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
+                        Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " using X-OC-MTIME: {$fileObject->last_modified_time->format(Tinebase_Record_Abstract::ISO8601LONG)} for {$_name}");
+                    }
 
-            $fileObject = $this->_fileObjectBackend->create($fileObject);
+                }
 
-            $treeNode = new Tinebase_Model_Tree_Node(array(
-                'name'          => $_name,
-                'object_id'     => $fileObject->getId(),
-                'parent_id'     => $parentId,
-                'acl_node'      => $parentNode && empty($parentNode->acl_node) ? null : $parentNode->acl_node,
-            ));
+                $fileObject = $this->_fileObjectBackend->create($fileObject);
 
-            if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
-                ' ' . print_r($treeNode->toArray(), true));
+                $treeNode = new Tinebase_Model_Tree_Node(array(
+                    'name' => $_name,
+                    'object_id' => $fileObject->getId(),
+                    'parent_id' => $parentId,
+                    'acl_node' => $parentNode && empty($parentNode->acl_node) ? null : $parentNode->acl_node,
+                ));
 
-            $treeNode = $this->_getTreeNodeBackend()->create($treeNode);
+                if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
+                    Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
+                        ' ' . print_r($treeNode->toArray(), true));
+                }
+
+                $treeNode = $this->_getTreeNodeBackend()->create($treeNode);
+            }
 
             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
             $transactionId = null;
@@ -1729,7 +1814,12 @@ class Tinebase_FileSystem implements
                 $this->_recursiveInheritPropertyUpdate($_node, Tinebase_Model_Tree_Node::XPROPS_REVISION, $newValue, $oldValue, false);
             }
 
-            $newNode = $this->_getTreeNodeBackend()->update($_node);
+            /** @var Tinebase_Model_Tree_Node $newNode */
+            $newNode = $this->_getTreeNodeBackend()->update($_node, false);
+
+            if (isset($_node->grants)) {
+                $newNode->grants = $_node->grants;
+            }
 
             $this->_getTreeNodeBackend()->updated($newNode, $currentNodeObject);
 
@@ -1750,14 +1840,15 @@ class Tinebase_FileSystem implements
      * @param string $_property
      * @param string $_newValue
      * @param string $_oldValue
+     * @param bool   $_ignoreACL
      */
-    protected function _recursiveInheritPropertyUpdate(Tinebase_Model_Tree_Node $_node, $_property, $_newValue, $_oldValue)
+    protected function _recursiveInheritPropertyUpdate(Tinebase_Model_Tree_Node $_node, $_property, $_newValue, $_oldValue, $_ignoreACL = true)
     {
         $childIds = $this->getAllChildIds(array($_node->getId()), array(array(
             'field'     => $_property,
             'operator'  => 'equals',
             'value'     => $_oldValue
-        )));
+        )), $_ignoreACL);
         if (count($childIds) > 0) {
             $this->_getTreeNodeBackend()->updateMultiple($childIds, array($_property => $_newValue));
         }
@@ -2002,6 +2093,7 @@ class Tinebase_FileSystem implements
             $hashes = array();
         }
 
+        // hard delete is ok here
         $this->_fileObjectBackend->delete($toDeleteIds);
         if (true === $this->_indexingActive) {
             Tinebase_Fulltext_Indexer::getInstance()->removeFileContentsFromIndex($toDeleteIds);
@@ -2677,7 +2769,6 @@ class Tinebase_FileSystem implements
         $invalidHashes = array();
         $created = 0;
         $deleted = 0;
-        $transactionManager = Tinebase_TransactionManager::getInstance();
 
         foreach($treeNodeBackend->search(
                 new Tinebase_Model_Tree_Node_Filter(array(
@@ -2693,7 +2784,11 @@ class Tinebase_FileSystem implements
                 continue;
             }
 
-            foreach ($node->available_revisions as $revision) {
+            $availableRevisions = $node->available_revisions;
+            if (!is_array($availableRevisions)) {
+                $availableRevisions = explode(',', $availableRevisions);
+            }
+            foreach ($availableRevisions as $revision) {
                 if ($node->revision != $revision) {
                     $treeNodeBackend->setRevision($revision);
                     try {
index 3b9afcf..efc4d5a 100644 (file)
@@ -1438,4 +1438,27 @@ class Tinebase_Frontend_Json extends Tinebase_Frontend_Json_Abstract
         
         return $result;
     }
+
+    /**
+     * returns the replication modification logs
+     *
+     * @param $hash
+     * @return array
+     * @throws Tinebase_Exception_AccessDenied
+     */
+    public function getBlob($hash)
+    {
+        if (! Tinebase_Core::getUser()->hasRight('Tinebase', Tinebase_Acl_Rights::REPLICATION)) {
+            throw new Tinebase_Exception_AccessDenied('you do not have access to blobs');
+        }
+
+        $fileObject = new Tinebase_Model_Tree_FileObject(array('hash' => $hash), true);
+        $path = $fileObject->getFilesystemPath();
+        $result = '';
+        if (is_file($path)) {
+            $result = file_get_contents($path);
+        }
+
+        return $result;
+    }
 }
index 90cdc4b..d5f25c9 100644 (file)
@@ -115,6 +115,15 @@ class Tinebase_Model_Tree_FileObject extends Tinebase_Record_Abstract
         'last_modified_time',
         'deleted_time'
     );
+
+    /**
+     * name of fields that should be omitted from modlog
+     *
+     * @var array list of modlog omit fields
+     */
+    protected $_modlogOmitFields = array('indexed_hash', 'preview_count', 'revision_size', 'available_revisions');
+
+    protected static $_isReplicable = true;
     
     /**
      * converts a string or Addressbook_Model_List to a list id
@@ -170,4 +179,25 @@ class Tinebase_Model_Tree_FileObject extends Tinebase_Record_Abstract
 
         parent::runConvertToRecord();
     }
+
+    /**
+     * @param bool $value
+     * @return bool
+     */
+    public static function setReplicable($value)
+    {
+        $return = static::$_isReplicable;
+        static::$_isReplicable = $value;
+        return $return;
+    }
+
+    /**
+     * returns true if this record should be replicated
+     *
+     * @return bool
+     */
+    public function isReplicable()
+    {
+        return static::$_isReplicable && (self::TYPE_FILE === $this->type || self::TYPE_FOLDER === $this->type);
+    }
 }
index e000558..e2b460f 100644 (file)
@@ -71,7 +71,8 @@ class Tinebase_Model_Tree_Node extends Tinebase_Record_Abstract
      *
      * @var array list of modlog omit fields
      */
-    protected $_modlogOmitFields = array('hash', 'available_revisions', 'revision_size', 'path', 'indexed_hash');
+    protected $_modlogOmitFields = array('revision', 'contenttype', 'description', 'revision_size', 'indexed_hash',
+        'hash', 'size', 'preview_count', 'available_revisions', 'path');
 
     /**
      * if foreign Id fields should be resolved on search and get from json
@@ -277,4 +278,14 @@ class Tinebase_Model_Tree_Node extends Tinebase_Record_Abstract
         return parent::isValid($_throwExceptionOnInvalidData);
     }
 
+    /**
+     * returns true if this record should be replicated
+     *
+     * @return boolean
+     */
+    public function isReplicable()
+    {
+        return $this->type === Tinebase_Model_Tree_FileObject::TYPE_FILE || $this->type ===
+            Tinebase_Model_Tree_FileObject::TYPE_FOLDER;
+    }
 }
index ff05d19..8cf9a10 100644 (file)
@@ -112,6 +112,9 @@ class Tinebase_Model_Tree_Node_Filter extends Tinebase_Model_Filter_GrantsFilter
         ),
         'isIndexed'             => array(
             'filter'                => 'Tinebase_Model_Tree_Node_IsIndexedFilter',
+        ),
+        'is_deleted'            => array(
+            'filter'                => 'Tinebase_Model_Filter_Bool'
         )
     );
 
index 07e495b..b78ccf2 100644 (file)
@@ -295,4 +295,11 @@ interface Tinebase_Record_Interface extends ArrayAccess, IteratorAggregate
      * @return array
      */
     public function getFields();
+
+    /**
+     * returns modlog omit fields
+     *
+     * @return array
+     */
+    public function getModlogOmitFields();
 }
index 2d644ab..2d5bf1b 100644 (file)
@@ -1220,4 +1220,26 @@ class Tinebase_Setup_Update_Release10 extends Setup_Update_Abstract
 
         $this->setApplicationVersion('Tinebase', '10.30');
     }
+
+    /**
+     * update to 10.31
+     *
+     * add is_deleted column to tree nodes
+     */
+    public function update_30()
+    {
+        if (! $this->_backend->columnExists('is_deleted', 'tree_nodes')) {
+            $declaration = new Setup_Backend_Schema_Field_Xml('<field>
+                    <name>is_deleted</name>
+                    <type>boolean</type>
+                    <default>false</default>
+                    <notnull>true</notnull>
+                </field>');
+            $this->_backend->addCol('tree_nodes', $declaration);
+
+            $this->setTableVersion('tree_nodes', 4);
+        }
+
+        $this->setApplicationVersion('Tinebase', '10.31');
+    }
 }
index 8be429e..40c5da9 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>Tinebase</name>
-    <version>10.30</version>
+    <version>10.31</version>
     <tables>
         <table>
             <name>applications</name>
 
         <table>
             <name>tree_nodes</name>
-            <version>3</version>
+            <version>4</version>
             <declaration>
                 <field>
                     <name>id</name>
                     <type>text</type>
                     <length>65535</length>
                 </field>
+                <field>
+                    <name>is_deleted</name>
+                    <type>boolean</type>
+                    <default>false</default>
+                    <notnull>true</notnull>
+                </field>
                 <index>
                     <name>id</name>
                     <primary>true</primary>
index 2c61595..59eba3b 100644 (file)
@@ -74,7 +74,7 @@ class Tinebase_Timemachine_ModificationLog implements Tinebase_Controller_Interf
         'creation_time',
         'last_modified_by',
         'last_modified_time',
-        'is_deleted',
+        //'is_deleted',
         'deleted_time',
         'deleted_by',
         'seq',
@@ -1177,6 +1177,38 @@ class Tinebase_Timemachine_ModificationLog implements Tinebase_Controller_Interf
         return array_keys($result);
     }
 
+    public function fetchBlobFromMaster($hash)
+    {
+        $slaveConfiguration = Tinebase_Config::getInstance()->{Tinebase_Config::REPLICATION_SLAVE};
+        $tine20Url = $slaveConfiguration->{Tinebase_Config::MASTER_URL};
+        $tine20LoginName = $slaveConfiguration->{Tinebase_Config::MASTER_USERNAME};
+        $tine20Password = $slaveConfiguration->{Tinebase_Config::MASTER_PASSWORD};
+
+        // check if we are a replication slave
+        if (empty($tine20Url) || empty($tine20LoginName) || empty($tine20Password)) {
+            return true;
+        }
+
+        $tine20Service = new Zend_Service_Tine20($tine20Url);
+
+        $authResponse = $tine20Service->login($tine20LoginName, $tine20Password);
+        if (!is_array($authResponse) || !isset($authResponse['success']) || $authResponse['success'] !== true) {
+            throw new Tinebase_Exception_AccessDenied('login failed');
+        }
+        unset($authResponse);
+
+        $tinebaseProxy = $tine20Service->getProxy('Tinebase');
+        /** @noinspection PhpUndefinedMethodInspection */
+        $result = $tinebaseProxy->getBlob($hash);
+
+        $fileObject = new Tinebase_Model_Tree_FileObject(array('hash' => $hash), true);
+        $path = $fileObject->getFilesystemPath();
+        if (!is_dir(dirname($path))) {
+            mkdir(dirname($path));
+        }
+        file_put_contents($path, $result);
+    }
+
     /**
      * @return bool
      * @throws Tinebase_Exception_AccessDenied
index 237128b..4b8012f 100644 (file)
@@ -58,4 +58,55 @@ class Tinebase_Tree implements Tinebase_Controller_Interface
     {
         return Filemanager_Controller_Node::getInstance()->get($_id);
     }
+
+    public function applyReplicationModificationLog(Tinebase_Model_ModificationLog $_modification)
+    {
+        $treeBackend = Tinebase_FileSystem::getInstance()->_getTreeNodeBackend();
+        switch($_modification->change_type) {
+            case Tinebase_Timemachine_ModificationLog::CREATED:
+                $diff = new Tinebase_Record_Diff(json_decode($_modification->new_value, true));
+                $node = new Tinebase_Model_Tree_Node($diff->diff);
+                $this->_prepareReplicationRecord($node);
+                /**
+                 * things that can go wrong:
+                 * * name not unique...
+                 * * parent_id was deleted
+                 * * revisionProps, notificationProps, acl_node
+                 */
+                $treeBackend->create($node);
+                break;
+
+            case Tinebase_Timemachine_ModificationLog::UPDATED:
+                $diff = new Tinebase_Record_Diff(json_decode($_modification->new_value, true));
+                /** @var Tinebase_Model_Tree_Node $record */
+                $record = $treeBackend->get($_modification->record_id, true);
+                if (isset($diff->diff['grants'])) {
+                    Tinebase_Tree_NodeGrants::getInstance()->getGrantsForRecord($record);
+                }
+                $record->applyDiff($diff);
+                $this->_prepareReplicationRecord($record);
+                $treeBackend->update($record);
+                if (isset($diff->diff['grants']) && $record->acl_node === $record->getId()) {
+                    Tinebase_FileSystem::getInstance()->setGrantsForNode($record, $record->grants);
+                    //Tinebase_Tree_NodeGrants::getInstance()->setGrants($record);
+                }
+                break;
+
+            case Tinebase_Timemachine_ModificationLog::DELETED:
+                $treeBackend->softDelete(array($_modification->record_id));
+                break;
+
+            default:
+                throw new Tinebase_Exception_UnexpectedValue('change_type ' . $_modification->change_type . ' unknown');
+        }
+    }
+
+    /**
+     * @param Tinebase_Model_Tree_Node $_record
+     */
+    protected function _prepareReplicationRecord(Tinebase_Model_Tree_Node $_record)
+    {
+        // unset properties that are maintained only locally
+        $_record->preview_count = null;
+    }
 }
\ No newline at end of file
index 49a8b77..822fd46 100644 (file)
@@ -19,6 +19,8 @@
  */
 class Tinebase_Tree_FileObject extends Tinebase_Backend_Sql_Abstract
 {
+    use Tinebase_Controller_Record_ModlogTrait;
+
     /**
      * Table name without prefix
      *
@@ -59,6 +61,16 @@ class Tinebase_Tree_FileObject extends Tinebase_Backend_Sql_Abstract
     protected $_revision = null;
 
     /**
+     * the singleton pattern
+     *
+     * @return Tinebase_Tree_FileObject
+     */
+    public static function getInstance()
+    {
+        return Tinebase_FileSystem::getInstance()->getFileObjectBackend();
+    }
+
+    /**
      * the constructor
      *
      * allowed options:
@@ -206,40 +218,48 @@ class Tinebase_Tree_FileObject extends Tinebase_Backend_Sql_Abstract
         if (empty($_record->hash)) {
             return;
         }
-        
+
         $createRevision = $this->_keepOldRevisions || $_mode === 'create';
-        $updateRevision = FALSE;
+        $updateRevision = false;
 
         // do not create a revision if the hash did not change! What point in creating a revision if the file in the filesystem is still the same?
         if ($_mode !== 'create') {
             /** @var Tinebase_Model_Tree_FileObject $currentRecord */
-            $currentRecord = $this->get($_record);
-            if ($currentRecord->hash !== NULL && !empty($currentRecord->revision)) {
+            $currentRecord = $this->get($_record, true);
+            if ($currentRecord->hash !== null && !empty($currentRecord->revision)) {
                 if ($currentRecord->hash === $_record->hash && (int)$currentRecord->size === (int)$_record->size) {
                     return;
                 }
-                $updateRevision = TRUE;
+                $updateRevision = true;
                 if (Tinebase_Model_Tree_FileObject::TYPE_FOLDER === $_record->type) {
-                    $createRevision = FALSE;
+                    $createRevision = false;
                 }
             } else {
-                $createRevision = TRUE;
+                $createRevision = true;
             }
         }
-        
-        if (! $createRevision && ! $updateRevision) {
+
+        if (!$createRevision && !$updateRevision) {
             return;
         }
 
+        if (!empty($_record->hash) && is_file($_record->getFilesystemPath())) {
+            if (false === ($_record->size = filesize($_record->getFilesystemPath()))) {
+                $_record->size = 0;
+            }
+        } elseif(empty($_record->size)) {
+            $_record->size = 0;
+        }
+
         $data = array(
             'creation_time' => Tinebase_DateTime::now()->toString(Tinebase_Record_Abstract::ISO8601LONG),
-            'created_by'    => is_object(Tinebase_Core::getUser()) ? Tinebase_Core::getUser()->getId() : null,
-            'hash'          => $_record->hash,
-            'size'          => $_record->size,
+            'created_by' => is_object(Tinebase_Core::getUser()) ? Tinebase_Core::getUser()->getId() : null,
+            'hash' => $_record->hash,
+            'size' => $_record->size,
             'preview_count' => (int)$_record->preview_count,
-            'revision'      => false === $createRevision && Tinebase_Model_Tree_FileObject::TYPE_FOLDER === $_record->type ? 1 : $this->_getNextRevision($_record),
+            'revision' => false === $createRevision && Tinebase_Model_Tree_FileObject::TYPE_FOLDER === $_record->type ? 1 : $this->_getNextRevision($_record),
         );
-            
+
         if ($createRevision) {
             $data['id'] = $_record->getId();
             $this->_db->insert($this->_tablePrefix . 'tree_filerevisions', $data);
@@ -248,7 +268,8 @@ class Tinebase_Tree_FileObject extends Tinebase_Backend_Sql_Abstract
                 // update total size
                 $this->_db->update($this->_tablePrefix . $this->_tableName,
                     array('revision_size' => new Zend_Db_Expr($this->_db->quoteIdentifier('revision_size') . ' + ' . (int)$_record->size)),
-                    $this->_db->quoteInto($this->_db->quoteIdentifier($this->_tablePrefix . $this->_tableName . '.id') . ' = ?', $_record->getId()));
+                    $this->_db->quoteInto($this->_db->quoteIdentifier($this->_tablePrefix . $this->_tableName . '.id') . ' = ?',
+                        $_record->getId()));
             }
 
         } elseif ($updateRevision) {
@@ -257,11 +278,62 @@ class Tinebase_Tree_FileObject extends Tinebase_Backend_Sql_Abstract
             );
             $this->_db->update($this->_tablePrefix . 'tree_filerevisions', $data, $where);
 
-            if (Tinebase_Model_Tree_FileObject::TYPE_FILE === $_record->type && (int)$_record->revision_size !== (int) $_record->size) {
+            if (Tinebase_Model_Tree_FileObject::TYPE_FILE === $_record->type && (int)$_record->revision_size !== (int)$_record->size) {
                 // update total size
                 $this->_db->update($this->_tablePrefix . $this->_tableName,
                     array('revision_size' => $_record->size),
-                    $this->_db->quoteInto($this->_db->quoteIdentifier($this->_tablePrefix . $this->_tableName . '.id') . ' = ?', $_record->getId()));
+                    $this->_db->quoteInto($this->_db->quoteIdentifier($this->_tablePrefix . $this->_tableName . '.id') . ' = ?',
+                        $_record->getId()));
+            }
+        }
+    }
+
+    /**
+     * do something after creation of record
+     *
+     * @param Tinebase_Record_Interface $_newRecord
+     * @param Tinebase_Record_Interface $_recordToCreate
+     * @return void
+     */
+    protected function _inspectAfterCreate(Tinebase_Record_Interface $_newRecord, Tinebase_Record_Interface $_recordToCreate)
+    {
+        $this->_writeModLog($_newRecord, null);
+        Tinebase_Notes::getInstance()->addSystemNote($_newRecord, Tinebase_Core::getUser(), Tinebase_Model_Note::SYSTEM_NOTE_NAME_CREATED);
+    }
+
+    /**
+     * Updates existing entry
+     *
+     * @param Tinebase_Record_Interface $_record
+     * @param boolean $_isReplicable
+     * @throws Tinebase_Exception_Record_Validation|Tinebase_Exception_InvalidArgument
+     * @return Tinebase_Record_Interface Record|NULL
+     */
+    public function update(Tinebase_Record_Interface $_record, $_isReplicable = true)
+    {
+        $oldIsReplicable = Tinebase_Model_Tree_FileObject::setReplicable($_isReplicable);
+
+        $oldRecord = $this->get($_record->getId(), true);
+        $newRecord = parent::update($_record);
+
+        $currentMods = $this->_writeModLog($newRecord, $oldRecord);
+        if (null !== $currentMods && $currentMods->count() > 0) {
+            Tinebase_Notes::getInstance()->addSystemNote($newRecord, Tinebase_Core::getUser(), Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED, $currentMods);
+        }
+
+        Tinebase_Model_Tree_FileObject::setReplicable($oldIsReplicable);
+
+        return $newRecord;
+    }
+
+    /**
+     * @param array $_ids
+     */
+    protected function _inspectBeforeSoftDelete(array $_ids)
+    {
+        if (!empty($_ids)) {
+            foreach($this->getMultiple($_ids) as $object) {
+                $this->_writeModLog(null, $object);
             }
         }
     }
@@ -478,4 +550,54 @@ class Tinebase_Tree_FileObject extends Tinebase_Backend_Sql_Abstract
 
         return $stmt->rowCount();
     }
+
+    /**
+     * @param Tinebase_Model_ModificationLog $_modification
+     * @throws Tinebase_Exception_UnexpectedValue
+     */
+    public function applyReplicationModificationLog(Tinebase_Model_ModificationLog $_modification)
+    {
+        switch($_modification->change_type) {
+            case Tinebase_Timemachine_ModificationLog::CREATED:
+                $diff = new Tinebase_Record_Diff(json_decode($_modification->new_value, true));
+                $record = new Tinebase_Model_Tree_FileObject($diff->diff);
+                $this->_prepareReplicationRecord($record);
+                $this->create($record);
+                if (Tinebase_Model_Tree_FileObject::TYPE_FILE === $record->type && null !== $record->hash &&
+                        !is_file($record->getFilesystemPath())) {
+                    Tinebase_Timemachine_ModificationLog::getInstance()->fetchBlobFromMaster($record->hash);
+                }
+                break;
+
+            case Tinebase_Timemachine_ModificationLog::UPDATED:
+                $diff = new Tinebase_Record_Diff(json_decode($_modification->new_value, true));
+                /** @var Tinebase_Model_Tree_FileObject $record */
+                $record = $this->get($_modification->record_id, true);
+                $oldHash = $record->hash;
+                $record->applyDiff($diff);
+                $this->_prepareReplicationRecord($record);
+                if (Tinebase_Model_Tree_FileObject::TYPE_FILE === $record->type && $oldHash !== $record->hash &&
+                    null !== $record->hash && !is_file($record->getFilesystemPath())) {
+                    Tinebase_Timemachine_ModificationLog::getInstance()->fetchBlobFromMaster($record->hash);
+                }
+                $this->update($record);
+                break;
+
+            case Tinebase_Timemachine_ModificationLog::DELETED:
+                $this->softDelete(array($_modification->record_id));
+                break;
+
+            default:
+                throw new Tinebase_Exception_UnexpectedValue('change_type ' . $_modification->change_type . ' unknown');
+        }
+    }
+
+    /**
+     * @param Tinebase_Model_Tree_FileObject $_record
+     */
+    protected function _prepareReplicationRecord(Tinebase_Model_Tree_FileObject $_record)
+    {
+        // unset properties that are maintained only locally
+        $_record->indexed_hash = null;
+    }
 }
index d8cbf7a..ec23220 100644 (file)
@@ -72,11 +72,17 @@ class Tinebase_Tree_Node extends Tinebase_Backend_Sql_Abstract
      */
     public function __construct($_dbAdapter = NULL, $_options = array())
     {
-        // we don't use modlog here because the name is unique. If the only do a soft delete, it is not possible to create the same node again!
         if (isset($_options[Tinebase_Config::FILESYSTEM_ENABLE_NOTIFICATIONS]) && true === $_options[Tinebase_Config::FILESYSTEM_ENABLE_NOTIFICATIONS]) {
             $this->_notificationActive = $_options[Tinebase_Config::FILESYSTEM_ENABLE_NOTIFICATIONS];
         }
 
+        if (isset($_options[Tinebase_Config::FILESYSTEM_MODLOGACTIVE]) && true === $_options[Tinebase_Config::FILESYSTEM_MODLOGACTIVE]) {
+            $this->_modlogActive = $_options[Tinebase_Config::FILESYSTEM_MODLOGACTIVE];
+        } else {
+            $this->_omitModLog = true;
+        }
+
+
         parent::__construct($_dbAdapter, $_options);
     }
 
@@ -152,17 +158,18 @@ class Tinebase_Tree_Node extends Tinebase_Backend_Sql_Abstract
      * Updates existing entry
      *
      * @param Tinebase_Record_Interface $_record
+     * @param boolean                   $_doModLog
      * @throws Tinebase_Exception_Record_Validation|Tinebase_Exception_InvalidArgument
      * @return Tinebase_Record_Interface Record|NULL
      */
-    public function update(Tinebase_Record_Interface $_record, $_doModLog = false)
+    public function update(Tinebase_Record_Interface $_record, $_doModLog = true)
     {
-        $oldRecord = $this->get($_record->getId());
+        $oldRecord = $this->get($_record->getId(), true);
         $newRecord = parent::update($_record);
 
         if (true === $_doModLog) {
             $currentMods = $this->_writeModLog($newRecord, $oldRecord);
-            if ($currentMods->count() > 0) {
+            if (null !== $currentMods && $currentMods->count() > 0) {
                 Tinebase_Notes::getInstance()->addSystemNote($newRecord, Tinebase_Core::getUser(), Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED, $currentMods);
             }
         }
@@ -183,7 +190,7 @@ class Tinebase_Tree_Node extends Tinebase_Backend_Sql_Abstract
     public function updated(Tinebase_Record_Interface $_newRecord, Tinebase_Record_Interface $_oldRecord)
     {
         $currentMods = $this->_writeModLog($_newRecord, $_oldRecord);
-        if ($currentMods->count() > 0) {
+        if (null !== $currentMods && $currentMods->count() > 0) {
             Tinebase_Notes::getInstance()->addSystemNote($_newRecord, Tinebase_Core::getUser(), Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED, $currentMods);
 
             if (true === $this->_notificationActive && Tinebase_Model_Tree_FileObject::TYPE_FILE === $_newRecord->type) {
@@ -197,6 +204,50 @@ class Tinebase_Tree_Node extends Tinebase_Backend_Sql_Abstract
     }
 
     /**
+     * Updates multiple entries
+     *
+     * @param array $_ids to update
+     * @param array $_data
+     * @return integer number of affected rows
+     * @throws Tinebase_Exception_Record_Validation|Tinebase_Exception_InvalidArgument
+     */
+    public function updateMultiple($_ids, $_data)
+    {
+        $oldRecords = null;
+        if ($this->_omitModLog === true) {
+            $oldRecords = $this->getMultiple($_ids);
+        }
+
+        $result = parent::updateMultiple($_ids, $_data);
+
+        if (null !== $oldRecords) {
+            foreach ($oldRecords as $oldRecord) {
+                $newRecord = $this->get($oldRecord->getId());
+                $currentMods = $this->_writeModLog($newRecord, $oldRecord);
+                if (null !== $currentMods && $currentMods->count() > 0) {
+                    Tinebase_Notes::getInstance()->addSystemNote($newRecord, Tinebase_Core::getUser(),
+                        Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED, $currentMods);
+
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param array $_ids
+     */
+    protected function _inspectBeforeSoftDelete(array $_ids)
+    {
+        if (!empty($_ids)) {
+            foreach($this->getMultiple($_ids) as $node) {
+                $this->_writeModLog(null, $node);
+            }
+        }
+    }
+
+    /**
      * @param Tinebase_Model_Tree_Node $_newRecord
      * @param Tinebase_Model_Tree_Node|null $_oldRecord
      */
@@ -254,10 +305,12 @@ class Tinebase_Tree_Node extends Tinebase_Backend_Sql_Abstract
      * 
      * @param  string|Tinebase_Model_Tree_Node  $parentId   the id of the parent node
      * @param  string|Tinebase_Model_Tree_Node  $childName  the name of the child node
+     * @param  boolean                          $getDeleted = false
+     * @param  boolean                          $throw = true
      * @throws Tinebase_Exception_NotFound
      * @return Tinebase_Model_Tree_Node
      */
-    public function getChild($parentId, $childName)
+    public function getChild($parentId, $childName, $getDeleted = false, $throw = true)
     {
         $parentId  = $parentId  instanceof Tinebase_Model_Tree_Node ? $parentId->getId() : $parentId;
         $childName = $childName instanceof Tinebase_Model_Tree_Node ? $childName->name   : $childName;
@@ -274,10 +327,17 @@ class Tinebase_Tree_Node extends Tinebase_Backend_Sql_Abstract
                 'value'     => $childName
             )
         ), Tinebase_Model_Filter_FilterGroup::CONDITION_AND, array('ignoreAcl' => true));
+        if (true === $getDeleted) {
+            $searchFilter->addFilter(new Tinebase_Model_Filter_Bool('is_deleted', 'equals',
+                Tinebase_Model_Filter_Bool::VALUE_NOTSET));
+        }
         $child = $this->search($searchFilter)->getFirstRecord();
         
         if (!$child || $childName !== $child->name) {
-            throw new Tinebase_Exception_NotFound('child: ' . $childName . ' not found!');
+            if (true === $throw) {
+                throw new Tinebase_Exception_NotFound('child: ' . $childName . ' not found!');
+            }
+            return null;
         }
         
         return $child;
@@ -488,7 +548,7 @@ class Tinebase_Tree_Node extends Tinebase_Backend_Sql_Abstract
     /**
      * @param Tinebase_Tree_FileObject $_fileObjectBackend
      * @param array $_folderIds
-     * @param bool
+     * @return bool
      */
     protected function _recalculateFolderSize(Tinebase_Tree_FileObject $_fileObjectBackend, array $_folderIds)
     {