Back to index

d-push  2.0
filestatemachine.php
Go to the documentation of this file.
00001 <?php
00002 /***********************************************
00003 * File      :   filestatemachine.php
00004 * Project   :   Z-Push
00005 * Descr     :   This class handles state requests;
00006 *               Each Import/Export mechanism can
00007 *               store its own state information,
00008 *               which is stored through the
00009 *               state machine.
00010 *
00011 * Created   :   01.10.2007
00012 *
00013 * Copyright 2007 - 2011 Zarafa Deutschland GmbH
00014 *
00015 * This program is free software: you can redistribute it and/or modify
00016 * it under the terms of the GNU Affero General Public License, version 3,
00017 * as published by the Free Software Foundation with the following additional
00018 * term according to sec. 7:
00019 *
00020 * According to sec. 7 of the GNU Affero General Public License, version 3,
00021 * the terms of the AGPL are supplemented with the following terms:
00022 *
00023 * "Zarafa" is a registered trademark of Zarafa B.V.
00024 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00025 * The licensing of the Program under the AGPL does not imply a trademark license.
00026 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00027 *
00028 * However, if you propagate an unmodified version of the Program you are
00029 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00030 * Furthermore you may use our trademarks where it is necessary to indicate
00031 * the intended purpose of a product or service provided you use it in accordance
00032 * with honest practices in industrial or commercial matters.
00033 * If you want to propagate modified versions of the Program under the name "Z-Push",
00034 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00035 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00036 *
00037 * This program is distributed in the hope that it will be useful,
00038 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00039 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00040 * GNU Affero General Public License for more details.
00041 *
00042 * You should have received a copy of the GNU Affero General Public License
00043 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00044 *
00045 * Consult LICENSE file for details
00046 ************************************************/
00047 
00048 class FileStateMachine implements IStateMachine {
00049     private $userfilename;
00050 
00059     public function FileStateMachine() {
00060         if (!defined('STATE_DIR'))
00061             throw new FatalMisconfigurationException("No configuration for the state directory available.");
00062 
00063         if (substr(STATE_DIR, -1,1) != "/")
00064             throw new FatalMisconfigurationException("The configured state directory should terminate with a '/'");
00065 
00066         if (!file_exists(STATE_DIR))
00067             throw new FatalMisconfigurationException("The configured state directory does not exist or can not be accessed: ". STATE_DIR);
00068         // checks if the directory exists and tries to create the necessary subfolders if they do not exist
00069         $this->getDirectoryForDevice(Request::GetDeviceID());
00070         $this->userfilename = STATE_DIR . 'users';
00071 
00072         if (!touch($this->userfilename))
00073             throw new FatalMisconfigurationException("Not possible to write to the configured state directory.");
00074     }
00075 
00091     public function GetStateHash($devid, $type, $key = false, $counter = false) {
00092         $filename = $this->getFullFilePath($devid, $type, $key, $counter);
00093 
00094         // the filemodification time is enough to track changes
00095         if(file_exists($filename))
00096             return filemtime($filename);
00097         else
00098             throw new StateNotFoundException(sprintf("FileStateMachine->GetStateHash(): Could not locate state '%s'",$filename));
00099     }
00100 
00116     public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true) {
00117         if ($counter && $cleanstates)
00118             $this->CleanStates($devid, $type, $key, $counter);
00119 
00120         // Read current sync state
00121         $filename = $this->getFullFilePath($devid, $type, $key, $counter);
00122 
00123         ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->GetState() on file: '%s'", $filename));
00124 
00125         if(file_exists($filename)) {
00126             return unserialize(file_get_contents($filename));
00127         }
00128         // throw an exception on all other states, but not FAILSAVE as it's most of the times not there by default
00129         else if ($type !== IStateMachine::FAILSAVE)
00130             throw new StateNotFoundException(sprintf("FileStateMachine->GetState(): Could not locate state '%s'",$filename));
00131     }
00132 
00146     public function SetState($state, $devid, $type, $key = false, $counter = false) {
00147         $state = serialize($state);
00148 
00149         $filename = $this->getFullFilePath($devid, $type, $key, $counter);
00150         if (($bytes = file_put_contents($filename, $state)) === false)
00151             throw new FatalMisconfigurationException(sprintf("FileStateMachine->SetState(): Could not write state '%s'",$filename));
00152 
00153         ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->SetState() written %d bytes on file: '%s'", $bytes, $filename));
00154         return $bytes;
00155     }
00156 
00171     public function CleanStates($devid, $type, $key, $counter = false) {
00172         $matching_files = glob($this->getFullFilePath($devid, $type, $key). "*", GLOB_NOSORT);
00173         if (is_array($matching_files)) {
00174             foreach($matching_files as $state) {
00175                 $file = false;
00176                 if($counter !== false && preg_match('/([0-9]+)$/', $state, $matches)) {
00177                     if($matches[1] < $counter) {
00178                         $candidate = $this->getFullFilePath($devid, $type, $key, (int)$matches[1]);
00179 
00180                         if ($candidate == $state)
00181                             $file = $candidate;
00182                     }
00183                 }
00184                 else if ($counter === false)
00185                     $file =  $this->getFullFilePath($devid, $type, $key);
00186 
00187                 if ($file !== false) {
00188                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->CleanStates(): Deleting file: '%s'", $file));
00189                     unlink ($file);
00190                 }
00191             }
00192         }
00193     }
00194 
00204     public function LinkUserDevice($username, $devid) {
00205         include_once("simplemutex.php");
00206         $mutex = new SimpleMutex();
00207 
00208         // exclusive block
00209         if ($mutex->Block()) {
00210             $filecontents = @file_get_contents($this->userfilename);
00211 
00212             if ($filecontents)
00213                 $users = unserialize($filecontents);
00214             else
00215                 $users = array();
00216 
00217             $changed = false;
00218 
00219             // add user/device to the list
00220             if (!isset($users[$username])) {
00221                 $users[$username] = array();
00222                 $changed = true;
00223             }
00224             if (!isset($users[$username][$devid])) {
00225                 $users[$username][$devid] = 1;
00226                 $changed = true;
00227             }
00228 
00229             if ($changed) {
00230                 $bytes = file_put_contents($this->userfilename, serialize($users));
00231                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->LinkUserDevice(): wrote %d bytes to users file", $bytes));
00232             }
00233             else
00234                 ZLog::Write(LOGLEVEL_DEBUG, "FileStateMachine->LinkUserDevice(): nothing changed");
00235 
00236             $mutex->Release();
00237         }
00238     }
00239 
00249     public function UnLinkUserDevice($username, $devid) {
00250         include_once("simplemutex.php");
00251         $mutex = new SimpleMutex();
00252 
00253         // exclusive block
00254         if ($mutex->Block()) {
00255             $filecontents = @file_get_contents($this->userfilename);
00256 
00257             if ($filecontents)
00258                 $users = unserialize($filecontents);
00259             else
00260                 $users = array();
00261 
00262             $changed = false;
00263 
00264             // is this user listed at all?
00265             if (isset($users[$username])) {
00266                 if (isset($users[$username][$devid])) {
00267                     unset($users[$username][$devid]);
00268                     $changed = true;
00269                 }
00270 
00271                 // if there is no device left, remove the user
00272                 if (empty($users[$username])) {
00273                     unset($users[$username]);
00274                     $changed = true;
00275                 }
00276             }
00277 
00278             if ($changed) {
00279                 $bytes = file_put_contents($this->userfilename, serialize($users));
00280                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->UnLinkUserDevice(): wrote %d bytes to users file", $bytes));
00281             }
00282             else
00283                 ZLog::Write(LOGLEVEL_DEBUG, "FileStateMachine->UnLinkUserDevice(): nothing changed");
00284 
00285             $mutex->Release();
00286         }
00287     }
00288 
00298     public function GetAllDevices($username = false) {
00299         $out = array();
00300         if ($username === false) {
00301             foreach (glob(STATE_DIR. "/*/*/*-".IStateMachine::DEVICEDATA, GLOB_NOSORT) as $devdata)
00302                 if (preg_match('/\/([A-Za-z0-9]+)-'. IStateMachine::DEVICEDATA. '$/', $devdata, $matches))
00303                     $out[] = $matches[1];
00304             return $out;
00305         }
00306         else {
00307             $filecontents = file_get_contents($this->userfilename);
00308             if ($filecontents)
00309                 $users = unserialize($filecontents);
00310             else
00311                 $users = array();
00312 
00313             // get device list for the user
00314             if (isset($users[$username]))
00315                 return array_keys($users[$username]);
00316             else
00317                 return array();
00318         }
00319     }
00320 
00321 
00339     private function getFullFilePath($devid, $type, $key = false, $counter = false, $doNotCreateDirs = false) {
00340         $testkey = $devid . (($key !== false)? "-". $key : "") . (($type !== "")? "-". $type : "");
00341         if (preg_match('/^[a-zA-Z0-9-]+$/', $testkey, $matches) || ($type == "" && $key === false))
00342             $internkey = $testkey . (($counter && is_int($counter))?"-".$counter:"");
00343         else
00344             throw new StateInvalidException("FileStateMachine->getFullFilePath(): Invalid state deviceid, type, key or in any combination");
00345 
00346         return $this->getDirectoryForDevice($devid, $doNotCreateDirs) ."/". $internkey;
00347     }
00348 
00361     private function getDirectoryForDevice($devid, $doNotCreateDirs = false) {
00362         $firstLevel = substr(strtolower($devid), -1, 1);
00363         $secondLevel = substr(strtolower($devid), -2, 1);
00364 
00365         $dir = STATE_DIR . $firstLevel . "/" . $secondLevel;
00366         if (is_dir($dir))
00367             return $dir;
00368 
00369         if ($doNotCreateDirs === false) {
00370             // try to create the subdirectory structure necessary
00371             $fldir = STATE_DIR . $firstLevel;
00372             if (!is_dir($fldir)) {
00373                 $dirOK = mkdir($fldir);
00374                 if (!$dirOK)
00375                     throw new FatalMisconfigurationException("FileStateMachine->getDirectoryForDevice(): Not possible to create state sub-directory: ". $fldir);
00376             }
00377 
00378             if (!is_dir($dir)) {
00379                 $dirOK = mkdir($dir);
00380                 if (!$dirOK)
00381                     throw new FatalMisconfigurationException("FileStateMachine->getDirectoryForDevice(): Not possible to create state sub-directory: ". $dir);
00382             }
00383             else
00384                 return $dir;
00385         }
00386         return false;
00387     }
00388 
00389 }
00390 ?>