added etag support and possibility to update from a caldav source using etag
authorPaul Mehrer <p.mehrer@metaways.de>
Mon, 11 Aug 2014 11:59:40 +0000 (13:59 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 4 Sep 2014 09:26:40 +0000 (11:26 +0200)
Change-Id: I56a472417b0f53a6493240f214898f56cd0dd5de
Reviewed-on: http://gerrit.tine20.com/customers/959
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Calendar/Backend/Sql.php
tine20/Calendar/Frontend/Cli.php
tine20/Calendar/Import/CalDav/Client.php

index 275146b..163b800 100644 (file)
@@ -826,4 +826,41 @@ class Calendar_Backend_Sql extends Tinebase_Backend_Sql_Abstract
             }
         }
     }
+    
+    /**
+     * sets etags, expects ids as keys and etags as value
+     * 
+     * @param array $etags
+     */
+    public function setETags(array $etags)
+    {
+        foreach ($etags as $id => $etag) {
+            $where  = array(
+                $this->_db->quoteInto($this->_db->quoteIdentifier($this->_identifier) . ' = ?', $id),
+            );
+            $this->_db->update($this->_tablePrefix . $this->_tableName, array('etag' => $etag), $where);
+        }
+    }
+    
+    /**
+     * checks if there is and event with this id and etag
+     * 
+     * @param string $id
+     * @param string $etag
+     */
+    public function checkETag($id, $etag)
+    {
+        $select = $this->_db->select();
+        $select->from(array($this->_tableName => $this->_tablePrefix . $this->_tableName), $this->_identifier);
+        $select->where($this->_db->quoteIdentifier($this->_identifier) . ' = ?', $id);
+        $select->where($this->_db->quoteIdentifier('etag') . ' = ?', $etag);
+        
+        $stmt = $select->query();
+        
+        $stmt->execute();
+        if ($stmt->rowCount() > 0) {
+            return true;
+        }
+        return false;
+    }
 }
