source: trunk/zpush/lib/default/filestatemachine.php @ 7589

Revision 7589, 15.0 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

Line 
1<?php
2/***********************************************
3* File      :   filestatemachine.php
4* Project   :   Z-Push
5* Descr     :   This class handles state requests;
6*               Each Import/Export mechanism can
7*               store its own state information,
8*               which is stored through the
9*               state machine.
10*
11* Created   :   01.10.2007
12*
13* Copyright 2007 - 2012 Zarafa Deutschland GmbH
14*
15* This program is free software: you can redistribute it and/or modify
16* it under the terms of the GNU Affero General Public License, version 3,
17* as published by the Free Software Foundation with the following additional
18* term according to sec. 7:
19*
20* According to sec. 7 of the GNU Affero General Public License, version 3,
21* the terms of the AGPL are supplemented with the following terms:
22*
23* "Zarafa" is a registered trademark of Zarafa B.V.
24* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
25* The licensing of the Program under the AGPL does not imply a trademark license.
26* Therefore any rights, title and interest in our trademarks remain entirely with us.
27*
28* However, if you propagate an unmodified version of the Program you are
29* allowed to use the term "Z-Push" to indicate that you distribute the Program.
30* Furthermore you may use our trademarks where it is necessary to indicate
31* the intended purpose of a product or service provided you use it in accordance
32* with honest practices in industrial or commercial matters.
33* If you want to propagate modified versions of the Program under the name "Z-Push",
34* you may only do so if you have a written permission by Zarafa Deutschland GmbH
35* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
36*
37* This program is distributed in the hope that it will be useful,
38* but WITHOUT ANY WARRANTY; without even the implied warranty of
39* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40* GNU Affero General Public License for more details.
41*
42* You should have received a copy of the GNU Affero General Public License
43* along with this program.  If not, see <http://www.gnu.org/licenses/>.
44*
45* Consult LICENSE file for details
46************************************************/
47
48class FileStateMachine implements IStateMachine {
49    private $userfilename;
50
51    /**
52     * Constructor
53     *
54     * Performs some basic checks and initilizes the state directory
55     *
56     * @access public
57     * @throws FatalMisconfigurationException
58     */
59    public function FileStateMachine() {
60        if (!defined('STATE_DIR'))
61            throw new FatalMisconfigurationException("No configuration for the state directory available.");
62
63        if (substr(STATE_DIR, -1,1) != "/")
64            throw new FatalMisconfigurationException("The configured state directory should terminate with a '/'");
65
66        if (!file_exists(STATE_DIR))
67            throw new FatalMisconfigurationException("The configured state directory does not exist or can not be accessed: ". STATE_DIR);
68        // checks if the directory exists and tries to create the necessary subfolders if they do not exist
69        $this->getDirectoryForDevice(Request::GetDeviceID());
70        $this->userfilename = STATE_DIR . 'users';
71
72        if (!touch($this->userfilename))
73            throw new FatalMisconfigurationException("Not possible to write to the configured state directory.");
74    }
75
76    /**
77     * Gets a hash value indicating the latest dataset of the named
78     * state with a specified key and counter.
79     * If the state is changed between two calls of this method
80     * the returned hash should be different
81     *
82     * @param string    $devid              the device id
83     * @param string    $type               the state type
84     * @param string    $key                (opt)
85     * @param string    $counter            (opt)
86     *
87     * @access public
88     * @return string
89     * @throws StateNotFoundException, StateInvalidException
90     */
91    public function GetStateHash($devid, $type, $key = false, $counter = false) {
92        $filename = $this->getFullFilePath($devid, $type, $key, $counter);
93
94        // the filemodification time is enough to track changes
95        if(file_exists($filename))
96            return filemtime($filename);
97        else
98            throw new StateNotFoundException(sprintf("FileStateMachine->GetStateHash(): Could not locate state '%s'",$filename));
99    }
100
101    /**
102     * Gets a state for a specified key and counter.
103     * This method sould call IStateMachine->CleanStates()
104     * to remove older states (same key, previous counters)
105     *
106     * @param string    $devid              the device id
107     * @param string    $type               the state type
108     * @param string    $key                (opt)
109     * @param string    $counter            (opt)
110     * @param string    $cleanstates        (opt)
111     *
112     * @access public
113     * @return mixed
114     * @throws StateNotFoundException, StateInvalidException
115     */
116    public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true) {
117        if ($counter && $cleanstates)
118            $this->CleanStates($devid, $type, $key, $counter);
119
120        // Read current sync state
121        $filename = $this->getFullFilePath($devid, $type, $key, $counter);
122
123        ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->GetState() on file: '%s'", $filename));
124
125        if(file_exists($filename)) {
126            return unserialize(file_get_contents($filename));
127        }
128        // throw an exception on all other states, but not FAILSAVE as it's most of the times not there by default
129        else if ($type !== IStateMachine::FAILSAVE)
130            throw new StateNotFoundException(sprintf("FileStateMachine->GetState(): Could not locate state '%s'",$filename));
131    }
132
133    /**
134     * Writes ta state to for a key and counter
135     *
136     * @param mixed     $state
137     * @param string    $devid              the device id
138     * @param string    $type               the state type
139     * @param string    $key                (opt)
140     * @param int       $counter            (opt)
141     *
142     * @access public
143     * @return boolean
144     * @throws StateInvalidException
145     */
146    public function SetState($state, $devid, $type, $key = false, $counter = false) {
147        $state = serialize($state);
148
149        $filename = $this->getFullFilePath($devid, $type, $key, $counter);
150        if (($bytes = file_put_contents($filename, $state)) === false)
151            throw new FatalMisconfigurationException(sprintf("FileStateMachine->SetState(): Could not write state '%s'",$filename));
152
153        ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->SetState() written %d bytes on file: '%s'", $bytes, $filename));
154        return $bytes;
155    }
156
157    /**
158     * Cleans up all older states
159     * If called with a $counter, all states previous state counter can be removed
160     * If called without $counter, all keys (independently from the counter) can be removed
161     *
162     * @param string    $devid              the device id
163     * @param string    $type               the state type
164     * @param string    $key
165     * @param string    $counter            (opt)
166     *
167     * @access public
168     * @return
169     * @throws StateInvalidException
170     */
171    public function CleanStates($devid, $type, $key, $counter = false) {
172        $matching_files = glob($this->getFullFilePath($devid, $type, $key). "*", GLOB_NOSORT);
173        if (is_array($matching_files)) {
174            foreach($matching_files as $state) {
175                $file = false;
176                if($counter !== false && preg_match('/([0-9]+)$/', $state, $matches)) {
177                    if($matches[1] < $counter) {
178                        $candidate = $this->getFullFilePath($devid, $type, $key, (int)$matches[1]);
179
180                        if ($candidate == $state)
181                            $file = $candidate;
182                    }
183                }
184                else if ($counter === false)
185                    $file =  $this->getFullFilePath($devid, $type, $key);
186
187                if ($file !== false) {
188                    ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->CleanStates(): Deleting file: '%s'", $file));
189                    unlink ($file);
190                }
191            }
192        }
193    }
194
195    /**
196     * Links a user to a device
197     *
198     * @param string    $username
199     * @param string    $devid
200     *
201     * @access public
202     * @return array
203     */
204    public function LinkUserDevice($username, $devid) {
205        include_once("simplemutex.php");
206        $mutex = new SimpleMutex();
207
208        // exclusive block
209        if ($mutex->Block()) {
210            $filecontents = @file_get_contents($this->userfilename);
211
212            if ($filecontents)
213                $users = unserialize($filecontents);
214            else
215                $users = array();
216
217            $changed = false;
218
219            // add user/device to the list
220            if (!isset($users[$username])) {
221                $users[$username] = array();
222                $changed = true;
223            }
224            if (!isset($users[$username][$devid])) {
225                $users[$username][$devid] = 1;
226                $changed = true;
227            }
228
229            if ($changed) {
230                $bytes = file_put_contents($this->userfilename, serialize($users));
231                ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->LinkUserDevice(): wrote %d bytes to users file", $bytes));
232            }
233            else
234                ZLog::Write(LOGLEVEL_DEBUG, "FileStateMachine->LinkUserDevice(): nothing changed");
235
236            $mutex->Release();
237        }
238    }
239
240   /**
241     * Unlinks a device from a user
242     *
243     * @param string    $username
244     * @param string    $devid
245     *
246     * @access public
247     * @return array
248     */
249    public function UnLinkUserDevice($username, $devid) {
250        include_once("simplemutex.php");
251        $mutex = new SimpleMutex();
252
253        // exclusive block
254        if ($mutex->Block()) {
255            $filecontents = @file_get_contents($this->userfilename);
256
257            if ($filecontents)
258                $users = unserialize($filecontents);
259            else
260                $users = array();
261
262            $changed = false;
263
264            // is this user listed at all?
265            if (isset($users[$username])) {
266                if (isset($users[$username][$devid])) {
267                    unset($users[$username][$devid]);
268                    $changed = true;
269                }
270
271                // if there is no device left, remove the user
272                if (empty($users[$username])) {
273                    unset($users[$username]);
274                    $changed = true;
275                }
276            }
277
278            if ($changed) {
279                $bytes = file_put_contents($this->userfilename, serialize($users));
280                ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->UnLinkUserDevice(): wrote %d bytes to users file", $bytes));
281            }
282            else
283                ZLog::Write(LOGLEVEL_DEBUG, "FileStateMachine->UnLinkUserDevice(): nothing changed");
284
285            $mutex->Release();
286        }
287    }
288
289    /**
290     * Returns an array with all device ids for a user.
291     * If no user is set, all device ids should be returned
292     *
293     * @param string    $username   (opt)
294     *
295     * @access public
296     * @return array
297     */
298    public function GetAllDevices($username = false) {
299        $out = array();
300        if ($username === false) {
301            foreach (glob(STATE_DIR. "/*/*/*-".IStateMachine::DEVICEDATA, GLOB_NOSORT) as $devdata)
302                if (preg_match('/\/([A-Za-z0-9]+)-'. IStateMachine::DEVICEDATA. '$/', $devdata, $matches))
303                    $out[] = $matches[1];
304            return $out;
305        }
306        else {
307            $filecontents = file_get_contents($this->userfilename);
308            if ($filecontents)
309                $users = unserialize($filecontents);
310            else
311                $users = array();
312
313            // get device list for the user
314            if (isset($users[$username]))
315                return array_keys($users[$username]);
316            else
317                return array();
318        }
319    }
320
321
322    /**----------------------------------------------------------------------------------------------------------
323     * Private FileStateMachine stuff
324     */
325
326    /**
327     * Returns the full path incl. filename for a key (generally uuid) and a counter
328     *
329     * @param string    $devid              the device id
330     * @param string    $type               the state type
331     * @param string    $key                (opt)
332     * @param string    $counter            (opt) default false
333     * @param boolean   $doNotCreateDirs    (opt) indicates if missing subdirectories should be created, default false
334     *
335     * @access private
336     * @return string
337     * @throws StateInvalidException
338     */
339    private function getFullFilePath($devid, $type, $key = false, $counter = false, $doNotCreateDirs = false) {
340        $testkey = $devid . (($key !== false)? "-". $key : "") . (($type !== "")? "-". $type : "");
341        if (preg_match('/^[a-zA-Z0-9-]+$/', $testkey, $matches) || ($type == "" && $key === false))
342            $internkey = $testkey . (($counter && is_int($counter))?"-".$counter:"");
343        else
344            throw new StateInvalidException("FileStateMachine->getFullFilePath(): Invalid state deviceid, type, key or in any combination");
345
346        return $this->getDirectoryForDevice($devid, $doNotCreateDirs) ."/". $internkey;
347    }
348
349    /**
350     * Checks if the configured path exists and if a subfolder structure is available
351     *  A two level deep subdirectory structure is build to save the states.
352     *  The subdirectories where to save, are determined with device id
353     *
354     * @param string    $devid                  the device id
355     * @param boolen    $doNotCreateDirs        (opt) by default false - indicates if the subdirs should be created
356     *
357     * @access private
358     * @return string/boolean                   returns the full directory of false if the dirs can not be created
359     * @throws FatalMisconfigurationException   when configured directory is not writeable
360     */
361    private function getDirectoryForDevice($devid, $doNotCreateDirs = false) {
362        $firstLevel = substr(strtolower($devid), -1, 1);
363        $secondLevel = substr(strtolower($devid), -2, 1);
364
365        $dir = STATE_DIR . $firstLevel . "/" . $secondLevel;
366        if (is_dir($dir))
367            return $dir;
368
369        if ($doNotCreateDirs === false) {
370            // try to create the subdirectory structure necessary
371            $fldir = STATE_DIR . $firstLevel;
372            if (!is_dir($fldir)) {
373                $dirOK = mkdir($fldir);
374                if (!$dirOK)
375                    throw new FatalMisconfigurationException("FileStateMachine->getDirectoryForDevice(): Not possible to create state sub-directory: ". $fldir);
376            }
377
378            if (!is_dir($dir)) {
379                $dirOK = mkdir($dir);
380                if (!$dirOK)
381                    throw new FatalMisconfigurationException("FileStateMachine->getDirectoryForDevice(): Not possible to create state sub-directory: ". $dir);
382            }
383            else
384                return $dir;
385        }
386        return false;
387    }
388
389}
390?>
Note: See TracBrowser for help on using the repository browser.