index c097fb6..e531aad 100644 (file)
@@ -29,6 +29,13 @@ class Calendar_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
      * @return void
      */
     protected $_help = array(
+        'updateCalDavData' => array(
+            'description'    => 'update calendar/events from a CalDav source using etags',
+            'params'         => array(
+                'url'        => 'CalDav source URL',
+                'caldavuserfile' => 'CalDav user file containing utf8 username;pwd',
+             )
+        ),
         'importCalDavData' => array(
             'description'    => 'import calendar/events from a CalDav source',
             'params'         => array(
@@ -189,6 +196,27 @@ class Calendar_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
     }
     
     /**
+     * update calendar/events from a CalDav source using etags
+     * 
+     * param Zend_Console_Getopt $_opts
+     */
+    public function updateCalDavData(Zend_Console_Getopt $_opts)
+    {
+        $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile'));
+        
+        $writer = new Zend_Log_Writer_Stream('php://output');
+        $writer->addFilter(new Zend_Log_Filter_Priority(4));
+        Tinebase_Core::getLogger()->addWriter($writer);
+        
+        $users = $this->_readCalDavUserFile($args['caldavuserfile']);
+        
+        $client = new Calendar_Import_CalDav_Client(array('baseUri' => $args['url']), 'MacOSX');
+        $client->setVerifyPeer(false);
+        
+        $client->updateAllCalendarDataForUsers($users);
+    }
+    
+    /**
      * read caldav user credentials file
      * 
      * - file should have the following format (CSV):
index ac5ff0b..13358b4 100644 (file)
@@ -53,6 +53,15 @@ class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
 <b:calendar-multiget xmlns:a="DAV:" xmlns:b="urn:ietf:params:xml:ns:caldav">
   <a:prop>
     <b:calendar-data />
+    <a:getetag />
+  </a:prop>
+';
+    
+    const getEventETagsRequest =
+'<?xml version="1.0"?>
+<b:calendar-multiget xmlns:a="DAV:" xmlns:b="urn:ietf:params:xml:ns:caldav">
+  <a:prop>
+    <a:getetag />
   </a:prop>
 ';
     
@@ -213,6 +222,60 @@ class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
         }
     }
     
+    public function updateAllCalendarData()
+    {
+        if (count($this->calendarICSs) < 1 && ! $this->findAllCalendarICSs())
+            return false;
+        
+        $newICSs = array();
+        $calendarEventBackend = Calendar_Controller_Event::getInstance()->getBackend();
+        
+        foreach ($this->calendarICSs as $calUri => $calICSs) {
+            $start = 0;
+            $max = count($calICSs);
+            $etags = array();
+            do {
+                $requestEnd = '';
+                for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
+                    $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
+                }
+                $start = $i;
+                $requestEnd .= '</b:calendar-multiget>';
+                $result = $this->calDavRequest('REPORT', $calUri, self::getEventETagsRequest . $requestEnd, 1);
+                
+                foreach ($result as $key => $value) {
+                    if (isset($value['{DAV:}getetag'])) {
+                        $name = explode('/', $key);
+                        $name = end($name);
+                        $id = ($pos = strpos($name, '.')) === false ? $name : substr($name, 0, $pos);
+                        $etags[$key] = array( 'id' => $id, 'etag' => $value['{DAV:}getetag']);
+                    }
+                }
+            } while($start < $max);
+            
+            //check etags
+            foreach ($etags as $ics => $data) {
+                if (! $calendarEventBackend->checkETag($data['id'], $data['etag'])) {
+                    if (!isset($newICSs[$calUri])) {
+                        $newICSs[$calUri] = array();
+                    }
+                    $newICSs[$calUri][] = $ics;
+                }
+            }
+        }
+        
+        if (($count = count($newICSs)) > 0) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
+                Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $count . ' calendar(s) changed for: ' . $this->userName);
+            $this->calendarICSs = $newICSs;
+            $this->importAllCalendarData();
+        } else {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
+                Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' no changes found for: ' . $this->userName);
+            
+        }
+    }
+    
     public function importAllCalendarData($onlyCurrentUserOrganizer = false)
     {
         if (count($this->calendarICSs) < 1 && ! $this->findAllCalendarICSs()) {
@@ -231,6 +294,7 @@ class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
         $application_id = Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId();
         $type = Tinebase_Model_Container::TYPE_PERSONAL; //Tinebase_Model_Container::TYPE_SHARED;
         $defaultContainer = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
+        $calendarEventBackend = Calendar_Controller_Event::getInstance()->getBackend();
         
         //decide which calendar to use as default calendar
         //if there is a remote default calendar, use that. If not, use the first we find
@@ -257,6 +321,7 @@ class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
             $start = 0;
             $max = count($calICSs);
             do {
+                $etags = array();
                 $requestEnd = '';
                 for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
                     $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
@@ -271,12 +336,15 @@ class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
                         $name = explode('/', $key);
                         $name = end($name);
                         try {
-                            Calendar_Frontend_WebDAV_Event::create(
+                            $event = Calendar_Frontend_WebDAV_Event::create(
                                 $container,
                                 $name,
                                 $data,
                                 $onlyCurrentUserOrganizer
                             );
+                            if ($event) {
+                                $etags[$event->getRecord()->getId()] = $value['{DAV:}getetag'];
+                            }
                         } catch (Exception $e) {
                             // don't warn on VTODOs
                             if (strpos($data, 'BEGIN:VTODO') !== false) {
@@ -284,14 +352,15 @@ class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
                                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Skipping VTODO');
                             } else {
                                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
-                                    Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not create event from data: '
-                                            . $data);
+                                    Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not create event from data: ' . $data);
                                 Tinebase_Exception::log($e);
                             }
                         }
                     }
                 }
-            } while ($start < $max);
+                
+                $calendarEventBackend->setETags($etags);
+            } while($start < $max);
         }
         return true;
     }
@@ -375,6 +444,23 @@ class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
         return new Tinebase_Record_RecordSet('Tinebase_Model_Grants', $grants, TRUE);
     }
     
+    public function updateAllCalendarDataForUsers(array $users)
+    {
+        if (!$this->findCurrentUserPrincipalForUsers($users))
+            return false;
+        
+        $result = true;
+        foreach ($users as $username => $pwd) {
+            $this->clearCurrentUserCalendarData();
+            $this->userName = $username;
+            $this->password = $pwd;
+            if (!$this->updateAllCalendarData()) {
+                $result = false;
+            }
+        }
+        return $result;
+    }
+    
     public function importAllCalendarDataForUsers(array $users)
     {
         if (!$this->findCurrentUserPrincipalForUsers($users)) {