From bd834220959bf7edcc4cce4bfe6c77e0a4649b8b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 7 Sep 2012 18:30:48 +0200 Subject: [PATCH 001/418] put filestorages in a namespace --- apps/files_encryption/tests/proxy.php | 2 +- apps/files_external/appinfo/app.php | 16 ++--- apps/files_external/lib/amazons3.php | 14 ++-- apps/files_external/lib/config.php | 18 ++--- apps/files_external/lib/dropbox.php | 18 ++--- apps/files_external/lib/ftp.php | 6 +- apps/files_external/lib/google.php | 18 ++--- apps/files_external/lib/smb.php | 4 +- apps/files_external/lib/streamwrapper.php | 5 +- apps/files_external/lib/swift.php | 12 ++-- apps/files_external/lib/webdav.php | 12 ++-- apps/files_external/personal.php | 2 +- apps/files_external/tests/amazons3.php | 2 +- apps/files_external/tests/ftp.php | 2 +- apps/files_external/tests/google.php | 2 +- apps/files_external/tests/smb.php | 2 +- apps/files_external/tests/swift.php | 2 +- apps/files_external/tests/webdav.php | 2 +- apps/files_sharing/appinfo/app.php | 4 +- apps/files_sharing/lib/sharedstorage.php | 68 ++++++++++--------- lib/base.php | 3 + lib/{filestorage => files/storage}/common.php | 44 +++++------- .../storage}/commontest.php | 10 +-- lib/{filestorage => files/storage}/local.php | 17 +++-- .../storage/storage.php} | 28 +++----- lib/files/storage/temporary.php | 26 +++++++ lib/filestorage/temporary.php | 17 ----- lib/filesystem.php | 12 ++-- lib/filesystemview.php | 12 ++-- lib/util.php | 4 +- tests/lib/cache/file.php | 2 +- tests/lib/filestorage.php | 2 +- tests/lib/filestorage/commontest.php | 2 +- tests/lib/filestorage/local.php | 2 +- tests/lib/filesystem.php | 4 +- 35 files changed, 205 insertions(+), 191 deletions(-) rename lib/{filestorage => files/storage}/common.php (83%) rename lib/{filestorage => files/storage}/commontest.php (88%) rename lib/{filestorage => files/storage}/local.php (90%) rename lib/{filestorage.php => files/storage/storage.php} (71%) create mode 100644 lib/files/storage/temporary.php delete mode 100644 lib/filestorage/temporary.php diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 042011a6c8..d600bbc407 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -30,7 +30,7 @@ class Test_CryptProxy extends UnitTestCase { //set up temporary storage OC_Filesystem::clearMounts(); - OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/'); + OC_Filesystem::mount('\OC\Files\Storage\Temporary',array(),'/'); OC_Filesystem::init('/'.$user.'/files'); diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php index 837d35c9c6..c58cfcd0f5 100644 --- a/apps/files_external/appinfo/app.php +++ b/apps/files_external/appinfo/app.php @@ -6,14 +6,14 @@ * See the COPYING-README file. */ -OC::$CLASSPATH['OC_FileStorage_StreamWrapper']='apps/files_external/lib/streamwrapper.php'; -OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_external/lib/ftp.php'; -OC::$CLASSPATH['OC_Filestorage_DAV']='apps/files_external/lib/webdav.php'; -OC::$CLASSPATH['OC_Filestorage_Google']='apps/files_external/lib/google.php'; -OC::$CLASSPATH['OC_Filestorage_SWIFT']='apps/files_external/lib/swift.php'; -OC::$CLASSPATH['OC_Filestorage_SMB']='apps/files_external/lib/smb.php'; -OC::$CLASSPATH['OC_Filestorage_AmazonS3']='apps/files_external/lib/amazons3.php'; -OC::$CLASSPATH['OC_Filestorage_Dropbox']='apps/files_external/lib/dropbox.php'; +OC::$CLASSPATH['OC\Files\Storage\StreamWrapper']='apps/files_external/lib/streamwrapper.php'; +OC::$CLASSPATH['OC\Files\Storage\FTP']='apps/files_external/lib/ftp.php'; +OC::$CLASSPATH['OC\Files\Storage\DAV']='apps/files_external/lib/webdav.php'; +OC::$CLASSPATH['OC\Files\Storage\Google']='apps/files_external/lib/google.php'; +OC::$CLASSPATH['OC\Files\Storage\SWIFT']='apps/files_external/lib/swift.php'; +OC::$CLASSPATH['OC\Files\Storage\SMB']='apps/files_external/lib/smb.php'; +OC::$CLASSPATH['OC\Files\Storage\AmazonS3']='apps/files_external/lib/amazons3.php'; +OC::$CLASSPATH['OC\Files\Storage\Dropbox']='apps/files_external/lib/dropbox.php'; OC::$CLASSPATH['OC_Mount_Config']='apps/files_external/lib/config.php'; OCP\App::registerAdmin('files_external', 'settings'); diff --git a/apps/files_external/lib/amazons3.php b/apps/files_external/lib/amazons3.php index 41ec3c70b4..a17a70ed38 100644 --- a/apps/files_external/lib/amazons3.php +++ b/apps/files_external/lib/amazons3.php @@ -22,7 +22,9 @@ require_once 'aws-sdk/sdk.class.php'; -class OC_Filestorage_AmazonS3 extends OC_Filestorage_Common { +namespace OC\Files\Storage; + +class AmazonS3 extends \OC\Files\Storage\Common { private $s3; private $bucket; @@ -33,7 +35,7 @@ class OC_Filestorage_AmazonS3 extends OC_Filestorage_Common { // TODO options: storage class, encryption server side, encrypt before upload? public function __construct($params) { - $this->s3 = new AmazonS3(array('key' => $params['key'], 'secret' => $params['secret'])); + $this->s3 = new \AmazonS3(array('key' => $params['key'], 'secret' => $params['secret'])); $this->bucket = $params['bucket']; } @@ -96,7 +98,7 @@ class OC_Filestorage_AmazonS3 extends OC_Filestorage_Common { foreach ($response->body->CommonPrefixes as $object) { $files[] = basename($object->Prefix); } - OC_FakeDirStream::$dirs['amazons3'.$path] = $files; + \OC_FakeDirStream::$dirs['amazons3'.$path] = $files; return opendir('fakedir://amazons3'.$path); } return false; @@ -160,7 +162,7 @@ class OC_Filestorage_AmazonS3 extends OC_Filestorage_Common { switch ($mode) { case 'r': case 'rb': - $tmpFile = OC_Helper::tmpFile(); + $tmpFile = \OC_Helper::tmpFile(); $handle = fopen($tmpFile, 'w'); $response = $this->s3->get_object($this->bucket, $path, array('fileDownload' => $handle)); if ($response->isOK()) { @@ -184,8 +186,8 @@ class OC_Filestorage_AmazonS3 extends OC_Filestorage_Common { } else { $ext = ''; } - $tmpFile = OC_Helper::tmpFile($ext); - OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); + $tmpFile = \OC_Helper::tmpFile($ext); + \OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); file_put_contents($tmpFile, $source); diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index eec31ec2ef..db09fbdd82 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -39,14 +39,14 @@ class OC_Mount_Config { */ public static function getBackends() { return array( - 'OC_Filestorage_Local' => array('backend' => 'Local', 'configuration' => array('datadir' => 'Location')), - 'OC_Filestorage_AmazonS3' => array('backend' => 'Amazon S3', 'configuration' => array('key' => 'Key', 'secret' => '*Secret', 'bucket' => 'Bucket')), - 'OC_Filestorage_Dropbox' => array('backend' => 'Dropbox', 'configuration' => array('configured' => '#configured','app_key' => 'App key', 'app_secret' => 'App secret', 'token' => '#token', 'token_secret' => '#token_secret'), 'custom' => 'dropbox'), - 'OC_Filestorage_FTP' => array('backend' => 'FTP', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'root' => '&Root', 'secure' => '!Secure ftps://')), - 'OC_Filestorage_Google' => array('backend' => 'Google Drive', 'configuration' => array('configured' => '#configured', 'token' => '#token', 'token_secret' => '#token secret'), 'custom' => 'google'), - 'OC_Filestorage_SWIFT' => array('backend' => 'OpenStack Swift', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'token' => '*Token', 'root' => '&Root', 'secure' => '!Secure ftps://')), - 'OC_Filestorage_SMB' => array('backend' => 'SMB', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'share' => 'Share', 'root' => '&Root')), - 'OC_Filestorage_DAV' => array('backend' => 'WebDAV', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'root' => '&Root', 'secure' => '!Secure https://')) + '\OC\Files\Storage\Local' => array('backend' => 'Local', 'configuration' => array('datadir' => 'Location')), + '\OC\Files\Storage\AmazonS3' => array('backend' => 'Amazon S3', 'configuration' => array('key' => 'Key', 'secret' => '*Secret', 'bucket' => 'Bucket')), + '\OC\Files\Storage\Dropbox' => array('backend' => 'Dropbox', 'configuration' => array('configured' => '#configured','app_key' => 'App key', 'app_secret' => 'App secret', 'token' => '#token', 'token_secret' => '#token_secret'), 'custom' => 'dropbox'), + '\OC\Files\Storage\FTP' => array('backend' => 'FTP', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'root' => '&Root', 'secure' => '!Secure ftps://')), + '\OC\Files\Storage\Google' => array('backend' => 'Google Drive', 'configuration' => array('configured' => '#configured', 'token' => '#token', 'token_secret' => '#token secret'), 'custom' => 'google'), + '\OC\Files\Storage\SWIFT' => array('backend' => 'OpenStack Swift', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'token' => '*Token', 'root' => '&Root', 'secure' => '!Secure ftps://')), + '\OC\Files\Storage\SMB' => array('backend' => 'SMB', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'share' => 'Share', 'root' => '&Root')), + '\OC\Files\Storage\DAV' => array('backend' => 'WebDAV', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'root' => '&Root', 'secure' => '!Secure https://')) ); } @@ -124,7 +124,7 @@ class OC_Mount_Config { if ($isPersonal) { // Verify that the mount point applies for the current user // Prevent non-admin users from mounting local storage - if ($applicable != OCP\User::getUser() || $class == 'OC_Filestorage_Local') { + if ($applicable != OCP\User::getUser() || $class == '\OC\Files\Storage\Local') { return false; } $mountPoint = '/'.$applicable.'/files/'.ltrim($mountPoint, '/'); diff --git a/apps/files_external/lib/dropbox.php b/apps/files_external/lib/dropbox.php index bb86894e55..746364668f 100755 --- a/apps/files_external/lib/dropbox.php +++ b/apps/files_external/lib/dropbox.php @@ -22,7 +22,9 @@ require_once 'Dropbox/autoload.php'; -class OC_Filestorage_Dropbox extends OC_Filestorage_Common { +namespace OC\Files\Storage; + +class Dropbox extends \OC\Files\Storage\Common { private $dropbox; private $metaData = array(); @@ -31,11 +33,11 @@ class OC_Filestorage_Dropbox extends OC_Filestorage_Common { public function __construct($params) { if (isset($params['configured']) && $params['configured'] == 'true' && isset($params['app_key']) && isset($params['app_secret']) && isset($params['token']) && isset($params['token_secret'])) { - $oauth = new Dropbox_OAuth_Curl($params['app_key'], $params['app_secret']); + $oauth = new \Dropbox_OAuth_Curl($params['app_key'], $params['app_secret']); $oauth->setToken($params['token'], $params['token_secret']); - $this->dropbox = new Dropbox_API($oauth, 'dropbox'); + $this->dropbox = new \Dropbox_API($oauth, 'dropbox'); } else { - throw new Exception('Creating OC_Filestorage_Dropbox storage failed'); + throw new \Exception('Creating \OC\Files\Storage\Dropbox storage failed'); } } @@ -95,7 +97,7 @@ class OC_Filestorage_Dropbox extends OC_Filestorage_Common { foreach ($contents as $file) { $files[] = basename($file['path']); } - OC_FakeDirStream::$dirs['dropbox'.$path] = $files; + \OC_FakeDirStream::$dirs['dropbox'.$path] = $files; return opendir('fakedir://dropbox'.$path); } return false; @@ -177,7 +179,7 @@ class OC_Filestorage_Dropbox extends OC_Filestorage_Common { switch ($mode) { case 'r': case 'rb': - $tmpFile = OC_Helper::tmpFile(); + $tmpFile = \OC_Helper::tmpFile(); try { $data = $this->dropbox->getFile($path); file_put_contents($tmpFile, $data); @@ -203,8 +205,8 @@ class OC_Filestorage_Dropbox extends OC_Filestorage_Common { } else { $ext = ''; } - $tmpFile = OC_Helper::tmpFile($ext); - OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); + $tmpFile = \OC_Helper::tmpFile($ext); + \OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); file_put_contents($tmpFile, $source); diff --git a/apps/files_external/lib/ftp.php b/apps/files_external/lib/ftp.php index 261141455c..140a21ffe1 100644 --- a/apps/files_external/lib/ftp.php +++ b/apps/files_external/lib/ftp.php @@ -6,7 +6,9 @@ * See the COPYING-README file. */ -class OC_FileStorage_FTP extends OC_FileStorage_StreamWrapper{ +namespace OC\Files\Storage; + +class FTP extends \OC\Files\Storage\StreamWrapper{ private $password; private $user; private $host; @@ -69,7 +71,7 @@ class OC_FileStorage_FTP extends OC_FileStorage_StreamWrapper{ $ext=''; } $tmpFile=OCP\Files::tmpFile($ext); - OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + \OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); if($this->file_exists($path)) { $this->getFile($path,$tmpFile); } diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php index 9b83dcee53..51c5d219aa 100644 --- a/apps/files_external/lib/google.php +++ b/apps/files_external/lib/google.php @@ -22,7 +22,9 @@ require_once 'Google/common.inc.php'; -class OC_Filestorage_Google extends OC_Filestorage_Common { +namespace OC\Files\Storage; + +class Google extends \OC\Files\Storage\Common { private $consumer; private $oauth_token; @@ -35,12 +37,12 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { if (isset($params['configured']) && $params['configured'] == 'true' && isset($params['token']) && isset($params['token_secret'])) { $consumer_key = isset($params['consumer_key']) ? $params['consumer_key'] : 'anonymous'; $consumer_secret = isset($params['consumer_secret']) ? $params['consumer_secret'] : 'anonymous'; - $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); - $this->oauth_token = new OAuthToken($params['token'], $params['token_secret']); - $this->sig_method = new OAuthSignatureMethod_HMAC_SHA1(); + $this->consumer = new \OAuthConsumer($consumer_key, $consumer_secret); + $this->oauth_token = new \OAuthToken($params['token'], $params['token_secret']); + $this->sig_method = new \OAuthSignatureMethod_HMAC_SHA1(); $this->entries = array(); } else { - throw new Exception('Creating OC_Filestorage_Google storage failed'); + throw new \Exception('Creating \OC\Files\Storage\Google storage failed'); } } @@ -96,7 +98,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } if ($isDownload) { - $tmpFile = OC_Helper::tmpFile(); + $tmpFile = \OC_Helper::tmpFile(); $handle = fopen($tmpFile, 'w'); curl_setopt($curl, CURLOPT_FILE, $handle); } @@ -399,7 +401,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { } else { $ext = ''; } - $tmpFile = OC_Helper::tmpFile($ext); + $tmpFile = \OC_Helper::tmpFile($ext); OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); @@ -438,7 +440,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common { } if (isset($uploadUri) && $handle = fopen($path, 'r')) { $uploadUri .= '?convert=false'; - $mimetype = OC_Helper::getMimeType($path); + $mimetype = \OC_Helper::getMimeType($path); $size = filesize($path); $headers = array('X-Upload-Content-Type: ' => $mimetype, 'X-Upload-Content-Length: ' => $size); $postData = ''; diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index e5ba7a1774..d4882ed024 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -8,7 +8,9 @@ require_once 'smb4php/smb.php'; -class OC_FileStorage_SMB extends OC_FileStorage_StreamWrapper{ +namespace OC\Files\Storage; + +class SMB extends \OC\Files\Storage\StreamWrapper{ private $password; private $user; private $host; diff --git a/apps/files_external/lib/streamwrapper.php b/apps/files_external/lib/streamwrapper.php index 7263ef2325..750bdbaf4d 100644 --- a/apps/files_external/lib/streamwrapper.php +++ b/apps/files_external/lib/streamwrapper.php @@ -6,8 +6,9 @@ * See the COPYING-README file. */ +namespace OC\Files\Storage; -abstract class OC_FileStorage_StreamWrapper extends OC_Filestorage_Common{ +abstract class StreamWrapper extends \OC\Files\Storage\Common{ abstract public function constructUrl($path); public function mkdir($path) { @@ -84,6 +85,4 @@ abstract class OC_FileStorage_StreamWrapper extends OC_Filestorage_Common{ return stat($this->constructUrl($path)); } - - } diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index c29d28b44c..c67cf59df6 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -8,7 +8,9 @@ require_once 'php-cloudfiles/cloudfiles.php'; -class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ +namespace OC\Files\Storage; + +class SWIFT extends \OC\Files\Storage\Common{ private $host; private $root; private $user; @@ -272,10 +274,10 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ if(!$this->root || $this->root[0]!='/') { $this->root='/'.$this->root; } - $this->auth = new CF_Authentication($this->user, $this->token, null, $this->host); + $this->auth = new \CF_Authentication($this->user, $this->token, null, $this->host); $this->auth->authenticate(); - $this->conn = new CF_Connection($this->auth); + $this->conn = new \CF_Connection($this->auth); if(!$this->containerExists($this->root)) { $this->rootContainer=$this->createContainer('/'); @@ -341,7 +343,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ $subContainers=$this->getSubContainers($container); $files=array_merge($files,$subContainers); $id=$this->getContainerName($path); - OC_FakeDirStream::$dirs[$id]=$files; + \OC_FakeDirStream::$dirs[$id]=$files; return opendir('fakedir://'.$id); } @@ -426,7 +428,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ case 'c': case 'c+': $tmpFile=$this->getTmpFile($path); - OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + \OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); self::$tempFiles[$tmpFile]=$path; return fopen('close://'.$tmpFile,$mode); } diff --git a/apps/files_external/lib/webdav.php b/apps/files_external/lib/webdav.php index 3c18b227fa..70210b49ee 100644 --- a/apps/files_external/lib/webdav.php +++ b/apps/files_external/lib/webdav.php @@ -6,7 +6,9 @@ * See the COPYING-README file. */ -class OC_FileStorage_DAV extends OC_Filestorage_Common{ +namespace OC\Files\Storage; + +class DAV extends \OC\Files\Storage\Common{ private $password; private $user; private $host; @@ -42,7 +44,7 @@ class OC_FileStorage_DAV extends OC_Filestorage_Common{ 'password' => $this->password, ); - $this->client = new OC_Connector_Sabre_Client($settings); + $this->client = new \OC_Connector_Sabre_Client($settings); if($caview = \OCP\Files::getStorage('files_external')) { $certPath=\OCP\Config::getSystemValue('datadirectory').$caview->getAbsolutePath("").'rootcerts.crt'; @@ -78,12 +80,12 @@ class OC_FileStorage_DAV extends OC_Filestorage_Common{ try{ $response=$this->client->propfind($path, array(),1); $id=md5('webdav'.$this->root.$path); - OC_FakeDirStream::$dirs[$id]=array(); + \OC_FakeDirStream::$dirs[$id]=array(); $files=array_keys($response); array_shift($files);//the first entry is the current directory foreach($files as $file) { $file = urldecode(basename($file)); - OC_FakeDirStream::$dirs[$id][]=$file; + \OC_FakeDirStream::$dirs[$id][]=$file; } return opendir('fakedir://'.$id); }catch(Exception $e) { @@ -161,7 +163,7 @@ class OC_FileStorage_DAV extends OC_Filestorage_Common{ $ext=''; } $tmpFile=OCP\Files::tmpFile($ext); - OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + \OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); if($this->file_exists($path)) { $this->getFile($path,$tmpFile); } diff --git a/apps/files_external/personal.php b/apps/files_external/personal.php index f0d76460f5..a678f345c8 100755 --- a/apps/files_external/personal.php +++ b/apps/files_external/personal.php @@ -24,7 +24,7 @@ OCP\Util::addScript('files_external', 'settings'); OCP\Util::addStyle('files_external', 'settings'); $backends = OC_Mount_Config::getBackends(); // Remove local storage -unset($backends['OC_Filestorage_Local']); +unset($backends['\OC\Files\Storage\Local']); $tmpl = new OCP\Template('files_external', 'settings'); $tmpl->assign('isAdminPage', false, false); $tmpl->assign('mounts', OC_Mount_Config::getPersonalMountPoints()); diff --git a/apps/files_external/tests/amazons3.php b/apps/files_external/tests/amazons3.php index b9b4cf65bd..ad1c453abb 100644 --- a/apps/files_external/tests/amazons3.php +++ b/apps/files_external/tests/amazons3.php @@ -34,7 +34,7 @@ if (!is_array($config) or !isset($config['amazons3']) or !$config['amazons3']['r $id = uniqid(); $this->config = include('apps/files_external/tests/config.php'); $this->config['amazons3']['bucket'] = $id; // Make sure we have a new empty bucket to work in - $this->instance = new OC_Filestorage_AmazonS3($this->config['amazons3']); + $this->instance = new \OC\Files\Storage\AmazonS3($this->config['amazons3']); } public function tearDown() { diff --git a/apps/files_external/tests/ftp.php b/apps/files_external/tests/ftp.php index 12f3ec3908..105f7b485b 100644 --- a/apps/files_external/tests/ftp.php +++ b/apps/files_external/tests/ftp.php @@ -18,7 +18,7 @@ if(!is_array($config) or !isset($config['ftp']) or !$config['ftp']['run']) { $id=uniqid(); $this->config=include('apps/files_external/tests/config.php'); $this->config['ftp']['root'].='/'.$id;//make sure we have an new empty folder to work in - $this->instance=new OC_Filestorage_FTP($this->config['ftp']); + $this->instance=new \OC\Files\Storage\FTP($this->config['ftp']); } public function tearDown() { diff --git a/apps/files_external/tests/google.php b/apps/files_external/tests/google.php index d2a6358ade..e96c2263e1 100644 --- a/apps/files_external/tests/google.php +++ b/apps/files_external/tests/google.php @@ -33,7 +33,7 @@ if(!is_array($config) or !isset($config['google']) or !$config['google']['run']) $id=uniqid(); $this->config=include('apps/files_external/tests/config.php'); $this->config['google']['root'].='/'.$id;//make sure we have an new empty folder to work in - $this->instance=new OC_Filestorage_Google($this->config['google']); + $this->instance=new \OC\Files\Storage\Google($this->config['google']); } public function tearDown() { diff --git a/apps/files_external/tests/smb.php b/apps/files_external/tests/smb.php index 7de4fddbb3..a0c5d83473 100644 --- a/apps/files_external/tests/smb.php +++ b/apps/files_external/tests/smb.php @@ -19,7 +19,7 @@ if(!is_array($config) or !isset($config['smb']) or !$config['smb']['run']) { $id=uniqid(); $this->config=include('apps/files_external/tests/config.php'); $this->config['smb']['root'].=$id;//make sure we have an new empty folder to work in - $this->instance=new OC_Filestorage_SMB($this->config['smb']); + $this->instance=new \OC\Files\Storage\SMB($this->config['smb']); } public function tearDown() { diff --git a/apps/files_external/tests/swift.php b/apps/files_external/tests/swift.php index a6f5eace1c..3e1485b0f3 100644 --- a/apps/files_external/tests/swift.php +++ b/apps/files_external/tests/swift.php @@ -18,7 +18,7 @@ if(!is_array($config) or !isset($config['swift']) or !$config['swift']['run']) { $id=uniqid(); $this->config=include('apps/files_external/tests/config.php'); $this->config['swift']['root'].='/'.$id;//make sure we have an new empty folder to work in - $this->instance=new OC_Filestorage_SWIFT($this->config['swift']); + $this->instance=new \OC\Files\Storage\SWIFT($this->config['swift']); } diff --git a/apps/files_external/tests/webdav.php b/apps/files_external/tests/webdav.php index 74d980aa3f..6b1121c9d0 100644 --- a/apps/files_external/tests/webdav.php +++ b/apps/files_external/tests/webdav.php @@ -18,7 +18,7 @@ if(!is_array($config) or !isset($config['webdav']) or !$config['webdav']['run']) $id=uniqid(); $this->config=include('apps/files_external/tests/config.php'); $this->config['webdav']['root'].='/'.$id;//make sure we have an new empty folder to work in - $this->instance=new OC_Filestorage_DAV($this->config['webdav']); + $this->instance=new \OC\Files\Storage\DAV($this->config['webdav']); } public function tearDown() { diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 109f86b2e8..210c78ad17 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -2,8 +2,8 @@ OC::$CLASSPATH['OC_Share_Backend_File'] = "apps/files_sharing/lib/share/file.php"; OC::$CLASSPATH['OC_Share_Backend_Folder'] = 'apps/files_sharing/lib/share/folder.php'; -OC::$CLASSPATH['OC_Filestorage_Shared'] = "apps/files_sharing/lib/sharedstorage.php"; -OCP\Util::connectHook('OC_Filesystem', 'setup', 'OC_Filestorage_Shared', 'setup'); +OC::$CLASSPATH['OC\Files\Storage\Shared'] = "apps/files_sharing/lib/sharedstorage.php"; +OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); OCP\Util::addScript('files_sharing', 'share'); diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 6dba76955a..f1539610b0 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -20,10 +20,12 @@ * */ +namespace OC\Files\Storage; + /** * Convert target path to source path and pass the function call to the correct storage provider */ -class OC_Filestorage_Shared extends OC_Filestorage_Common { +class Shared extends \OC\Files\Storage\Common { private $sharedFolder; private $files = array(); @@ -50,7 +52,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { if (isset($this->files[$folder])) { $file = $this->files[$folder]; } else { - $file = OCP\Share::getItemSharedWith('folder', $folder, OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + $file = OCP\Share::getItemSharedWith('folder', $folder, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); } if ($file) { $this->files[$target]['path'] = $file['path'].substr($target, strlen($folder)); @@ -58,7 +60,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { return $this->files[$target]; } } else { - $file = OCP\Share::getItemSharedWith('file', $target, OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + $file = OCP\Share::getItemSharedWith('file', $target, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); if ($file) { $this->files[$target] = $file; return $this->files[$target]; @@ -78,7 +80,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { $file = $this->getFile($target); if (isset($file['path'])) { $uid = substr($file['path'], 1, strpos($file['path'], '/', 1) - 1); - OC_Filesystem::mount('OC_Filestorage_Local', array('datadir' => OC_User::getHome($uid)), $uid); + \OC_Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => \OC_User::getHome($uid)), $uid); return $file['path']; } return false; @@ -103,7 +105,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { * @return Source file path with mount point stripped out */ private function getInternalPath($path) { - $mountPoint = OC_Filesystem::getMountPoint($path); + $mountPoint = \OC_Filesystem::getMountPoint($path); $internalPath = substr($path, strlen($mountPoint)); return $internalPath; } @@ -112,7 +114,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) { return false; } else if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->mkdir($this->getInternalPath($source)); } return false; @@ -120,7 +122,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { public function rmdir($path) { if (($source = $this->getSourcePath($path)) && $this->isDeletable($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->rmdir($this->getInternalPath($source)); } return false; @@ -128,11 +130,11 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { public function opendir($path) { if ($path == '' || $path == '/') { - $files = OCP\Share::getItemsSharedWith('file', OC_Share_Backend_Folder::FORMAT_OPENDIR); - OC_FakeDirStream::$dirs['shared'] = $files; + $files = OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_Folder::FORMAT_OPENDIR); + \OC_FakeDirStream::$dirs['shared'] = $files; return opendir('fakedir://shared'); } else if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->opendir($this->getInternalPath($source)); } return false; @@ -142,7 +144,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { if ($path == '' || $path == '/') { return true; } else if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->is_dir($this->getInternalPath($source)); } return false; @@ -150,7 +152,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { public function is_file($path) { if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->is_file($this->getInternalPath($source)); } return false; @@ -163,7 +165,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { $stat['ctime'] = $this->filectime($path); return $stat; } else if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->stat($this->getInternalPath($source)); } return false; @@ -173,7 +175,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { if ($path == '' || $path == '/') { return 'dir'; } else if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->filetype($this->getInternalPath($source)); } return false; @@ -183,7 +185,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { if ($path == '' || $path == '/' || $this->is_dir($path)) { return 0; } else if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->filesize($this->getInternalPath($source)); } return false; @@ -225,7 +227,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { if ($path == '' || $path == '/') { return true; } else if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->file_exists($this->getInternalPath($source)); } return false; @@ -246,7 +248,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { } else { $source = $this->getSourcePath($path); if ($source) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->filectime($this->getInternalPath($source)); } } @@ -267,7 +269,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { } else { $source = $this->getSourcePath($path); if ($source) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->filemtime($this->getInternalPath($source)); } } @@ -280,8 +282,8 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { 'target' => $this->sharedFolder.$path, 'source' => $source, ); - OCP\Util::emitHook('OC_Filestorage_Shared', 'file_get_contents', $info); - $storage = OC_Filesystem::getStorage($source); + OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); + $storage = \OC_Filesystem::getStorage($source); return $storage->file_get_contents($this->getInternalPath($source)); } } @@ -296,8 +298,8 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { 'target' => $this->sharedFolder.$path, 'source' => $source, ); - OCP\Util::emitHook('OC_Filestorage_Shared', 'file_put_contents', $info); - $storage = OC_Filesystem::getStorage($source); + OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); + $storage = \OC_Filesystem::getStorage($source); $result = $storage->file_put_contents($this->getInternalPath($source), $data); return $result; } @@ -308,7 +310,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { // Delete the file if DELETE permission is granted if ($source = $this->getSourcePath($path)) { if ($this->isDeletable($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->unlink($this->getInternalPath($source)); } else if (dirname($path) == '/' || dirname($path) == '.') { // Unshare the file from the user if in the root of the Shared folder @@ -332,7 +334,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { if (dirname($path1) == dirname($path2)) { // Rename the file if UPDATE permission is granted if ($this->isUpdatable($path1)) { - $storage = OC_Filesystem::getStorage($oldSource); + $storage = \OC_Filesystem::getStorage($oldSource); return $storage->rename($this->getInternalPath($oldSource), $this->getInternalPath($newSource)); } } else { @@ -347,7 +349,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { return $this->unlink($path1); } } else { - $storage = OC_Filesystem::getStorage($oldSource); + $storage = \OC_Filesystem::getStorage($oldSource); return $storage->rename($this->getInternalPath($oldSource), $this->getInternalPath($newSource)); } } @@ -361,7 +363,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { if ($this->isCreatable(dirname($path2))) { $source = $this->fopen($path1, 'r'); $target = $this->fopen($path2, 'w'); - return OC_Helper::streamCopy($source, $target); + return \OC_Helper::streamCopy($source, $target); } return false; } @@ -392,8 +394,8 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { 'source' => $source, 'mode' => $mode, ); - OCP\Util::emitHook('OC_Filestorage_Shared', 'fopen', $info); - $storage = OC_Filesystem::getStorage($source); + OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info); + $storage = \OC_Filesystem::getStorage($source); return $storage->fopen($this->getInternalPath($source), $mode); } return false; @@ -404,7 +406,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { return 'httpd/unix-directory'; } if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->getMimeType($this->getInternalPath($source)); } return false; @@ -413,21 +415,21 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { public function free_space($path) { $source = $this->getSourcePath($path); if ($source) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->free_space($this->getInternalPath($source)); } } public function getLocalFile($path) { if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->getLocalFile($this->getInternalPath($source)); } return false; } public function touch($path, $mtime = null) { if ($source = $this->getSourcePath($path)) { - $storage = OC_Filesystem::getStorage($source); + $storage = \OC_Filesystem::getStorage($source); return $storage->touch($this->getInternalPath($source), $mtime); } return false; @@ -435,7 +437,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common { public static function setup($options) { $user_dir = $options['user_dir']; - OC_Filesystem::mount('OC_Filestorage_Shared', array('sharedFolder' => '/Shared'), $user_dir.'/Shared/'); + \OC_Filesystem::mount('\OC\Files\Storage\Shared', array('sharedFolder' => '/Shared'), $user_dir.'/Shared/'); } /** diff --git a/lib/base.php b/lib/base.php index 1c6cc70b0e..3b2ab8d5eb 100644 --- a/lib/base.php +++ b/lib/base.php @@ -81,6 +81,9 @@ class OC{ elseif(strpos($className, 'OC_')===0) { $path = strtolower(str_replace('_', '/', substr($className, 3)) . '.php'); } + elseif(strpos($className, 'OC\\')===0) { + $path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); + } elseif(strpos($className, 'OCP\\')===0) { $path = 'public/'.strtolower(str_replace('\\', '/', substr($className, 3)) . '.php'); } diff --git a/lib/filestorage/common.php b/lib/files/storage/common.php similarity index 83% rename from lib/filestorage/common.php rename to lib/files/storage/common.php index 351714437c..0a60ff757e 100644 --- a/lib/filestorage/common.php +++ b/lib/files/storage/common.php @@ -1,38 +1,26 @@ . -*/ + * Copyright (c) 2012 Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; /** * Storage backend class for providing common filesystem operation methods * which are not storage-backend specific. * - * OC_Filestorage_Common is never used directly; it is extended by all other + * \OC\Files\Storage\Common is never used directly; it is extended by all other * storage backends, where its methods may be overridden, and additional * (backend-specific) methods are defined. * - * Some OC_Filestorage_Common methods call functions which are first defined + * Some \OC\Files\Storage\Common methods call functions which are first defined * in classes which extend it, e.g. $this->stat() . */ -abstract class OC_Filestorage_Common extends OC_Filestorage { +abstract class Common extends \OC\Files\Storage\Storage { public function __construct($parameters) {} // abstract public function mkdir($path); @@ -104,7 +92,7 @@ abstract class OC_Filestorage_Common extends OC_Filestorage { public function copy($path1,$path2) { $source=$this->fopen($path1,'r'); $target=$this->fopen($path2,'w'); - $count=OC_Helper::streamCopy($source,$target); + $count=\OC_Helper::streamCopy($source,$target); return $count>0; } // abstract public function fopen($path,$mode); @@ -198,9 +186,9 @@ abstract class OC_Filestorage_Common extends OC_Filestorage { }else{ $extension=''; } - $tmpFile=OC_Helper::tmpFile($extension); + $tmpFile=\OC_Helper::tmpFile($extension); file_put_contents($tmpFile,$head); - $mime=OC_Helper::getMimeType($tmpFile); + $mime=\OC_Helper::getMimeType($tmpFile); unlink($tmpFile); return $mime; } @@ -227,13 +215,13 @@ abstract class OC_Filestorage_Common extends OC_Filestorage { }else{ $extension=''; } - $tmpFile=OC_Helper::tmpFile($extension); + $tmpFile=\OC_Helper::tmpFile($extension); $target=fopen($tmpFile,'w'); - OC_Helper::streamCopy($source,$target); + \OC_Helper::streamCopy($source,$target); return $tmpFile; } public function getLocalFolder($path) { - $baseDir=OC_Helper::tmpFolder(); + $baseDir=\OC_Helper::tmpFolder(); $this->addLocalFolder($path,$baseDir); return $baseDir; } diff --git a/lib/filestorage/commontest.php b/lib/files/storage/commontest.php similarity index 88% rename from lib/filestorage/commontest.php rename to lib/files/storage/commontest.php index b88bb232c3..773bfffcb4 100644 --- a/lib/filestorage/commontest.php +++ b/lib/files/storage/commontest.php @@ -22,18 +22,20 @@ */ /** - * test implementation for OC_FileStorage_Common with OC_FileStorage_Local + * test implementation for \OC\Files\Storage\Common with \OC\Files\Storage\Local */ -class OC_Filestorage_CommonTest extends OC_Filestorage_Common{ +namespace OC\Files\Storage; + +class CommonTest extends \OC\Files\Storage\Common{ /** * underlying local storage used for missing functions - * @var OC_FileStorage_Local + * @var \OC\Files\Storage\Local */ private $storage; public function __construct($params) { - $this->storage=new OC_Filestorage_Local($params); + $this->storage=new \OC\Files\Storage\Local($params); } public function mkdir($path) { diff --git a/lib/filestorage/local.php b/lib/files/storage/local.php similarity index 90% rename from lib/filestorage/local.php rename to lib/files/storage/local.php index e26d3d3ef9..e47441499a 100644 --- a/lib/filestorage/local.php +++ b/lib/files/storage/local.php @@ -1,8 +1,17 @@ + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + /** * for local filestore, we only have to map the paths */ -class OC_Filestorage_Local extends OC_Filestorage_Common{ +class Local extends \OC\Files\Storage\Common{ protected $datadir; public function __construct($arguments) { $this->datadir=$arguments['datadir']; @@ -86,11 +95,11 @@ class OC_Filestorage_Local extends OC_Filestorage_Common{ } public function rename($path1,$path2) { if (!$this->isUpdatable($path1)) { - OC_Log::write('core','unable to rename, file is not writable : '.$path1,OC_Log::ERROR); + \OC_Log::write('core','unable to rename, file is not writable : '.$path1,\OC_Log::ERROR); return false; } if(! $this->file_exists($path1)) { - OC_Log::write('core','unable to rename, file does not exists : '.$path1,OC_Log::ERROR); + \OC_Log::write('core','unable to rename, file does not exists : '.$path1,\OC_Log::ERROR); return false; } @@ -129,7 +138,7 @@ class OC_Filestorage_Local extends OC_Filestorage_Common{ public function getMimeType($path) { if($this->isReadable($path)) { - return OC_Helper::getMimeType($this->datadir.$path); + return \OC_Helper::getMimeType($this->datadir.$path); }else{ return false; } diff --git a/lib/filestorage.php b/lib/files/storage/storage.php similarity index 71% rename from lib/filestorage.php rename to lib/files/storage/storage.php index 5bfd09253d..a2f6cb7ec3 100644 --- a/lib/filestorage.php +++ b/lib/files/storage/storage.php @@ -1,29 +1,17 @@ . -*/ + * Copyright (c) 2012 Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; /** * Provde a common interface to all different storage options */ -abstract class OC_Filestorage{ +abstract class Storage{ abstract public function __construct($parameters); abstract public function mkdir($path); abstract public function rmdir($path); diff --git a/lib/files/storage/temporary.php b/lib/files/storage/temporary.php new file mode 100644 index 0000000000..0e69c9112a --- /dev/null +++ b/lib/files/storage/temporary.php @@ -0,0 +1,26 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +/** + * local storage backnd in temporary folder for testing purpores + */ +class Temporary extends OC\Files\Storage\Local{ + public function __construct($arguments) { + $this->datadir=\OC_Helper::tmpFolder(); + } + + public function cleanUp() { + \OC_Helper::rmdirr($this->datadir); + } + + public function __destruct() { + $this->cleanUp(); + } +} diff --git a/lib/filestorage/temporary.php b/lib/filestorage/temporary.php deleted file mode 100644 index 876ba045a6..0000000000 --- a/lib/filestorage/temporary.php +++ /dev/null @@ -1,17 +0,0 @@ -datadir=OC_Helper::tmpFolder(); - } - - public function cleanUp() { - OC_Helper::rmdirr($this->datadir); - } - - public function __destruct() { - $this->cleanUp(); - } -} diff --git a/lib/filesystem.php b/lib/filesystem.php index 92eb4fa477..9b022d8fef 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -48,7 +48,7 @@ class OC_Filesystem{ static private $mounts=array(); public static $loaded=false; /** - * @var OC_Filestorage $defaultInstance + * @var \OC\Files\Storage\Storage $defaultInstance */ static private $defaultInstance; @@ -181,7 +181,7 @@ class OC_Filesystem{ /** * get the storage object for a path * @param string path - * @return OC_Filestorage + * @return \OC\Files\Storage\Storage */ static public function getStorage($path) { $mountpoint=self::getMountPoint($path); @@ -276,7 +276,7 @@ class OC_Filesystem{ * create a new storage of a specific type * @param string type * @param array arguments - * @return OC_Filestorage + * @return \OC\Files\Storage\Storage */ static private function createStorage($class,$arguments) { if(class_exists($class)) { @@ -320,9 +320,9 @@ class OC_Filesystem{ } /** - * mount an OC_Filestorage in our virtual filesystem - * @param OC_Filestorage storage - * @param string mountpoint + * mount an \OC\Files\Storage\Storage in our virtual filesystem + * @param \OC\Files\Storage\Storage storage + * @param string mountpoint */ static public function mount($class,$arguments,$mountpoint) { if($mountpoint[0]!='/') { diff --git a/lib/filesystemview.php b/lib/filesystemview.php index 743f940301..36fef48551 100644 --- a/lib/filesystemview.php +++ b/lib/filesystemview.php @@ -35,7 +35,7 @@ * are triggered correctly. * * Filesystem functions are not called directly; they are passed to the correct - * OC_Filestorage object + * \OC\Files\Storage\Storage object */ class OC_FilesystemView { @@ -115,7 +115,7 @@ class OC_FilesystemView { /** * get the storage object for a path * @param string path - * @return OC_Filestorage + * @return \OC\Files\Storage\Storage */ public function getStorage($path) { if (!isset($this->storage_cache[$path])) { @@ -161,7 +161,7 @@ class OC_FilesystemView { /** * the following functions operate with arguments and return values identical * to those of their PHP built-in equivalents. Mostly they are merely wrappers - * for OC_Filestorage via basicOperation(). + * for \OC\Files\Storage\Storage via basicOperation(). */ public function mkdir($path) { return $this->basicOperation('mkdir', $path, array('create', 'write')); @@ -173,7 +173,7 @@ class OC_FilesystemView { return $this->basicOperation('opendir', $path, array('read')); } public function readdir($handle) { - $fsLocal= new OC_Filestorage_Local( array( 'datadir' => '/' ) ); + $fsLocal= new \OC\Files\Storage\Local( array( 'datadir' => '/' ) ); return $fsLocal->readdir( $handle ); } public function is_dir($path) { @@ -540,7 +540,7 @@ class OC_FilesystemView { } /** - * @brief abstraction layer for basic filesystem functions: wrapper for OC_Filestorage + * @brief abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage * @param string $operation * @param string #path * @param array (optional) hooks @@ -549,7 +549,7 @@ class OC_FilesystemView { * * This method takes requests for basic filesystem functions (e.g. reading & writing * files), processes hooks and proxies, sanitises paths, and finally passes them on to - * OC_Filestorage for delegation to a storage backend for execution + * \OC\Files\Storage\Storage for delegation to a storage backend for execution */ private function basicOperation($operation, $path, $hooks=array(), $extraParam=null) { $postFix=(substr($path,-1,1)==='/')?'/':''; diff --git a/lib/util.php b/lib/util.php index 5046550d6a..78976c9660 100755 --- a/lib/util.php +++ b/lib/util.php @@ -34,7 +34,7 @@ class OC_Util { $CONFIG_DATADIRECTORY = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); //first set up the local "root" storage if(!self::$rootMounted) { - OC_Filesystem::mount('OC_Filestorage_Local',array('datadir'=>$CONFIG_DATADIRECTORY),'/'); + OC_Filesystem::mount('\OC\Files\Storage\Local',array('datadir'=>$CONFIG_DATADIRECTORY),'/'); self::$rootMounted=true; } @@ -46,7 +46,7 @@ class OC_Util { mkdir( $userdirectory, 0755, true ); } //jail the user into his "home" directory - OC_Filesystem::mount('OC_Filestorage_Local', array('datadir' => $user_root), $user); + OC_Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $user_root), $user); OC_Filesystem::init($user_dir); $quotaProxy=new OC_FileProxy_Quota(); OC_FileProxy::register($quotaProxy); diff --git a/tests/lib/cache/file.php b/tests/lib/cache/file.php index 00be005d08..1dd1ff7fa8 100644 --- a/tests/lib/cache/file.php +++ b/tests/lib/cache/file.php @@ -39,7 +39,7 @@ class Test_Cache_File extends Test_Cache { //set up temporary storage OC_Filesystem::clearMounts(); - OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/'); + OC_Filesystem::mount('\OC\Files\Storage\Temporary',array(),'/'); OC_User::clearBackends(); OC_User::useBackend(new OC_User_Dummy()); diff --git a/tests/lib/filestorage.php b/tests/lib/filestorage.php index 3f7bb7b62d..bfb40bcd67 100644 --- a/tests/lib/filestorage.php +++ b/tests/lib/filestorage.php @@ -22,7 +22,7 @@ abstract class Test_FileStorage extends UnitTestCase { /** - * @var OC_Filestorage instance + * @var \OC\Files\Storage\Storage instance */ protected $instance; diff --git a/tests/lib/filestorage/commontest.php b/tests/lib/filestorage/commontest.php index 89e83589e5..0e52e91678 100644 --- a/tests/lib/filestorage/commontest.php +++ b/tests/lib/filestorage/commontest.php @@ -30,7 +30,7 @@ class Test_Filestorage_CommonTest extends Test_FileStorage { if(!file_exists($this->tmpDir)) { mkdir($this->tmpDir); } - $this->instance=new OC_Filestorage_CommonTest(array('datadir'=>$this->tmpDir)); + $this->instance=new \OC\Files\Storage\CommonTest(array('datadir'=>$this->tmpDir)); } public function tearDown() { diff --git a/tests/lib/filestorage/local.php b/tests/lib/filestorage/local.php index f68fb69b97..7bb27830ba 100644 --- a/tests/lib/filestorage/local.php +++ b/tests/lib/filestorage/local.php @@ -27,7 +27,7 @@ class Test_Filestorage_Local extends Test_FileStorage { private $tmpDir; public function setUp() { $this->tmpDir=OC_Helper::tmpFolder(); - $this->instance=new OC_Filestorage_Local(array('datadir'=>$this->tmpDir)); + $this->instance=new \OC\Files\Storage\Local(array('datadir'=>$this->tmpDir)); } public function tearDown() { diff --git a/tests/lib/filesystem.php b/tests/lib/filesystem.php index 1cfa35e305..5f5d393ed5 100644 --- a/tests/lib/filesystem.php +++ b/tests/lib/filesystem.php @@ -46,13 +46,13 @@ class Test_Filesystem extends UnitTestCase{ } public function testMount() { - OC_Filesystem::mount('OC_Filestorage_Local',self::getStorageData(),'/'); + OC_Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/'); $this->assertEqual('/',OC_Filesystem::getMountPoint('/')); $this->assertEqual('/',OC_Filesystem::getMountPoint('/some/folder')); $this->assertEqual('',OC_Filesystem::getInternalPath('/')); $this->assertEqual('some/folder',OC_Filesystem::getInternalPath('/some/folder')); - OC_Filesystem::mount('OC_Filestorage_Local',self::getStorageData(),'/some'); + OC_Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/some'); $this->assertEqual('/',OC_Filesystem::getMountPoint('/')); $this->assertEqual('/some/',OC_Filesystem::getMountPoint('/some/folder')); $this->assertEqual('/some/',OC_Filesystem::getMountPoint('/some/')); From e8d3a4768541980fcc391117e5a173875b2f75e0 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 12 Sep 2012 22:50:10 +0200 Subject: [PATCH 002/418] add getId to storage backends --- lib/files/storage/common.php | 1 + lib/files/storage/commontest.php | 3 +++ lib/files/storage/local.php | 3 +++ lib/files/storage/storage.php | 1 + 4 files changed, 8 insertions(+) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index 0a60ff757e..1f8b620480 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -23,6 +23,7 @@ namespace OC\Files\Storage; abstract class Common extends \OC\Files\Storage\Storage { public function __construct($parameters) {} +// abstract public function getId(); // abstract public function mkdir($path); // abstract public function rmdir($path); // abstract public function opendir($path); diff --git a/lib/files/storage/commontest.php b/lib/files/storage/commontest.php index 773bfffcb4..2b42cfe115 100644 --- a/lib/files/storage/commontest.php +++ b/lib/files/storage/commontest.php @@ -38,6 +38,9 @@ class CommonTest extends \OC\Files\Storage\Common{ $this->storage=new \OC\Files\Storage\Local($params); } + public function getId(){ + return 'test::'.$this->storage->getId(); + } public function mkdir($path) { return $this->storage->mkdir($path); } diff --git a/lib/files/storage/local.php b/lib/files/storage/local.php index e47441499a..78e4877a00 100644 --- a/lib/files/storage/local.php +++ b/lib/files/storage/local.php @@ -19,6 +19,9 @@ class Local extends \OC\Files\Storage\Common{ $this->datadir.='/'; } } + public function getId(){ + return 'local::'.$this->datadir; + } public function mkdir($path) { return @mkdir($this->datadir.$path); } diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index a2f6cb7ec3..bd4cc4743b 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -13,6 +13,7 @@ namespace OC\Files\Storage; */ abstract class Storage{ abstract public function __construct($parameters); + abstract public function getId(); abstract public function mkdir($path); abstract public function rmdir($path); abstract public function opendir($path); From 68f65b657c398d44508d20b56ccf9ddbee8e3521 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 12 Sep 2012 22:53:10 +0200 Subject: [PATCH 003/418] add OC_Filesystem::resolvePath to get the storage backend and internal path for a file in one go --- lib/filesystem.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/filesystem.php b/lib/filesystem.php index 9b022d8fef..ef8b3fef5b 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -194,6 +194,24 @@ class OC_Filesystem{ } } + /** + * resolve a path to a storage and internal path + * @param string $path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path){ + $mountpoint=self::getMountPoint($path); + if($mountpoint) { + if(!isset(OC_Filesystem::$storages[$mountpoint])) { + $mount=OC_Filesystem::$mounts[$mountpoint]; + OC_Filesystem::$storages[$mountpoint]=OC_Filesystem::createStorage($mount['class'],$mount['arguments']); + } + $storage = OC_Filesystem::$storages[$mountpoint]; + $internalPath=substr($path,strlen($mountpoint)); + return array($storage, $internalPath); + } + } + static public function init($root) { if(self::$defaultInstance) { return false; From c94fe38d3900acff474c909aaa730d7ab51f914d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 12 Sep 2012 23:25:57 +0200 Subject: [PATCH 004/418] add getPermissions to storage backends to get all permission flags in one go --- lib/files/storage/common.php | 19 +++++++++++++++++++ lib/files/storage/storage.php | 1 + 2 files changed, 20 insertions(+) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index 1f8b620480..b2b4ea8eee 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -54,6 +54,25 @@ abstract class Common extends \OC\Files\Storage\Storage { public function isSharable($path) { return $this->isReadable($path); } + public function getPermissions($path){ + $permissions = 0; + if($this->isCreatable($path)){ + $permissions |= OCP\Share::PERMISSION_CREATE; + } + if($this->isReadable($path)){ + $permissions |= OCP\Share::PERMISSION_READ; + } + if($this->isUpdatable($path)){ + $permissions |= OCP\Share::PERMISSION_UPDATE; + } + if($this->isDeletable($path)){ + $permissions |= OCP\Share::PERMISSION_DELETE; + } + if($this->isSharable($path)){ + $permissions |= OCP\Share::PERMISSION_SHARE; + } + return $permissions; + } // abstract public function file_exists($path); public function filectime($path) { $stat = $this->stat($path); diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index bd4cc4743b..f149c61830 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -27,6 +27,7 @@ abstract class Storage{ abstract public function isUpdatable($path); abstract public function isDeletable($path); abstract public function isSharable($path); + abstract public function getPermissions($path); abstract public function file_exists($path); abstract public function filectime($path); abstract public function filemtime($path); From 954596c251cb3132878d8d5eafcc37c47b3f520e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 16 Sep 2012 16:52:32 +0200 Subject: [PATCH 005/418] rework filecache to work directly on storage backends wip --- lib/files/cache/cache.php | 189 ++++++++++++++++++++++++++++++++++++ lib/files/cache/scanner.php | 78 +++++++++++++++ lib/files/file.php | 61 ++++++++++++ 3 files changed, 328 insertions(+) create mode 100644 lib/files/cache/cache.php create mode 100644 lib/files/cache/scanner.php create mode 100644 lib/files/file.php diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php new file mode 100644 index 0000000000..8fc575948d --- /dev/null +++ b/lib/files/cache/cache.php @@ -0,0 +1,189 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Cache { + /** + * @var array partial data for the cache + */ + private static $partial = array(); + + /** + * get the stored metadata of a file or folder + * + * @param \OC\Files\File or int $file + * @return array + */ + static public function get($file) { + if ($file instanceof \OC\Files\File) { + $where = 'WHERE `storage` = ? AND `path_hash` = ?'; + $params = array($file->getStorageId(), $file->getInternalPath()); + } else { //file id + $where = 'WHERE `fileid` = ?'; + $params = array($file); + } + $query = \OC_DB::prepare( + 'SELECT `id`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime` + FROM `*PREFIX*filecache` ' . $where); + $result=$query->execute($params); + return $result->fetchRow(); + } + + /** + * store meta data for a file or folder + * + * @param \OC\Files\File $file + * @param array $data + * + * @return int file id + */ + static public function put(\OC\Files\File $file, array $data) { + if ($id = self::getId($file) > -1) { + self::update($id, $data); + return $id; + } else { + $key = $file->getStorageId() . '::' . $file->getInternalPath(); + if (isset(self::$partial[$key])) { //add any saved partial data + $data = array_merge($data, self::$partial[$key]); + unset(self::$partial[$key]); + } + + $requiredFields = array('size', 'mtime', 'mimetype'); + foreach ($requiredFields as $field) { + if (!isset($data[$field])) { //data not complete save as partial and return + self::$partial[$key] = $data; + return -1; + } + } + + $data['path'] = $file->getInternalPath(); + $data['parent'] = self::getParentId($file); + $data['name'] = basename($file->getInternalPath()); + + list($queryParts, $params) = self::buildParts($data); + $queryParts[] = '`storage`'; + $params[] = $file->getStorageId(); + $valuesPlaceholder = array_fill(0, count($queryParts), '?'); + + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ' VALUES(' . implode(', ', $valuesPlaceholder) . ')'); + $query->execute($params); + + return \OC_DB::insertid('*PREFIX*filecache'); + } + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + static public function update($id, array $data) { + list($queryParts, $params) = self::buildParts($data); + $params[] = $id; + + $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? WHERE fileid = ?'); + $query->execute($params); + } + + /** + * extract query parts and params array from data array + * + * @param array $data + * @return array + */ + private static function buildParts(array $data) { + $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime'); + + $params = array(); + $queryParts = array(); + foreach ($data as $name => $value) { + if (array_search($name, $fields) !== false) { + $params[] = $value; + $queryParts[] = '`' . $name . '`'; + if ($name === 'path') { + $params[] = md5($value); + $queryParts[] = '`path_hash`'; + } elseif ($name === 'mimetype') { + $params[] = substr($value, 0, strpos($value, '/')); + $queryParts[] = '`mimepart`'; + } + } + } + return array($queryParts, $params); + } + + /** + * get the file id for a file + * + * @param \OC\Files\File $file + * @return int + */ + static public function getId(\OC\Files\File $file) { + $storageId = $file->getStorageId(); + $pathHash = md5($file->getInternalPath()); + + $query = \OC_DB::prepare('SELECT id FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); + $result = $query->execute(array($storageId, $pathHash)); + + if ($row = $result->fetchRow()) { + return $row['id']; + } else { + return -1; + } + } + + /** + * get the id of the parent folder of a file + * + * @param \OC\Files\File $file + * return int + */ + static public function getParentId(\OC\Files\File $file) { + $path = $file->getInternalPath(); + if ($path === '/' or $path === '') { + return -1; + } else { + return self::getId(new \OC\Files\File($file->getStorage(), dirname($path))); + } + } + + /** + * check if a file is available in the cache + * + * @param \OC\Files\File $file + * @return bool + */ + static public function inCache(\OC\Files\File $file) { + return self::getId($file) != -1; + } + + /** + * remove a file or folder from the cache + * + * @param \OC\Files\File $file + */ + public function remove(\OC\Files\File $file) { + $storageId = $file->getStorageId(); + $pathHash = md5($file->getInternalPath()); + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); + $query->execute(array($storageId, $pathHash)); + } + + /** + * remove all entries for files that are stored on $storage form the cache + * + * @param \OC\Files\Storage\Storage $storage + */ + public function removeStorage(\OC\Files\Storage\Storage $storage) { + $storageId = $storage->getId(); + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE storage=?'); + $query->execute(array($storageId)); + } +} diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php new file mode 100644 index 0000000000..8f2c5bda5c --- /dev/null +++ b/lib/files/cache/scanner.php @@ -0,0 +1,78 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Scanner { + const SCAN_RECURSIVE = true; + const SCAN_SHALLOW = false; + + /** + * get all the metadata of a file or folder + * * + * @param \OC\Files\File $file + * @return array with metadata of the file + */ + public static function getData(\OC\Files\File $file) { + $data = array(); + $storage = $file->getStorage(); + $path = $file->getInternalPath(); + if (!$storage->isReadable($path)) return null; //cant read, nothing we can do + clearstatcache(); + $data['mimetype'] = $storage->getMimeType($path); + $data['mtime'] = $storage->filemtime($path); + if ($data['mimetype'] == 'httpd/unix-directory') { + $data['size'] = -1; //unknown + $data['permissions'] = $storage->getPermissions($path . '/'); + } else { + $data['size'] = $storage->filesize($path); + $data['permissions'] = $storage->getPermissions($path); + } + return $data; + } + + /** + * scan a single file and store it in the cache + * + * @param \OC\Files\File $file + * @return array with metadata of the scanned file + */ + public static function scanFile(\OC\Files\File $file) { + $data = self::getData($file); + Cache::put($file, $data); + return $data; + } + + /** + * scan all the files in a folder and store them in the cache + * + * @param \OC\Files\File $folder + * @param SCAN_RECURSIVE/SCAN_SHALLOW $recursive + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + public static function scan(\OC\Files\File $folder, $recursive) { + $size = 0; + $storage = $folder->getStorage(); + $path = $folder->getInternalPath(); + if ($dh = $storage->opendir($path)) { + while ($file = readdir($dh)) { + if ($file !== '.' and $file !== '..') { + $child = new \OC\Files\File($storage, $path . '/' . $file); + $data = self::scanFile($child); + if ($recursive === self::SCAN_RECURSIVE and $data['mimetype'] === 'httpd/unix-directory') { + $data['size'] = self::scan($child, self::SCAN_RECURSIVE); + } + if ($data['size'] >= 0 and $size >= 0) { + $size += $data['size']; + } + } + } + } + return $size; + } +} diff --git a/lib/files/file.php b/lib/files/file.php new file mode 100644 index 0000000000..b6432e6345 --- /dev/null +++ b/lib/files/file.php @@ -0,0 +1,61 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files; + +/** + * representation of the location a file or folder is stored + */ + +class File{ + /** + * @var Storage\Storage $storage + */ + private $storage; + /** + * @var string internalPath + */ + private $internalPath; + + public function __construct(Storage\Storage $storage, $internalPath){ + $this->storage = $storage; + $this->internalPath = $internalPath; + } + + public static function resolve($fullPath){ + $storage = null; + $internalPath = ''; + list($storage, $internalPath) = \OC_Filesystem::resolvePath($fullPath); + return new File($storage, $internalPath); + } + + /** + * get the internal path of the file inside the filestorage + * @return string + */ + public function getInternalPath(){ + return $this->internalPath; + } + + /** + * get the storage the file is stored in + * @return \OC\Files\Storage\Storage + */ + public function getStorage(){ + return $this->storage; + } + + /** + * get the id of the storage the file is stored in + * @return string + */ + public function getStorageId(){ + return $this->storage->getId(); + } + +} From 153cd802a9bbc0f5a5f67fe428af7ca78185011f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 22 Sep 2012 14:40:04 +0200 Subject: [PATCH 006/418] add partial file data to the result of Cache::get --- lib/files/cache/cache.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 8fc575948d..9d90ab041f 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -31,7 +31,13 @@ class Cache { $query = \OC_DB::prepare( 'SELECT `id`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime` FROM `*PREFIX*filecache` ' . $where); - $result=$query->execute($params); + $result = $query->execute($params); + + //merge partial data + $key = $file->getStorageId() . '::' . $file->getInternalPath(); + if (isset(self::$partial[$key])) { + $result=array_merge($result, self::$partial[$key]); + } return $result->fetchRow(); } From 96b798a59f236f4251ced87ecac9da55e82d1685 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 22 Sep 2012 14:51:15 +0200 Subject: [PATCH 007/418] move storage backend test cases to their own namespace --- apps/files_external/tests/amazons3.php | 8 +++--- apps/files_external/tests/ftp.php | 8 +++--- apps/files_external/tests/google.php | 6 +++-- apps/files_external/tests/smb.php | 8 +++--- apps/files_external/tests/swift.php | 6 +++-- apps/files_external/tests/webdav.php | 6 +++-- .../storage}/commontest.php | 8 +++--- .../{filestorage => files/storage}/local.php | 10 ++++---- .../storage/storage.php} | 25 ++++++++++--------- 9 files changed, 50 insertions(+), 35 deletions(-) rename tests/lib/{filestorage => files/storage}/commontest.php (91%) rename tests/lib/{filestorage => files/storage}/local.php (87%) rename tests/lib/{filestorage.php => files/storage/storage.php} (93%) diff --git a/apps/files_external/tests/amazons3.php b/apps/files_external/tests/amazons3.php index ad1c453abb..cf372aecbd 100644 --- a/apps/files_external/tests/amazons3.php +++ b/apps/files_external/tests/amazons3.php @@ -20,12 +20,14 @@ * License along with this library. If not, see . */ +namespace Test\Files\Storage; + $config = include('apps/files_external/tests/config.php'); if (!is_array($config) or !isset($config['amazons3']) or !$config['amazons3']['run']) { - abstract class Test_Filestorage_AmazonS3 extends Test_FileStorage{} + abstract class AmazonS3 extends Storage{} return; } else { - class Test_Filestorage_AmazonS3 extends Test_FileStorage { + class AmazonS3 extends Storage { private $config; private $id; @@ -38,7 +40,7 @@ if (!is_array($config) or !isset($config['amazons3']) or !$config['amazons3']['r } public function tearDown() { - $s3 = new AmazonS3(array('key' => $this->config['amazons3']['key'], 'secret' => $this->config['amazons3']['secret'])); + $s3 = new \AmazonS3(array('key' => $this->config['amazons3']['key'], 'secret' => $this->config['amazons3']['secret'])); if ($s3->delete_all_objects($this->id)) { $s3->delete_bucket($this->id); } diff --git a/apps/files_external/tests/ftp.php b/apps/files_external/tests/ftp.php index 105f7b485b..8a0821e25a 100644 --- a/apps/files_external/tests/ftp.php +++ b/apps/files_external/tests/ftp.php @@ -6,12 +6,14 @@ * See the COPYING-README file. */ +namespace Test\Files\Storage; + $config=include('apps/files_external/tests/config.php'); if(!is_array($config) or !isset($config['ftp']) or !$config['ftp']['run']) { - abstract class Test_Filestorage_FTP extends Test_FileStorage{} + abstract class FTP extends Storage{} return; }else{ - class Test_Filestorage_FTP extends Test_FileStorage { + class FTP extends Storage { private $config; public function setUp() { @@ -22,7 +24,7 @@ if(!is_array($config) or !isset($config['ftp']) or !$config['ftp']['run']) { } public function tearDown() { - OCP\Files::rmdirr($this->instance->constructUrl('')); + \OCP\Files::rmdirr($this->instance->constructUrl('')); } } } diff --git a/apps/files_external/tests/google.php b/apps/files_external/tests/google.php index e96c2263e1..fff6461c91 100644 --- a/apps/files_external/tests/google.php +++ b/apps/files_external/tests/google.php @@ -20,12 +20,14 @@ * License along with this library. If not, see . */ +namespace Test\Files\Storage; + $config=include('apps/files_external/tests/config.php'); if(!is_array($config) or !isset($config['google']) or !$config['google']['run']) { - abstract class Test_Filestorage_Google extends Test_FileStorage{} + abstract class Google extends Storage{} return; }else{ - class Test_Filestorage_Google extends Test_FileStorage { + class Google extends Storage { private $config; diff --git a/apps/files_external/tests/smb.php b/apps/files_external/tests/smb.php index a0c5d83473..87b110a241 100644 --- a/apps/files_external/tests/smb.php +++ b/apps/files_external/tests/smb.php @@ -8,11 +8,13 @@ $config=include('apps/files_external/tests/config.php'); +namespace Test\Files\Storage; + if(!is_array($config) or !isset($config['smb']) or !$config['smb']['run']) { - abstract class Test_Filestorage_SMB extends Test_FileStorage{} + abstract class SMB extends Storage{} return; }else{ - class Test_Filestorage_SMB extends Test_FileStorage { + class SMB extends Storage { private $config; public function setUp() { @@ -23,7 +25,7 @@ if(!is_array($config) or !isset($config['smb']) or !$config['smb']['run']) { } public function tearDown() { - OCP\Files::rmdirr($this->instance->constructUrl('')); + \OCP\Files::rmdirr($this->instance->constructUrl('')); } } } diff --git a/apps/files_external/tests/swift.php b/apps/files_external/tests/swift.php index 3e1485b0f3..98a97f99b5 100644 --- a/apps/files_external/tests/swift.php +++ b/apps/files_external/tests/swift.php @@ -6,12 +6,14 @@ * See the COPYING-README file. */ +namespace Test\Files\Storage; + $config=include('apps/files_external/tests/config.php'); if(!is_array($config) or !isset($config['swift']) or !$config['swift']['run']) { - abstract class Test_Filestorage_SWIFT extends Test_FileStorage{} + abstract class SWIFT extends Storage{} return; }else{ - class Test_Filestorage_SWIFT extends Test_FileStorage { + class SWIFT extends Storage { private $config; public function setUp() { diff --git a/apps/files_external/tests/webdav.php b/apps/files_external/tests/webdav.php index 6b1121c9d0..cddd0d513e 100644 --- a/apps/files_external/tests/webdav.php +++ b/apps/files_external/tests/webdav.php @@ -6,12 +6,14 @@ * See the COPYING-README file. */ +namespace Test\Files\Storage; + $config=include('apps/files_external/tests/config.php'); if(!is_array($config) or !isset($config['webdav']) or !$config['webdav']['run']) { - abstract class Test_Filestorage_DAV extends Test_FileStorage{} + abstract class DAV extends Storage{} return; }else{ - class Test_Filestorage_DAV extends Test_FileStorage { + class DAV extends Storage { private $config; public function setUp() { diff --git a/tests/lib/filestorage/commontest.php b/tests/lib/files/storage/commontest.php similarity index 91% rename from tests/lib/filestorage/commontest.php rename to tests/lib/files/storage/commontest.php index 0e52e91678..2e3808e2d9 100644 --- a/tests/lib/filestorage/commontest.php +++ b/tests/lib/files/storage/commontest.php @@ -20,7 +20,9 @@ * */ -class Test_Filestorage_CommonTest extends Test_FileStorage { +namespace Test\Files\Storage; + +class CommonTest extends Storage { /** * @var string tmpDir */ @@ -34,8 +36,8 @@ class Test_Filestorage_CommonTest extends Test_FileStorage { } public function tearDown() { - OC_Helper::rmdirr($this->tmpDir); + \OC_Helper::rmdirr($this->tmpDir); } } -?> \ No newline at end of file +?> diff --git a/tests/lib/filestorage/local.php b/tests/lib/files/storage/local.php similarity index 87% rename from tests/lib/filestorage/local.php rename to tests/lib/files/storage/local.php index 7bb27830ba..6672368e67 100644 --- a/tests/lib/filestorage/local.php +++ b/tests/lib/files/storage/local.php @@ -20,19 +20,19 @@ * */ -class Test_Filestorage_Local extends Test_FileStorage { +namespace Test\Files\Storage; + +class Local extends Storage { /** * @var string tmpDir */ private $tmpDir; public function setUp() { - $this->tmpDir=OC_Helper::tmpFolder(); + $this->tmpDir=\OC_Helper::tmpFolder(); $this->instance=new \OC\Files\Storage\Local(array('datadir'=>$this->tmpDir)); } public function tearDown() { - OC_Helper::rmdirr($this->tmpDir); + \OC_Helper::rmdirr($this->tmpDir); } } - -?> \ No newline at end of file diff --git a/tests/lib/filestorage.php b/tests/lib/files/storage/storage.php similarity index 93% rename from tests/lib/filestorage.php rename to tests/lib/files/storage/storage.php index bfb40bcd67..9c8581b259 100644 --- a/tests/lib/filestorage.php +++ b/tests/lib/files/storage/storage.php @@ -20,7 +20,9 @@ * */ -abstract class Test_FileStorage extends UnitTestCase { +namespace Test\Files\Storage; + +abstract class Storage extends \UnitTestCase { /** * @var \OC\Files\Storage\Storage instance */ @@ -83,7 +85,7 @@ abstract class Test_FileStorage extends UnitTestCase { * test the various uses of file_get_contents and file_put_contents */ public function testGetPutContents() { - $sourceFile=OC::$SERVERROOT.'/tests/data/lorem.txt'; + $sourceFile=\OC::$SERVERROOT.'/tests/data/lorem.txt'; $sourceText=file_get_contents($sourceFile); //fill a file with string data @@ -103,21 +105,21 @@ abstract class Test_FileStorage extends UnitTestCase { $this->assertEqual('httpd/unix-directory',$this->instance->getMimeType('/')); $this->assertEqual(false,$this->instance->getMimeType('/non/existing/file')); - $textFile=OC::$SERVERROOT.'/tests/data/lorem.txt'; + $textFile=\OC::$SERVERROOT.'/tests/data/lorem.txt'; $this->instance->file_put_contents('/lorem.txt',file_get_contents($textFile,'r')); $this->assertEqual('text/plain',$this->instance->getMimeType('/lorem.txt')); - $pngFile=OC::$SERVERROOT.'/tests/data/logo-wide.png'; + $pngFile=\OC::$SERVERROOT.'/tests/data/logo-wide.png'; $this->instance->file_put_contents('/logo-wide.png',file_get_contents($pngFile,'r')); $this->assertEqual('image/png',$this->instance->getMimeType('/logo-wide.png')); - $svgFile=OC::$SERVERROOT.'/tests/data/logo-wide.svg'; + $svgFile=\OC::$SERVERROOT.'/tests/data/logo-wide.svg'; $this->instance->file_put_contents('/logo-wide.svg',file_get_contents($svgFile,'r')); $this->assertEqual('image/svg+xml',$this->instance->getMimeType('/logo-wide.svg')); } public function testCopyAndMove() { - $textFile=OC::$SERVERROOT.'/tests/data/lorem.txt'; + $textFile=\OC::$SERVERROOT.'/tests/data/lorem.txt'; $this->instance->file_put_contents('/source.txt',file_get_contents($textFile)); $this->instance->copy('/source.txt','/target.txt'); $this->assertTrue($this->instance->file_exists('/target.txt')); @@ -130,7 +132,7 @@ abstract class Test_FileStorage extends UnitTestCase { } public function testLocal() { - $textFile=OC::$SERVERROOT.'/tests/data/lorem.txt'; + $textFile=\OC::$SERVERROOT.'/tests/data/lorem.txt'; $this->instance->file_put_contents('/lorem.txt',file_get_contents($textFile)); $localFile=$this->instance->getLocalFile('/lorem.txt'); $this->assertTrue(file_exists($localFile)); @@ -151,7 +153,7 @@ abstract class Test_FileStorage extends UnitTestCase { } public function testStat() { - $textFile=OC::$SERVERROOT.'/tests/data/lorem.txt'; + $textFile=\OC::$SERVERROOT.'/tests/data/lorem.txt'; $ctimeStart=time(); $this->instance->file_put_contents('/lorem.txt',file_get_contents($textFile)); $this->assertTrue($this->instance->isReadable('/lorem.txt')); @@ -198,7 +200,6 @@ abstract class Test_FileStorage extends UnitTestCase { fclose($fh); clearstatcache(); $mtimeEnd=time(); - $originalCTime=$cTime; $mTime=$this->instance->filemtime('/lorem.txt'); $this->assertTrue(($mtimeStart-1)<=$mTime); $this->assertTrue($mTime<=($mtimeEnd+1)); @@ -208,11 +209,11 @@ abstract class Test_FileStorage extends UnitTestCase { } public function testSearch() { - $textFile=OC::$SERVERROOT.'/tests/data/lorem.txt'; + $textFile=\OC::$SERVERROOT.'/tests/data/lorem.txt'; $this->instance->file_put_contents('/lorem.txt',file_get_contents($textFile,'r')); - $pngFile=OC::$SERVERROOT.'/tests/data/logo-wide.png'; + $pngFile=\OC::$SERVERROOT.'/tests/data/logo-wide.png'; $this->instance->file_put_contents('/logo-wide.png',file_get_contents($pngFile,'r')); - $svgFile=OC::$SERVERROOT.'/tests/data/logo-wide.svg'; + $svgFile=\OC::$SERVERROOT.'/tests/data/logo-wide.svg'; $this->instance->file_put_contents('/logo-wide.svg',file_get_contents($svgFile,'r')); $result=$this->instance->search('logo'); $this->assertEqual(2,count($result)); From b206d16b104f0474cfd41944b857a3981948b94d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 22 Sep 2012 14:51:34 +0200 Subject: [PATCH 008/418] add support for loading namespaced test cases --- lib/base.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/base.php b/lib/base.php index 7a09946b9d..38568e2749 100644 --- a/lib/base.php +++ b/lib/base.php @@ -95,6 +95,9 @@ class OC{ } elseif(strpos($className, 'Test_')===0) { $path = 'tests/lib/'.strtolower(str_replace('_', '/', substr($className, 5)) . '.php'); + } + elseif(strpos($className, 'Test\\')===0) { + $path = 'tests/lib/'.strtolower(str_replace('\\', '/', substr($className, 5)) . '.php'); }else{ return false; } From 46896be0d482f5dcec85ec32b232dd8a205f3e41 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 22 Sep 2012 15:03:17 +0200 Subject: [PATCH 009/418] fix namespace error in temporary storage backend --- lib/files/storage/temporary.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files/storage/temporary.php b/lib/files/storage/temporary.php index 0e69c9112a..ffc55e2750 100644 --- a/lib/files/storage/temporary.php +++ b/lib/files/storage/temporary.php @@ -11,7 +11,7 @@ namespace OC\Files\Storage; /** * local storage backnd in temporary folder for testing purpores */ -class Temporary extends OC\Files\Storage\Local{ +class Temporary extends Local{ public function __construct($arguments) { $this->datadir=\OC_Helper::tmpFolder(); } From 73eedd8fc8326616a356c8b8c40826d9dba92f31 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 22 Sep 2012 15:43:10 +0200 Subject: [PATCH 010/418] some simple test cases for the new filecache --- tests/lib/files/cache/cache.php | 88 +++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/lib/files/cache/cache.php diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php new file mode 100644 index 0000000000..168489a721 --- /dev/null +++ b/tests/lib/files/cache/cache.php @@ -0,0 +1,88 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +use \OC\Files\Cache\Cache as FileCache; + +class Cache extends \UnitTestCase { + /** + * @var \OC\Files\Storage\Temporary $storage; + */ + private $storage; + + private function createPath($path) { + return new \OC\Files\File($this->storage, $path); + } + + public function testSimple() { + $file1 = $this->createPath('foo'); + $file2 = $this->createPath('foo/bar'); + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); + $data2 = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + + $this->assertFalse(FileCache::inCache($file1)); + $this->assertEqual(FileCache::get($file1), null); + + $id1 = FileCache::put($file1, $data1); + $this->assertTrue(FileCache::inCache($file1)); + $cacheData1 = FileCache::get($file1); + foreach ($data1 as $key => $value) { + $this->assertEqual($value, $cacheData1[$key]); + } + $this->assertEqual($cacheData1['fileid'], $id1); + $this->assertEqual($id1, FileCache::getId($file1)); + + $this->assertFalse(FileCache::inCache($file2)); + $id2 = FileCache::put($file2, $data2); + $this->assertTrue(FileCache::inCache($file2)); + $cacheData2 = FileCache::get($file2); + foreach ($data2 as $key => $value) { + $this->assertEqual($value, $cacheData2[$key]); + } + $this->assertEqual($cacheData1['fileid'], $cacheData2['parent']); + $this->assertEqual($cacheData2['fileid'], $id2); + $this->assertEqual($id2, FileCache::getId($file2)); + $this->assertEqual($id1, FileCache::getParentId($file2)); + + $newSize = 1050; + $newId2 = FileCache::put($file2, array('size' => $newSize)); + $cacheData2 = FileCache::get($file2); + $this->assertEqual($newId2, $id2); + $this->assertEqual($cacheData2['size'], $newSize); + $this->assertEqual($cacheData1, FileCache::get($file1)); + + FileCache::remove($file2); + $this->assertFalse(FileCache::inCache($file2)); + $this->assertEqual(FileCache::get($file2), null); + $this->assertTrue(FileCache::inCache($file1)); + + $this->assertEqual($cacheData1, FileCache::get($id1)); + } + + public function testPartial() { + $file1 = $this->createPath('foo'); + + FileCache::put($file1, array('size' => 10)); + $this->assertEqual(array('size' => 10), FileCache::get($file1)); + + FileCache::put($file1, array('mtime' => 15)); + $this->assertEqual(array('size' => 10, 'mtime' => 15), FileCache::get($file1)); + + FileCache::put($file1, array('size' => 12)); + $this->assertEqual(array('size' => 12, 'mtime' => 15), FileCache::get($file1)); + } + + public function tearDown() { + FileCache::removeStorage($this->storage); + } + + public function setUp() { + $this->storage = new \OC\Files\Storage\Temporary(array()); + } +} From 97b0eabc850f64949282b471288c93d7148c72ff Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 22 Sep 2012 15:43:48 +0200 Subject: [PATCH 011/418] fix several problems in the new filecache in order to complete the tests --- lib/files/cache/cache.php | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 9d90ab041f..5d4cb9dd2b 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -23,22 +23,25 @@ class Cache { static public function get($file) { if ($file instanceof \OC\Files\File) { $where = 'WHERE `storage` = ? AND `path_hash` = ?'; - $params = array($file->getStorageId(), $file->getInternalPath()); + $params = array($file->getStorageId(), md5($file->getInternalPath())); } else { //file id $where = 'WHERE `fileid` = ?'; $params = array($file); } $query = \OC_DB::prepare( - 'SELECT `id`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime` + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime` FROM `*PREFIX*filecache` ' . $where); $result = $query->execute($params); + $data = $result->fetchRow(); //merge partial data - $key = $file->getStorageId() . '::' . $file->getInternalPath(); - if (isset(self::$partial[$key])) { - $result=array_merge($result, self::$partial[$key]); + if (!$data and $file instanceof \OC\Files\File) { + $key = $file->getStorageId() . '::' . $file->getInternalPath(); + if (isset(self::$partial[$key])) { + $data = self::$partial[$key]; + } } - return $result->fetchRow(); + return $data; } /** @@ -50,13 +53,13 @@ class Cache { * @return int file id */ static public function put(\OC\Files\File $file, array $data) { - if ($id = self::getId($file) > -1) { + if (($id = self::getId($file)) > -1) { self::update($id, $data); return $id; } else { $key = $file->getStorageId() . '::' . $file->getInternalPath(); if (isset(self::$partial[$key])) { //add any saved partial data - $data = array_merge($data, self::$partial[$key]); + $data = array_merge(self::$partial[$key], $data); unset(self::$partial[$key]); } @@ -77,7 +80,7 @@ class Cache { $params[] = $file->getStorageId(); $valuesPlaceholder = array_fill(0, count($queryParts), '?'); - $query = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ' VALUES(' . implode(', ', $valuesPlaceholder) . ')'); + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ') VALUES(' . implode(', ', $valuesPlaceholder) . ')'); $query->execute($params); return \OC_DB::insertid('*PREFIX*filecache'); @@ -135,11 +138,11 @@ class Cache { $storageId = $file->getStorageId(); $pathHash = md5($file->getInternalPath()); - $query = \OC_DB::prepare('SELECT id FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); + $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); $result = $query->execute(array($storageId, $pathHash)); if ($row = $result->fetchRow()) { - return $row['id']; + return $row['fileid']; } else { return -1; } @@ -175,7 +178,7 @@ class Cache { * * @param \OC\Files\File $file */ - public function remove(\OC\Files\File $file) { + static public function remove(\OC\Files\File $file) { $storageId = $file->getStorageId(); $pathHash = md5($file->getInternalPath()); $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); @@ -187,7 +190,7 @@ class Cache { * * @param \OC\Files\Storage\Storage $storage */ - public function removeStorage(\OC\Files\Storage\Storage $storage) { + static public function removeStorage(\OC\Files\Storage\Storage $storage) { $storageId = $storage->getId(); $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE storage=?'); $query->execute(array($storageId)); From 6fafd5d4e9d23cd98738a3b00ce0053d6b6e3dff Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 22 Sep 2012 15:48:39 +0200 Subject: [PATCH 012/418] this should be done elsewhere if needed --- lib/files/cache/cache.php | 2 +- lib/files/cache/scanner.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 5d4cb9dd2b..0c1d6788ec 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -152,7 +152,7 @@ class Cache { * get the id of the parent folder of a file * * @param \OC\Files\File $file - * return int + * @return int */ static public function getParentId(\OC\Files\File $file) { $path = $file->getInternalPath(); diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 8f2c5bda5c..e8e9c71c4e 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -23,7 +23,6 @@ class Scanner { $storage = $file->getStorage(); $path = $file->getInternalPath(); if (!$storage->isReadable($path)) return null; //cant read, nothing we can do - clearstatcache(); $data['mimetype'] = $storage->getMimeType($path); $data['mtime'] = $storage->filemtime($path); if ($data['mimetype'] == 'httpd/unix-directory') { From f360d7c736b08f48f1abcea1e0b93dc890178c94 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 23 Sep 2012 01:51:00 +0200 Subject: [PATCH 013/418] add getId to shared storage backend --- apps/files_sharing/lib/sharedstorage.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index f1539610b0..876e719956 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -91,7 +91,7 @@ class Shared extends \OC\Files\Storage\Common { * @param string Shared target file path * @return Returns CRUDS permissions granted or false if not found */ - private function getPermissions($target) { + public function getPermissions($target) { $file = $this->getFile($target); if (isset($file['permissions'])) { return $file['permissions']; @@ -449,4 +449,8 @@ class Shared extends \OC\Files\Storage\Common { //TODO return false; } + + public function getId(){ + return 'shared::' . $this->sharedFolder; + } } From dcf995fff3ecc2780d3187744397461d1a14c041 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 23 Sep 2012 15:25:03 +0200 Subject: [PATCH 014/418] add Cache::getFolderContent --- lib/files/cache/cache.php | 19 +++++++++++++++++++ tests/lib/files/cache/cache.php | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 0c1d6788ec..f8808ad0b4 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -44,6 +44,25 @@ class Cache { return $data; } + /** + * get the metadata of all files stored in $folder + * + * @param \OC\Files\File $folder + * @return array + */ + static public function getFolderContents($folder) { + $fileId = self::getId($folder); + if ($fileId > -1) { + $query = \OC_DB::prepare( + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime` + FROM `*PREFIX*filecache` WHERE parent = ?'); + $result = $query->execute(array($fileId)); + return $result->fetchAll(); + } else { + return array(); + } + } + /** * store meta data for a file or folder * diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index 168489a721..9f35cd889a 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -78,6 +78,29 @@ class Cache extends \UnitTestCase { $this->assertEqual(array('size' => 12, 'mtime' => 15), FileCache::get($file1)); } + public function testFolder() { + $file1 = $this->createPath('folder'); + $file2 = $this->createPath('folder/bar'); + $file3 = $this->createPath('folder/foo'); + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); + $fileData = array(); + $fileData['bar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + $fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file'); + + FileCache::put($file1, $data1); + FileCache::put($file2, $fileData['bar']); + FileCache::put($file3, $fileData['foo']); + + $content = FileCache::getFolderContents($file1); + $this->assertEqual(count($content), 2); + foreach ($content as $cachedData) { + $data = $fileData[$cachedData['name']]; + foreach ($data as $name => $value) { + $this->assertEqual($value, $cachedData[$name]); + } + } + } + public function tearDown() { FileCache::removeStorage($this->storage); } From 522d7df860fb47d3cc56e315850dda2797569ac5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 26 Sep 2012 17:52:02 +0200 Subject: [PATCH 015/418] convert Cache to a non-static class that handles the cache for a single storage backend --- lib/files/cache/cache.php | 121 ++++++++++++++++++-------------- lib/files/cache/scanner.php | 1 + tests/lib/files/cache/cache.php | 82 +++++++++++----------- 3 files changed, 110 insertions(+), 94 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index f8808ad0b4..167cc5d13f 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -9,21 +9,36 @@ namespace OC\Files\Cache; class Cache { + /** + * @var \OC\Files\Storage\Storage + */ + private $storage; + /** * @var array partial data for the cache */ - private static $partial = array(); + private $partial = array(); + + private $storageId; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->storageId = $storage->getId(); + } /** * get the stored metadata of a file or folder * - * @param \OC\Files\File or int $file + * @param string/int $file * @return array */ - static public function get($file) { - if ($file instanceof \OC\Files\File) { + public function get($file) { + if (is_string($file)) { $where = 'WHERE `storage` = ? AND `path_hash` = ?'; - $params = array($file->getStorageId(), md5($file->getInternalPath())); + $params = array($this->storageId, md5($file)); } else { //file id $where = 'WHERE `fileid` = ?'; $params = array($file); @@ -35,23 +50,28 @@ class Cache { $data = $result->fetchRow(); //merge partial data - if (!$data and $file instanceof \OC\Files\File) { - $key = $file->getStorageId() . '::' . $file->getInternalPath(); - if (isset(self::$partial[$key])) { - $data = self::$partial[$key]; + if (!$data and is_string($file)) { + if (isset($this->partial[$file])) { + $data = $this->partial[$file]; } + } else { + //fix types + $data['fileid'] = (int)$data['fileid']; + $data['size'] = (int)$data['size']; + $data['mtime'] = (int)$data['mtime']; } + return $data; } /** * get the metadata of all files stored in $folder * - * @param \OC\Files\File $folder + * @param string $folder * @return array */ - static public function getFolderContents($folder) { - $fileId = self::getId($folder); + public function getFolderContents($folder) { + $fileId = $this->getId($folder); if ($fileId > -1) { $query = \OC_DB::prepare( 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime` @@ -66,43 +86,42 @@ class Cache { /** * store meta data for a file or folder * - * @param \OC\Files\File $file + * @param string $file * @param array $data * * @return int file id */ - static public function put(\OC\Files\File $file, array $data) { - if (($id = self::getId($file)) > -1) { - self::update($id, $data); + public function put($file, array $data) { + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); return $id; } else { - $key = $file->getStorageId() . '::' . $file->getInternalPath(); - if (isset(self::$partial[$key])) { //add any saved partial data - $data = array_merge(self::$partial[$key], $data); - unset(self::$partial[$key]); + if (isset($this->partial[$file])) { //add any saved partial data + $data = array_merge($this->partial[$file], $data); + unset($this->partial[$file]); } $requiredFields = array('size', 'mtime', 'mimetype'); foreach ($requiredFields as $field) { if (!isset($data[$field])) { //data not complete save as partial and return - self::$partial[$key] = $data; + $this->partial[$file] = $data; return -1; } } - $data['path'] = $file->getInternalPath(); - $data['parent'] = self::getParentId($file); - $data['name'] = basename($file->getInternalPath()); + $data['path'] = $file; + $data['parent'] = $this->getParentId($file); + $data['name'] = basename($file); - list($queryParts, $params) = self::buildParts($data); + list($queryParts, $params) = $this->buildParts($data); $queryParts[] = '`storage`'; - $params[] = $file->getStorageId(); + $params[] = $this->storageId; $valuesPlaceholder = array_fill(0, count($queryParts), '?'); $query = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ') VALUES(' . implode(', ', $valuesPlaceholder) . ')'); $query->execute($params); - return \OC_DB::insertid('*PREFIX*filecache'); + return (int) \OC_DB::insertid('*PREFIX*filecache'); } } @@ -112,8 +131,8 @@ class Cache { * @param int $id * @param array $data */ - static public function update($id, array $data) { - list($queryParts, $params) = self::buildParts($data); + public function update($id, array $data) { + list($queryParts, $params) = $this->buildParts($data); $params[] = $id; $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? WHERE fileid = ?'); @@ -126,7 +145,7 @@ class Cache { * @param array $data * @return array */ - private static function buildParts(array $data) { + static function buildParts(array $data) { $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime'); $params = array(); @@ -150,15 +169,14 @@ class Cache { /** * get the file id for a file * - * @param \OC\Files\File $file + * @param string $file * @return int */ - static public function getId(\OC\Files\File $file) { - $storageId = $file->getStorageId(); - $pathHash = md5($file->getInternalPath()); + public function getId($file) { + $pathHash = md5($file); $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); - $result = $query->execute(array($storageId, $pathHash)); + $result = $query->execute(array($this->storageId, $pathHash)); if ($row = $result->fetchRow()) { return $row['fileid']; @@ -170,48 +188,43 @@ class Cache { /** * get the id of the parent folder of a file * - * @param \OC\Files\File $file + * @param string $file * @return int */ - static public function getParentId(\OC\Files\File $file) { - $path = $file->getInternalPath(); - if ($path === '/' or $path === '') { + public function getParentId($file) { + if ($file === '/' or $file === '') { return -1; } else { - return self::getId(new \OC\Files\File($file->getStorage(), dirname($path))); + return $this->getId(dirname($file)); } } /** * check if a file is available in the cache * - * @param \OC\Files\File $file + * @param string $file * @return bool */ - static public function inCache(\OC\Files\File $file) { - return self::getId($file) != -1; + public function inCache($file) { + return $this->getId($file) != -1; } /** * remove a file or folder from the cache * - * @param \OC\Files\File $file + * @param string $file */ - static public function remove(\OC\Files\File $file) { - $storageId = $file->getStorageId(); - $pathHash = md5($file->getInternalPath()); + public function remove($file) { + $pathHash = md5($file); $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); - $query->execute(array($storageId, $pathHash)); + $query->execute(array($this->storageId, $pathHash)); } /** - * remove all entries for files that are stored on $storage form the cache - * - * @param \OC\Files\Storage\Storage $storage + * remove all entries for files that are stored on the storage from the cache */ - static public function removeStorage(\OC\Files\Storage\Storage $storage) { - $storageId = $storage->getId(); + public function clear() { $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE storage=?'); - $query->execute(array($storageId)); + $query->execute(array($this->storageId)); } } diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index e8e9c71c4e..17fc0fb2e8 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -15,6 +15,7 @@ class Scanner { /** * get all the metadata of a file or folder * * + * * @param \OC\Files\File $file * @return array with metadata of the file */ diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index 9f35cd889a..24dd38fa73 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -16,82 +16,83 @@ class Cache extends \UnitTestCase { */ private $storage; - private function createPath($path) { - return new \OC\Files\File($this->storage, $path); - } + /** + * @var \OC\Files\Cache\Cache $cache + */ + private $cache; public function testSimple() { - $file1 = $this->createPath('foo'); - $file2 = $this->createPath('foo/bar'); + $file1 = 'foo'; + $file2 = 'foo/bar'; $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); $data2 = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); - $this->assertFalse(FileCache::inCache($file1)); - $this->assertEqual(FileCache::get($file1), null); + $this->assertFalse($this->cache->inCache($file1)); + $this->assertEqual($this->cache->get($file1), null); - $id1 = FileCache::put($file1, $data1); - $this->assertTrue(FileCache::inCache($file1)); - $cacheData1 = FileCache::get($file1); + $id1 = $this->cache->put($file1, $data1); + $this->assertTrue($this->cache->inCache($file1)); + $cacheData1 = $this->cache->get($file1); foreach ($data1 as $key => $value) { $this->assertEqual($value, $cacheData1[$key]); } $this->assertEqual($cacheData1['fileid'], $id1); - $this->assertEqual($id1, FileCache::getId($file1)); + $this->assertEqual($id1, $this->cache->getId($file1)); - $this->assertFalse(FileCache::inCache($file2)); - $id2 = FileCache::put($file2, $data2); - $this->assertTrue(FileCache::inCache($file2)); - $cacheData2 = FileCache::get($file2); + $this->assertFalse($this->cache->inCache($file2)); + $id2 = $this->cache->put($file2, $data2); + $this->assertTrue($this->cache->inCache($file2)); + $cacheData2 = $this->cache->get($file2); foreach ($data2 as $key => $value) { $this->assertEqual($value, $cacheData2[$key]); } $this->assertEqual($cacheData1['fileid'], $cacheData2['parent']); $this->assertEqual($cacheData2['fileid'], $id2); - $this->assertEqual($id2, FileCache::getId($file2)); - $this->assertEqual($id1, FileCache::getParentId($file2)); + $this->assertEqual($id2, $this->cache->getId($file2)); + $this->assertEqual($id1, $this->cache->getParentId($file2)); $newSize = 1050; - $newId2 = FileCache::put($file2, array('size' => $newSize)); - $cacheData2 = FileCache::get($file2); + $newId2 = $this->cache->put($file2, array('size' => $newSize)); + $cacheData2 = $this->cache->get($file2); $this->assertEqual($newId2, $id2); $this->assertEqual($cacheData2['size'], $newSize); - $this->assertEqual($cacheData1, FileCache::get($file1)); + $this->assertEqual($cacheData1, $this->cache->get($file1)); - FileCache::remove($file2); - $this->assertFalse(FileCache::inCache($file2)); - $this->assertEqual(FileCache::get($file2), null); - $this->assertTrue(FileCache::inCache($file1)); + $this->cache->remove($file2); + $this->assertFalse($this->cache->inCache($file2)); + $this->assertEqual($this->cache->get($file2), null); + $this->assertTrue($this->cache->inCache($file1)); - $this->assertEqual($cacheData1, FileCache::get($id1)); + $this->assertEqual($cacheData1, $this->cache->get($id1)); } public function testPartial() { - $file1 = $this->createPath('foo'); + $file1 = 'foo'; - FileCache::put($file1, array('size' => 10)); - $this->assertEqual(array('size' => 10), FileCache::get($file1)); + $this->cache->put($file1, array('size' => 10)); + $this->assertEqual(array('size' => 10), $this->cache->get($file1)); - FileCache::put($file1, array('mtime' => 15)); - $this->assertEqual(array('size' => 10, 'mtime' => 15), FileCache::get($file1)); + $this->cache->put($file1, array('mtime' => 15)); + $this->assertEqual(array('size' => 10, 'mtime' => 15), $this->cache->get($file1)); - FileCache::put($file1, array('size' => 12)); - $this->assertEqual(array('size' => 12, 'mtime' => 15), FileCache::get($file1)); + $this->cache->put($file1, array('size' => 12)); + $this->assertEqual(array('size' => 12, 'mtime' => 15), $this->cache->get($file1)); } public function testFolder() { - $file1 = $this->createPath('folder'); - $file2 = $this->createPath('folder/bar'); - $file3 = $this->createPath('folder/foo'); + $file1 = 'folder'; + $file2 = 'folder/bar'; + $file3 = 'folder/foo'; $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); $fileData = array(); $fileData['bar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); $fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file'); - FileCache::put($file1, $data1); - FileCache::put($file2, $fileData['bar']); - FileCache::put($file3, $fileData['foo']); + $this->cache->put($file1, $data1); + $this->cache->put($file2, $fileData['bar']); + $this->cache->put($file3, $fileData['foo']); - $content = FileCache::getFolderContents($file1); + $content = $this->cache->getFolderContents($file1); $this->assertEqual(count($content), 2); foreach ($content as $cachedData) { $data = $fileData[$cachedData['name']]; @@ -102,10 +103,11 @@ class Cache extends \UnitTestCase { } public function tearDown() { - FileCache::removeStorage($this->storage); + $this->cache->clear(); } public function setUp() { $this->storage = new \OC\Files\Storage\Temporary(array()); + $this->cache = new \OC\Files\Cache\Cache($this->storage); } } From 92555eff711ff391f3af5ef6fb6f6414bfe012cf Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 2 Oct 2012 23:34:45 +0200 Subject: [PATCH 016/418] add encrypted column to the new filecache --- lib/files/cache/cache.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 167cc5d13f..9d8275e5e0 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -44,7 +44,7 @@ class Cache { $params = array($file); } $query = \OC_DB::prepare( - 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime` + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` FROM `*PREFIX*filecache` ' . $where); $result = $query->execute($params); $data = $result->fetchRow(); @@ -59,6 +59,7 @@ class Cache { $data['fileid'] = (int)$data['fileid']; $data['size'] = (int)$data['size']; $data['mtime'] = (int)$data['mtime']; + $data['encrypted'] = (bool)$data['encrypted']; } return $data; @@ -74,7 +75,7 @@ class Cache { $fileId = $this->getId($folder); if ($fileId > -1) { $query = \OC_DB::prepare( - 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime` + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` FROM `*PREFIX*filecache` WHERE parent = ?'); $result = $query->execute(array($fileId)); return $result->fetchAll(); @@ -112,6 +113,7 @@ class Cache { $data['path'] = $file; $data['parent'] = $this->getParentId($file); $data['name'] = basename($file); + $data['encrypted'] = isset($data['encrypted']) ? ((int)$data['encrypted']) : 0; list($queryParts, $params) = $this->buildParts($data); $queryParts[] = '`storage`'; @@ -121,7 +123,7 @@ class Cache { $query = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ') VALUES(' . implode(', ', $valuesPlaceholder) . ')'); $query->execute($params); - return (int) \OC_DB::insertid('*PREFIX*filecache'); + return (int)\OC_DB::insertid('*PREFIX*filecache'); } } @@ -146,7 +148,7 @@ class Cache { * @return array */ static function buildParts(array $data) { - $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime'); + $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'encrypted'); $params = array(); $queryParts = array(); From 1ed89760be5e1c5cf5b3fa7d40d845750b2840ab Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 2 Oct 2012 23:35:51 +0200 Subject: [PATCH 017/418] dont need this anymore --- tests/lib/files/cache/cache.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index 24dd38fa73..48cdc1c8c8 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -8,8 +8,6 @@ namespace Test\Files\Cache; -use \OC\Files\Cache\Cache as FileCache; - class Cache extends \UnitTestCase { /** * @var \OC\Files\Storage\Temporary $storage; From 6134e554f2306c5309a793d4d3149a0f9b05ea5f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 2 Oct 2012 23:46:35 +0200 Subject: [PATCH 018/418] new filecache table --- db_structure.xml | 114 +++++++++++++++++------------------------------ lib/util.php | 2 +- 2 files changed, 41 insertions(+), 75 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index 99a30cb613..8bbc6e5cc6 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -53,19 +53,27 @@ - *dbprefix*fscache + *dbprefix*filecache - id - 1 + fileid integer 0 true + 1 4 + + storage + text + + true + 64 + + path text @@ -85,9 +93,9 @@ parent integer - 0 + true - 8 + 4 @@ -95,39 +103,7 @@ text true - 300 - - - - user - text - - true - 64 - - - - size - integer - 0 - true - 8 - - - - ctime - integer - 0 - true - 8 - - - - mtime - integer - 0 - true - 8 + 250 @@ -135,7 +111,7 @@ text true - 96 + 64 @@ -146,32 +122,37 @@ 32 + + size + integer + + true + 4 + + + + mtime + integer + + true + 4 + + encrypted integer 0 true - 1 - - - - versioned - integer - 0 - true - 1 - - - - writable - integer - 0 - true - 1 + 4 - fscache_path_hash_index + storage_path_hash + true + + storage + ascending + path_hash ascending @@ -179,23 +160,8 @@ - parent_index - - parent - ascending - - - - - name_index - - name - ascending - - - - - parent_name_index + parent_name_hash + true parent ascending diff --git a/lib/util.php b/lib/util.php index 31dd376b9c..264d61319d 100755 --- a/lib/util.php +++ b/lib/util.php @@ -81,7 +81,7 @@ class OC_Util { */ public static function getVersion() { // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.9.0. This is not visible to the user - return array(4,86,11); + return array(4,86,12); } /** From 636c75ce061492751d0fb001e0bf7219df379f09 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Oct 2012 11:23:33 +0200 Subject: [PATCH 019/418] better parent path bahaviour for the filecache --- lib/files/cache/cache.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 9d8275e5e0..79673771e5 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -194,10 +194,14 @@ class Cache { * @return int */ public function getParentId($file) { - if ($file === '/' or $file === '') { + if ($file === '') { return -1; } else { - return $this->getId(dirname($file)); + $parent = dirname($file); + if ($parent === '.') { + $parent = ''; + } + return $this->getId($parent); } } From 96d7cd59978cc416bc0d9c5ac487af23692ef1d8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Oct 2012 11:24:10 +0200 Subject: [PATCH 020/418] correct namespace usage in common filestorage backend --- lib/files/storage/common.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index b2b4ea8eee..3886a04182 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -57,19 +57,19 @@ abstract class Common extends \OC\Files\Storage\Storage { public function getPermissions($path){ $permissions = 0; if($this->isCreatable($path)){ - $permissions |= OCP\Share::PERMISSION_CREATE; + $permissions |= \OCP\Share::PERMISSION_CREATE; } if($this->isReadable($path)){ - $permissions |= OCP\Share::PERMISSION_READ; + $permissions |= \OCP\Share::PERMISSION_READ; } if($this->isUpdatable($path)){ - $permissions |= OCP\Share::PERMISSION_UPDATE; + $permissions |= \OCP\Share::PERMISSION_UPDATE; } if($this->isDeletable($path)){ - $permissions |= OCP\Share::PERMISSION_DELETE; + $permissions |= \OCP\Share::PERMISSION_DELETE; } if($this->isSharable($path)){ - $permissions |= OCP\Share::PERMISSION_SHARE; + $permissions |= \OCP\Share::PERMISSION_SHARE; } return $permissions; } From e415e90c6d8a2e8bb129be7c63a7077c56ab3da8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Oct 2012 11:24:49 +0200 Subject: [PATCH 021/418] make filestorage scanner non-static and add a simple test case --- lib/files/cache/scanner.php | 64 ++++++++++++++++++++----------- tests/lib/files/cache/cache.php | 1 + tests/lib/files/cache/scanner.php | 47 +++++++++++++++++++++++ 3 files changed, 90 insertions(+), 22 deletions(-) create mode 100644 tests/lib/files/cache/scanner.php diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 17fc0fb2e8..721d0d825e 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -9,29 +9,42 @@ namespace OC\Files\Cache; class Scanner { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + private $cache; + const SCAN_RECURSIVE = true; const SCAN_SHALLOW = false; + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->cache = new Cache($storage); + } + /** * get all the metadata of a file or folder * * * - * @param \OC\Files\File $file + * @param string $path * @return array with metadata of the file */ - public static function getData(\OC\Files\File $file) { + public function getData($path) { $data = array(); - $storage = $file->getStorage(); - $path = $file->getInternalPath(); - if (!$storage->isReadable($path)) return null; //cant read, nothing we can do - $data['mimetype'] = $storage->getMimeType($path); - $data['mtime'] = $storage->filemtime($path); + if (!$this->storage->isReadable($path)) return null; //cant read, nothing we can do + $data['mimetype'] = $this->storage->getMimeType($path); + $data['mtime'] = $this->storage->filemtime($path); if ($data['mimetype'] == 'httpd/unix-directory') { $data['size'] = -1; //unknown - $data['permissions'] = $storage->getPermissions($path . '/'); + $data['permissions'] = $this->storage->getPermissions($path . '/'); } else { - $data['size'] = $storage->filesize($path); - $data['permissions'] = $storage->getPermissions($path); + $data['size'] = $this->storage->filesize($path); + $data['permissions'] = $this->storage->getPermissions($path); } return $data; } @@ -39,33 +52,40 @@ class Scanner { /** * scan a single file and store it in the cache * - * @param \OC\Files\File $file + * @param string $file * @return array with metadata of the scanned file */ - public static function scanFile(\OC\Files\File $file) { - $data = self::getData($file); - Cache::put($file, $data); + public function scanFile($file) { + $data = $this->getData($file); + if ($file !== '') { + $parent = dirname($file); + if ($parent === '.') { + $parent = ''; + } + if (!$this->cache->inCache($parent)) { + $this->scanFile($parent); + } + } + $this->cache->put($file, $data); return $data; } /** * scan all the files in a folder and store them in the cache * - * @param \OC\Files\File $folder + * @param string $path * @param SCAN_RECURSIVE/SCAN_SHALLOW $recursive * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ - public static function scan(\OC\Files\File $folder, $recursive) { + public function scan($path, $recursive) { $size = 0; - $storage = $folder->getStorage(); - $path = $folder->getInternalPath(); - if ($dh = $storage->opendir($path)) { + if ($dh = $this->storage->opendir($path)) { while ($file = readdir($dh)) { if ($file !== '.' and $file !== '..') { - $child = new \OC\Files\File($storage, $path . '/' . $file); - $data = self::scanFile($child); + $child = $path . '/' . $file; + $data = $this->scanFile($child); if ($recursive === self::SCAN_RECURSIVE and $data['mimetype'] === 'httpd/unix-directory') { - $data['size'] = self::scan($child, self::SCAN_RECURSIVE); + $data['size'] = $this->scan($child, self::SCAN_RECURSIVE); } if ($data['size'] >= 0 and $size >= 0) { $size += $data['size']; diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index 48cdc1c8c8..8cedadbf19 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -34,6 +34,7 @@ class Cache extends \UnitTestCase { foreach ($data1 as $key => $value) { $this->assertEqual($value, $cacheData1[$key]); } + $this->assertEqual($cacheData1['mimepart'], 'foo'); $this->assertEqual($cacheData1['fileid'], $id1); $this->assertEqual($id1, $this->cache->getId($file1)); diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php new file mode 100644 index 0000000000..3d1c1546ab --- /dev/null +++ b/tests/lib/files/cache/scanner.php @@ -0,0 +1,47 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +class Scanner extends \UnitTestCase { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var \OC\Files\Cache\Scanner $scanner + */ + private $scanner; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + private $cache; + + function testFile() { + $data = "dummy file data\n"; + $this->storage->file_put_contents('foo.txt', $data); + $this->scanner->scanFile('foo.txt'); + + $this->assertEqual($this->cache->inCache('foo.txt'), true); + $cachedData = $this->cache->get('foo.txt'); + $this->assertEqual($cachedData['size'], strlen($data)); + $this->assertEqual($cachedData['mimetype'], 'text/plain'); + } + + function setUp() { + $this->storage = new \OC\Files\Storage\Temporary(array()); + $this->scanner = new \OC\Files\Cache\Scanner($this->storage); + $this->cache = new \OC\Files\Cache\Cache($this->storage); + } + + function tearDown() { +// $this->cache->clear(); + } +} From 3c8e5ea3581cbbfc5acd3c229080bc63548d2827 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Oct 2012 11:31:13 +0200 Subject: [PATCH 022/418] this index cant be unique due to collisions of the root of different storages --- db_structure.xml | 1 - lib/util.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index 8bbc6e5cc6..e0b9dc11e9 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -161,7 +161,6 @@ parent_name_hash - true parent ascending diff --git a/lib/util.php b/lib/util.php index 264d61319d..be9d1f4fb5 100755 --- a/lib/util.php +++ b/lib/util.php @@ -81,7 +81,7 @@ class OC_Util { */ public static function getVersion() { // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.9.0. This is not visible to the user - return array(4,86,12); + return array(4,86,13); } /** From 85be00be6595a3af168f6df963ffdeac8c3c6d5e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Oct 2012 11:40:09 +0200 Subject: [PATCH 023/418] add some more test cases for the scanner --- lib/files/cache/scanner.php | 6 ++++-- tests/lib/files/cache/scanner.php | 23 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 721d0d825e..7d449204e8 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -77,12 +77,14 @@ class Scanner { * @param SCAN_RECURSIVE/SCAN_SHALLOW $recursive * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ - public function scan($path, $recursive) { + public function scan($path, $recursive = self::SCAN_RECURSIVE) { + $this->scanFile($path); + $size = 0; if ($dh = $this->storage->opendir($path)) { while ($file = readdir($dh)) { if ($file !== '.' and $file !== '..') { - $child = $path . '/' . $file; + $child = ($path !== '') ? $path . '/' . $file : $file; $data = $this->scanFile($child); if ($recursive === self::SCAN_RECURSIVE and $data['mimetype'] === 'httpd/unix-directory') { $data['size'] = $this->scan($child, self::SCAN_RECURSIVE); diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index 3d1c1546ab..7ee28dfc3f 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -33,6 +33,27 @@ class Scanner extends \UnitTestCase { $cachedData = $this->cache->get('foo.txt'); $this->assertEqual($cachedData['size'], strlen($data)); $this->assertEqual($cachedData['mimetype'], 'text/plain'); + $this->assertNotEqual($cachedData['parent'], -1); //parent folders should be scanned automatically + + $data = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->storage->file_put_contents('foo.png', $data); + $this->scanner->scanFile('foo.png'); + + $this->assertEqual($this->cache->inCache('foo.png'), true); + $cachedData = $this->cache->get('foo.png'); + $this->assertEqual($cachedData['size'], strlen($data)); + $this->assertEqual($cachedData['mimetype'], 'image/png'); + } + + function testFolder() { + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->storage->file_put_contents('foo.txt', $textData); + $this->storage->file_put_contents('foo.png', $imgData); + + $this->scanner->scan(''); + $this->assertEqual($this->cache->inCache('foo.txt'), true); + $this->assertEqual($this->cache->inCache('foo.png'), true); } function setUp() { @@ -42,6 +63,6 @@ class Scanner extends \UnitTestCase { } function tearDown() { -// $this->cache->clear(); + $this->cache->clear(); } } From b9b9fd9dbaae47b3a15aed9694c18b95404550b0 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Oct 2012 13:07:19 +0200 Subject: [PATCH 024/418] more tests and fixes for the filesystem scanner --- lib/files/cache/scanner.php | 15 +++++++++--- tests/lib/files/cache/scanner.php | 39 ++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 7d449204e8..e8f54c34be 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -86,15 +86,24 @@ class Scanner { if ($file !== '.' and $file !== '..') { $child = ($path !== '') ? $path . '/' . $file : $file; $data = $this->scanFile($child); - if ($recursive === self::SCAN_RECURSIVE and $data['mimetype'] === 'httpd/unix-directory') { - $data['size'] = $this->scan($child, self::SCAN_RECURSIVE); + if ($data['mimetype'] === 'httpd/unix-directory') { + if ($recursive === self::SCAN_RECURSIVE) { + $data['size'] = $this->scan($child, self::SCAN_RECURSIVE); + } else { + $data['size'] = -1; + } } - if ($data['size'] >= 0 and $size >= 0) { + if ($data['size'] === -1) { + $size = -1; + } elseif ($size !== -1) { $size += $data['size']; } } } } + if ($size !== -1) { + $this->cache->put($path, array('size' => $size)); + } return $size; } } diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index 7ee28dfc3f..41286e69d3 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -45,15 +45,52 @@ class Scanner extends \UnitTestCase { $this->assertEqual($cachedData['mimetype'], 'image/png'); } - function testFolder() { + private function fillTestFolders() { $textData = "dummy file data\n"; $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->storage->mkdir('folder'); $this->storage->file_put_contents('foo.txt', $textData); $this->storage->file_put_contents('foo.png', $imgData); + $this->storage->file_put_contents('folder/bar.txt', $textData); + } + + function testFolder() { + $this->fillTestFolders(); $this->scanner->scan(''); + $this->assertEqual($this->cache->inCache(''), true); $this->assertEqual($this->cache->inCache('foo.txt'), true); $this->assertEqual($this->cache->inCache('foo.png'), true); + $this->assertEqual($this->cache->inCache('folder'), true); + $this->assertEqual($this->cache->inCache('folder/bar.txt'), true); + + $cachedDataText = $this->cache->get('foo.txt'); + $cachedDataText2 = $this->cache->get('foo.txt'); + $cachedDataImage = $this->cache->get('foo.png'); + $cachedDataFolder = $this->cache->get(''); + $cachedDataFolder2 = $this->cache->get('folder'); + + $this->assertEqual($cachedDataImage['parent'], $cachedDataText['parent']); + $this->assertEqual($cachedDataFolder['fileid'], $cachedDataImage['parent']); + $this->assertEqual($cachedDataFolder['size'], $cachedDataImage['size'] + $cachedDataText['size'] + $cachedDataText2['size']); + $this->assertEqual($cachedDataFolder2['size'], $cachedDataText2['size']); + } + + function testShallow() { + $this->fillTestFolders(); + + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $this->assertEqual($this->cache->inCache(''), true); + $this->assertEqual($this->cache->inCache('foo.txt'), true); + $this->assertEqual($this->cache->inCache('foo.png'), true); + $this->assertEqual($this->cache->inCache('folder'), true); + $this->assertEqual($this->cache->inCache('folder/bar.txt'), false); + + $cachedDataFolder = $this->cache->get(''); + $cachedDataFolder2 = $this->cache->get('folder'); + + $this->assertEqual($cachedDataFolder['size'], -1); + $this->assertEqual($cachedDataFolder2['size'], -1); } function setUp() { From c815fd5a5c8cba52b24327584e689498cb03fb3e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 8 Oct 2012 14:31:13 +0200 Subject: [PATCH 025/418] fix files_external test cases --- apps/files_external/tests/dropbox.php | 8 +++++--- apps/files_external/tests/smb.php | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/files_external/tests/dropbox.php b/apps/files_external/tests/dropbox.php index 64eb2556c9..e77e0767c9 100644 --- a/apps/files_external/tests/dropbox.php +++ b/apps/files_external/tests/dropbox.php @@ -6,19 +6,21 @@ * See the COPYING-README file. */ +namespace Test\Files\Storage; + $config=include('files_external/tests/config.php'); if(!is_array($config) or !isset($config['dropbox']) or !$config['dropbox']['run']) { - abstract class Test_Filestorage_Dropbox extends Test_FileStorage{} + abstract class Dropbox extends Storage{} return; }else{ - class Test_Filestorage_Dropbox extends Test_FileStorage { + class Dropbox extends Storage { private $config; public function setUp() { $id=uniqid(); $this->config=include('files_external/tests/config.php'); $this->config['dropbox']['root'].='/'.$id;//make sure we have an new empty folder to work in - $this->instance=new OC_Filestorage_Dropbox($this->config['dropbox']); + $this->instance=new \OC\Files\Storage\Dropbox($this->config['dropbox']); } public function tearDown() { diff --git a/apps/files_external/tests/smb.php b/apps/files_external/tests/smb.php index 87b110a241..24728dc237 100644 --- a/apps/files_external/tests/smb.php +++ b/apps/files_external/tests/smb.php @@ -6,9 +6,10 @@ * See the COPYING-README file. */ +namespace Test\Files\Storage; + $config=include('apps/files_external/tests/config.php'); -namespace Test\Files\Storage; if(!is_array($config) or !isset($config['smb']) or !$config['smb']['run']) { abstract class SMB extends Storage{} From 13515effc99bfad7e776d00476e897fa396a8c6c Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 8 Oct 2012 14:58:21 +0200 Subject: [PATCH 026/418] add Cache::getStatus --- lib/files/cache/cache.php | 29 +++++++++++++++++++++++++++++ tests/lib/files/cache/cache.php | 10 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 79673771e5..5ef49246ea 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -9,6 +9,11 @@ namespace OC\Files\Cache; class Cache { + const NOT_FOUND = 0; + const PARTIAL = 1; //only partial data available, file not cached in the database + const SHALLOW = 2; //folder in cache, but not all child files are completely scanned + const COMPLETE = 3; + /** * @var \OC\Files\Storage\Storage */ @@ -233,4 +238,28 @@ class Cache { $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE storage=?'); $query->execute(array($this->storageId)); } + + /** + * @param string $file + * + * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + $pathHash = md5($file); + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); + $result = $query->execute(array($this->storageId, $pathHash)); + if ($row = $result->fetchRow()) { + if ((int)$row['size'] === -1) { + return self::SHALLOW; + } else { + return self::COMPLETE; + } + } else { + if (isset($this->partial[$file])) { + return self::PARTIAL; + } else { + return self::NOT_FOUND; + } + } + } } diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index 8cedadbf19..177cf1c045 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -101,6 +101,16 @@ class Cache extends \UnitTestCase { } } + function testStatus() { + $this->assertEquals(\OC\Files\Cache\Cache::NOT_FOUND, $this->cache->getStatus('foo')); + $this->cache->put('foo', array('size' => -1)); + $this->assertEquals(\OC\Files\Cache\Cache::PARTIAL, $this->cache->getStatus('foo')); + $this->cache->put('foo', array('size' => -1, 'mtime' => 20, 'mimetype' => 'foo/file')); + $this->assertEquals(\OC\Files\Cache\Cache::SHALLOW, $this->cache->getStatus('foo')); + $this->cache->put('foo', array('size' => 10)); + $this->assertEquals(\OC\Files\Cache\Cache::COMPLETE, $this->cache->getStatus('foo')); + } + public function tearDown() { $this->cache->clear(); } From d717a5e55ca54c6ddbaf2f35f0f19b707dcfe1c8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 10 Oct 2012 11:51:14 +0200 Subject: [PATCH 027/418] remove old filecache --- lib/filecache.php | 511 --------------------------------------- lib/filecache/cached.php | 81 ------- lib/filecache/update.php | 217 ----------------- lib/filesystem.php | 12 +- lib/util.php | 7 - 5 files changed, 2 insertions(+), 826 deletions(-) delete mode 100644 lib/filecache.php delete mode 100644 lib/filecache/cached.php delete mode 100644 lib/filecache/update.php diff --git a/lib/filecache.php b/lib/filecache.php deleted file mode 100644 index 8fcb6fd940..0000000000 --- a/lib/filecache.php +++ /dev/null @@ -1,511 +0,0 @@ -. -* -*/ - -/** - * provide caching for filesystem info in the database - * - * not used by OC_Filesystem for reading filesystem info, - * instread apps should use OC_FileCache::get where possible - * - * It will try to keep the data up to date but changes from outside ownCloud can invalidate the cache - */ -class OC_FileCache{ - /** - * get the filesystem info from the cache - * @param string path - * @param string root (optional) - * @return array - * - * returns an associative array with the following keys: - * - size - * - mtime - * - ctime - * - mimetype - * - encrypted - * - versioned - */ - public static function get($path,$root=false) { - if(OC_FileCache_Update::hasUpdated($path,$root)) { - if($root===false) {//filesystem hooks are only valid for the default root - OC_Hook::emit('OC_Filesystem','post_write',array('path'=>$path)); - }else{ - OC_FileCache_Update::update($path,$root); - } - } - return OC_FileCache_Cached::get($path,$root); - } - - /** - * put filesystem info in the cache - * @param string $path - * @param array data - * @param string root (optional) - * - * $data is an assiciative array in the same format as returned by get - */ - public static function put($path,$data,$root=false) { - if($root===false) { - $root=OC_Filesystem::getRoot(); - } - $fullpath=$root.$path; - $parent=self::getParentId($fullpath); - $id=self::getId($fullpath,''); - if(isset(OC_FileCache_Cached::$savedData[$fullpath])) { - $data=array_merge(OC_FileCache_Cached::$savedData[$fullpath],$data); - unset(OC_FileCache_Cached::$savedData[$fullpath]); - } - if($id!=-1) { - self::update($id,$data); - return; - } - - // add parent directory to the file cache if it does not exist yet. - if ($parent == -1 && $fullpath != $root) { - $parentDir = dirname($path); - self::scanFile($parentDir); - $parent = self::getParentId($fullpath); - } - - if(!isset($data['size']) or !isset($data['mtime'])) {//save incomplete data for the next time we write it - OC_FileCache_Cached::$savedData[$fullpath]=$data; - return; - } - if(!isset($data['encrypted'])) { - $data['encrypted']=false; - } - if(!isset($data['versioned'])) { - $data['versioned']=false; - } - $mimePart=dirname($data['mimetype']); - $data['size']=(int)$data['size']; - $data['ctime']=(int)$data['mtime']; - $data['writable']=(int)$data['writable']; - $data['encrypted']=(int)$data['encrypted']; - $data['versioned']=(int)$data['versioned']; - $user=OC_User::getUser(); - $query=OC_DB::prepare('INSERT INTO `*PREFIX*fscache`(`parent`, `name`, `path`, `path_hash`, `size`, `mtime`, `ctime`, `mimetype`, `mimepart`,`user`,`writable`,`encrypted`,`versioned`) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)'); - $result=$query->execute(array($parent,basename($fullpath),$fullpath,md5($fullpath),$data['size'],$data['mtime'],$data['ctime'],$data['mimetype'],$mimePart,$user,$data['writable'],$data['encrypted'],$data['versioned'])); - if(OC_DB::isError($result)) { - OC_Log::write('files','error while writing file('.$fullpath.') to cache',OC_Log::ERROR); - } - - if($cache=OC_Cache::getUserCache(true)) { - $cache->remove('fileid/'.$fullpath);//ensure we don't have -1 cached - } - } - - /** - * update filesystem info of a file - * @param int $id - * @param array $data - */ - private static function update($id,$data) { - $arguments=array(); - $queryParts=array(); - foreach(array('size','mtime','ctime','mimetype','encrypted','versioned','writable') as $attribute) { - if(isset($data[$attribute])) { - //Convert to int it args are false - if($data[$attribute] === false) { - $arguments[] = 0; - }else{ - $arguments[] = $data[$attribute]; - } - $queryParts[]='`'.$attribute.'`=?'; - } - } - if(isset($data['mimetype'])) { - $arguments[]=dirname($data['mimetype']); - $queryParts[]='`mimepart`=?'; - } - $arguments[]=$id; - - $sql = 'UPDATE `*PREFIX*fscache` SET '.implode(' , ',$queryParts).' WHERE `id`=?'; - $query=OC_DB::prepare($sql); - $result=$query->execute($arguments); - if(OC_DB::isError($result)) { - OC_Log::write('files','error while updating file('.$id.') in cache',OC_Log::ERROR); - } - } - - /** - * register a file move in the cache - * @param string oldPath - * @param string newPath - * @param string root (optional) - */ - public static function move($oldPath,$newPath,$root=false) { - if($root===false) { - $root=OC_Filesystem::getRoot(); - } - // If replacing an existing file, delete the file - if (self::inCache($newPath, $root)) { - self::delete($newPath, $root); - } - $oldPath=$root.$oldPath; - $newPath=$root.$newPath; - $newParent=self::getParentId($newPath); - $query=OC_DB::prepare('UPDATE `*PREFIX*fscache` SET `parent`=? ,`name`=?, `path`=?, `path_hash`=? WHERE `path_hash`=?'); - $query->execute(array($newParent,basename($newPath),$newPath,md5($newPath),md5($oldPath))); - - if(($cache=OC_Cache::getUserCache(true)) && $cache->hasKey('fileid/'.$oldPath)) { - $cache->set('fileid/'.$newPath,$cache->get('fileid/'.$oldPath)); - $cache->remove('fileid/'.$oldPath); - } - - $query=OC_DB::prepare('SELECT `path` FROM `*PREFIX*fscache` WHERE `path` LIKE ?'); - $oldLength=strlen($oldPath); - $updateQuery=OC_DB::prepare('UPDATE `*PREFIX*fscache` SET `path`=?, `path_hash`=? WHERE `path_hash`=?'); - while($row= $query->execute(array($oldPath.'/%'))->fetchRow()) { - $old=$row['path']; - $new=$newPath.substr($old,$oldLength); - $updateQuery->execute(array($new,md5($new),md5($old))); - - if(($cache=OC_Cache::getUserCache(true)) && $cache->hasKey('fileid/'.$old)) { - $cache->set('fileid/'.$new,$cache->get('fileid/'.$old)); - $cache->remove('fileid/'.$old); - } - } - } - - /** - * delete info from the cache - * @param string path - * @param string root (optional) - */ - public static function delete($path,$root=false) { - if($root===false) { - $root=OC_Filesystem::getRoot(); - } - $query=OC_DB::prepare('DELETE FROM `*PREFIX*fscache` WHERE `path_hash`=?'); - $query->execute(array(md5($root.$path))); - - //delete everything inside the folder - $query=OC_DB::prepare('DELETE FROM `*PREFIX*fscache` WHERE `path` LIKE ?'); - $query->execute(array($root.$path.'/%')); - - OC_Cache::remove('fileid/'.$root.$path); - } - - /** - * return array of filenames matching the querty - * @param string $query - * @param boolean $returnData - * @param string root (optional) - * @return array of filepaths - */ - public static function search($search,$returnData=false,$root=false) { - if($root===false) { - $root=OC_Filesystem::getRoot(); - } - $rootLen=strlen($root); - if(!$returnData) { - $query=OC_DB::prepare('SELECT `path` FROM `*PREFIX*fscache` WHERE `name` LIKE ? AND `user`=?'); - }else{ - $query=OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` WHERE `name` LIKE ? AND `user`=?'); - } - $result=$query->execute(array("%$search%",OC_User::getUser())); - $names=array(); - while($row=$result->fetchRow()) { - if(!$returnData) { - $names[]=substr($row['path'],$rootLen); - }else{ - $row['path']=substr($row['path'],$rootLen); - $names[]=$row; - } - } - return $names; - } - - /** - * get all files and folders in a folder - * @param string path - * @param string root (optional) - * @return array - * - * returns an array of assiciative arrays with the following keys: - * - name - * - size - * - mtime - * - ctime - * - mimetype - * - encrypted - * - versioned - */ - public static function getFolderContent($path,$root=false,$mimetype_filter='') { - if(OC_FileCache_Update::hasUpdated($path,$root,true)) { - OC_FileCache_Update::updateFolder($path,$root); - } - return OC_FileCache_Cached::getFolderContent($path,$root,$mimetype_filter); - } - - /** - * check if a file or folder is in the cache - * @param string $path - * @param string root (optional) - * @return bool - */ - public static function inCache($path,$root=false) { - return self::getId($path,$root)!=-1; - } - - /** - * get the file id as used in the cache - * @param string path - * @param string root (optional) - * @return int - */ - public static function getId($path,$root=false) { - if($root===false) { - $root=OC_Filesystem::getRoot(); - } - - $fullPath=$root.$path; - if(($cache=OC_Cache::getUserCache(true)) && $cache->hasKey('fileid/'.$fullPath)) { - return $cache->get('fileid/'.$fullPath); - } - - $query=OC_DB::prepare('SELECT `id` FROM `*PREFIX*fscache` WHERE `path_hash`=?'); - $result=$query->execute(array(md5($fullPath))); - if(OC_DB::isError($result)) { - OC_Log::write('files','error while getting file id of '.$path,OC_Log::ERROR); - return -1; - } - - $result=$result->fetchRow(); - if(is_array($result)) { - $id=$result['id']; - }else{ - $id=-1; - } - if($cache=OC_Cache::getUserCache(true)) { - $cache->set('fileid/'.$fullPath,$id); - } - - return $id; - } - - /** - * get the file path from the id, relative to the home folder of the user - * @param int id - * @param string user (optional) - * @return string - */ - public static function getPath($id,$user='') { - if(!$user) { - $user=OC_User::getUser(); - } - $query=OC_DB::prepare('SELECT `path` FROM `*PREFIX*fscache` WHERE `id`=? AND `user`=?'); - $result=$query->execute(array($id,$user)); - $row=$result->fetchRow(); - $path=$row['path']; - $root='/'.$user.'/files'; - if(substr($path,0,strlen($root))!=$root) { - return false; - } - return substr($path,strlen($root)); - } - - /** - * get the file id of the parent folder, taking into account '/' has no parent - * @param string $path - * @return int - */ - private static function getParentId($path) { - if($path=='/') { - return -1; - }else{ - return self::getId(dirname($path),''); - } - } - - /** - * adjust the size of the parent folders - * @param string $path - * @param int $sizeDiff - * @param string root (optinal) - */ - public static function increaseSize($path,$sizeDiff, $root=false) { - if($sizeDiff==0) return; - $id=self::getId($path,$root); - while($id!=-1) {//walk up the filetree increasing the size of all parent folders - $query=OC_DB::prepare('UPDATE `*PREFIX*fscache` SET `size`=`size`+? WHERE `id`=?'); - $query->execute(array($sizeDiff,$id)); - $id=self::getParentId($path); - $path=dirname($path); - } - } - - /** - * recursively scan the filesystem and fill the cache - * @param string $path - * @param OC_EventSource $enventSource (optional) - * @param int count (optional) - * @param string root (optional) - */ - public static function scan($path,$eventSource=false,&$count=0,$root=false) { - if($eventSource) { - $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); - } - $lastSend=$count; - // NOTE: Ugly hack to prevent shared files from going into the cache (the source already exists somewhere in the cache) - if (substr($path, 0, 7) == '/Shared') { - return; - } - if($root===false) { - $view=OC_Filesystem::getView(); - }else{ - $view=new OC_FilesystemView($root); - } - self::scanFile($path,$root); - $dh=$view->opendir($path.'/'); - $totalSize=0; - if($dh) { - while (($filename = readdir($dh)) !== false) { - if($filename != '.' and $filename != '..') { - $file=$path.'/'.$filename; - if($view->is_dir($file.'/')) { - self::scan($file,$eventSource,$count,$root); - }else{ - $totalSize+=self::scanFile($file,$root); - $count++; - if($count>$lastSend+25 and $eventSource) { - $lastSend=$count; - $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); - } - } - } - } - } - - OC_FileCache_Update::cleanFolder($path,$root); - self::increaseSize($path,$totalSize,$root); - } - - /** - * scan a single file - * @param string path - * @param string root (optional) - * @return int size of the scanned file - */ - public static function scanFile($path,$root=false) { - // NOTE: Ugly hack to prevent shared files from going into the cache (the source already exists somewhere in the cache) - if (substr($path, 0, 7) == '/Shared') { - return; - } - if($root===false) { - $view=OC_Filesystem::getView(); - }else{ - $view=new OC_FilesystemView($root); - } - if(!$view->is_readable($path)) return; //cant read, nothing we can do - clearstatcache(); - $mimetype=$view->getMimeType($path); - $stat=$view->stat($path); - if($mimetype=='httpd/unix-directory') { - $stat['size'] = 0; - $writable=$view->is_writable($path.'/'); - }else{ - $writable=$view->is_writable($path); - } - $stat['mimetype']=$mimetype; - $stat['writable']=$writable; - if($path=='/') { - $path=''; - } - self::put($path,$stat,$root); - return $stat['size']; - } - - /** - * find files by mimetype - * @param string $part1 - * @param string $part2 (optional) - * @param string root (optional) - * @return array of file paths - * - * $part1 and $part2 together form the complete mimetype. - * e.g. searchByMime('text','plain') - * - * seccond mimetype part can be ommited - * e.g. searchByMime('audio') - */ - public static function searchByMime($part1,$part2=null,$root=false) { - if($root===false) { - $root=OC_Filesystem::getRoot(); - } - $rootLen=strlen($root); - $root .= '%'; - $user=OC_User::getUser(); - if(!$part2) { - $query=OC_DB::prepare('SELECT `path` FROM `*PREFIX*fscache` WHERE `mimepart`=? AND `user`=? AND `path` LIKE ?'); - $result=$query->execute(array($part1,$user, $root)); - }else{ - $query=OC_DB::prepare('SELECT `path` FROM `*PREFIX*fscache` WHERE `mimetype`=? AND `user`=? AND `path` LIKE ? '); - $result=$query->execute(array($part1.'/'.$part2,$user, $root)); - } - $names=array(); - while($row=$result->fetchRow()) { - $names[]=substr($row['path'],$rootLen); - } - return $names; - } - - /** - * clean old pre-path_hash entries - */ - public static function clean() { - $query=OC_DB::prepare('DELETE FROM `*PREFIX*fscache` WHERE LENGTH(`path_hash`)<30'); - $query->execute(); - } - - /** - * clear filecache entries - * @param string user (optonal) - */ - public static function clear($user='') { - if($user) { - $query=OC_DB::prepare('DELETE FROM `*PREFIX*fscache` WHERE `user`=?'); - $query->execute(array($user)); - }else{ - $query=OC_DB::prepare('DELETE FROM `*PREFIX*fscache`'); - $query->execute(); - } - } - - /** - * trigger an update for the cache by setting the mtimes to 0 - * @param string $user (optional) - */ - public static function triggerUpdate($user=''){ - if($user) { - $query=OC_DB::prepare('UPDATE `*PREFIX*fscache` SET `mtime`=0 WHERE `user`=? AND `mimetype`="httpd/unix-directory"'); - $query->execute(array($user)); - }else{ - $query=OC_DB::prepare('UPDATE `*PREFIX*fscache` SET `mtime`=0 AND `mimetype`="httpd/unix-directory"'); - $query->execute(); - } - } -} - -//watch for changes and try to keep the cache up to date -OC_Hook::connect('OC_Filesystem','post_write','OC_FileCache_Update','fileSystemWatcherWrite'); -OC_Hook::connect('OC_Filesystem','post_delete','OC_FileCache_Update','fileSystemWatcherDelete'); -OC_Hook::connect('OC_Filesystem','post_rename','OC_FileCache_Update','fileSystemWatcherRename'); diff --git a/lib/filecache/cached.php b/lib/filecache/cached.php deleted file mode 100644 index 9b1eb4f780..0000000000 --- a/lib/filecache/cached.php +++ /dev/null @@ -1,81 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - - -/** - * get data from the filecache without checking for updates - */ -class OC_FileCache_Cached{ - public static $savedData=array(); - - public static function get($path,$root=false) { - if($root===false) { - $root=OC_Filesystem::getRoot(); - } - $path=$root.$path; - $stmt=OC_DB::prepare('SELECT `path`,`ctime`,`mtime`,`mimetype`,`size`,`encrypted`,`versioned`,`writable` FROM `*PREFIX*fscache` WHERE `path_hash`=?'); - if ( ! OC_DB::isError($stmt) ) { - $result=$stmt->execute(array(md5($path))); - if ( ! OC_DB::isError($result) ) { - $result = $result->fetchRow(); - } else { - OC:Log::write('OC_FileCache_Cached', 'could not execute get: '. OC_DB::getErrorMessage($result), OC_Log::ERROR); - $result = false; - } - } else { - OC_Log::write('OC_FileCache_Cached', 'could not prepare get: '. OC_DB::getErrorMessage($stmt), OC_Log::ERROR); - $result = false; - } - if(is_array($result)) { - if(isset(self::$savedData[$path])) { - $result=array_merge($result, self::$savedData[$path]); - } - return $result; - }else{ - if(isset(self::$savedData[$path])) { - return self::$savedData[$path]; - }else{ - return array(); - } - } - } - - /** - * get all files and folders in a folder - * @param string path - * @param string root (optional) - * @return array - * - * returns an array of assiciative arrays with the following keys: - * - path - * - name - * - size - * - mtime - * - ctime - * - mimetype - * - encrypted - * - versioned - */ - public static function getFolderContent($path,$root=false,$mimetype_filter='') { - if($root===false) { - $root=OC_Filesystem::getRoot(); - } - $parent=OC_FileCache::getId($path, $root); - if($parent==-1) { - return array(); - } - $query=OC_DB::prepare('SELECT `id`,`path`,`name`,`ctime`,`mtime`,`mimetype`,`size`,`encrypted`,`versioned`,`writable` FROM `*PREFIX*fscache` WHERE `parent`=? AND (`mimetype` LIKE ? OR `mimetype` = ?)'); - $result=$query->execute(array($parent, $mimetype_filter.'%', 'httpd/unix-directory'))->fetchAll(); - if(is_array($result)) { - return $result; - }else{ - OC_Log::write('files', 'getFolderContent(): file not found in cache ('.$path.')', OC_Log::DEBUG); - return false; - } - } -} \ No newline at end of file diff --git a/lib/filecache/update.php b/lib/filecache/update.php deleted file mode 100644 index 1b81f70d77..0000000000 --- a/lib/filecache/update.php +++ /dev/null @@ -1,217 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - - -/** - * handles updating the filecache according to outside changes - */ -class OC_FileCache_Update{ - /** - * check if a file or folder is updated outside owncloud - * @param string path - * @param string root (optional) - * @param boolean folder - * @return bool - */ - public static function hasUpdated($path,$root=false,$folder=false) { - if($root===false) { - $view=OC_Filesystem::getView(); - }else{ - $view=new OC_FilesystemView($root); - } - if(!$view->file_exists($path)) { - return false; - } - $cachedData=OC_FileCache_Cached::get($path, $root); - if(isset($cachedData['mtime'])) { - $cachedMTime=$cachedData['mtime']; - if($folder) { - return $view->hasUpdated($path.'/', $cachedMTime); - }else{ - return $view->hasUpdated($path, $cachedMTime); - } - }else{//file not in cache, so it has to be updated - if(($path=='/' or $path=='') and $root===false) {//dont auto update the home folder, it will be scanned - return false; - } - return true; - } - } - - /** - * delete non existing files from the cache - */ - public static function cleanFolder($path,$root=false) { - if($root===false) { - $view=OC_Filesystem::getView(); - }else{ - $view=new OC_FilesystemView($root); - } - - $cachedContent=OC_FileCache_Cached::getFolderContent($path,$root); - foreach($cachedContent as $fileData) { - $path=$fileData['path']; - $file=$view->getRelativePath($path); - if(!$view->file_exists($file)) { - if($root===false) {//filesystem hooks are only valid for the default root - OC_Hook::emit('OC_Filesystem', 'post_delete', array('path'=>$file)); - }else{ - self::delete($file, $root); - } - } - } - } - - /** - * update the cache according to changes in the folder - * @param string path - * @param string root (optional) - */ - public static function updateFolder($path,$root=false) { - if($root===false) { - $view=OC_Filesystem::getView(); - }else{ - $view=new OC_FilesystemView($root); - } - $dh=$view->opendir($path.'/'); - if($dh) {//check for changed/new files - while (($filename = readdir($dh)) !== false) { - if($filename != '.' and $filename != '..' and $filename != '') { - $file=$path.'/'.$filename; - $isDir=$view->is_dir($file); - if(self::hasUpdated($file, $root, $isDir)) { - if($isDir){ - self::updateFolder($file, $root); - }elseif($root===false) {//filesystem hooks are only valid for the default root - OC_Hook::emit('OC_Filesystem', 'post_write', array('path'=>$file)); - }else{ - self::update($file, $root); - } - } - } - } - } - - self::cleanFolder($path, $root); - - //update the folder last, so we can calculate the size correctly - if($root===false) {//filesystem hooks are only valid for the default root - OC_Hook::emit('OC_Filesystem', 'post_write', array('path'=>$path)); - }else{ - self::update($path, $root); - } - } - - /** - * called when changes are made to files - * @param array $params - * @param string root (optional) - */ - public static function fileSystemWatcherWrite($params) { - $path=$params['path']; - self::update($path); - } - - /** - * called when files are deleted - * @param array $params - * @param string root (optional) - */ - public static function fileSystemWatcherDelete($params) { - $path=$params['path']; - self::delete($path); - } - - /** - * called when files are deleted - * @param array $params - * @param string root (optional) - */ - public static function fileSystemWatcherRename($params) { - $oldPath=$params['oldpath']; - $newPath=$params['newpath']; - self::rename($oldPath, $newPath); - } - - /** - * update the filecache according to changes to the filesystem - * @param string path - * @param string root (optional) - */ - public static function update($path,$root=false) { - if($root===false) { - $view=OC_Filesystem::getView(); - }else{ - $view=new OC_FilesystemView($root); - } - - $mimetype=$view->getMimeType($path); - - $size=0; - $cached=OC_FileCache_Cached::get($path,$root); - $cachedSize=isset($cached['size'])?$cached['size']:0; - - if($view->is_dir($path.'/')) { - if(OC_FileCache::inCache($path, $root)) { - $cachedContent=OC_FileCache_Cached::getFolderContent($path, $root); - foreach($cachedContent as $file) { - $size+=$file['size']; - } - $mtime=$view->filemtime($path.'/'); - $ctime=$view->filectime($path.'/'); - $writable=$view->is_writable($path.'/'); - OC_FileCache::put($path, array('size'=>$size,'mtime'=>$mtime,'ctime'=>$ctime,'mimetype'=>$mimetype,'writable'=>$writable)); - }else{ - $count=0; - OC_FileCache::scan($path, null, $count, $root); - return; //increaseSize is already called inside scan - } - }else{ - $size=OC_FileCache::scanFile($path, $root); - } - OC_FileCache::increaseSize(dirname($path), $size-$cachedSize, $root); - } - - /** - * update the filesystem after a delete has been detected - * @param string path - * @param string root (optional) - */ - public static function delete($path,$root=false) { - $cached=OC_FileCache_Cached::get($path, $root); - if(!isset($cached['size'])) { - return; - } - $size=$cached['size']; - OC_FileCache::increaseSize(dirname($path), -$size, $root); - OC_FileCache::delete($path, $root); - } - - /** - * update the filesystem after a rename has been detected - * @param string oldPath - * @param string newPath - * @param string root (optional) - */ - public static function rename($oldPath,$newPath,$root=false) { - if(!OC_FileCache::inCache($oldPath, $root)) { - return; - } - if($root===false) { - $view=OC_Filesystem::getView(); - }else{ - $view=new OC_FilesystemView($root); - } - - $cached=OC_FileCache_Cached::get($oldPath, $root); - $oldSize=$cached['size']; - OC_FileCache::increaseSize(dirname($oldPath), -$oldSize, $root); - OC_FileCache::increaseSize(dirname($newPath), $oldSize, $root); - OC_FileCache::move($oldPath, $newPath); - } -} diff --git a/lib/filesystem.php b/lib/filesystem.php index 0d24b7203b..b73b52d420 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -254,13 +254,6 @@ class OC_Filesystem{ } } } - - $mtime=filemtime(OC::$SERVERROOT.'/config/mount.php'); - $previousMTime=OC_Appconfig::getValue('files','mountconfigmtime',0); - if($mtime>$previousMTime) {//mount config has changed, filecache needs to be updated - OC_FileCache::triggerUpdate(); - OC_Appconfig::setValue('files','mountconfigmtime',$mtime); - } } self::$loaded=true; @@ -399,7 +392,7 @@ class OC_Filesystem{ } /** - * checks if a file is blacklsited for storage in the filesystem + * checks if a file is blacklisted for storage in the filesystem * Listens to write and rename hooks * @param array $data from hook */ @@ -419,7 +412,7 @@ class OC_Filesystem{ } /** - * following functions are equivilent to their php buildin equivilents for arguments/return values. + * following functions are equivalent to their php builtin equivalents for arguments/return values. */ static public function mkdir($path) { return self::$defaultInstance->mkdir($path); @@ -592,4 +585,3 @@ OC_Hook::connect('OC_Filesystem','post_delete','OC_Filesystem','removeETagHook') OC_Hook::connect('OC_Filesystem','post_rename','OC_Filesystem','removeETagHook'); OC_Util::setupFS(); -require_once 'filecache.php'; diff --git a/lib/util.php b/lib/util.php index b21580cb01..557f731522 100755 --- a/lib/util.php +++ b/lib/util.php @@ -58,13 +58,6 @@ class OC_Util { OC_Filesystem::mount($options['class'], $options['options'], $mountPoint); } } - - $mtime=filemtime($user_root.'/mount.php'); - $previousMTime=OC_Preferences::getValue($user,'files','mountconfigmtime',0); - if($mtime>$previousMTime) {//mount config has changed, filecache needs to be updated - OC_FileCache::triggerUpdate($user); - OC_Preferences::setValue($user,'files','mountconfigmtime',$mtime); - } } OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $user_dir)); } From 9df60d27bd9dad6a37a0d763b808a5b1c5ade5c5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 10 Oct 2012 11:54:44 +0200 Subject: [PATCH 028/418] move some code around --- lib/files/filesystem.php | 629 +++++++++++++++++++++ lib/{filesystemview.php => files/view.php} | 387 +++++++------ lib/filesystem.php | 562 ------------------ 3 files changed, 835 insertions(+), 743 deletions(-) create mode 100644 lib/files/filesystem.php rename lib/{filesystemview.php => files/view.php} (62%) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php new file mode 100644 index 0000000000..d0a96b59a0 --- /dev/null +++ b/lib/files/filesystem.php @@ -0,0 +1,629 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Class for abstraction of filesystem functions + * This class won't call any filesystem functions for itself but but will pass them to the correct OC_Filestorage object + * this class should also handle all the file permission related stuff + * + * Hooks provided: + * read(path) + * write(path, &run) + * post_write(path) + * create(path, &run) (when a file is created, both create and write will be emited in that order) + * post_create(path) + * delete(path, &run) + * post_delete(path) + * rename(oldpath,newpath, &run) + * post_rename(oldpath,newpath) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emited in that order) + * post_rename(oldpath,newpath) + * + * the &run parameter can be set to false to prevent the operation from occuring + */ + +namespace OC\Files; + +class Filesystem { + static private $storages = array(); + static private $mounts = array(); + public static $loaded = false; + /** + * @var \OC\Files\Storage\Storage $defaultInstance + */ + static private $defaultInstance; + + + /** + * classname which used for hooks handling + * used as signalclass in OC_Hooks::emit() + */ + const CLASSNAME = 'OC_Filesystem'; + + /** + * signalname emited before file renaming + * + * @param oldpath + * @param newpath + */ + const signal_rename = 'rename'; + + /** + * signal emited after file renaming + * + * @param oldpath + * @param newpath + */ + const signal_post_rename = 'post_rename'; + + /** + * signal emited before file/dir creation + * + * @param path + * @param run changing this flag to false in hook handler will cancel event + */ + const signal_create = 'create'; + + /** + * signal emited after file/dir creation + * + * @param path + * @param run changing this flag to false in hook handler will cancel event + */ + const signal_post_create = 'post_create'; + + /** + * signal emits before file/dir copy + * + * @param oldpath + * @param newpath + * @param run changing this flag to false in hook handler will cancel event + */ + const signal_copy = 'copy'; + + /** + * signal emits after file/dir copy + * + * @param oldpath + * @param newpath + */ + const signal_post_copy = 'post_copy'; + + /** + * signal emits before file/dir save + * + * @param path + * @param run changing this flag to false in hook handler will cancel event + */ + const signal_write = 'write'; + + /** + * signal emits after file/dir save + * + * @param path + */ + const signal_post_write = 'post_write'; + + /** + * signal emits when reading file/dir + * + * @param path + */ + const signal_read = 'read'; + + /** + * signal emits when removing file/dir + * + * @param path + */ + const signal_delete = 'delete'; + + /** + * parameters definitions for signals + */ + const signal_param_path = 'path'; + const signal_param_oldpath = 'oldpath'; + const signal_param_newpath = 'newpath'; + + /** + * run - changing this flag to false in hook handler will cancel event + */ + const signal_param_run = 'run'; + + /** + * get the mountpoint of the storage object for a path + ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account + * + * @param string path + * @return string + */ + static public function getMountPoint($path) { + OC_Hook::emit(self::CLASSNAME, 'get_mountpoint', array('path' => $path)); + if (!$path) { + $path = '/'; + } + if ($path[0] !== '/') { + $path = '/' . $path; + } + $path = str_replace('//', '/', $path); + $foundMountPoint = ''; + $mountPoints = array_keys(OC_Filesystem::$mounts); + foreach ($mountPoints as $mountpoint) { + if ($mountpoint == $path) { + return $mountpoint; + } + if (strpos($path, $mountpoint) === 0 and strlen($mountpoint) > strlen($foundMountPoint)) { + $foundMountPoint = $mountpoint; + } + } + return $foundMountPoint; + } + + /** + * get the part of the path relative to the mountpoint of the storage it's stored in + * + * @param string path + * @return bool + */ + static public function getInternalPath($path) { + $mountPoint = self::getMountPoint($path); + $internalPath = substr($path, strlen($mountPoint)); + return $internalPath; + } + + /** + * get the storage object for a path + * + * @param string path + * @return \OC\Files\Storage\Storage + */ + static public function getStorage($path) { + $mountpoint = self::getMountPoint($path); + if ($mountpoint) { + if (!isset(OC_Filesystem::$storages[$mountpoint])) { + $mount = OC_Filesystem::$mounts[$mountpoint]; + OC_Filesystem::$storages[$mountpoint] = OC_Filesystem::createStorage($mount['class'], $mount['arguments']); + } + return OC_Filesystem::$storages[$mountpoint]; + } + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + $mountpoint = self::getMountPoint($path); + if ($mountpoint) { + if (!isset(OC_Filesystem::$storages[$mountpoint])) { + $mount = OC_Filesystem::$mounts[$mountpoint]; + OC_Filesystem::$storages[$mountpoint] = OC_Filesystem::createStorage($mount['class'], $mount['arguments']); + } + $storage = OC_Filesystem::$storages[$mountpoint]; + $internalPath = substr($path, strlen($mountpoint)); + return array($storage, $internalPath); + } + } + + static public function init($root) { + if (self::$defaultInstance) { + return false; + } + self::$defaultInstance = new OC_FilesystemView($root); + +//load custom mount config + if (is_file(OC::$SERVERROOT . '/config/mount.php')) { + $mountConfig = include(OC::$SERVERROOT . '/config/mount.php'); + if (isset($mountConfig['global'])) { + foreach ($mountConfig['global'] as $mountPoint => $options) { + self::mount($options['class'], $options['options'], $mountPoint); + } + } + + if (isset($mountConfig['group'])) { + foreach ($mountConfig['group'] as $group => $mounts) { + if (OC_Group::inGroup(OC_User::getUser(), $group)) { + foreach ($mounts as $mountPoint => $options) { + $mountPoint = self::setUserVars($mountPoint); + foreach ($options as &$option) { + $option = self::setUserVars($option); + } + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } + } + + if (isset($mountConfig['user'])) { + foreach ($mountConfig['user'] as $user => $mounts) { + if ($user === 'all' or strtolower($user) === strtolower(OC_User::getUser())) { + foreach ($mounts as $mountPoint => $options) { + $mountPoint = self::setUserVars($mountPoint); + foreach ($options as &$option) { + $option = self::setUserVars($option); + } + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } + } + } + + self::$loaded = true; + } + + /** + * fill in the correct values for $user, and $password placeholders + * + * @param string intput + * @return string + */ + private static function setUserVars($input) { + return str_replace('$user', OC_User::getUser(), $input); + } + + /** + * get the default filesystem view + * + * @return OC_FilesystemView + */ + static public function getView() { + return self::$defaultInstance; + } + + /** + * tear down the filesystem, removing all storage providers + */ + static public function tearDown() { + self::$storages = array(); + } + + /** + * create a new storage of a specific type + * + * @param string type + * @param array arguments + * @return \OC\Files\Storage\Storage + */ + static private function createStorage($class, $arguments) { + if (class_exists($class)) { + try { + return new $class($arguments); + } catch (Exception $exception) { + OC_Log::write('core', $exception->getMessage(), OC_Log::ERROR); + return false; + } + } else { + OC_Log::write('core', 'storage backend ' . $class . ' not found', OC_Log::ERROR); + return false; + } + } + + /** + * change the root to a fake root + * + * @param string fakeRoot + * @return bool + */ + static public function chroot($fakeRoot) { + return self::$defaultInstance->chroot($fakeRoot); + } + + /** + * @brief get the relative path of the root data directory for the current user + * @return string + * + * Returns path like /admin/files + */ + static public function getRoot() { + return self::$defaultInstance->getRoot(); + } + + /** + * clear all mounts and storage backends + */ + public static function clearMounts() { + self::$mounts = array(); + self::$storages = array(); + } + + /** + * mount an \OC\Files\Storage\Storage in our virtual filesystem + * + * @param \OC\Files\Storage\Storage storage + * @param string mountpoint + */ + static public function mount($class, $arguments, $mountpoint) { + if ($mountpoint[0] != '/') { + $mountpoint = '/' . $mountpoint; + } + if (substr($mountpoint, -1) !== '/') { + $mountpoint = $mountpoint . '/'; + } + self::$mounts[$mountpoint] = array('class' => $class, 'arguments' => $arguments); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed + * + * @param string path + * @return string + */ + static public function getLocalFile($path) { + return self::$defaultInstance->getLocalFile($path); + } + + /** + * @param string path + * @return string + */ + static public function getLocalFolder($path) { + return self::$defaultInstance->getLocalFolder($path); + } + + /** + * return path to file which reflects one visible in browser + * + * @param string path + * @return string + */ + static public function getLocalPath($path) { + $datadir = OC_User::getHome(OC_User::getUser()) . '/files'; + $newpath = $path; + if (strncmp($newpath, $datadir, strlen($datadir)) == 0) { + $newpath = substr($path, strlen($datadir)); + } + return $newpath; + } + + /** + * check if the requested path is valid + * + * @param string path + * @return bool + */ + static public function isValidPath($path) { + if (!$path || $path[0] !== '/') { + $path = '/' . $path; + } + if (strstr($path, '/../') || strrchr($path, '/') === '/..') { + return false; + } + return true; + } + + /** + * checks if a file is blacklisted for storage in the filesystem + * Listens to write and rename hooks + * + * @param array $data from hook + */ + static public function isBlacklisted($data) { + $blacklist = array('.htaccess'); + if (isset($data['path'])) { + $path = $data['path']; + } else if (isset($data['newpath'])) { + $path = $data['newpath']; + } + if (isset($path)) { + $filename = strtolower(basename($path)); + if (in_array($filename, $blacklist)) { + $data['run'] = false; + } + } + } + + /** + * following functions are equivalent to their php builtin equivalents for arguments/return values. + */ + static public function mkdir($path) { + return self::$defaultInstance->mkdir($path); + } + + static public function rmdir($path) { + return self::$defaultInstance->rmdir($path); + } + + static public function opendir($path) { + return self::$defaultInstance->opendir($path); + } + + static public function readdir($path) { + return self::$defaultInstance->readdir($path); + } + + static public function is_dir($path) { + return self::$defaultInstance->is_dir($path); + } + + static public function is_file($path) { + return self::$defaultInstance->is_file($path); + } + + static public function stat($path) { + return self::$defaultInstance->stat($path); + } + + static public function filetype($path) { + return self::$defaultInstance->filetype($path); + } + + static public function filesize($path) { + return self::$defaultInstance->filesize($path); + } + + static public function readfile($path) { + return self::$defaultInstance->readfile($path); + } + + /** + * @deprecated Replaced by isReadable() as part of CRUDS + */ + static public function is_readable($path) { + return self::$defaultInstance->is_readable($path); + } + + /** + * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS + */ + static public function is_writable($path) { + return self::$defaultInstance->is_writable($path); + } + + static public function isCreatable($path) { + return self::$defaultInstance->isCreatable($path); + } + + static public function isReadable($path) { + return self::$defaultInstance->isReadable($path); + } + + static public function isUpdatable($path) { + return self::$defaultInstance->isUpdatable($path); + } + + static public function isDeletable($path) { + return self::$defaultInstance->isDeletable($path); + } + + static public function isSharable($path) { + return self::$defaultInstance->isSharable($path); + } + + static public function file_exists($path) { + return self::$defaultInstance->file_exists($path); + } + + static public function filectime($path) { + return self::$defaultInstance->filectime($path); + } + + static public function filemtime($path) { + return self::$defaultInstance->filemtime($path); + } + + static public function touch($path, $mtime = null) { + return self::$defaultInstance->touch($path, $mtime); + } + + static public function file_get_contents($path) { + return self::$defaultInstance->file_get_contents($path); + } + + static public function file_put_contents($path, $data) { + return self::$defaultInstance->file_put_contents($path, $data); + } + + static public function unlink($path) { + return self::$defaultInstance->unlink($path); + } + + static public function rename($path1, $path2) { + return self::$defaultInstance->rename($path1, $path2); + } + + static public function copy($path1, $path2) { + return self::$defaultInstance->copy($path1, $path2); + } + + static public function fopen($path, $mode) { + return self::$defaultInstance->fopen($path, $mode); + } + + static public function toTmpFile($path) { + return self::$defaultInstance->toTmpFile($path); + } + + static public function fromTmpFile($tmpFile, $path) { + return self::$defaultInstance->fromTmpFile($tmpFile, $path); + } + + static public function getMimeType($path) { + return self::$defaultInstance->getMimeType($path); + } + + static public function hash($type, $path, $raw = false) { + return self::$defaultInstance->hash($type, $path, $raw); + } + + static public function free_space($path = '/') { + return self::$defaultInstance->free_space($path); + } + + static public function search($query) { + return OC_FileCache::search($query); + } + + /** + * check if a file or folder has been updated since $time + * + * @param int $time + * @return bool + */ + static public function hasUpdated($path, $time) { + return self::$defaultInstance->hasUpdated($path, $time); + } + + static public function removeETagHook($params, $root = false) { + if (isset($params['path'])) { + $path = $params['path']; + } else { + $path = $params['oldpath']; + } + + if ($root) { // reduce path to the required part of it (no 'username/files') + $fakeRootView = new OC_FilesystemView($root); + $count = 1; + $path = str_replace(OC_App::getStorage("files")->getAbsolutePath($path), "", $fakeRootView->getAbsolutePath($path), $count); + } + + $path = self::normalizePath($path); + OC_Connector_Sabre_Node::removeETagPropertyForPath($path); + } + + /** + * normalize a path + * + * @param string path + * @param bool $stripTrailingSlash + * @return string + */ + public static function normalizePath($path, $stripTrailingSlash = true) { + if ($path == '') { + return '/'; + } +//no windows style slashes + $path = str_replace('\\', '/', $path); +//add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } +//remove trainling slash + if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { + $path = substr($path, 0, -1); + } +//remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } +//normalize unicode if possible + if (class_exists('Normalizer')) { + $path = Normalizer::normalize($path); + } + return $path; + } +} + +OC_Hook::connect('OC_Filesystem', 'post_write', 'OC_Filesystem', 'removeETagHook'); +OC_Hook::connect('OC_Filesystem', 'post_delete', 'OC_Filesystem', 'removeETagHook'); +OC_Hook::connect('OC_Filesystem', 'post_rename', 'OC_Filesystem', 'removeETagHook'); + +OC_Util::setupFS(); diff --git a/lib/filesystemview.php b/lib/files/view.php similarity index 62% rename from lib/filesystemview.php rename to lib/files/view.php index 071fc781f1..8c3f288f44 100644 --- a/lib/filesystemview.php +++ b/lib/files/view.php @@ -1,26 +1,11 @@ . + * Copyright (c) 2012 Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ - /** * Class to provide access to ownCloud filesystem via a "view", and methods for * working with files within that view (e.g. read, write, delete, etc.). Each @@ -38,41 +23,45 @@ * \OC\Files\Storage\Storage object */ -class OC_FilesystemView { - private $fakeRoot=''; - private $internal_path_cache=array(); - private $storage_cache=array(); +namespace OC\Files; + +class View { + private $fakeRoot = ''; + private $internal_path_cache = array(); + private $storage_cache = array(); public function __construct($root) { - $this->fakeRoot=$root; + $this->fakeRoot = $root; } public function getAbsolutePath($path) { - if(!$path) { - $path='/'; + if (!$path) { + $path = '/'; } - if($path[0]!=='/') { - $path='/'.$path; + if ($path[0] !== '/') { + $path = '/' . $path; } - return $this->fakeRoot.$path; + return $this->fakeRoot . $path; } /** - * change the root to a fake toor - * @param string fakeRoot - * @return bool - */ + * change the root to a fake toor + * + * @param string fakeRoot + * @return bool + */ public function chroot($fakeRoot) { - if(!$fakeRoot=='') { - if($fakeRoot[0]!=='/') { - $fakeRoot='/'.$fakeRoot; + if (!$fakeRoot == '') { + if ($fakeRoot[0] !== '/') { + $fakeRoot = '/' . $fakeRoot; } } - $this->fakeRoot=$fakeRoot; + $this->fakeRoot = $fakeRoot; } /** * get the fake root + * * @return string */ public function getRoot() { @@ -80,10 +69,11 @@ class OC_FilesystemView { } /** - * get the part of the path relative to the mountpoint of the storage it's stored in - * @param string path - * @return bool - */ + * get the part of the path relative to the mountpoint of the storage it's stored in + * + * @param string path + * @return bool + */ public function getInternalPath($path) { if (!isset($this->internal_path_cache[$path])) { $this->internal_path_cache[$path] = OC_Filesystem::getInternalPath($this->getAbsolutePath($path)); @@ -93,30 +83,32 @@ class OC_FilesystemView { /** * get path relative to the root of the view + * * @param string path * @return string */ public function getRelativePath($path) { - if($this->fakeRoot=='') { + if ($this->fakeRoot == '') { return $path; } - if(strpos($path, $this->fakeRoot)!==0) { + if (strpos($path, $this->fakeRoot) !== 0) { return null; - }else{ - $path=substr($path, strlen($this->fakeRoot)); - if(strlen($path)===0) { + } else { + $path = substr($path, strlen($this->fakeRoot)); + if (strlen($path) === 0) { return '/'; - }else{ + } else { return $path; } } } /** - * get the storage object for a path - * @param string path - * @return \OC\Files\Storage\Storage - */ + * get the storage object for a path + * + * @param string path + * @return \OC\Files\Storage\Storage + */ public function getStorage($path) { if (!isset($this->storage_cache[$path])) { $this->storage_cache[$path] = OC_Filesystem::getStorage($this->getAbsolutePath($path)); @@ -125,35 +117,37 @@ class OC_FilesystemView { } /** - * get the mountpoint of the storage object for a path + * get the mountpoint of the storage object for a path ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account - * - * @param string path - * @return string - */ + * + * @param string path + * @return string + */ public function getMountPoint($path) { return OC_Filesystem::getMountPoint($this->getAbsolutePath($path)); } /** - * return the path to a local version of the file - * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed - * @param string path - * @return string - */ + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed + * + * @param string path + * @return string + */ public function getLocalFile($path) { - $parent=substr($path, 0, strrpos($path,'/')); - if(OC_Filesystem::isValidPath($parent) and $storage=$this->getStorage($path)) { + $parent = substr($path, 0, strrpos($path, '/')); + if (OC_Filesystem::isValidPath($parent) and $storage = $this->getStorage($path)) { return $storage->getLocalFile($this->getInternalPath($path)); } } + /** * @param string path * @return string */ public function getLocalFolder($path) { - $parent=substr($path, 0, strrpos($path,'/')); - if(OC_Filesystem::isValidPath($parent) and $storage=$this->getStorage($path)) { + $parent = substr($path, 0, strrpos($path, '/')); + if (OC_Filesystem::isValidPath($parent) and $storage = $this->getStorage($path)) { return $storage->getLocalFolder($this->getInternalPath($path)); } } @@ -166,105 +160,127 @@ class OC_FilesystemView { public function mkdir($path) { return $this->basicOperation('mkdir', $path, array('create', 'write')); } + public function rmdir($path) { return $this->basicOperation('rmdir', $path, array('delete')); } + public function opendir($path) { return $this->basicOperation('opendir', $path, array('read')); } + public function readdir($handle) { - $fsLocal= new \OC\Files\Storage\Local( array( 'datadir' => '/' ) ); - return $fsLocal->readdir( $handle ); + $fsLocal = new \OC\Files\Storage\Local(array('datadir' => '/')); + return $fsLocal->readdir($handle); } + public function is_dir($path) { - if($path=='/') { + if ($path == '/') { return true; } return $this->basicOperation('is_dir', $path); } + public function is_file($path) { - if($path=='/') { + if ($path == '/') { return false; } return $this->basicOperation('is_file', $path); } + public function stat($path) { return $this->basicOperation('stat', $path); } + public function filetype($path) { return $this->basicOperation('filetype', $path); } + public function filesize($path) { return $this->basicOperation('filesize', $path); } + public function readfile($path) { @ob_end_clean(); - $handle=$this->fopen($path, 'rb'); + $handle = $this->fopen($path, 'rb'); if ($handle) { - $chunkSize = 8192;// 8 MB chunks + $chunkSize = 8192; // 8 MB chunks while (!feof($handle)) { echo fread($handle, $chunkSize); flush(); } - $size=$this->filesize($path); + $size = $this->filesize($path); return $size; } return false; } + /** - * @deprecated Replaced by isReadable() as part of CRUDS - */ + * @deprecated Replaced by isReadable() as part of CRUDS + */ public function is_readable($path) { - return $this->basicOperation('isReadable',$path); + return $this->basicOperation('isReadable', $path); } + /** - * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS - */ + * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS + */ public function is_writable($path) { - return $this->basicOperation('isUpdatable',$path); + return $this->basicOperation('isUpdatable', $path); } + public function isCreatable($path) { return $this->basicOperation('isCreatable', $path); } + public function isReadable($path) { return $this->basicOperation('isReadable', $path); } + public function isUpdatable($path) { return $this->basicOperation('isUpdatable', $path); } + public function isDeletable($path) { return $this->basicOperation('isDeletable', $path); } + public function isSharable($path) { return $this->basicOperation('isSharable', $path); } + public function file_exists($path) { - if($path=='/') { + if ($path == '/') { return true; } return $this->basicOperation('file_exists', $path); } + public function filectime($path) { return $this->basicOperation('filectime', $path); } + public function filemtime($path) { return $this->basicOperation('filemtime', $path); } - public function touch($path, $mtime=null) { + + public function touch($path, $mtime = null) { return $this->basicOperation('touch', $path, array('write'), $mtime); } + public function file_get_contents($path) { return $this->basicOperation('file_get_contents', $path, array('read')); } + public function file_put_contents($path, $data) { - if(is_resource($data)) {//not having to deal with streams in file_put_contents makes life easier + if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path)); if (OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) && OC_Filesystem::isValidPath($path)) { $path = $this->getRelativePath($absolutePath); $exists = $this->file_exists($path); $run = true; - if( $this->fakeRoot==OC_Filesystem::getRoot() ){ - if(!$exists) { + if ($this->fakeRoot == OC_Filesystem::getRoot()) { + if (!$exists) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_create, @@ -283,83 +299,86 @@ class OC_FilesystemView { ) ); } - if(!$run) { + if (!$run) { return false; } - $target=$this->fopen($path, 'w'); - if($target) { - $count=OC_Helper::streamCopy($data, $target); + $target = $this->fopen($path, 'w'); + if ($target) { + $count = OC_Helper::streamCopy($data, $target); fclose($target); fclose($data); - if( $this->fakeRoot==OC_Filesystem::getRoot() ){ - if(!$exists) { + if ($this->fakeRoot == OC_Filesystem::getRoot()) { + if (!$exists) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_create, - array( OC_Filesystem::signal_param_path => $path) + array(OC_Filesystem::signal_param_path => $path) ); } OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_write, - array( OC_Filesystem::signal_param_path => $path) + array(OC_Filesystem::signal_param_path => $path) ); } OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count); return $count > 0; - }else{ + } else { return false; } } - }else{ + } else { return $this->basicOperation('file_put_contents', $path, array('create', 'write'), $data); } } + public function unlink($path) { return $this->basicOperation('unlink', $path, array('delete')); } - public function deleteAll( $directory, $empty = false ) { - return $this->basicOperation( 'deleteAll', $directory, array('delete'), $empty ); + + public function deleteAll($directory, $empty = false) { + return $this->basicOperation('deleteAll', $directory, array('delete'), $empty); } + public function rename($path1, $path2) { - $postFix1=(substr($path1,-1,1)==='/')?'/':''; - $postFix2=(substr($path2,-1,1)==='/')?'/':''; + $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : ''; + $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : ''; $absolutePath1 = OC_Filesystem::normalizePath($this->getAbsolutePath($path1)); $absolutePath2 = OC_Filesystem::normalizePath($this->getAbsolutePath($path2)); - if(OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) { + if (OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) { $path1 = $this->getRelativePath($absolutePath1); $path2 = $this->getRelativePath($absolutePath2); - if($path1 == null or $path2 == null) { + if ($path1 == null or $path2 == null) { return false; } - $run=true; - if( $this->fakeRoot==OC_Filesystem::getRoot() ){ + $run = true; + if ($this->fakeRoot == OC_Filesystem::getRoot()) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_rename, - array( - OC_Filesystem::signal_param_oldpath => $path1, - OC_Filesystem::signal_param_newpath => $path2, - OC_Filesystem::signal_param_run => &$run - ) + array( + OC_Filesystem::signal_param_oldpath => $path1, + OC_Filesystem::signal_param_newpath => $path2, + OC_Filesystem::signal_param_run => &$run + ) ); } - if($run) { - $mp1 = $this->getMountPoint($path1.$postFix1); - $mp2 = $this->getMountPoint($path2.$postFix2); - if($mp1 == $mp2) { - if($storage = $this->getStorage($path1)) { - $result = $storage->rename($this->getInternalPath($path1.$postFix1), $this->getInternalPath($path2.$postFix2)); + if ($run) { + $mp1 = $this->getMountPoint($path1 . $postFix1); + $mp2 = $this->getMountPoint($path2 . $postFix2); + if ($mp1 == $mp2) { + if ($storage = $this->getStorage($path1)) { + $result = $storage->rename($this->getInternalPath($path1 . $postFix1), $this->getInternalPath($path2 . $postFix2)); } } else { - $source = $this->fopen($path1.$postFix1, 'r'); - $target = $this->fopen($path2.$postFix2, 'w'); + $source = $this->fopen($path1 . $postFix1, 'r'); + $target = $this->fopen($path2 . $postFix2, 'w'); $count = OC_Helper::streamCopy($source, $target); $storage1 = $this->getStorage($path1); - $storage1->unlink($this->getInternalPath($path1.$postFix1)); - $result = $count>0; + $storage1->unlink($this->getInternalPath($path1 . $postFix1)); + $result = $count > 0; } - if( $this->fakeRoot==OC_Filesystem::getRoot() ){ + if ($this->fakeRoot == OC_Filesystem::getRoot()) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_rename, @@ -373,31 +392,32 @@ class OC_FilesystemView { } } } + public function copy($path1, $path2) { - $postFix1=(substr($path1,-1,1)==='/')?'/':''; - $postFix2=(substr($path2,-1,1)==='/')?'/':''; + $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : ''; + $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : ''; $absolutePath1 = OC_Filesystem::normalizePath($this->getAbsolutePath($path1)); $absolutePath2 = OC_Filesystem::normalizePath($this->getAbsolutePath($path2)); - if(OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) { + if (OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) { $path1 = $this->getRelativePath($absolutePath1); $path2 = $this->getRelativePath($absolutePath2); - if($path1 == null or $path2 == null) { + if ($path1 == null or $path2 == null) { return false; } - $run=true; - if( $this->fakeRoot==OC_Filesystem::getRoot() ){ + $run = true; + if ($this->fakeRoot == OC_Filesystem::getRoot()) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_copy, array( OC_Filesystem::signal_param_oldpath => $path1, - OC_Filesystem::signal_param_newpath=>$path2, + OC_Filesystem::signal_param_newpath => $path2, OC_Filesystem::signal_param_run => &$run ) ); - $exists=$this->file_exists($path2); - if($run and !$exists) { + $exists = $this->file_exists($path2); + if ($run and !$exists) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_create, @@ -407,7 +427,7 @@ class OC_FilesystemView { ) ); } - if($run) { + if ($run) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_write, @@ -418,28 +438,28 @@ class OC_FilesystemView { ); } } - if($run) { - $mp1=$this->getMountPoint($path1.$postFix1); - $mp2=$this->getMountPoint($path2.$postFix2); - if($mp1 == $mp2) { - if($storage = $this->getStorage($path1.$postFix1)) { - $result=$storage->copy($this->getInternalPath($path1.$postFix1), $this->getInternalPath($path2.$postFix2)); + if ($run) { + $mp1 = $this->getMountPoint($path1 . $postFix1); + $mp2 = $this->getMountPoint($path2 . $postFix2); + if ($mp1 == $mp2) { + if ($storage = $this->getStorage($path1 . $postFix1)) { + $result = $storage->copy($this->getInternalPath($path1 . $postFix1), $this->getInternalPath($path2 . $postFix2)); } } else { - $source = $this->fopen($path1.$postFix1, 'r'); - $target = $this->fopen($path2.$postFix2, 'w'); + $source = $this->fopen($path1 . $postFix1, 'r'); + $target = $this->fopen($path2 . $postFix2, 'w'); $result = OC_Helper::streamCopy($source, $target); } - if( $this->fakeRoot==OC_Filesystem::getRoot() ){ + if ($this->fakeRoot == OC_Filesystem::getRoot()) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_copy, array( OC_Filesystem::signal_param_oldpath => $path1, - OC_Filesystem::signal_param_newpath=>$path2 + OC_Filesystem::signal_param_newpath => $path2 ) ); - if(!$exists) { + if (!$exists) { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_create, @@ -449,7 +469,7 @@ class OC_FilesystemView { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_write, - array( OC_Filesystem::signal_param_path => $path2) + array(OC_Filesystem::signal_param_path => $path2) ); } else { // no real copy, file comes from somewhere else, e.g. version rollback -> just update the file cache and the webdav properties without all the other post_write actions OC_FileCache_Update::update($path2, $this->fakeRoot); @@ -459,12 +479,13 @@ class OC_FilesystemView { } } } + public function fopen($path, $mode) { - $hooks=array(); - switch($mode) { + $hooks = array(); + switch ($mode) { case 'r': case 'rb': - $hooks[]='read'; + $hooks[] = 'read'; break; case 'r+': case 'rb+': @@ -474,8 +495,8 @@ class OC_FilesystemView { case 'xb+': case 'a+': case 'ab+': - $hooks[]='read'; - $hooks[]='write'; + $hooks[] = 'read'; + $hooks[] = 'write'; break; case 'w': case 'wb': @@ -483,22 +504,23 @@ class OC_FilesystemView { case 'xb': case 'a': case 'ab': - $hooks[]='write'; + $hooks[] = 'write'; break; default: - OC_Log::write('core','invalid mode ('.$mode.') for '.$path,OC_Log::ERROR); + OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, OC_Log::ERROR); } return $this->basicOperation('fopen', $path, $hooks, $mode); } + public function toTmpFile($path) { - if(OC_Filesystem::isValidPath($path)) { + if (OC_Filesystem::isValidPath($path)) { $source = $this->fopen($path, 'r'); - if($source) { - $extension=''; - $extOffset=strpos($path, '.'); - if($extOffset !== false) { - $extension=substr($path, strrpos($path,'.')); + if ($source) { + $extension = ''; + $extOffset = strpos($path, '.'); + if ($extOffset !== false) { + $extension = substr($path, strrpos($path, '.')); } $tmpFile = OC_Helper::tmpFile($extension); file_put_contents($tmpFile, $source); @@ -506,13 +528,14 @@ class OC_FilesystemView { } } } + public function fromTmpFile($tmpFile, $path) { - if(OC_Filesystem::isValidPath($path)) { - if(!$tmpFile) { + if (OC_Filesystem::isValidPath($path)) { + if (!$tmpFile) { debug_print_backtrace(); } - $source=fopen($tmpFile, 'r'); - if($source) { + $source = fopen($tmpFile, 'r'); + if ($source) { $this->file_put_contents($path, $source); unlink($tmpFile); return true; @@ -526,8 +549,9 @@ class OC_FilesystemView { public function getMimeType($path) { return $this->basicOperation('getMimeType', $path); } + public function hash($type, $path, $raw = false) { - $postFix=(substr($path,-1,1)==='/')?'/':''; + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path)); if (OC_FileProxy::runPreProxies('hash', $absolutePath) && OC_Filesystem::isValidPath($path)) { $path = $this->getRelativePath($absolutePath); @@ -538,11 +562,11 @@ class OC_FilesystemView { OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_read, - array( OC_Filesystem::signal_param_path => $path) + array(OC_Filesystem::signal_param_path => $path) ); } - if ($storage = $this->getStorage($path.$postFix)) { - $result = $storage->hash($type, $this->getInternalPath($path.$postFix), $raw); + if ($storage = $this->getStorage($path . $postFix)) { + $result = $storage->hash($type, $this->getInternalPath($path . $postFix), $raw); $result = OC_FileProxy::runPostProxies('hash', $absolutePath, $result); return $result; } @@ -550,7 +574,7 @@ class OC_FilesystemView { return null; } - public function free_space($path='/') { + public function free_space($path = '/') { return $this->basicOperation('free_space', $path); } @@ -566,26 +590,26 @@ class OC_FilesystemView { * files), processes hooks and proxies, sanitises paths, and finally passes them on to * \OC\Files\Storage\Storage for delegation to a storage backend for execution */ - private function basicOperation($operation, $path, $hooks=array(), $extraParam=null) { - $postFix=(substr($path,-1,1)==='/')?'/':''; + private function basicOperation($operation, $path, $hooks = array(), $extraParam = null) { + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path)); - if(OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and OC_Filesystem::isValidPath($path)) { + if (OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and OC_Filesystem::isValidPath($path)) { $path = $this->getRelativePath($absolutePath); - if($path == null) { + if ($path == null) { return false; } - $internalPath = $this->getInternalPath($path.$postFix); - $run=$this->runHooks($hooks,$path); - if($run and $storage = $this->getStorage($path.$postFix)) { - if(!is_null($extraParam)) { + $internalPath = $this->getInternalPath($path . $postFix); + $run = $this->runHooks($hooks, $path); + if ($run and $storage = $this->getStorage($path . $postFix)) { + if (!is_null($extraParam)) { $result = $storage->$operation($internalPath, $extraParam); } else { $result = $storage->$operation($internalPath); } $result = OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result); - if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()) { - if($operation!='fopen') {//no post hooks for fopen, the file stream is still open - $this->runHooks($hooks,$path,true); + if (OC_Filesystem::$loaded and $this->fakeRoot == OC_Filesystem::getRoot()) { + if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open + $this->runHooks($hooks, $path, true); } } return $result; @@ -594,24 +618,24 @@ class OC_FilesystemView { return null; } - private function runHooks($hooks,$path,$post=false) { - $prefix=($post)?'post_':''; - $run=true; - if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()) { - foreach($hooks as $hook) { - if($hook!='read') { + private function runHooks($hooks, $path, $post = false) { + $prefix = ($post) ? 'post_' : ''; + $run = true; + if (OC_Filesystem::$loaded and $this->fakeRoot == OC_Filesystem::getRoot()) { + foreach ($hooks as $hook) { + if ($hook != 'read') { OC_Hook::emit( OC_Filesystem::CLASSNAME, - $prefix.$hook, + $prefix . $hook, array( OC_Filesystem::signal_param_run => &$run, OC_Filesystem::signal_param_path => $path ) ); - } elseif(!$post) { + } elseif (!$post) { OC_Hook::emit( OC_Filesystem::CLASSNAME, - $prefix.$hook, + $prefix . $hook, array( OC_Filesystem::signal_param_path => $path ) @@ -624,6 +648,7 @@ class OC_FilesystemView { /** * check if a file or folder has been updated since $time + * * @param int $time * @return bool */ diff --git a/lib/filesystem.php b/lib/filesystem.php index b73b52d420..b1200f95bf 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -22,566 +22,4 @@ */ -/** - * Class for abstraction of filesystem functions - * This class won't call any filesystem functions for itself but but will pass them to the correct OC_Filestorage object - * this class should also handle all the file permission related stuff - * - * Hooks provided: - * read(path) - * write(path, &run) - * post_write(path) - * create(path, &run) (when a file is created, both create and write will be emited in that order) - * post_create(path) - * delete(path, &run) - * post_delete(path) - * rename(oldpath,newpath, &run) - * post_rename(oldpath,newpath) - * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emited in that order) - * post_rename(oldpath,newpath) - * - * the &run parameter can be set to false to prevent the operation from occuring - */ -class OC_Filesystem{ - static private $storages=array(); - static private $mounts=array(); - public static $loaded=false; - /** - * @var \OC\Files\Storage\Storage $defaultInstance - */ - static private $defaultInstance; - - - /** - * classname which used for hooks handling - * used as signalclass in OC_Hooks::emit() - */ - const CLASSNAME = 'OC_Filesystem'; - - /** - * signalname emited before file renaming - * @param oldpath - * @param newpath - */ - const signal_rename = 'rename'; - - /** - * signal emited after file renaming - * @param oldpath - * @param newpath - */ - const signal_post_rename = 'post_rename'; - - /** - * signal emited before file/dir creation - * @param path - * @param run changing this flag to false in hook handler will cancel event - */ - const signal_create = 'create'; - - /** - * signal emited after file/dir creation - * @param path - * @param run changing this flag to false in hook handler will cancel event - */ - const signal_post_create = 'post_create'; - - /** - * signal emits before file/dir copy - * @param oldpath - * @param newpath - * @param run changing this flag to false in hook handler will cancel event - */ - const signal_copy = 'copy'; - - /** - * signal emits after file/dir copy - * @param oldpath - * @param newpath - */ - const signal_post_copy = 'post_copy'; - - /** - * signal emits before file/dir save - * @param path - * @param run changing this flag to false in hook handler will cancel event - */ - const signal_write = 'write'; - - /** - * signal emits after file/dir save - * @param path - */ - const signal_post_write = 'post_write'; - - /** - * signal emits when reading file/dir - * @param path - */ - const signal_read = 'read'; - - /** - * signal emits when removing file/dir - * @param path - */ - const signal_delete = 'delete'; - - /** - * parameters definitions for signals - */ - const signal_param_path = 'path'; - const signal_param_oldpath = 'oldpath'; - const signal_param_newpath = 'newpath'; - - /** - * run - changing this flag to false in hook handler will cancel event - */ - const signal_param_run = 'run'; - - /** - * get the mountpoint of the storage object for a path - ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account - * - * @param string path - * @return string - */ - static public function getMountPoint($path) { - OC_Hook::emit(self::CLASSNAME,'get_mountpoint',array('path'=>$path)); - if(!$path) { - $path='/'; - } - if($path[0]!=='/') { - $path='/'.$path; - } - $path=str_replace('//', '/',$path); - $foundMountPoint=''; - $mountPoints=array_keys(OC_Filesystem::$mounts); - foreach($mountPoints as $mountpoint) { - if($mountpoint==$path) { - return $mountpoint; - } - if(strpos($path,$mountpoint)===0 and strlen($mountpoint)>strlen($foundMountPoint)) { - $foundMountPoint=$mountpoint; - } - } - return $foundMountPoint; - } - - /** - * get the part of the path relative to the mountpoint of the storage it's stored in - * @param string path - * @return bool - */ - static public function getInternalPath($path) { - $mountPoint=self::getMountPoint($path); - $internalPath=substr($path,strlen($mountPoint)); - return $internalPath; - } - /** - * get the storage object for a path - * @param string path - * @return \OC\Files\Storage\Storage - */ - static public function getStorage($path) { - $mountpoint=self::getMountPoint($path); - if($mountpoint) { - if(!isset(OC_Filesystem::$storages[$mountpoint])) { - $mount=OC_Filesystem::$mounts[$mountpoint]; - OC_Filesystem::$storages[$mountpoint]=OC_Filesystem::createStorage($mount['class'],$mount['arguments']); - } - return OC_Filesystem::$storages[$mountpoint]; - } - } - - /** - * resolve a path to a storage and internal path - * @param string $path - * @return array consisting of the storage and the internal path - */ - static public function resolvePath($path){ - $mountpoint=self::getMountPoint($path); - if($mountpoint) { - if(!isset(OC_Filesystem::$storages[$mountpoint])) { - $mount=OC_Filesystem::$mounts[$mountpoint]; - OC_Filesystem::$storages[$mountpoint]=OC_Filesystem::createStorage($mount['class'],$mount['arguments']); - } - $storage = OC_Filesystem::$storages[$mountpoint]; - $internalPath=substr($path,strlen($mountpoint)); - return array($storage, $internalPath); - } - } - - static public function init($root) { - if(self::$defaultInstance) { - return false; - } - self::$defaultInstance=new OC_FilesystemView($root); - - //load custom mount config - if(is_file(OC::$SERVERROOT.'/config/mount.php')) { - $mountConfig=include(OC::$SERVERROOT.'/config/mount.php'); - if(isset($mountConfig['global'])) { - foreach($mountConfig['global'] as $mountPoint=>$options) { - self::mount($options['class'],$options['options'],$mountPoint); - } - } - - if(isset($mountConfig['group'])) { - foreach($mountConfig['group'] as $group=>$mounts) { - if(OC_Group::inGroup(OC_User::getUser(),$group)) { - foreach($mounts as $mountPoint=>$options) { - $mountPoint=self::setUserVars($mountPoint); - foreach($options as &$option) { - $option=self::setUserVars($option); - } - self::mount($options['class'],$options['options'],$mountPoint); - } - } - } - } - - if(isset($mountConfig['user'])) { - foreach($mountConfig['user'] as $user=>$mounts) { - if($user==='all' or strtolower($user)===strtolower(OC_User::getUser())) { - foreach($mounts as $mountPoint=>$options) { - $mountPoint=self::setUserVars($mountPoint); - foreach($options as &$option) { - $option=self::setUserVars($option); - } - self::mount($options['class'],$options['options'],$mountPoint); - } - } - } - } - } - - self::$loaded=true; - } - - /** - * fill in the correct values for $user, and $password placeholders - * @param string intput - * @return string - */ - private static function setUserVars($input) { - return str_replace('$user',OC_User::getUser(),$input); - } - - /** - * get the default filesystem view - * @return OC_FilesystemView - */ - static public function getView() { - return self::$defaultInstance; - } - - /** - * tear down the filesystem, removing all storage providers - */ - static public function tearDown() { - self::$storages=array(); - } - - /** - * create a new storage of a specific type - * @param string type - * @param array arguments - * @return \OC\Files\Storage\Storage - */ - static private function createStorage($class,$arguments) { - if(class_exists($class)) { - try { - return new $class($arguments); - } catch (Exception $exception) { - OC_Log::write('core', $exception->getMessage(), OC_Log::ERROR); - return false; - } - }else{ - OC_Log::write('core','storage backend '.$class.' not found',OC_Log::ERROR); - return false; - } - } - - /** - * change the root to a fake root - * @param string fakeRoot - * @return bool - */ - static public function chroot($fakeRoot) { - return self::$defaultInstance->chroot($fakeRoot); - } - - /** - * @brief get the relative path of the root data directory for the current user - * @return string - * - * Returns path like /admin/files - */ - static public function getRoot() { - return self::$defaultInstance->getRoot(); - } - - /** - * clear all mounts and storage backends - */ - public static function clearMounts() { - self::$mounts=array(); - self::$storages=array(); - } - - /** - * mount an \OC\Files\Storage\Storage in our virtual filesystem - * @param \OC\Files\Storage\Storage storage - * @param string mountpoint - */ - static public function mount($class,$arguments,$mountpoint) { - if($mountpoint[0]!='/') { - $mountpoint='/'.$mountpoint; - } - if(substr($mountpoint,-1)!=='/') { - $mountpoint=$mountpoint.'/'; - } - self::$mounts[$mountpoint]=array('class'=>$class,'arguments'=>$arguments); - } - - /** - * return the path to a local version of the file - * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed - * @param string path - * @return string - */ - static public function getLocalFile($path) { - return self::$defaultInstance->getLocalFile($path); - } - /** - * @param string path - * @return string - */ - static public function getLocalFolder($path) { - return self::$defaultInstance->getLocalFolder($path); - } - - /** - * return path to file which reflects one visible in browser - * @param string path - * @return string - */ - static public function getLocalPath($path) { - $datadir = OC_User::getHome(OC_User::getUser()).'/files'; - $newpath = $path; - if (strncmp($newpath, $datadir, strlen($datadir)) == 0) { - $newpath = substr($path, strlen($datadir)); - } - return $newpath; - } - - /** - * check if the requested path is valid - * @param string path - * @return bool - */ - static public function isValidPath($path) { - if(!$path || $path[0]!=='/') { - $path='/'.$path; - } - if(strstr($path,'/../') || strrchr($path, '/') === '/..' ) { - return false; - } - return true; - } - - /** - * checks if a file is blacklisted for storage in the filesystem - * Listens to write and rename hooks - * @param array $data from hook - */ - static public function isBlacklisted($data) { - $blacklist = array('.htaccess'); - if (isset($data['path'])) { - $path = $data['path']; - } else if (isset($data['newpath'])) { - $path = $data['newpath']; - } - if (isset($path)) { - $filename = strtolower(basename($path)); - if (in_array($filename, $blacklist)) { - $data['run'] = false; - } - } - } - - /** - * following functions are equivalent to their php builtin equivalents for arguments/return values. - */ - static public function mkdir($path) { - return self::$defaultInstance->mkdir($path); - } - static public function rmdir($path) { - return self::$defaultInstance->rmdir($path); - } - static public function opendir($path) { - return self::$defaultInstance->opendir($path); - } - static public function readdir($path) { - return self::$defaultInstance->readdir($path); - } - static public function is_dir($path) { - return self::$defaultInstance->is_dir($path); - } - static public function is_file($path) { - return self::$defaultInstance->is_file($path); - } - static public function stat($path) { - return self::$defaultInstance->stat($path); - } - static public function filetype($path) { - return self::$defaultInstance->filetype($path); - } - static public function filesize($path) { - return self::$defaultInstance->filesize($path); - } - static public function readfile($path) { - return self::$defaultInstance->readfile($path); - } - /** - * @deprecated Replaced by isReadable() as part of CRUDS - */ - static public function is_readable($path) { - return self::$defaultInstance->is_readable($path); - } - /** - * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS - */ - static public function is_writable($path) { - return self::$defaultInstance->is_writable($path); - } - static public function isCreatable($path) { - return self::$defaultInstance->isCreatable($path); - } - static public function isReadable($path) { - return self::$defaultInstance->isReadable($path); - } - static public function isUpdatable($path) { - return self::$defaultInstance->isUpdatable($path); - } - static public function isDeletable($path) { - return self::$defaultInstance->isDeletable($path); - } - static public function isSharable($path) { - return self::$defaultInstance->isSharable($path); - } - static public function file_exists($path) { - return self::$defaultInstance->file_exists($path); - } - static public function filectime($path) { - return self::$defaultInstance->filectime($path); - } - static public function filemtime($path) { - return self::$defaultInstance->filemtime($path); - } - static public function touch($path, $mtime=null) { - return self::$defaultInstance->touch($path, $mtime); - } - static public function file_get_contents($path) { - return self::$defaultInstance->file_get_contents($path); - } - static public function file_put_contents($path,$data) { - return self::$defaultInstance->file_put_contents($path,$data); - } - static public function unlink($path) { - return self::$defaultInstance->unlink($path); - } - static public function rename($path1,$path2) { - return self::$defaultInstance->rename($path1,$path2); - } - static public function copy($path1,$path2) { - return self::$defaultInstance->copy($path1,$path2); - } - static public function fopen($path,$mode) { - return self::$defaultInstance->fopen($path,$mode); - } - static public function toTmpFile($path) { - return self::$defaultInstance->toTmpFile($path); - } - static public function fromTmpFile($tmpFile,$path) { - return self::$defaultInstance->fromTmpFile($tmpFile,$path); - } - - static public function getMimeType($path) { - return self::$defaultInstance->getMimeType($path); - } - static public function hash($type,$path, $raw = false) { - return self::$defaultInstance->hash($type,$path, $raw); - } - - static public function free_space($path='/') { - return self::$defaultInstance->free_space($path); - } - - static public function search($query) { - return OC_FileCache::search($query); - } - - /** - * check if a file or folder has been updated since $time - * @param int $time - * @return bool - */ - static public function hasUpdated($path,$time) { - return self::$defaultInstance->hasUpdated($path,$time); - } - - static public function removeETagHook($params, $root = false) { - if (isset($params['path'])) { - $path=$params['path']; - } else { - $path=$params['oldpath']; - } - - if ($root) { // reduce path to the required part of it (no 'username/files') - $fakeRootView = new OC_FilesystemView($root); - $count = 1; - $path=str_replace(OC_App::getStorage("files")->getAbsolutePath($path), "", $fakeRootView->getAbsolutePath($path), $count); - } - - $path = self::normalizePath($path); - OC_Connector_Sabre_Node::removeETagPropertyForPath($path); - } - - /** - * normalize a path - * @param string path - * @param bool $stripTrailingSlash - * @return string - */ - public static function normalizePath($path,$stripTrailingSlash=true) { - if($path=='') { - return '/'; - } - //no windows style slashes - $path=str_replace('\\','/',$path); - //add leading slash - if($path[0]!=='/') { - $path='/'.$path; - } - //remove trainling slash - if($stripTrailingSlash and strlen($path)>1 and substr($path,-1,1)==='/') { - $path=substr($path,0,-1); - } - //remove duplicate slashes - while(strpos($path,'//')!==false) { - $path=str_replace('//','/',$path); - } - //normalize unicode if possible - if(class_exists('Normalizer')) { - $path=Normalizer::normalize($path); - } - return $path; - } -} -OC_Hook::connect('OC_Filesystem','post_write', 'OC_Filesystem','removeETagHook'); -OC_Hook::connect('OC_Filesystem','post_delete','OC_Filesystem','removeETagHook'); -OC_Hook::connect('OC_Filesystem','post_rename','OC_Filesystem','removeETagHook'); - -OC_Util::setupFS(); From 07c53841899262fdd34304302ffaaf2704800c38 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 10 Oct 2012 12:25:46 +0200 Subject: [PATCH 029/418] fix namespaces in filesystem and filesystemview --- lib/app.php | 6 +- lib/files/filesystem.php | 153 ++++++++++++----------- lib/files/view.php | 260 +++++++++++++++++++++------------------ 3 files changed, 225 insertions(+), 194 deletions(-) diff --git a/lib/app.php b/lib/app.php index 395230156f..4d7353e99d 100755 --- a/lib/app.php +++ b/lib/app.php @@ -714,16 +714,16 @@ class OC_App{ /** * @param string $appid - * @return OC_FilesystemView + * @return \OC\Files\View */ public static function getStorage($appid) { if(OC_App::isEnabled($appid)) {//sanity check if(OC_User::isLoggedIn()) { - $view = new OC_FilesystemView('/'.OC_User::getUser()); + $view = new \OC\Files\View('/'.OC_User::getUser()); if(!$view->file_exists($appid)) { $view->mkdir($appid); } - return new OC_FilesystemView('/'.OC_User::getUser().'/'.$appid); + return new \OC\Files\View('/'.OC_User::getUser().'/'.$appid); }else{ OC_Log::write('core', 'Can\'t get app storage, app, user not logged in', OC_Log::ERROR); return false; diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index d0a96b59a0..f23f3a79e9 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -15,16 +15,16 @@ * read(path) * write(path, &run) * post_write(path) - * create(path, &run) (when a file is created, both create and write will be emited in that order) + * create(path, &run) (when a file is created, both create and write will be emitted in that order) * post_create(path) * delete(path, &run) * post_delete(path) * rename(oldpath,newpath, &run) * post_rename(oldpath,newpath) - * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emited in that order) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order) * post_rename(oldpath,newpath) * - * the &run parameter can be set to false to prevent the operation from occuring + * the &run parameter can be set to false to prevent the operation from occurring */ namespace OC\Files; @@ -34,7 +34,7 @@ class Filesystem { static private $mounts = array(); public static $loaded = false; /** - * @var \OC\Files\Storage\Storage $defaultInstance + * @var \OC\Files\View $defaultInstance */ static private $defaultInstance; @@ -46,80 +46,80 @@ class Filesystem { const CLASSNAME = 'OC_Filesystem'; /** - * signalname emited before file renaming + * signalname emitted before file renaming * - * @param oldpath - * @param newpath + * @param string $oldpath + * @param string $newpath */ const signal_rename = 'rename'; /** - * signal emited after file renaming + * signal emitted after file renaming * - * @param oldpath - * @param newpath + * @param string $oldpath + * @param string $newpath */ const signal_post_rename = 'post_rename'; /** - * signal emited before file/dir creation + * signal emitted before file/dir creation * - * @param path - * @param run changing this flag to false in hook handler will cancel event + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event */ const signal_create = 'create'; /** - * signal emited after file/dir creation + * signal emitted after file/dir creation * - * @param path - * @param run changing this flag to false in hook handler will cancel event + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event */ const signal_post_create = 'post_create'; /** * signal emits before file/dir copy * - * @param oldpath - * @param newpath - * @param run changing this flag to false in hook handler will cancel event + * @param string $oldpath + * @param string $newpath + * @param bool $run changing this flag to false in hook handler will cancel event */ const signal_copy = 'copy'; /** * signal emits after file/dir copy * - * @param oldpath - * @param newpath + * @param string $oldpath + * @param string $newpath */ const signal_post_copy = 'post_copy'; /** * signal emits before file/dir save * - * @param path - * @param run changing this flag to false in hook handler will cancel event + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event */ const signal_write = 'write'; /** * signal emits after file/dir save * - * @param path + * @param string $path */ const signal_post_write = 'post_write'; /** * signal emits when reading file/dir * - * @param path + * @param string $path */ const signal_read = 'read'; /** * signal emits when removing file/dir * - * @param path + * @param string $path */ const signal_delete = 'delete'; @@ -139,11 +139,11 @@ class Filesystem { * get the mountpoint of the storage object for a path ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account * - * @param string path + * @param string $path * @return string */ static public function getMountPoint($path) { - OC_Hook::emit(self::CLASSNAME, 'get_mountpoint', array('path' => $path)); + \OC_Hook::emit(self::CLASSNAME, 'get_mountpoint', array('path' => $path)); if (!$path) { $path = '/'; } @@ -152,7 +152,7 @@ class Filesystem { } $path = str_replace('//', '/', $path); $foundMountPoint = ''; - $mountPoints = array_keys(OC_Filesystem::$mounts); + $mountPoints = array_keys(self::$mounts); foreach ($mountPoints as $mountpoint) { if ($mountpoint == $path) { return $mountpoint; @@ -167,7 +167,7 @@ class Filesystem { /** * get the part of the path relative to the mountpoint of the storage it's stored in * - * @param string path + * @param string $path * @return bool */ static public function getInternalPath($path) { @@ -179,17 +179,19 @@ class Filesystem { /** * get the storage object for a path * - * @param string path + * @param string $path * @return \OC\Files\Storage\Storage */ static public function getStorage($path) { $mountpoint = self::getMountPoint($path); if ($mountpoint) { - if (!isset(OC_Filesystem::$storages[$mountpoint])) { - $mount = OC_Filesystem::$mounts[$mountpoint]; - OC_Filesystem::$storages[$mountpoint] = OC_Filesystem::createStorage($mount['class'], $mount['arguments']); + if (!isset(self::$storages[$mountpoint])) { + $mount = self::$mounts[$mountpoint]; + self::$storages[$mountpoint] = self::createStorage($mount['class'], $mount['arguments']); } - return OC_Filesystem::$storages[$mountpoint]; + return self::$storages[$mountpoint]; + }else{ + return null; } } @@ -202,13 +204,15 @@ class Filesystem { static public function resolvePath($path) { $mountpoint = self::getMountPoint($path); if ($mountpoint) { - if (!isset(OC_Filesystem::$storages[$mountpoint])) { - $mount = OC_Filesystem::$mounts[$mountpoint]; - OC_Filesystem::$storages[$mountpoint] = OC_Filesystem::createStorage($mount['class'], $mount['arguments']); + if (!isset(self::$storages[$mountpoint])) { + $mount = self::$mounts[$mountpoint]; + self::$storages[$mountpoint] = self::createStorage($mount['class'], $mount['arguments']); } - $storage = OC_Filesystem::$storages[$mountpoint]; + $storage = self::$storages[$mountpoint]; $internalPath = substr($path, strlen($mountpoint)); return array($storage, $internalPath); + }else{ + return array(null, null); } } @@ -216,11 +220,11 @@ class Filesystem { if (self::$defaultInstance) { return false; } - self::$defaultInstance = new OC_FilesystemView($root); + self::$defaultInstance = new View($root); -//load custom mount config - if (is_file(OC::$SERVERROOT . '/config/mount.php')) { - $mountConfig = include(OC::$SERVERROOT . '/config/mount.php'); + //load custom mount config + if (is_file(\OC::$SERVERROOT . '/config/mount.php')) { + $mountConfig = include 'config/mount.php'; if (isset($mountConfig['global'])) { foreach ($mountConfig['global'] as $mountPoint => $options) { self::mount($options['class'], $options['options'], $mountPoint); @@ -229,7 +233,7 @@ class Filesystem { if (isset($mountConfig['group'])) { foreach ($mountConfig['group'] as $group => $mounts) { - if (OC_Group::inGroup(OC_User::getUser(), $group)) { + if (\OC_Group::inGroup(\OC_User::getUser(), $group)) { foreach ($mounts as $mountPoint => $options) { $mountPoint = self::setUserVars($mountPoint); foreach ($options as &$option) { @@ -243,7 +247,7 @@ class Filesystem { if (isset($mountConfig['user'])) { foreach ($mountConfig['user'] as $user => $mounts) { - if ($user === 'all' or strtolower($user) === strtolower(OC_User::getUser())) { + if ($user === 'all' or strtolower($user) === strtolower(\OC_User::getUser())) { foreach ($mounts as $mountPoint => $options) { $mountPoint = self::setUserVars($mountPoint); foreach ($options as &$option) { @@ -257,22 +261,24 @@ class Filesystem { } self::$loaded = true; + + return true; } /** * fill in the correct values for $user, and $password placeholders * - * @param string intput + * @param string $input * @return string */ private static function setUserVars($input) { - return str_replace('$user', OC_User::getUser(), $input); + return str_replace('$user', \OC_User::getUser(), $input); } /** * get the default filesystem view * - * @return OC_FilesystemView + * @return View */ static public function getView() { return self::$defaultInstance; @@ -288,20 +294,20 @@ class Filesystem { /** * create a new storage of a specific type * - * @param string type - * @param array arguments + * @param string $type + * @param array $arguments * @return \OC\Files\Storage\Storage */ static private function createStorage($class, $arguments) { if (class_exists($class)) { try { return new $class($arguments); - } catch (Exception $exception) { - OC_Log::write('core', $exception->getMessage(), OC_Log::ERROR); + } catch (\Exception $exception) { + \OC_Log::write('core', $exception->getMessage(), \OC_Log::ERROR); return false; } } else { - OC_Log::write('core', 'storage backend ' . $class . ' not found', OC_Log::ERROR); + \OC_Log::write('core', 'storage backend ' . $class . ' not found', \OC_Log::ERROR); return false; } } @@ -309,7 +315,7 @@ class Filesystem { /** * change the root to a fake root * - * @param string fakeRoot + * @param string $fakeRoot * @return bool */ static public function chroot($fakeRoot) { @@ -337,8 +343,9 @@ class Filesystem { /** * mount an \OC\Files\Storage\Storage in our virtual filesystem * - * @param \OC\Files\Storage\Storage storage - * @param string mountpoint + * @param \OC\Files\Storage\Storage $storage + * @param array $arguments + * @param string $mountpoint */ static public function mount($class, $arguments, $mountpoint) { if ($mountpoint[0] != '/') { @@ -354,7 +361,7 @@ class Filesystem { * return the path to a local version of the file * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed * - * @param string path + * @param string $path * @return string */ static public function getLocalFile($path) { @@ -362,7 +369,7 @@ class Filesystem { } /** - * @param string path + * @param string $path * @return string */ static public function getLocalFolder($path) { @@ -372,11 +379,11 @@ class Filesystem { /** * return path to file which reflects one visible in browser * - * @param string path + * @param string $path * @return string */ static public function getLocalPath($path) { - $datadir = OC_User::getHome(OC_User::getUser()) . '/files'; + $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files'; $newpath = $path; if (strncmp($newpath, $datadir, strlen($datadir)) == 0) { $newpath = substr($path, strlen($datadir)); @@ -387,7 +394,7 @@ class Filesystem { /** * check if the requested path is valid * - * @param string path + * @param string $path * @return bool */ static public function isValidPath($path) { @@ -468,7 +475,7 @@ class Filesystem { * @deprecated Replaced by isReadable() as part of CRUDS */ static public function is_readable($path) { - return self::$defaultInstance->is_readable($path); + return self::$defaultInstance->isReadable($path); } /** @@ -559,7 +566,7 @@ class Filesystem { } static public function search($query) { - return OC_FileCache::search($query); + return Cache\Cache::search($query); } /** @@ -580,19 +587,19 @@ class Filesystem { } if ($root) { // reduce path to the required part of it (no 'username/files') - $fakeRootView = new OC_FilesystemView($root); + $fakeRootView = new View($root); $count = 1; - $path = str_replace(OC_App::getStorage("files")->getAbsolutePath($path), "", $fakeRootView->getAbsolutePath($path), $count); + $path = str_replace(\OC_App::getStorage("files")->getAbsolutePath($path), "", $fakeRootView->getAbsolutePath($path), $count); } $path = self::normalizePath($path); - OC_Connector_Sabre_Node::removeETagPropertyForPath($path); + \OC_Connector_Sabre_Node::removeETagPropertyForPath($path); } /** * normalize a path * - * @param string path + * @param string $path * @param bool $stripTrailingSlash * @return string */ @@ -606,7 +613,7 @@ class Filesystem { if ($path[0] !== '/') { $path = '/' . $path; } -//remove trainling slash +//remove trailing slash if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { $path = substr($path, 0, -1); } @@ -616,14 +623,14 @@ class Filesystem { } //normalize unicode if possible if (class_exists('Normalizer')) { - $path = Normalizer::normalize($path); + $path = \Normalizer::normalize($path); } return $path; } } -OC_Hook::connect('OC_Filesystem', 'post_write', 'OC_Filesystem', 'removeETagHook'); -OC_Hook::connect('OC_Filesystem', 'post_delete', 'OC_Filesystem', 'removeETagHook'); -OC_Hook::connect('OC_Filesystem', 'post_rename', 'OC_Filesystem', 'removeETagHook'); +\OC_Hook::connect('OC_Filesystem', 'post_write', 'OC_Filesystem', 'removeETagHook'); +\OC_Hook::connect('OC_Filesystem', 'post_delete', 'OC_Filesystem', 'removeETagHook'); +\OC_Hook::connect('OC_Filesystem', 'post_rename', 'OC_Filesystem', 'removeETagHook'); -OC_Util::setupFS(); +\OC_Util::setupFS(); diff --git a/lib/files/view.php b/lib/files/view.php index 8c3f288f44..230455479c 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -45,9 +45,9 @@ class View { } /** - * change the root to a fake toor + * change the root to a fake root * - * @param string fakeRoot + * @param string $fakeRoot * @return bool */ public function chroot($fakeRoot) { @@ -71,12 +71,12 @@ class View { /** * get the part of the path relative to the mountpoint of the storage it's stored in * - * @param string path + * @param string $path * @return bool */ public function getInternalPath($path) { if (!isset($this->internal_path_cache[$path])) { - $this->internal_path_cache[$path] = OC_Filesystem::getInternalPath($this->getAbsolutePath($path)); + $this->internal_path_cache[$path] = Filesystem::getInternalPath($this->getAbsolutePath($path)); } return $this->internal_path_cache[$path]; } @@ -84,7 +84,7 @@ class View { /** * get path relative to the root of the view * - * @param string path + * @param string $path * @return string */ public function getRelativePath($path) { @@ -106,12 +106,12 @@ class View { /** * get the storage object for a path * - * @param string path + * @param string $path * @return \OC\Files\Storage\Storage */ public function getStorage($path) { if (!isset($this->storage_cache[$path])) { - $this->storage_cache[$path] = OC_Filesystem::getStorage($this->getAbsolutePath($path)); + $this->storage_cache[$path] = Filesystem::getStorage($this->getAbsolutePath($path)); } return $this->storage_cache[$path]; } @@ -120,35 +120,39 @@ class View { * get the mountpoint of the storage object for a path ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account * - * @param string path + * @param string $path * @return string */ public function getMountPoint($path) { - return OC_Filesystem::getMountPoint($this->getAbsolutePath($path)); + return Filesystem::getMountPoint($this->getAbsolutePath($path)); } /** * return the path to a local version of the file * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed * - * @param string path + * @param string $path * @return string */ public function getLocalFile($path) { $parent = substr($path, 0, strrpos($path, '/')); - if (OC_Filesystem::isValidPath($parent) and $storage = $this->getStorage($path)) { + if (Filesystem::isValidPath($parent) and $storage = $this->getStorage($path)) { return $storage->getLocalFile($this->getInternalPath($path)); + } else { + return null; } } /** - * @param string path + * @param string $path * @return string */ public function getLocalFolder($path) { $parent = substr($path, 0, strrpos($path, '/')); - if (OC_Filesystem::isValidPath($parent) and $storage = $this->getStorage($path)) { + if (Filesystem::isValidPath($parent) and $storage = $this->getStorage($path)) { return $storage->getLocalFolder($this->getInternalPath($path)); + } else { + return null; } } @@ -274,28 +278,28 @@ class View { public function file_put_contents($path, $data) { if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier - $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path)); - if (OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) && OC_Filesystem::isValidPath($path)) { + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) && Filesystem::isValidPath($path)) { $path = $this->getRelativePath($absolutePath); $exists = $this->file_exists($path); $run = true; - if ($this->fakeRoot == OC_Filesystem::getRoot()) { + if ($this->fakeRoot == Filesystem::getRoot()) { if (!$exists) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_create, + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_create, array( - OC_Filesystem::signal_param_path => $path, - OC_Filesystem::signal_param_run => &$run + Filesystem::signal_param_path => $path, + Filesystem::signal_param_run => &$run ) ); } - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_write, + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_write, array( - OC_Filesystem::signal_param_path => $path, - OC_Filesystem::signal_param_run => &$run + Filesystem::signal_param_path => $path, + Filesystem::signal_param_run => &$run ) ); } @@ -304,28 +308,30 @@ class View { } $target = $this->fopen($path, 'w'); if ($target) { - $count = OC_Helper::streamCopy($data, $target); + $count = \OC_Helper::streamCopy($data, $target); fclose($target); fclose($data); - if ($this->fakeRoot == OC_Filesystem::getRoot()) { + if ($this->fakeRoot == Filesystem::getRoot()) { if (!$exists) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_post_create, - array(OC_Filesystem::signal_param_path => $path) + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_create, + array(Filesystem::signal_param_path => $path) ); } - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_post_write, - array(OC_Filesystem::signal_param_path => $path) + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + array(Filesystem::signal_param_path => $path) ); } - OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count); + \OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count); return $count > 0; } else { return false; } + } else { + return false; } } else { return $this->basicOperation('file_put_contents', $path, array('create', 'write'), $data); @@ -343,9 +349,9 @@ class View { public function rename($path1, $path2) { $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : ''; $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : ''; - $absolutePath1 = OC_Filesystem::normalizePath($this->getAbsolutePath($path1)); - $absolutePath2 = OC_Filesystem::normalizePath($this->getAbsolutePath($path2)); - if (OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) { + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + if (\OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) and Filesystem::isValidPath($path2)) { $path1 = $this->getRelativePath($absolutePath1); $path2 = $this->getRelativePath($absolutePath2); @@ -353,13 +359,13 @@ class View { return false; } $run = true; - if ($this->fakeRoot == OC_Filesystem::getRoot()) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, OC_Filesystem::signal_rename, + if ($this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, Filesystem::signal_rename, array( - OC_Filesystem::signal_param_oldpath => $path1, - OC_Filesystem::signal_param_newpath => $path2, - OC_Filesystem::signal_param_run => &$run + Filesystem::signal_param_oldpath => $path1, + Filesystem::signal_param_newpath => $path2, + Filesystem::signal_param_run => &$run ) ); } @@ -369,36 +375,42 @@ class View { if ($mp1 == $mp2) { if ($storage = $this->getStorage($path1)) { $result = $storage->rename($this->getInternalPath($path1 . $postFix1), $this->getInternalPath($path2 . $postFix2)); + } else { + $result = false; } } else { $source = $this->fopen($path1 . $postFix1, 'r'); $target = $this->fopen($path2 . $postFix2, 'w'); - $count = OC_Helper::streamCopy($source, $target); + $count = \OC_Helper::streamCopy($source, $target); $storage1 = $this->getStorage($path1); $storage1->unlink($this->getInternalPath($path1 . $postFix1)); $result = $count > 0; } - if ($this->fakeRoot == OC_Filesystem::getRoot()) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_post_rename, + if ($this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_rename, array( - OC_Filesystem::signal_param_oldpath => $path1, - OC_Filesystem::signal_param_newpath => $path2 + Filesystem::signal_param_oldpath => $path1, + Filesystem::signal_param_newpath => $path2 ) ); } return $result; + } else { + return false; } + } else { + return false; } } public function copy($path1, $path2) { $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : ''; $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : ''; - $absolutePath1 = OC_Filesystem::normalizePath($this->getAbsolutePath($path1)); - $absolutePath2 = OC_Filesystem::normalizePath($this->getAbsolutePath($path2)); - if (OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) { + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + if (\OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) and Filesystem::isValidPath($path2)) { $path1 = $this->getRelativePath($absolutePath1); $path2 = $this->getRelativePath($absolutePath2); @@ -406,34 +418,34 @@ class View { return false; } $run = true; - if ($this->fakeRoot == OC_Filesystem::getRoot()) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_copy, + $exists = $this->file_exists($path2); + if ($this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_copy, array( - OC_Filesystem::signal_param_oldpath => $path1, - OC_Filesystem::signal_param_newpath => $path2, - OC_Filesystem::signal_param_run => &$run + Filesystem::signal_param_oldpath => $path1, + Filesystem::signal_param_newpath => $path2, + Filesystem::signal_param_run => &$run ) ); - $exists = $this->file_exists($path2); if ($run and !$exists) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_create, + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_create, array( - OC_Filesystem::signal_param_path => $path2, - OC_Filesystem::signal_param_run => &$run + Filesystem::signal_param_path => $path2, + Filesystem::signal_param_run => &$run ) ); } if ($run) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_write, + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_write, array( - OC_Filesystem::signal_param_path => $path2, - OC_Filesystem::signal_param_run => &$run + Filesystem::signal_param_path => $path2, + Filesystem::signal_param_run => &$run ) ); } @@ -444,39 +456,45 @@ class View { if ($mp1 == $mp2) { if ($storage = $this->getStorage($path1 . $postFix1)) { $result = $storage->copy($this->getInternalPath($path1 . $postFix1), $this->getInternalPath($path2 . $postFix2)); + } else { + $result = false; } } else { $source = $this->fopen($path1 . $postFix1, 'r'); $target = $this->fopen($path2 . $postFix2, 'w'); - $result = OC_Helper::streamCopy($source, $target); + $result = \OC_Helper::streamCopy($source, $target); } - if ($this->fakeRoot == OC_Filesystem::getRoot()) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_post_copy, + if ($this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_copy, array( - OC_Filesystem::signal_param_oldpath => $path1, - OC_Filesystem::signal_param_newpath => $path2 + Filesystem::signal_param_oldpath => $path1, + Filesystem::signal_param_newpath => $path2 ) ); if (!$exists) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_post_create, - array(OC_Filesystem::signal_param_path => $path2) + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_create, + array(Filesystem::signal_param_path => $path2) ); } - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_post_write, - array(OC_Filesystem::signal_param_path => $path2) + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + array(Filesystem::signal_param_path => $path2) ); } else { // no real copy, file comes from somewhere else, e.g. version rollback -> just update the file cache and the webdav properties without all the other post_write actions - OC_FileCache_Update::update($path2, $this->fakeRoot); - OC_Filesystem::removeETagHook(array("path" => $path2), $this->fakeRoot); +// OC_FileCache_Update::update($path2, $this->fakeRoot); + Filesystem::removeETagHook(array("path" => $path2), $this->fakeRoot); } return $result; + } else { + return false; } + } else { + return false; } } @@ -507,14 +525,14 @@ class View { $hooks[] = 'write'; break; default: - OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, OC_Log::ERROR); + \OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, \OC_Log::ERROR); } return $this->basicOperation('fopen', $path, $hooks, $mode); } public function toTmpFile($path) { - if (OC_Filesystem::isValidPath($path)) { + if (Filesystem::isValidPath($path)) { $source = $this->fopen($path, 'r'); if ($source) { $extension = ''; @@ -522,15 +540,19 @@ class View { if ($extOffset !== false) { $extension = substr($path, strrpos($path, '.')); } - $tmpFile = OC_Helper::tmpFile($extension); + $tmpFile = \OC_Helper::tmpFile($extension); file_put_contents($tmpFile, $source); return $tmpFile; + } else { + return false; } + } else { + return false; } } public function fromTmpFile($tmpFile, $path) { - if (OC_Filesystem::isValidPath($path)) { + if (Filesystem::isValidPath($path)) { if (!$tmpFile) { debug_print_backtrace(); } @@ -540,6 +562,7 @@ class View { unlink($tmpFile); return true; } else { + return false; } } else { return false; @@ -552,22 +575,22 @@ class View { public function hash($type, $path, $raw = false) { $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; - $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path)); - if (OC_FileProxy::runPreProxies('hash', $absolutePath) && OC_Filesystem::isValidPath($path)) { + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies('hash', $absolutePath) && Filesystem::isValidPath($path)) { $path = $this->getRelativePath($absolutePath); if ($path == null) { return false; } - if (OC_Filesystem::$loaded && $this->fakeRoot == OC_Filesystem::getRoot()) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_read, - array(OC_Filesystem::signal_param_path => $path) + if (Filesystem::$loaded && $this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_read, + array(Filesystem::signal_param_path => $path) ); } if ($storage = $this->getStorage($path . $postFix)) { $result = $storage->hash($type, $this->getInternalPath($path . $postFix), $raw); - $result = OC_FileProxy::runPostProxies('hash', $absolutePath, $result); + $result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result); return $result; } } @@ -581,9 +604,9 @@ class View { /** * @brief abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage * @param string $operation - * @param string #path - * @param array (optional) hooks - * @param mixed (optional) $extraParam + * @param string $path + * @param array $hooks (optional) + * @param mixed $extraParam (optional) * @return mixed * * This method takes requests for basic filesystem functions (e.g. reading & writing @@ -592,8 +615,8 @@ class View { */ private function basicOperation($operation, $path, $hooks = array(), $extraParam = null) { $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; - $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path)); - if (OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and OC_Filesystem::isValidPath($path)) { + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and Filesystem::isValidPath($path)) { $path = $this->getRelativePath($absolutePath); if ($path == null) { return false; @@ -606,8 +629,8 @@ class View { } else { $result = $storage->$operation($internalPath); } - $result = OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result); - if (OC_Filesystem::$loaded and $this->fakeRoot == OC_Filesystem::getRoot()) { + $result = \OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result); + if (Filesystem::$loaded and $this->fakeRoot == Filesystem::getRoot()) { if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open $this->runHooks($hooks, $path, true); } @@ -621,23 +644,23 @@ class View { private function runHooks($hooks, $path, $post = false) { $prefix = ($post) ? 'post_' : ''; $run = true; - if (OC_Filesystem::$loaded and $this->fakeRoot == OC_Filesystem::getRoot()) { + if (Filesystem::$loaded and $this->fakeRoot == Filesystem::getRoot()) { foreach ($hooks as $hook) { if ($hook != 'read') { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, + \OC_Hook::emit( + Filesystem::CLASSNAME, $prefix . $hook, array( - OC_Filesystem::signal_param_run => &$run, - OC_Filesystem::signal_param_path => $path + Filesystem::signal_param_run => &$run, + Filesystem::signal_param_path => $path ) ); } elseif (!$post) { - OC_Hook::emit( - OC_Filesystem::CLASSNAME, + \OC_Hook::emit( + Filesystem::CLASSNAME, $prefix . $hook, array( - OC_Filesystem::signal_param_path => $path + Filesystem::signal_param_path => $path ) ); } @@ -649,6 +672,7 @@ class View { /** * check if a file or folder has been updated since $time * + * @param string $path * @param int $time * @return bool */ From 4b9fbf46e554f8bb368e6cf5a8f75ca5c56e8228 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 10 Oct 2012 12:50:57 +0200 Subject: [PATCH 030/418] add depricated OC_Filessystem for compatibility --- lib/filesystem.php | 479 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 460 insertions(+), 19 deletions(-) diff --git a/lib/filesystem.php b/lib/filesystem.php index b1200f95bf..4189fe36c2 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -1,25 +1,466 @@ . -* -*/ + * Copyright (c) 2012 Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +/** + * Class for abstraction of filesystem functions + * This class won't call any filesystem functions for itself but but will pass them to the correct OC_Filestorage object + * this class should also handle all the file permission related stuff + * + * Hooks provided: + * read(path) + * write(path, &run) + * post_write(path) + * create(path, &run) (when a file is created, both create and write will be emitted in that order) + * post_create(path) + * delete(path, &run) + * post_delete(path) + * rename(oldpath,newpath, &run) + * post_rename(oldpath,newpath) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order) + * post_rename(oldpath,newpath) + * + * the &run parameter can be set to false to prevent the operation from occurring + */ +/** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ +class OC_Filesystem { + /** + * get the mountpoint of the storage object for a path + ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return string + */ + static public function getMountPoint($path) { + return \OC\Files\Filesystem::getMountPoint($path); + } + /** + * get the part of the path relative to the mountpoint of the storage it's stored in + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return bool + */ + static public function getInternalPath($path) { + return \OC\Files\Filesystem::getInternalPath($path); + } + + /** + * get the storage object for a path + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return \OC\Files\Storage\Storage + */ + static public function getStorage($path) { + return \OC\Files\Filesystem::getStorage($path); + } + + /** + * resolve a path to a storage and internal path + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + return \OC\Files\Filesystem::resolvePath($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function init($root) { + return \OC\Files\Filesystem::init($root); + } + + /** + * get the default filesystem view + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @return \OC\Files\View + */ + static public function getView() { + return \OC\Files\Filesystem::getView(); + } + + /** + * tear down the filesystem, removing all storage providers + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function tearDown() { + \OC\Files\Filesystem::tearDown(); + } + + /** + * change the root to a fake root + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $fakeRoot + * @return bool + */ + static public function chroot($fakeRoot) { + return \OC\Files\Filesystem::chroot($fakeRoot); + } + + /** + * @brief get the relative path of the root data directory for the current user + * @return string + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * Returns path like /admin/files + */ + static public function getRoot() { + return \OC\Files\Filesystem::getRoot(); + } + + /** + * clear all mounts and storage backends + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + public static function clearMounts() { + \OC\Files\Filesystem::clearMounts(); + } + + /** + * mount an \OC\Files\Storage\Storage in our virtual filesystem + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param \OC\Files\Storage\Storage $class + * @param array $arguments + * @param string $mountpoint + */ + static public function mount($class, $arguments, $mountpoint) { + \OC\Files\Filesystem::mount($class, $arguments, $mountpoint); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return string + */ + static public function getLocalFile($path) { + return \OC\Files\Filesystem::getLocalFile($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return string + */ + static public function getLocalFolder($path) { + return \OC\Files\Filesystem::getLocalFolder($path); + } + + /** + * return path to file which reflects one visible in browser + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return string + */ + static public function getLocalPath($path) { + return \OC\Files\Filesystem::getLocalPath($path); + } + + /** + * check if the requested path is valid + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @return bool + */ + static public function isValidPath($path) { + return \OC\Files\Filesystem::isValidPath($path); + } + + /** + * checks if a file is blacklisted for storage in the filesystem + * Listens to write and rename hooks + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param array $data from hook + */ + static public function isBlacklisted($data) { + \OC\Files\Filesystem::isBlacklisted($data); + } + + /** + * following functions are equivalent to their php builtin equivalents for arguments/return values. + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function mkdir($path) { + return \OC\Files\Filesystem::mkdir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function rmdir($path) { + return \OC\Files\Filesystem::rmdir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function opendir($path) { + return \OC\Files\Filesystem::opendir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function readdir($path) { + return \OC\Files\Filesystem::readdir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function is_dir($path) { + return \OC\Files\Filesystem::is_dir($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function is_file($path) { + return \OC\Files\Filesystem::is_file($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function stat($path) { + return \OC\Files\Filesystem::stat($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function filetype($path) { + return \OC\Files\Filesystem::filetype($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function filesize($path) { + return \OC\Files\Filesystem::filesize($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function readfile($path) { + return \OC\Files\Filesystem::readfile($path); + } + + /** + * @deprecated Replaced by isReadable() as part of CRUDS + */ + static public function is_readable($path) { + return \OC\Files\Filesystem::isReadable($path); + } + + /** + * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS + */ + static public function is_writable($path) { + return \OC\Files\Filesystem::is_writable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isCreatable($path) { + return \OC\Files\Filesystem::isCreatable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isReadable($path) { + return \OC\Files\Filesystem::isReadable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isUpdatable($path) { + return \OC\Files\Filesystem::isUpdatable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isDeletable($path) { + return \OC\Files\Filesystem::isDeletable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function isSharable($path) { + return \OC\Files\Filesystem::isSharable($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function file_exists($path) { + return \OC\Files\Filesystem::file_exists($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function filectime($path) { + return \OC\Files\Filesystem::filectime($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function filemtime($path) { + return \OC\Files\Filesystem::filemtime($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function touch($path, $mtime = null) { + return \OC\Files\Filesystem::touch($path, $mtime); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function file_get_contents($path) { + return \OC\Files\Filesystem::file_get_contents($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function file_put_contents($path, $data) { + return \OC\Files\Filesystem::file_put_contents($path, $data); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function unlink($path) { + return \OC\Files\Filesystem::unlink($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function rename($path1, $path2) { + return \OC\Files\Filesystem::rename($path1, $path2); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function copy($path1, $path2) { + return \OC\Files\Filesystem::copy($path1, $path2); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function fopen($path, $mode) { + return \OC\Files\Filesystem::fopen($path, $mode); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function toTmpFile($path) { + return \OC\Files\Filesystem::toTmpFile($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function fromTmpFile($tmpFile, $path) { + return \OC\Files\Filesystem::fromTmpFile($tmpFile, $path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function getMimeType($path) { + return \OC\Files\Filesystem::getMimeType($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function hash($type, $path, $raw = false) { + return \OC\Files\Filesystem::hash($type, $path, $raw); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function free_space($path = '/') { + return \OC\Files\Filesystem::free_space($path); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function search($query) { + return \OC\Files\Filesystem::search($query); + } + + /** + * check if a file or folder has been updated since $time + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @param int $time + * @return bool + */ + static public function hasUpdated($path, $time) { + return \OC\Files\Filesystem::hasUpdated($path, $time); + } + + /** + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + */ + static public function removeETagHook($params, $root = false) { + \OC\Files\Filesystem::removeETagHook($params, $root); + } + + /** + * normalize a path + * + * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem + * @param string $path + * @param bool $stripTrailingSlash + * @return string + */ + public static function normalizePath($path, $stripTrailingSlash = true) { + return \OC\Files\Filesystem::normalizePath($path, $stripTrailingSlash); + } +} From c88c54bbb054fe2d79b3a93604989d527b5dd444 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 10 Oct 2012 12:51:15 +0200 Subject: [PATCH 031/418] make sure we can do our tests again --- lib/cache/file.php | 4 ++-- tests/lib/cache/file.php | 2 +- tests/lib/filesystem.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/cache/file.php b/lib/cache/file.php index 27d8b19f36..f9ecf41dca 100644 --- a/lib/cache/file.php +++ b/lib/cache/file.php @@ -15,11 +15,11 @@ class OC_Cache_File{ } if(OC_User::isLoggedIn()) { $subdir = 'cache'; - $view = new OC_FilesystemView('/'.OC_User::getUser()); + $view = new \OC\Files\View('/'.OC_User::getUser()); if(!$view->file_exists($subdir)) { $view->mkdir($subdir); } - $this->storage = new OC_FilesystemView('/'.OC_User::getUser().'/'.$subdir); + $this->storage = new \OC\Files\View('/'.OC_User::getUser().'/'.$subdir); return $this->storage; }else{ OC_Log::write('core', 'Can\'t get cache storage, user not logged in', OC_Log::ERROR); diff --git a/tests/lib/cache/file.php b/tests/lib/cache/file.php index 1dd1ff7fa8..3e8fd8c1bb 100644 --- a/tests/lib/cache/file.php +++ b/tests/lib/cache/file.php @@ -51,7 +51,7 @@ class Test_Cache_File extends Test_Cache { OC_User::setUserId('test'); //set up the users dir - $rootView=new OC_FilesystemView(''); + $rootView=new \OC\Files\View(''); $rootView->mkdir('/test'); $this->instance=new OC_Cache_File(); diff --git a/tests/lib/filesystem.php b/tests/lib/filesystem.php index 8fc3ce641d..b402b86af1 100644 --- a/tests/lib/filesystem.php +++ b/tests/lib/filesystem.php @@ -84,7 +84,7 @@ class Test_Filesystem extends UnitTestCase { OC_Filesystem::mount('OC\Files\Storage\Temporary', array(), '/'); - $rootView=new OC_FilesystemView(''); + $rootView=new \OC\Files\View(''); $rootView->mkdir('/'.$user); $rootView->mkdir('/'.$user.'/files'); From aaa1b733642c41821a53bc6d04fab246bfe7f1e6 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 10 Oct 2012 13:18:36 +0200 Subject: [PATCH 032/418] don't use depricated OC_Filesystem --- apps/files/ajax/autocomplete.php | 6 +- apps/files/ajax/newfile.php | 6 +- apps/files/ajax/upload.php | 4 +- apps/files/appinfo/filesync.php | 4 +- apps/files/download.php | 8 +-- apps/files/index.php | 14 ++-- apps/files_encryption/lib/crypt.php | 6 +- apps/files_encryption/lib/cryptstream.php | 2 +- apps/files_encryption/lib/proxy.php | 6 +- apps/files_encryption/tests/proxy.php | 30 ++++----- .../ajax/addRootCertificate.php | 12 ++-- apps/files_external/lib/config.php | 12 ++-- apps/files_sharing/lib/sharedstorage.php | 66 +++++++++---------- apps/files_sharing/public.php | 8 +-- apps/files_versions/lib/hooks.php | 4 +- apps/files_versions/lib/versions.php | 16 ++--- lib/connector/sabre/directory.php | 10 +-- lib/connector/sabre/file.php | 10 +-- lib/connector/sabre/node.php | 8 +-- lib/filechunking.php | 38 +++++------ lib/fileproxy/quota.php | 2 +- lib/files.php | 54 +++++++-------- lib/files/file.php | 2 +- lib/helper.php | 2 +- lib/image.php | 2 +- lib/ocs.php | 4 +- lib/public/files.php | 2 +- lib/public/share.php | 6 +- lib/util.php | 10 +-- settings/personal.php | 2 +- tests/lib/cache/file.php | 4 +- tests/lib/filesystem.php | 56 ++++++++-------- 32 files changed, 209 insertions(+), 207 deletions(-) diff --git a/apps/files/ajax/autocomplete.php b/apps/files/ajax/autocomplete.php index fae38368a8..d0dab9c2bd 100644 --- a/apps/files/ajax/autocomplete.php +++ b/apps/files/ajax/autocomplete.php @@ -33,8 +33,8 @@ $query=strtolower($query); $files=array(); -if(OC_Filesystem::file_exists($base) and OC_Filesystem::is_dir($base)) { - $dh = OC_Filesystem::opendir($base); +if(\OC\Files\Filesystem::file_exists($base) and \OC\Files\Filesystem::is_dir($base)) { + $dh = \OC\Files\Filesystem::opendir($base); if($dh) { if(substr($base, -1, 1)!='/') { $base=$base.'/'; @@ -43,7 +43,7 @@ if(OC_Filesystem::file_exists($base) and OC_Filesystem::is_dir($base)) { if ($file != "." && $file != "..") { if(substr(strtolower($file), 0, $queryLen)==$query) { $item=$base.$file; - if((!$dirOnly or OC_Filesystem::is_dir($item))) { + if((!$dirOnly or \OC\Files\Filesystem::is_dir($item))) { $files[]=(object)array('id'=>$item,'label'=>$item,'name'=>$item); } } diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index 77d866979c..4d73970b68 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -63,12 +63,12 @@ if($source) { $ctx = stream_context_create(null, array('notification' =>'progress')); $sourceStream=fopen($source, 'rb', false, $ctx); $target=$dir.'/'.$filename; - $result=OC_Filesystem::file_put_contents($target, $sourceStream); + $result=\OC\Files\Filesystem::file_put_contents($target, $sourceStream); if($result) { $meta = OC_FileCache::get($target); $mime=$meta['mimetype']; $id = OC_FileCache::getId($target); - $eventSource->send('success', array('mime'=>$mime, 'size'=>OC_Filesystem::filesize($target), 'id' => $id)); + $eventSource->send('success', array('mime'=>$mime, 'size'=>\OC\Files\Filesystem::filesize($target), 'id' => $id)); } else { $eventSource->send('error', "Error while downloading ".$source. ' to '.$target); } @@ -76,7 +76,7 @@ if($source) { exit(); } else { if($content) { - if(OC_Filesystem::file_put_contents($dir.'/'.$filename, $content)) { + if(\OC\Files\Filesystem::file_put_contents($dir.'/'.$filename, $content)) { $meta = OC_FileCache::get($dir.'/'.$filename); $id = OC_FileCache::getId($dir.'/'.$filename); OCP\JSON::success(array("data" => array('content'=>$content, 'id' => $id))); diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php index a4dcd80a2e..8bf7f10e18 100644 --- a/apps/files/ajax/upload.php +++ b/apps/files/ajax/upload.php @@ -38,7 +38,7 @@ $totalSize=0; foreach($files['size'] as $size) { $totalSize+=$size; } -if($totalSize>OC_Filesystem::free_space('/')) { +if($totalSize>\OC\Files\Filesystem::free_space('/')) { OCP\JSON::error(array("data" => array( "message" => "Not enough space available" ))); exit(); } @@ -48,7 +48,7 @@ if(strpos($dir, '..') === false) { $fileCount=count($files['name']); for($i=0;$i<$fileCount;$i++) { $target = OCP\Files::buildNotExistingFileName(stripslashes($dir), $files['name'][$i]); - if(is_uploaded_file($files['tmp_name'][$i]) and OC_Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) { + if(is_uploaded_file($files['tmp_name'][$i]) and \OC\Files\Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) { $meta = OC_FileCache::get($target); $id = OC_FileCache::getId($target); $result[]=array( "status" => "success", 'mime'=>$meta['mimetype'],'size'=>$meta['size'], 'id'=>$id, 'name'=>basename($target)); diff --git a/apps/files/appinfo/filesync.php b/apps/files/appinfo/filesync.php index c1fe444cec..e755771de6 100644 --- a/apps/files/appinfo/filesync.php +++ b/apps/files/appinfo/filesync.php @@ -43,7 +43,7 @@ if ($type != 'oc_chunked') { die; } -if (!OC_Filesystem::is_file($file)) { +if (!\OC\Files\Filesystem::is_file($file)) { OC_Response::setStatus(OC_Response::STATUS_NOT_FOUND); die; } @@ -51,7 +51,7 @@ if (!OC_Filesystem::is_file($file)) { switch($_SERVER['REQUEST_METHOD']) { case 'PUT': $input = fopen("php://input", "r"); - $org_file = OC_Filesystem::fopen($file, 'rb'); + $org_file = \OC\Files\Filesystem::fopen($file, 'rb'); $info = array( 'name' => basename($file), ); diff --git a/apps/files/download.php b/apps/files/download.php index ff6aefbbe0..b00a50a045 100644 --- a/apps/files/download.php +++ b/apps/files/download.php @@ -29,7 +29,7 @@ OCP\User::checkLoggedIn(); $filename = $_GET["file"]; -if(!OC_Filesystem::file_exists($filename)) { +if(!\OC\Files\Filesystem::file_exists($filename)) { header("HTTP/1.0 404 Not Found"); $tmpl = new OCP\Template( '', '404', 'guest' ); $tmpl->assign('file',$filename); @@ -37,12 +37,12 @@ if(!OC_Filesystem::file_exists($filename)) { exit; } -$ftype=OC_Filesystem::getMimeType( $filename ); +$ftype=\OC\Files\Filesystem::getMimeType( $filename ); header('Content-Type:'.$ftype); header('Content-Disposition: attachment; filename="'.basename($filename).'"'); OCP\Response::disableCaching(); -header('Content-Length: '.OC_Filesystem::filesize($filename)); +header('Content-Length: '.\OC\Files\Filesystem::filesize($filename)); @ob_end_clean(); -OC_Filesystem::readfile( $filename ); +\OC\Files\Filesystem::readfile( $filename ); diff --git a/apps/files/index.php b/apps/files/index.php index 493087d26f..b02aaf81c0 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -38,7 +38,7 @@ OCP\App::setActiveNavigationEntry( 'files_index' ); // Load the files $dir = isset( $_GET['dir'] ) ? stripslashes($_GET['dir']) : ''; // Redirect if directory does not exist -if(!OC_Filesystem::is_dir($dir.'/')) { +if(!\OC\Files\Filesystem::is_dir($dir.'/')) { header('Location: '.$_SERVER['SCRIPT_NAME'].''); exit(); } @@ -85,26 +85,26 @@ $upload_max_filesize = OCP\Util::computerFileSize(ini_get('upload_max_filesize') $post_max_size = OCP\Util::computerFileSize(ini_get('post_max_size')); $maxUploadFilesize = min($upload_max_filesize, $post_max_size); -$freeSpace=OC_Filesystem::free_space('/'); +$freeSpace=\OC\Files\Filesystem::free_space('/'); $freeSpace=max($freeSpace,0); $maxUploadFilesize = min($maxUploadFilesize ,$freeSpace); $permissions = OCP\Share::PERMISSION_READ; -if (OC_Filesystem::isUpdatable($dir.'/')) { +if (\OC\Files\Filesystem::isUpdatable($dir.'/')) { $permissions |= OCP\Share::PERMISSION_UPDATE; } -if (OC_Filesystem::isDeletable($dir.'/')) { +if (\OC\Files\Filesystem::isDeletable($dir.'/')) { $permissions |= OCP\Share::PERMISSION_DELETE; } -if (OC_Filesystem::isSharable($dir.'/')) { +if (\OC\Files\Filesystem::isSharable($dir.'/')) { $permissions |= OCP\Share::PERMISSION_SHARE; } $tmpl = new OCP\Template( 'files', 'index', 'user' ); $tmpl->assign( 'fileList', $list->fetchPage(), false ); $tmpl->assign( 'breadcrumb', $breadcrumbNav->fetchPage(), false ); -$tmpl->assign( 'dir', OC_Filesystem::normalizePath($dir)); -$tmpl->assign( 'isCreatable', OC_Filesystem::isCreatable($dir.'/')); +$tmpl->assign( 'dir', \OC\Files\Filesystem::normalizePath($dir)); +$tmpl->assign( 'isCreatable', \OC\Files\Filesystem::isCreatable($dir.'/')); $tmpl->assign('permissions', $permissions); $tmpl->assign( 'files', $files ); $tmpl->assign( 'uploadMaxFilesize', $maxUploadFilesize); diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 38d8edf28c..6bee8accf7 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -44,7 +44,7 @@ class OC_Crypt { } public static function init($login,$password) { - $view=new OC_FilesystemView('/'); + $view=new \OC\Files\View('/'); if(!$view->file_exists('/'.$login)) { $view->mkdir('/'.$login); } @@ -90,7 +90,7 @@ class OC_Crypt { // Write the file $proxyEnabled=OC_FileProxy::$enabled; OC_FileProxy::$enabled=false; - $view=new OC_FilesystemView('/'.$username); + $view=new \OC\Files\View('/'.$username); $view->file_put_contents('/encryption.key',$enckey); OC_FileProxy::$enabled=$proxyEnabled; } @@ -98,7 +98,7 @@ class OC_Crypt { public static function changekeypasscode($oldPassword, $newPassword) { if(OCP\User::isLoggedIn()) { $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); + $view=new \OC\Files\View('/'.$username); // read old key $key=$view->file_get_contents('/encryption.key'); diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index 721a1b955d..89a071327a 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -38,7 +38,7 @@ class OC_CryptStream{ public function stream_open($path, $mode, $options, &$opened_path) { if(!self::$rootView) { - self::$rootView=new OC_FilesystemView(''); + self::$rootView=new \OC\Files\View(''); } $path=str_replace('crypt://','',$path); if(dirname($path)=='streams' and isset(self::$sourceStreams[basename($path)])) { diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index f61cd1e377..27abe3bb19 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -90,13 +90,13 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ fclose($result); $result=fopen('crypt://'.$path,$meta['mode']); }elseif(self::shouldEncrypt($path) and $meta['mode']!='r' and $meta['mode']!='rb') { - if(OC_Filesystem::file_exists($path) and OC_Filesystem::filesize($path)>0) { + if(\OC\Files\Filesystem::file_exists($path) and \OC\Files\Filesystem::filesize($path)>0) { //first encrypt the target file so we don't end up with a half encrypted file OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing',OCP\Util::DEBUG); - $tmp=fopen('php://temp'); + $tmp=fopen('php://temp', 'w+'); OCP\Files::streamCopy($result,$tmp); fclose($result); - OC_Filesystem::file_put_contents($path,$tmp); + \OC\Files\Filesystem::file_put_contents($path,$tmp); fclose($tmp); } $result=fopen('crypt://'.$path,$meta['mode']); diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index d600bbc407..c04e0e5c4f 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -29,13 +29,13 @@ class Test_CryptProxy extends UnitTestCase { OC_FileProxy::register(new OC_FileProxy_Encryption()); //set up temporary storage - OC_Filesystem::clearMounts(); - OC_Filesystem::mount('\OC\Files\Storage\Temporary',array(),'/'); + \OC\Files\Filesystem::clearMounts(); + \OC\Files\Filesystem::mount('\OC\Files\Storage\Temporary',array(),'/'); - OC_Filesystem::init('/'.$user.'/files'); + \OC\Files\Filesystem::init('/'.$user.'/files'); //set up the users home folder in the temp storage - $rootView=new OC_FilesystemView(''); + $rootView=new \OC\Files\View(''); $rootView->mkdir('/'.$user); $rootView->mkdir('/'.$user.'/files'); } @@ -51,13 +51,13 @@ class Test_CryptProxy extends UnitTestCase { $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; $original=file_get_contents($file); - OC_Filesystem::file_put_contents('/file',$original); + \OC\Files\Filesystem::file_put_contents('/file',$original); OC_FileProxy::$enabled=false; - $stored=OC_Filesystem::file_get_contents('/file'); + $stored=\OC\Files\Filesystem::file_get_contents('/file'); OC_FileProxy::$enabled=true; - $fromFile=OC_Filesystem::file_get_contents('/file'); + $fromFile=\OC\Files\Filesystem::file_get_contents('/file'); $this->assertNotEqual($original,$stored); $this->assertEqual(strlen($original),strlen($fromFile)); $this->assertEqual($original,$fromFile); @@ -68,8 +68,8 @@ class Test_CryptProxy extends UnitTestCase { $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; $original=file_get_contents($file); - $rootView=new OC_FilesystemView(''); - $view=new OC_FilesystemView('/'.OC_User::getUser()); + $rootView=new \OC\Files\View(''); + $view=new \OC\Files\View('/'.OC_User::getUser()); $userDir='/'.OC_User::getUser().'/files'; $rootView->file_put_contents($userDir.'/file',$original); @@ -90,13 +90,13 @@ class Test_CryptProxy extends UnitTestCase { $file=__DIR__.'/binary'; $original=file_get_contents($file); - OC_Filesystem::file_put_contents('/file',$original); + \OC\Files\Filesystem::file_put_contents('/file',$original); OC_FileProxy::$enabled=false; - $stored=OC_Filesystem::file_get_contents('/file'); + $stored=\OC\Files\Filesystem::file_get_contents('/file'); OC_FileProxy::$enabled=true; - $fromFile=OC_Filesystem::file_get_contents('/file'); + $fromFile=\OC\Files\Filesystem::file_get_contents('/file'); $this->assertNotEqual($original,$stored); $this->assertEqual(strlen($original),strlen($fromFile)); $this->assertEqual($original,$fromFile); @@ -104,13 +104,13 @@ class Test_CryptProxy extends UnitTestCase { $file=__DIR__.'/zeros'; $original=file_get_contents($file); - OC_Filesystem::file_put_contents('/file',$original); + \OC\Files\Filesystem::file_put_contents('/file',$original); OC_FileProxy::$enabled=false; - $stored=OC_Filesystem::file_get_contents('/file'); + $stored=\OC\Files\Filesystem::file_get_contents('/file'); OC_FileProxy::$enabled=true; - $fromFile=OC_Filesystem::file_get_contents('/file'); + $fromFile=\OC\Files\Filesystem::file_get_contents('/file'); $this->assertNotEqual($original,$stored); $this->assertEqual(strlen($original),strlen($fromFile)); } diff --git a/apps/files_external/ajax/addRootCertificate.php b/apps/files_external/ajax/addRootCertificate.php index e0a0239c95..6fef9aac1e 100644 --- a/apps/files_external/ajax/addRootCertificate.php +++ b/apps/files_external/ajax/addRootCertificate.php @@ -3,23 +3,23 @@ OCP\JSON::checkAppEnabled('files_external'); if ( !($filename = $_FILES['rootcert_import']['name']) ) { - header("Location: settings/personal.php"); + header("Location: settings/personal.php"); exit; } -$fh = fopen($_FILES['rootcert_import']['tmp_name'], 'r'); -$data = fread($fh, filesize($_FILES['rootcert_import']['tmp_name'])); +$fh = fopen($_FILES['rootcert_import']['tmp_name'], 'r'); +$data = fread($fh, filesize($_FILES['rootcert_import']['tmp_name'])); fclose($fh); $filename = $_FILES['rootcert_import']['name']; - -$view = new \OC_FilesystemView('/'.\OCP\User::getUser().'/files_external/uploads'); + +$view = new \OC\Files\View('/'.\OCP\User::getUser().'/files_external/uploads'); if (!$view->file_exists('')) $view->mkdir(''); $isValid = openssl_pkey_get_public($data); //maybe it was just the wrong file format, try to convert it... if ($isValid == false) { - $data = chunk_split(base64_encode($data), 64, "\n"); + $data = chunk_split(base64_encode($data), 64, "\n"); $data = "-----BEGIN CERTIFICATE-----\n".$data."-----END CERTIFICATE-----\n"; $isValid = openssl_pkey_get_public($data); } diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index cb32fed203..7be2336019 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -109,10 +109,10 @@ class OC_Mount_Config { return $personal; } - /** - * Add directory for mount point to the filesystem - * @param OC_Fileview instance $view - * @param string path to mount point + /** + * Add directory for mount point to the filesystem + * @param OC_Fileview instance $view + * @param string path to mount point */ private static function addMountPointDirectory($view, $path) { $dir = ''; @@ -142,11 +142,11 @@ class OC_Mount_Config { if ($applicable != OCP\User::getUser() || $class == '\OC\Files\Storage\Local') { return false; } - $view = new OC_FilesystemView('/'.OCP\User::getUser().'/files'); + $view = new \OC\Files\View('/'.OCP\User::getUser().'/files'); self::addMountPointDirectory($view, ltrim($mountPoint, '/')); $mountPoint = '/'.$applicable.'/files/'.ltrim($mountPoint, '/'); } else { - $view = new OC_FilesystemView('/'); + $view = new \OC\Files\View('/'); switch ($mountType) { case 'user': if ($applicable == "all") { diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 876e719956..e17c4b6e04 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -80,7 +80,7 @@ class Shared extends \OC\Files\Storage\Common { $file = $this->getFile($target); if (isset($file['path'])) { $uid = substr($file['path'], 1, strpos($file['path'], '/', 1) - 1); - \OC_Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => \OC_User::getHome($uid)), $uid); + \\OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => \OC_User::getHome($uid)), $uid); return $file['path']; } return false; @@ -105,7 +105,7 @@ class Shared extends \OC\Files\Storage\Common { * @return Source file path with mount point stripped out */ private function getInternalPath($path) { - $mountPoint = \OC_Filesystem::getMountPoint($path); + $mountPoint = \OC\Files\Filesystem::getMountPoint($path); $internalPath = substr($path, strlen($mountPoint)); return $internalPath; } @@ -114,7 +114,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) { return false; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->mkdir($this->getInternalPath($source)); } return false; @@ -122,7 +122,7 @@ class Shared extends \OC\Files\Storage\Common { public function rmdir($path) { if (($source = $this->getSourcePath($path)) && $this->isDeletable($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->rmdir($this->getInternalPath($source)); } return false; @@ -130,11 +130,11 @@ class Shared extends \OC\Files\Storage\Common { public function opendir($path) { if ($path == '' || $path == '/') { - $files = OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_Folder::FORMAT_OPENDIR); + $files = \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_Folder::FORMAT_OPENDIR); \OC_FakeDirStream::$dirs['shared'] = $files; return opendir('fakedir://shared'); } else if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->opendir($this->getInternalPath($source)); } return false; @@ -144,7 +144,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/') { return true; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->is_dir($this->getInternalPath($source)); } return false; @@ -152,7 +152,7 @@ class Shared extends \OC\Files\Storage\Common { public function is_file($path) { if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->is_file($this->getInternalPath($source)); } return false; @@ -165,7 +165,7 @@ class Shared extends \OC\Files\Storage\Common { $stat['ctime'] = $this->filectime($path); return $stat; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->stat($this->getInternalPath($source)); } return false; @@ -175,7 +175,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/') { return 'dir'; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->filetype($this->getInternalPath($source)); } return false; @@ -185,7 +185,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/' || $this->is_dir($path)) { return 0; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->filesize($this->getInternalPath($source)); } return false; @@ -195,7 +195,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '') { return false; } - return ($this->getPermissions($path) & OCP\Share::PERMISSION_CREATE); + return ($this->getPermissions($path) & \OCP\Share::PERMISSION_CREATE); } public function isReadable($path) { @@ -206,28 +206,28 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '') { return false; } - return ($this->getPermissions($path) & OCP\Share::PERMISSION_UPDATE); + return ($this->getPermissions($path) & \OCP\Share::PERMISSION_UPDATE); } public function isDeletable($path) { if ($path == '') { return true; } - return ($this->getPermissions($path) & OCP\Share::PERMISSION_DELETE); + return ($this->getPermissions($path) & \OCP\Share::PERMISSION_DELETE); } public function isSharable($path) { if ($path == '') { return false; } - return ($this->getPermissions($path) & OCP\Share::PERMISSION_SHARE); + return ($this->getPermissions($path) & \OCP\Share::PERMISSION_SHARE); } public function file_exists($path) { if ($path == '' || $path == '/') { return true; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->file_exists($this->getInternalPath($source)); } return false; @@ -248,7 +248,7 @@ class Shared extends \OC\Files\Storage\Common { } else { $source = $this->getSourcePath($path); if ($source) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->filectime($this->getInternalPath($source)); } } @@ -269,7 +269,7 @@ class Shared extends \OC\Files\Storage\Common { } else { $source = $this->getSourcePath($path); if ($source) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->filemtime($this->getInternalPath($source)); } } @@ -282,8 +282,8 @@ class Shared extends \OC\Files\Storage\Common { 'target' => $this->sharedFolder.$path, 'source' => $source, ); - OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); - $storage = \OC_Filesystem::getStorage($source); + \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->file_get_contents($this->getInternalPath($source)); } } @@ -298,8 +298,8 @@ class Shared extends \OC\Files\Storage\Common { 'target' => $this->sharedFolder.$path, 'source' => $source, ); - OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); - $storage = \OC_Filesystem::getStorage($source); + \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); + $storage = \OC\Files\Filesystem::getStorage($source); $result = $storage->file_put_contents($this->getInternalPath($source), $data); return $result; } @@ -310,7 +310,7 @@ class Shared extends \OC\Files\Storage\Common { // Delete the file if DELETE permission is granted if ($source = $this->getSourcePath($path)) { if ($this->isDeletable($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->unlink($this->getInternalPath($source)); } else if (dirname($path) == '/' || dirname($path) == '.') { // Unshare the file from the user if in the root of the Shared folder @@ -319,7 +319,7 @@ class Shared extends \OC\Files\Storage\Common { } else { $itemType = 'file'; } - return OCP\Share::unshareFromSelf($itemType, $path); + return \OCP\Share::unshareFromSelf($itemType, $path); } } return false; @@ -334,7 +334,7 @@ class Shared extends \OC\Files\Storage\Common { if (dirname($path1) == dirname($path2)) { // Rename the file if UPDATE permission is granted if ($this->isUpdatable($path1)) { - $storage = \OC_Filesystem::getStorage($oldSource); + $storage = \OC\Files\Filesystem::getStorage($oldSource); return $storage->rename($this->getInternalPath($oldSource), $this->getInternalPath($newSource)); } } else { @@ -349,7 +349,7 @@ class Shared extends \OC\Files\Storage\Common { return $this->unlink($path1); } } else { - $storage = \OC_Filesystem::getStorage($oldSource); + $storage = \OC\Files\Filesystem::getStorage($oldSource); return $storage->rename($this->getInternalPath($oldSource), $this->getInternalPath($newSource)); } } @@ -394,8 +394,8 @@ class Shared extends \OC\Files\Storage\Common { 'source' => $source, 'mode' => $mode, ); - OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info); - $storage = \OC_Filesystem::getStorage($source); + \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->fopen($this->getInternalPath($source), $mode); } return false; @@ -406,7 +406,7 @@ class Shared extends \OC\Files\Storage\Common { return 'httpd/unix-directory'; } if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->getMimeType($this->getInternalPath($source)); } return false; @@ -415,21 +415,21 @@ class Shared extends \OC\Files\Storage\Common { public function free_space($path) { $source = $this->getSourcePath($path); if ($source) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->free_space($this->getInternalPath($source)); } } public function getLocalFile($path) { if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->getLocalFile($this->getInternalPath($source)); } return false; } public function touch($path, $mtime = null) { if ($source = $this->getSourcePath($path)) { - $storage = \OC_Filesystem::getStorage($source); + $storage = \OC\Files\Filesystem::getStorage($source); return $storage->touch($this->getInternalPath($source), $mtime); } return false; @@ -437,7 +437,7 @@ class Shared extends \OC\Files\Storage\Common { public static function setup($options) { $user_dir = $options['user_dir']; - \OC_Filesystem::mount('\OC\Files\Storage\Shared', array('sharedFolder' => '/Shared'), $user_dir.'/Shared/'); + \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared', array('sharedFolder' => '/Shared'), $user_dir.'/Shared/'); } /** diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index 34340102a9..c5f4b39d70 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -73,7 +73,7 @@ if (isset($_GET['file']) || isset($_GET['dir'])) { if (isset($_GET['path'])) { $path .= $_GET['path']; $dir .= $_GET['path']; - if (!OC_Filesystem::file_exists($path)) { + if (!\OC\Files\Filesystem::file_exists($path)) { header('HTTP/1.0 404 Not Found'); $tmpl = new OCP\Template('', '404', 'guest'); $tmpl->printPage(); @@ -101,7 +101,7 @@ if (isset($_GET['file']) || isset($_GET['dir'])) { $tmpl = new OCP\Template('files_sharing', 'public', 'base'); $tmpl->assign('owner', $uidOwner); // Show file list - if (OC_Filesystem::is_dir($path)) { + if (\OC\Files\Filesystem::is_dir($path)) { OCP\Util::addStyle('files', 'files'); OCP\Util::addScript('files', 'files'); OCP\Util::addScript('files', 'filelist'); @@ -157,7 +157,7 @@ if (isset($_GET['file']) || isset($_GET['dir'])) { $tmpl->assign('uidOwner', $uidOwner); $tmpl->assign('dir', basename($dir)); $tmpl->assign('filename', basename($path)); - $tmpl->assign('mimetype', OC_Filesystem::getMimeType($path)); + $tmpl->assign('mimetype', \OC\Files\Filesystem::getMimeType($path)); $tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); if (isset($_GET['path'])) { $getPath = $_GET['path']; @@ -170,7 +170,7 @@ if (isset($_GET['file']) || isset($_GET['dir'])) { $tmpl->assign('uidOwner', $uidOwner); $tmpl->assign('dir', dirname($path)); $tmpl->assign('filename', basename($path)); - $tmpl->assign('mimetype', OC_Filesystem::getMimeType($path)); + $tmpl->assign('mimetype', \OC\Files\Filesystem::getMimeType($path)); if ($type == 'file') { $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files').'&file='.$_GET['file'].'&download'); } else { diff --git a/apps/files_versions/lib/hooks.php b/apps/files_versions/lib/hooks.php index 9ec0b01a7f..7cb3170df2 100644 --- a/apps/files_versions/lib/hooks.php +++ b/apps/files_versions/lib/hooks.php @@ -21,9 +21,9 @@ class Hooks { if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') { - $versions = new Storage( new \OC_FilesystemView('') ); + $versions = new Storage( new \OC\Files\View('') ); - $path = $params[\OC_Filesystem::signal_param_path]; + $path = $params[\OC\Files\Filesystem::signal_param_path]; if($path<>'') $versions->store( $path ); diff --git a/apps/files_versions/lib/versions.php b/apps/files_versions/lib/versions.php index 7d12e58f94..7f7837a742 100644 --- a/apps/files_versions/lib/versions.php +++ b/apps/files_versions/lib/versions.php @@ -58,8 +58,8 @@ class Storage { public function store($filename) { if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') { list($uid, $filename) = self::getUidAndFilename($filename); - $files_view = new \OC_FilesystemView('/'.$uid.'/files'); - $users_view = new \OC_FilesystemView('/'.$uid); + $files_view = new \OC\Files\View('/'.$uid.'/files'); + $users_view = new \OC\Files\View('/'.$uid); //check if source file already exist as version to avoid recursions. // todo does this check work? @@ -94,7 +94,7 @@ class Storage { // check mininterval if the file is being modified by the owner (all shared files should be versioned despite mininterval) if ($uid == \OCP\User::getUser()) { - $versions_fileview = new \OC_FilesystemView('/'.$uid.'/files_versions'); + $versions_fileview = new \OC\Files\View('/'.$uid.'/files_versions'); $versionsFolderName=\OCP\Config::getSystemValue('datadirectory'). $versions_fileview->getAbsolutePath(''); $matches=glob($versionsFolderName.'/'.$filename.'.v*'); sort($matches); @@ -127,7 +127,7 @@ class Storage { if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') { list($uid, $filename) = self::getUidAndFilename($filename); - $users_view = new \OC_FilesystemView('/'.$uid); + $users_view = new \OC\Files\View('/'.$uid); // rollback if( @$users_view->copy('files_versions'.$filename.'.v'.$revision, 'files'.$filename) ) { @@ -150,7 +150,7 @@ class Storage { public static function isversioned($filename) { if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') { list($uid, $filename) = self::getUidAndFilename($filename); - $versions_fileview = new \OC_FilesystemView('/'.$uid.'/files_versions'); + $versions_fileview = new \OC\Files\View('/'.$uid.'/files_versions'); $versionsFolderName=\OCP\Config::getSystemValue('datadirectory'). $versions_fileview->getAbsolutePath(''); @@ -178,7 +178,7 @@ class Storage { if( \OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true' ) { list($uid, $filename) = self::getUidAndFilename($filename); - $versions_fileview = new \OC_FilesystemView('/'.$uid.'/files_versions'); + $versions_fileview = new \OC\Files\View('/'.$uid.'/files_versions'); $versionsFolderName = \OCP\Config::getSystemValue('datadirectory'). $versions_fileview->getAbsolutePath(''); $versions = array(); @@ -190,7 +190,7 @@ class Storage { $i = 0; - $files_view = new \OC_FilesystemView('/'.$uid.'/files'); + $files_view = new \OC\Files\View('/'.$uid.'/files'); $local_file = $files_view->getLocalFile($filename); foreach( $matches as $ma ) { @@ -245,7 +245,7 @@ class Storage { public static function expire($filename) { if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') { list($uid, $filename) = self::getUidAndFilename($filename); - $versions_fileview = new \OC_FilesystemView('/'.$uid.'/files_versions'); + $versions_fileview = new \OC\Files\View('/'.$uid.'/files_versions'); $versionsFolderName=\OCP\Config::getSystemValue('datadirectory'). $versions_fileview->getAbsolutePath(''); diff --git a/lib/connector/sabre/directory.php b/lib/connector/sabre/directory.php index 413efef73b..4fff3ba51b 100644 --- a/lib/connector/sabre/directory.php +++ b/lib/connector/sabre/directory.php @@ -62,7 +62,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa } } else { $newPath = $this->path . '/' . $name; - OC_Filesystem::file_put_contents($newPath, $data); + \OC\Files\Filesystem::file_put_contents($newPath, $data); return OC_Connector_Sabre_Node::getETagPropertyForPath($newPath); } @@ -78,7 +78,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa public function createDirectory($name) { $newPath = $this->path . '/' . $name; - OC_Filesystem::mkdir($newPath); + \OC\Files\Filesystem::mkdir($newPath); } @@ -154,7 +154,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa public function childExists($name) { $path = $this->path . '/' . $name; - return OC_Filesystem::file_exists($path); + return \OC\Files\Filesystem::file_exists($path); } @@ -167,7 +167,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa if ($this->path != "/Shared") { foreach($this->getChildren() as $child) $child->delete(); - OC_Filesystem::rmdir($this->path); + \OC\Files\Filesystem::rmdir($this->path); } } @@ -181,7 +181,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa $rootInfo=OC_FileCache_Cached::get(''); return array( $rootInfo['size'], - OC_Filesystem::free_space() + \OC\Files\Filesystem::free_space() ); } diff --git a/lib/connector/sabre/file.php b/lib/connector/sabre/file.php index 5bd38240d4..1770b49128 100644 --- a/lib/connector/sabre/file.php +++ b/lib/connector/sabre/file.php @@ -45,7 +45,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D */ public function put($data) { - OC_Filesystem::file_put_contents($this->path,$data); + \OC\Files\Filesystem::file_put_contents($this->path,$data); return OC_Connector_Sabre_Node::getETagPropertyForPath($this->path); } @@ -57,7 +57,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D */ public function get() { - return OC_Filesystem::fopen($this->path,'rb'); + return \OC\Files\Filesystem::fopen($this->path,'rb'); } @@ -68,7 +68,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D */ public function delete() { - OC_Filesystem::unlink($this->path); + \OC\Files\Filesystem::unlink($this->path); } @@ -107,7 +107,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D * @return string|null Returns null if the ETag can not effectively be determined */ static protected function createETag($path) { - return OC_Filesystem::hash('md5', $path); + return \OC\Files\Filesystem::hash('md5', $path); } /** @@ -122,7 +122,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D return $this->fileinfo_cache['mimetype']; } - return OC_Filesystem::getMimeType($this->path); + return \OC\Files\Filesystem::getMimeType($this->path); } } diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php index 72de972377..e7e83507ea 100644 --- a/lib/connector/sabre/node.php +++ b/lib/connector/sabre/node.php @@ -80,7 +80,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr $newPath = $parentPath . '/' . $newName; $oldPath = $this->path; - OC_Filesystem::rename($this->path,$newPath); + \OC\Files\Filesystem::rename($this->path,$newPath); $this->path = $newPath; @@ -99,9 +99,9 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr */ protected function getFileinfoCache() { if (!isset($this->fileinfo_cache)) { - if ($fileinfo_cache = OC_FileCache::get($this->path)) { + if ($fileinfo_cache = \OC\Files\Filesystem::get($this->path)) { } else { - $fileinfo_cache = OC_Filesystem::stat($this->path); + $fileinfo_cache = \OC\Files\Filesystem::stat($this->path); } $this->fileinfo_cache = $fileinfo_cache; @@ -130,7 +130,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr * Even if the modification time is set to a custom value the access time is set to now. */ public function touch($mtime) { - OC_Filesystem::touch($this->path, $mtime); + \OC\Files\Filesystem::touch($this->path, $mtime); } /** diff --git a/lib/filechunking.php b/lib/filechunking.php index 5ab33c77ad..e616e3b12d 100644 --- a/lib/filechunking.php +++ b/lib/filechunking.php @@ -94,49 +94,49 @@ class OC_FileChunking { } public function file_assemble($path) { - $absolutePath = OC_Filesystem::normalizePath(OC_Filesystem::getView()->getAbsolutePath($path)); + $absolutePath = \OC\Files\Filesystem::normalizePath(\OC\Files\Filesystem::getView()->getAbsolutePath($path)); $data = ''; // use file_put_contents as method because that best matches what this function does - if (OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) && OC_Filesystem::isValidPath($path)) { - $path = OC_Filesystem::getView()->getRelativePath($absolutePath); - $exists = OC_Filesystem::file_exists($path); + if (OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) && \OC\Files\Filesystem::isValidPath($path)) { + $path = \OC\Files\Filesystem::getView()->getRelativePath($absolutePath); + $exists = \OC\Files\Filesystem::file_exists($path); $run = true; if(!$exists) { OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_create, + \OC\Files\Filesystem::CLASSNAME, + \OC\Files\Filesystem::signal_create, array( - OC_Filesystem::signal_param_path => $path, - OC_Filesystem::signal_param_run => &$run + \OC\Files\Filesystem::signal_param_path => $path, + \OC\Files\Filesystem::signal_param_run => &$run ) ); } OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_write, + \OC\Files\Filesystem::CLASSNAME, + \OC\Files\Filesystem::signal_write, array( - OC_Filesystem::signal_param_path => $path, - OC_Filesystem::signal_param_run => &$run + \OC\Files\Filesystem::signal_param_path => $path, + \OC\Files\Filesystem::signal_param_run => &$run ) ); if(!$run) { return false; } - $target = OC_Filesystem::fopen($path, 'w'); + $target = \OC\Files\Filesystem::fopen($path, 'w'); if($target) { $count = $this->assemble($target); fclose($target); if(!$exists) { OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_post_create, - array( OC_Filesystem::signal_param_path => $path) + \OC\Files\Filesystem::CLASSNAME, + \OC\Files\Filesystem::signal_post_create, + array( \OC\Files\Filesystem::signal_param_path => $path) ); } OC_Hook::emit( - OC_Filesystem::CLASSNAME, - OC_Filesystem::signal_post_write, - array( OC_Filesystem::signal_param_path => $path) + \OC\Files\Filesystem::CLASSNAME, + \OC\Files\Filesystem::signal_post_write, + array( \OC\Files\Filesystem::signal_param_path => $path) ); OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count); return $count > 0; diff --git a/lib/fileproxy/quota.php b/lib/fileproxy/quota.php index 5a0dbdb6fe..bc5ef9c8df 100644 --- a/lib/fileproxy/quota.php +++ b/lib/fileproxy/quota.php @@ -88,7 +88,7 @@ class OC_FileProxy_Quota extends OC_FileProxy{ public function preCopy($path1,$path2) { if(!self::$rootView){ - self::$rootView = new OC_FilesystemView(''); + self::$rootView = new \OC\Files\View(''); } return (self::$rootView->filesize($path1)<$this->getFreeSpace() or $this->getFreeSpace()==0); } diff --git a/lib/files.php b/lib/files.php index 2b2b8b42dc..29322cf2d0 100644 --- a/lib/files.php +++ b/lib/files.php @@ -46,10 +46,10 @@ class OC_Files { if ($path == '/Shared') { list($info) = OCP\Share::getItemsSharedWith('file', OC_Share_Backend_File::FORMAT_FILE_APP_ROOT); }else{ - $info['size'] = OC_Filesystem::filesize($path); - $info['mtime'] = OC_Filesystem::filemtime($path); - $info['ctime'] = OC_Filesystem::filectime($path); - $info['mimetype'] = OC_Filesystem::getMimeType($path); + $info['size'] = \OC\Files\Filesystem::filesize($path); + $info['mtime'] = \OC\Files\Filesystem::filemtime($path); + $info['ctime'] = \OC\Files\Filesystem::filectime($path); + $info['mimetype'] = \OC\Files\Filesystem::getMimeType($path); $info['encrypted'] = false; $info['versioned'] = false; } @@ -64,7 +64,7 @@ class OC_Files { * @param dir $directory path under datadirectory */ public static function getDirectoryContent($directory, $mimetype_filter = '') { - $directory=OC_Filesystem::normalizePath($directory); + $directory=\OC\Files\Filesystem::normalizePath($directory); if($directory=='/') { $directory=''; } @@ -151,17 +151,17 @@ class OC_Files { } foreach($files as $file) { $file=$dir.'/'.$file; - if(OC_Filesystem::is_file($file)) { - $tmpFile=OC_Filesystem::toTmpFile($file); + if(\OC\Files\Filesystem::is_file($file)) { + $tmpFile=OC_F\OC\Files\Filesystemilesystem::toTmpFile($file); self::$tmpFiles[]=$tmpFile; $zip->addFile($tmpFile,basename($file)); - }elseif(OC_Filesystem::is_dir($file)) { + }elseif(\OC\Files\Filesystem::is_dir($file)) { self::zipAddDir($file,$zip); } } $zip->close(); set_time_limit($executionTime); - }elseif(OC_Filesystem::is_dir($dir.'/'.$files)) { + }elseif(\OC\Files\Filesystem::is_dir($dir.'/'.$files)) { self::validateZipDownload($dir,$files); $executionTime = intval(ini_get('max_execution_time')); set_time_limit(0); @@ -179,7 +179,7 @@ class OC_Files { $filename=$dir.'/'.$files; } @ob_end_clean(); - if($zip or OC_Filesystem::is_readable($filename)) { + if($zip or \OC\Files\Filesystem::is_readable($filename)) { header('Content-Disposition: attachment; filename="'.basename($filename).'"'); header('Content-Transfer-Encoding: binary'); OC_Response::disableCaching(); @@ -188,9 +188,9 @@ class OC_Files { header('Content-Type: application/zip'); header('Content-Length: ' . filesize($filename)); }else{ - header('Content-Type: '.OC_Filesystem::getMimeType($filename)); + header('Content-Type: '.\OC\Files\Filesystem::getMimeType($filename)); } - }elseif($zip or !OC_Filesystem::file_exists($filename)) { + }elseif($zip or !\OC\Files\Filesystem::file_exists($filename)) { header("HTTP/1.0 404 Not Found"); $tmpl = new OC_Template( '', '404', 'guest' ); $tmpl->assign('file',$filename); @@ -201,7 +201,7 @@ class OC_Files { } if($only_header) { if(!$zip) - header("Content-Length: ".OC_Filesystem::filesize($filename)); + header("Content-Length: ".\OC\Files\Filesystem::filesize($filename)); return ; } if($zip) { @@ -215,7 +215,7 @@ class OC_Files { } unlink($filename); }else{ - OC_Filesystem::readfile($filename); + \OC\Files\Filesystem::readfile($filename); } foreach(self::$tmpFiles as $tmpFile) { if(file_exists($tmpFile) and is_file($tmpFile)) { @@ -232,11 +232,11 @@ class OC_Files { foreach($files as $file) { $filename=$file['name']; $file=$dir.'/'.$filename; - if(OC_Filesystem::is_file($file)) { - $tmpFile=OC_Filesystem::toTmpFile($file); + if(\OC\Files\Filesystem::is_file($file)) { + $tmpFile=\OC\Files\Filesystem::toTmpFile($file); OC_Files::$tmpFiles[]=$tmpFile; $zip->addFile($tmpFile,$internalDir.$filename); - }elseif(OC_Filesystem::is_dir($file)) { + }elseif(\OC\Files\Filesystem::is_dir($file)) { self::zipAddDir($file,$zip,$internalDir); } } @@ -253,7 +253,7 @@ class OC_Files { if(OC_User::isLoggedIn() && ($sourceDir != '' || $source != 'Shared')) { $targetFile=self::normalizePath($targetDir.'/'.$target); $sourceFile=self::normalizePath($sourceDir.'/'.$source); - return OC_Filesystem::rename($sourceFile,$targetFile); + return \OC\Files\Filesystem::rename($sourceFile,$targetFile); } else { return false; } @@ -271,7 +271,7 @@ class OC_Files { if(OC_User::isLoggedIn()) { $targetFile=$targetDir.'/'.$target; $sourceFile=$sourceDir.'/'.$source; - return OC_Filesystem::copy($sourceFile,$targetFile); + return \OC\Files\Filesystem::copy($sourceFile,$targetFile); } } @@ -286,9 +286,9 @@ class OC_Files { if(OC_User::isLoggedIn()) { $file=$dir.'/'.$name; if($type=='dir') { - return OC_Filesystem::mkdir($file); + return \OC\Files\Filesystem::mkdir($file); }elseif($type=='file') { - $fileHandle=OC_Filesystem::fopen($file, 'w'); + $fileHandle=\OC\Files\Filesystem::fopen($file, 'w'); if($fileHandle) { fclose($fileHandle); return true; @@ -308,7 +308,7 @@ class OC_Files { public static function delete($dir,$file) { if(OC_User::isLoggedIn() && ($dir!= '' || $file != 'Shared')) { $file=$dir.'/'.$file; - return OC_Filesystem::unlink($file); + return \OC\Files\Filesystem::unlink($file); } } @@ -339,10 +339,10 @@ class OC_Files { $totalsize = 0; if(is_array($files)) { foreach($files as $file) { - $totalsize += OC_Filesystem::filesize($dir.'/'.$file); + $totalsize += \OC\Files\Filesystem::filesize($dir.'/'.$file); } }else{ - $totalsize += OC_Filesystem::filesize($dir.'/'.$files); + $totalsize += \OC\Files\Filesystem::filesize($dir.'/'.$files); } if($totalsize > $zipLimit) { $l = OC_L10N::get('lib'); @@ -368,7 +368,7 @@ class OC_Files { * @return string guessed mime type */ static function getMimeType($path) { - return OC_Filesystem::getMimeType($path); + return \OC\Files\Filesystem::getMimeType($path); } /** @@ -378,7 +378,7 @@ class OC_Files { * @return array */ static function getTree($path) { - return OC_Filesystem::getTree($path); + return \OC\Files\Filesystem::getTree($path); } /** @@ -402,7 +402,7 @@ class OC_Files { $httpCode=$info['http_code']; curl_close($ch); if($httpCode==200 or $httpCode==0) { - OC_Filesystem::fromTmpFile($tmpfile,$dir.'/'.$file); + \OC\Files\Filesystem::fromTmpFile($tmpfile,$dir.'/'.$file); return true; }else{ return false; diff --git a/lib/files/file.php b/lib/files/file.php index b6432e6345..0d33cea7ee 100644 --- a/lib/files/file.php +++ b/lib/files/file.php @@ -30,7 +30,7 @@ class File{ public static function resolve($fullPath){ $storage = null; $internalPath = ''; - list($storage, $internalPath) = \OC_Filesystem::resolvePath($fullPath); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($fullPath); return new File($storage, $internalPath); } diff --git a/lib/helper.php b/lib/helper.php index 908a61b5a2..48e16cebd0 100644 --- a/lib/helper.php +++ b/lib/helper.php @@ -560,7 +560,7 @@ class OC_Helper { $newpath = $path . '/' . $filename; $counter = 2; - while (OC_Filesystem::file_exists($newpath)) { + while (\OC\Files\Filesystem::file_exists($newpath)) { $newname = $name . ' (' . $counter . ')' . $ext; $newpath = $path . '/' . $newname; $counter++; diff --git a/lib/image.php b/lib/image.php index 861353e039..94fe3ce827 100644 --- a/lib/image.php +++ b/lib/image.php @@ -470,7 +470,7 @@ class OC_Image { default: // this is mostly file created from encrypted file - $this->resource = imagecreatefromstring(\OC_Filesystem::file_get_contents(\OC_Filesystem::getLocalPath($imagepath))); + $this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagepath))); $itype = IMAGETYPE_PNG; OC_Log::write('core','OC_Image->loadFromFile, Default', OC_Log::DEBUG); break; diff --git a/lib/ocs.php b/lib/ocs.php index 7350c3c882..645380ddba 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -589,11 +589,11 @@ class OC_OCS { if(OC_User::userExists($user)) { // calculate the disc space $user_dir = '/'.$user.'/files'; - OC_Filesystem::init($user_dir); + \OC\Files\Filesystem::init($user_dir); $rootInfo=OC_FileCache::get(''); $sharedInfo=OC_FileCache::get('/Shared'); $used=$rootInfo['size']-$sharedInfo['size']; - $free=OC_Filesystem::free_space(); + $free=\OC\Files\Filesystem::free_space(); $total=$free+$used; if($total==0) $total=1; // prevent division by zero $relative=round(($used/$total)*10000)/100; diff --git a/lib/public/files.php b/lib/public/files.php index 90889c59ad..2d6775e01f 100644 --- a/lib/public/files.php +++ b/lib/public/files.php @@ -98,7 +98,7 @@ class Files { /** * @param string appid * @param $app app - * @return OC_FilesystemView + * @return \OC\Files\View */ public static function getStorage( $app ) { return \OC_App::getStorage( $app ); diff --git a/lib/public/share.php b/lib/public/share.php index 1db3a0b2c1..7c4ede5e88 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -515,7 +515,7 @@ class Share { $backend = self::getBackend($itemType); // Get filesystem root to add it to the file target and remove from the file source, match file_source with the file cache if ($itemType == 'file' || $itemType == 'folder') { - $root = \OC_Filesystem::getRoot(); + $root = \OC\Files\Filesystem::getRoot(); $where = 'INNER JOIN `*PREFIX*fscache` ON `file_source` = `*PREFIX*fscache`.`id`'; if (!isset($item)) { $where .= ' WHERE `file_target` IS NOT NULL'; @@ -602,7 +602,7 @@ class Share { } else { if ($itemType == 'file' || $itemType == 'folder') { $where .= ' `file_target` = ?'; - $item = \OC_Filesystem::normalizePath($item); + $item = \OC\Files\Filesystem::normalizePath($item); } else { $where .= ' `item_target` = ?'; } @@ -751,7 +751,7 @@ class Share { } else { $childItem['file_source'] = \OC_FileCache::getId($child['file_path']); } - $childItem['file_target'] = \OC_Filesystem::normalizePath($child['file_path']); + $childItem['file_target'] = \OC\Files\Filesystem::normalizePath($child['file_path']); } if (isset($item)) { if ($childItem[$column] == $item) { diff --git a/lib/util.php b/lib/util.php index 557f731522..5cf7657457 100755 --- a/lib/util.php +++ b/lib/util.php @@ -34,7 +34,7 @@ class OC_Util { $CONFIG_DATADIRECTORY = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); //first set up the local "root" storage if(!self::$rootMounted) { - OC_Filesystem::mount('\OC\Files\Storage\Local',array('datadir'=>$CONFIG_DATADIRECTORY),'/'); + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local',array('datadir'=>$CONFIG_DATADIRECTORY),'/'); self::$rootMounted=true; } @@ -46,8 +46,8 @@ class OC_Util { mkdir( $userdirectory, 0755, true ); } //jail the user into his "home" directory - OC_Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $user_root), $user); - OC_Filesystem::init($user_dir); + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $user_root), $user); + \OC\Files\Filesystem::init($user_dir); $quotaProxy=new OC_FileProxy_Quota(); OC_FileProxy::register($quotaProxy); // Load personal mount config @@ -55,7 +55,7 @@ class OC_Util { $mountConfig = include($user_root.'/mount.php'); if (isset($mountConfig['user'][$user])) { foreach ($mountConfig['user'][$user] as $mountPoint => $options) { - OC_Filesystem::mount($options['class'], $options['options'], $mountPoint); + \OC\Files\Filesystem::mount($options['class'], $options['options'], $mountPoint); } } } @@ -64,7 +64,7 @@ class OC_Util { } public static function tearDownFS() { - OC_Filesystem::tearDown(); + \OC\Files\Filesystem::tearDown(); self::$fsSetup=false; } diff --git a/settings/personal.php b/settings/personal.php index 9297f5d91c..54002d87c5 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -25,7 +25,7 @@ if (!isset($sharedInfo['size'])) { } $used=$rootInfo['size']-$sharedSize; if($used<0) $used=0; -$free=OC_Filesystem::free_space(); +$free=\OC\Files\Filesystem::free_space(); $total=$free+$used; if($total==0) $total=1; // prevent division by zero $relative=round(($used/$total)*10000)/100; diff --git a/tests/lib/cache/file.php b/tests/lib/cache/file.php index 3e8fd8c1bb..5dcd326880 100644 --- a/tests/lib/cache/file.php +++ b/tests/lib/cache/file.php @@ -38,8 +38,8 @@ class Test_Cache_File extends Test_Cache { } //set up temporary storage - OC_Filesystem::clearMounts(); - OC_Filesystem::mount('\OC\Files\Storage\Temporary',array(),'/'); + \OC\Files\Filesystem::clearMounts(); + \OC\Files\Filesystem::mount('\OC\Files\Storage\Temporary',array(),'/'); OC_User::clearBackends(); OC_User::useBackend(new OC_User_Dummy()); diff --git a/tests/lib/filesystem.php b/tests/lib/filesystem.php index b402b86af1..af3620f570 100644 --- a/tests/lib/filesystem.php +++ b/tests/lib/filesystem.php @@ -20,6 +20,8 @@ * */ +use \OC\Files\Filesystem as Filesystem; + class Test_Filesystem extends UnitTestCase { /** * @var array tmpDirs @@ -42,64 +44,64 @@ class Test_Filesystem extends UnitTestCase { } public function setUp() { - OC_Filesystem::clearMounts(); + Filesystem::clearMounts(); } public function testMount() { - OC_Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/'); - $this->assertEqual('/',OC_Filesystem::getMountPoint('/')); - $this->assertEqual('/',OC_Filesystem::getMountPoint('/some/folder')); - $this->assertEqual('',OC_Filesystem::getInternalPath('/')); - $this->assertEqual('some/folder',OC_Filesystem::getInternalPath('/some/folder')); + Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/'); + $this->assertEqual('/',Filesystem::getMountPoint('/')); + $this->assertEqual('/',Filesystem::getMountPoint('/some/folder')); + $this->assertEqual('',Filesystem::getInternalPath('/')); + $this->assertEqual('some/folder',Filesystem::getInternalPath('/some/folder')); - OC_Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/some'); - $this->assertEqual('/',OC_Filesystem::getMountPoint('/')); - $this->assertEqual('/some/',OC_Filesystem::getMountPoint('/some/folder')); - $this->assertEqual('/some/',OC_Filesystem::getMountPoint('/some/')); - $this->assertEqual('/',OC_Filesystem::getMountPoint('/some')); - $this->assertEqual('folder',OC_Filesystem::getInternalPath('/some/folder')); + Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/some'); + $this->assertEqual('/',Filesystem::getMountPoint('/')); + $this->assertEqual('/some/',Filesystem::getMountPoint('/some/folder')); + $this->assertEqual('/some/',Filesystem::getMountPoint('/some/')); + $this->assertEqual('/',Filesystem::getMountPoint('/some')); + $this->assertEqual('folder',Filesystem::getInternalPath('/some/folder')); } public function testNormalize() { - $this->assertEqual('/path', OC_Filesystem::normalizePath('/path/')); - $this->assertEqual('/path/', OC_Filesystem::normalizePath('/path/', false)); - $this->assertEqual('/path', OC_Filesystem::normalizePath('path')); - $this->assertEqual('/path', OC_Filesystem::normalizePath('\path')); - $this->assertEqual('/foo/bar', OC_Filesystem::normalizePath('/foo//bar/')); - $this->assertEqual('/foo/bar', OC_Filesystem::normalizePath('/foo////bar')); + $this->assertEqual('/path', Filesystem::normalizePath('/path/')); + $this->assertEqual('/path/', Filesystem::normalizePath('/path/', false)); + $this->assertEqual('/path', Filesystem::normalizePath('path')); + $this->assertEqual('/path', Filesystem::normalizePath('\path')); + $this->assertEqual('/foo/bar', Filesystem::normalizePath('/foo//bar/')); + $this->assertEqual('/foo/bar', Filesystem::normalizePath('/foo////bar')); if (class_exists('Normalizer')) { - $this->assertEqual("/foo/bar\xC3\xBC", OC_Filesystem::normalizePath("/foo/baru\xCC\x88")); + $this->assertEqual("/foo/bar\xC3\xBC", Filesystem::normalizePath("/foo/baru\xCC\x88")); } } public function testHooks() { - if(OC_Filesystem::getView()){ + if(Filesystem::getView()){ $user = OC_User::getUser(); }else{ $user=uniqid(); - OC_Filesystem::init('/'.$user.'/files'); + Filesystem::init('/'.$user.'/files'); } OC_Hook::clear('OC_Filesystem'); OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook'); - OC_Filesystem::mount('OC\Files\Storage\Temporary', array(), '/'); + Filesystem::mount('OC\Files\Storage\Temporary', array(), '/'); $rootView=new \OC\Files\View(''); $rootView->mkdir('/'.$user); $rootView->mkdir('/'.$user.'/files'); - OC_Filesystem::file_put_contents('/foo', 'foo'); - OC_Filesystem::mkdir('/bar'); - OC_Filesystem::file_put_contents('/bar//foo', 'foo'); + Filesystem::file_put_contents('/foo', 'foo'); + Filesystem::mkdir('/bar'); + Filesystem::file_put_contents('/bar//foo', 'foo'); $tmpFile = OC_Helper::tmpFile(); file_put_contents($tmpFile, 'foo'); $fh = fopen($tmpFile, 'r'); - OC_Filesystem::file_put_contents('/bar//foo', $fh); + Filesystem::file_put_contents('/bar//foo', $fh); } public function dummyHook($arguments) { $path = $arguments['path']; - $this->assertEqual($path, OC_Filesystem::normalizePath($path)); //the path passed to the hook should already be normalized + $this->assertEqual($path, Filesystem::normalizePath($path)); //the path passed to the hook should already be normalized } } From 08bb5dbe3a9b2bf502e5c6e0c944867015c3795b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Oct 2012 18:19:45 +0200 Subject: [PATCH 033/418] few more test cases for scanning folders --- tests/lib/files/cache/scanner.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index 41286e69d3..1f11b9d01d 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -91,6 +91,13 @@ class Scanner extends \UnitTestCase { $this->assertEqual($cachedDataFolder['size'], -1); $this->assertEqual($cachedDataFolder2['size'], -1); + + $this->scanner->scan('folder', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + + $cachedDataFolder = $this->cache->get(''); + $cachedDataFolder2 = $this->cache->get('folder'); + + $this->assertNotEqual($cachedDataFolder['size'], -1); } function setUp() { From a2785f57d2335d06f3fa9900298343c1d7f9253a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 10 Oct 2012 17:45:47 +0200 Subject: [PATCH 034/418] fix cache scanner test case --- tests/lib/files/cache/scanner.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index 1f11b9d01d..e3f47047f5 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -94,10 +94,9 @@ class Scanner extends \UnitTestCase { $this->scanner->scan('folder', \OC\Files\Cache\Scanner::SCAN_SHALLOW); - $cachedDataFolder = $this->cache->get(''); $cachedDataFolder2 = $this->cache->get('folder'); - $this->assertNotEqual($cachedDataFolder['size'], -1); + $this->assertNotEqual($cachedDataFolder2['size'], -1); } function setUp() { From 5c6e9518edde10e0c23d0d734d2cc6d161fc15c0 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 10 Oct 2012 17:46:29 +0200 Subject: [PATCH 035/418] drop Filesystem::getInternalPath and Filesystem::getStorage in favor of Filesystem::resolvePath --- apps/files_sharing/lib/sharedstorage.php | 86 ++++++++++++------------ lib/files/filesystem.php | 31 --------- lib/files/view.php | 61 ++++++----------- lib/filesystem.php | 22 ------ tests/lib/filesystem.php | 9 ++- 5 files changed, 71 insertions(+), 138 deletions(-) diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index e17c4b6e04..e12027a4f2 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -114,16 +114,16 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) { return false; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->mkdir($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->mkdir($internalPath); } return false; } public function rmdir($path) { if (($source = $this->getSourcePath($path)) && $this->isDeletable($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->rmdir($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->rmdir($internalPath); } return false; } @@ -134,8 +134,8 @@ class Shared extends \OC\Files\Storage\Common { \OC_FakeDirStream::$dirs['shared'] = $files; return opendir('fakedir://shared'); } else if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->opendir($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->opendir($internalPath); } return false; } @@ -144,16 +144,16 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/') { return true; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->is_dir($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->is_dir($internalPath); } return false; } public function is_file($path) { if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->is_file($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->is_file($internalPath); } return false; } @@ -165,8 +165,8 @@ class Shared extends \OC\Files\Storage\Common { $stat['ctime'] = $this->filectime($path); return $stat; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->stat($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->stat($internalPath); } return false; } @@ -175,8 +175,8 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/') { return 'dir'; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->filetype($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->filetype($internalPath); } return false; } @@ -185,8 +185,8 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/' || $this->is_dir($path)) { return 0; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->filesize($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->filesize($internalPath); } return false; } @@ -227,8 +227,8 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/') { return true; } else if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->file_exists($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->file_exists($internalPath); } return false; } @@ -248,8 +248,8 @@ class Shared extends \OC\Files\Storage\Common { } else { $source = $this->getSourcePath($path); if ($source) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->filectime($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->filectime($internalPath); } } } @@ -269,8 +269,8 @@ class Shared extends \OC\Files\Storage\Common { } else { $source = $this->getSourcePath($path); if ($source) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->filemtime($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->filemtime($internalPath); } } } @@ -283,8 +283,8 @@ class Shared extends \OC\Files\Storage\Common { 'source' => $source, ); \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->file_get_contents($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->file_get_contents($internalPath); } } @@ -299,8 +299,8 @@ class Shared extends \OC\Files\Storage\Common { 'source' => $source, ); \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); - $storage = \OC\Files\Filesystem::getStorage($source); - $result = $storage->file_put_contents($this->getInternalPath($source), $data); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + $result = $storage->file_put_contents($internalPath, $data); return $result; } return false; @@ -310,8 +310,8 @@ class Shared extends \OC\Files\Storage\Common { // Delete the file if DELETE permission is granted if ($source = $this->getSourcePath($path)) { if ($this->isDeletable($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->unlink($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->unlink($internalPath); } else if (dirname($path) == '/' || dirname($path) == '.') { // Unshare the file from the user if in the root of the Shared folder if ($this->is_dir($path)) { @@ -334,8 +334,9 @@ class Shared extends \OC\Files\Storage\Common { if (dirname($path1) == dirname($path2)) { // Rename the file if UPDATE permission is granted if ($this->isUpdatable($path1)) { - $storage = \OC\Files\Filesystem::getStorage($oldSource); - return $storage->rename($this->getInternalPath($oldSource), $this->getInternalPath($newSource)); + list($storage, $oldInternalPath)=\OC\Files\Filesystem::resolvePath($oldSource); + list( , $newInternalPath)=\OC\Files\Filesystem::resolvePath($newSource); + return $storage->rename($oldInternalPath, $newInternalPath); } } else { // Move the file if DELETE and CREATE permissions are granted @@ -349,8 +350,9 @@ class Shared extends \OC\Files\Storage\Common { return $this->unlink($path1); } } else { - $storage = \OC\Files\Filesystem::getStorage($oldSource); - return $storage->rename($this->getInternalPath($oldSource), $this->getInternalPath($newSource)); + list($storage, $oldInternalPath)=\OC\Files\Filesystem::resolvePath($oldSource); + list( , $newInternalPath)=\OC\Files\Filesystem::resolvePath($newSource); + return $storage->rename($oldInternalPath, $newInternalPath); } } } @@ -395,8 +397,8 @@ class Shared extends \OC\Files\Storage\Common { 'mode' => $mode, ); \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info); - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->fopen($this->getInternalPath($source), $mode); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->fopen($internalPath, $mode); } return false; } @@ -406,8 +408,8 @@ class Shared extends \OC\Files\Storage\Common { return 'httpd/unix-directory'; } if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->getMimeType($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->getMimeType($internalPath); } return false; } @@ -415,22 +417,22 @@ class Shared extends \OC\Files\Storage\Common { public function free_space($path) { $source = $this->getSourcePath($path); if ($source) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->free_space($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->free_space($internalPath); } } public function getLocalFile($path) { if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->getLocalFile($this->getInternalPath($source)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->getLocalFile($internalPath); } return false; } public function touch($path, $mtime = null) { if ($source = $this->getSourcePath($path)) { - $storage = \OC\Files\Filesystem::getStorage($source); - return $storage->touch($this->getInternalPath($source), $mtime); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + return $storage->touch($internalPath, $mtime); } return false; } diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index f23f3a79e9..841f30c767 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -164,37 +164,6 @@ class Filesystem { return $foundMountPoint; } - /** - * get the part of the path relative to the mountpoint of the storage it's stored in - * - * @param string $path - * @return bool - */ - static public function getInternalPath($path) { - $mountPoint = self::getMountPoint($path); - $internalPath = substr($path, strlen($mountPoint)); - return $internalPath; - } - - /** - * get the storage object for a path - * - * @param string $path - * @return \OC\Files\Storage\Storage - */ - static public function getStorage($path) { - $mountpoint = self::getMountPoint($path); - if ($mountpoint) { - if (!isset(self::$storages[$mountpoint])) { - $mount = self::$mounts[$mountpoint]; - self::$storages[$mountpoint] = self::createStorage($mount['class'], $mount['arguments']); - } - return self::$storages[$mountpoint]; - }else{ - return null; - } - } - /** * resolve a path to a storage and internal path * diff --git a/lib/files/view.php b/lib/files/view.php index 230455479c..bffeb434f2 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -68,19 +68,6 @@ class View { return $this->fakeRoot; } - /** - * get the part of the path relative to the mountpoint of the storage it's stored in - * - * @param string $path - * @return bool - */ - public function getInternalPath($path) { - if (!isset($this->internal_path_cache[$path])) { - $this->internal_path_cache[$path] = Filesystem::getInternalPath($this->getAbsolutePath($path)); - } - return $this->internal_path_cache[$path]; - } - /** * get path relative to the root of the view * @@ -103,19 +90,6 @@ class View { } } - /** - * get the storage object for a path - * - * @param string $path - * @return \OC\Files\Storage\Storage - */ - public function getStorage($path) { - if (!isset($this->storage_cache[$path])) { - $this->storage_cache[$path] = Filesystem::getStorage($this->getAbsolutePath($path)); - } - return $this->storage_cache[$path]; - } - /** * get the mountpoint of the storage object for a path ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account @@ -136,8 +110,9 @@ class View { */ public function getLocalFile($path) { $parent = substr($path, 0, strrpos($path, '/')); - if (Filesystem::isValidPath($parent) and $storage = $this->getStorage($path)) { - return $storage->getLocalFile($this->getInternalPath($path)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFile($internalPath); } else { return null; } @@ -149,8 +124,9 @@ class View { */ public function getLocalFolder($path) { $parent = substr($path, 0, strrpos($path, '/')); - if (Filesystem::isValidPath($parent) and $storage = $this->getStorage($path)) { - return $storage->getLocalFolder($this->getInternalPath($path)); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFolder($internalPath); } else { return null; } @@ -373,8 +349,10 @@ class View { $mp1 = $this->getMountPoint($path1 . $postFix1); $mp2 = $this->getMountPoint($path2 . $postFix2); if ($mp1 == $mp2) { - if ($storage = $this->getStorage($path1)) { - $result = $storage->rename($this->getInternalPath($path1 . $postFix1), $this->getInternalPath($path2 . $postFix2)); + list($storage, $internalPath1)=\OC\Files\Filesystem::resolvePath($path1 . $postFix1); + list( , $internalPath2)=\OC\Files\Filesystem::resolvePath($path2 . $postFix2); + if ($storage) { + $result = $storage->rename($internalPath1, $internalPath2); } else { $result = false; } @@ -382,8 +360,8 @@ class View { $source = $this->fopen($path1 . $postFix1, 'r'); $target = $this->fopen($path2 . $postFix2, 'w'); $count = \OC_Helper::streamCopy($source, $target); - $storage1 = $this->getStorage($path1); - $storage1->unlink($this->getInternalPath($path1 . $postFix1)); + list($storage1, $internalPath1)=\OC\Files\Filesystem::resolvePath($path1 . $postFix1); + $storage1->unlink($internalPath1); $result = $count > 0; } if ($this->fakeRoot == Filesystem::getRoot()) { @@ -454,8 +432,10 @@ class View { $mp1 = $this->getMountPoint($path1 . $postFix1); $mp2 = $this->getMountPoint($path2 . $postFix2); if ($mp1 == $mp2) { - if ($storage = $this->getStorage($path1 . $postFix1)) { - $result = $storage->copy($this->getInternalPath($path1 . $postFix1), $this->getInternalPath($path2 . $postFix2)); + list($storage, $internalPath1)=\OC\Files\Filesystem::resolvePath($path1 . $postFix1); + list( , $internalPath2)=\OC\Files\Filesystem::resolvePath($path2 . $postFix2); + if ($storage) { + $result = $storage->copy($internalPath1, $internalPath2); } else { $result = false; } @@ -588,8 +568,9 @@ class View { array(Filesystem::signal_param_path => $path) ); } - if ($storage = $this->getStorage($path . $postFix)) { - $result = $storage->hash($type, $this->getInternalPath($path . $postFix), $raw); + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path . $postFix); + if ($storage) { + $result = $storage->hash($type, $internalPath, $raw); $result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result); return $result; } @@ -621,9 +602,9 @@ class View { if ($path == null) { return false; } - $internalPath = $this->getInternalPath($path . $postFix); $run = $this->runHooks($hooks, $path); - if ($run and $storage = $this->getStorage($path . $postFix)) { + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path . $postFix); + if ($run and $storage) { if (!is_null($extraParam)) { $result = $storage->$operation($internalPath, $extraParam); } else { diff --git a/lib/filesystem.php b/lib/filesystem.php index 4189fe36c2..587eb50d9e 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -44,28 +44,6 @@ class OC_Filesystem { return \OC\Files\Filesystem::getMountPoint($path); } - /** - * get the part of the path relative to the mountpoint of the storage it's stored in - * - * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem - * @param string $path - * @return bool - */ - static public function getInternalPath($path) { - return \OC\Files\Filesystem::getInternalPath($path); - } - - /** - * get the storage object for a path - * - * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem - * @param string $path - * @return \OC\Files\Storage\Storage - */ - static public function getStorage($path) { - return \OC\Files\Filesystem::getStorage($path); - } - /** * resolve a path to a storage and internal path * diff --git a/tests/lib/filesystem.php b/tests/lib/filesystem.php index af3620f570..6e7b4fb781 100644 --- a/tests/lib/filesystem.php +++ b/tests/lib/filesystem.php @@ -51,15 +51,18 @@ class Test_Filesystem extends UnitTestCase { Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/'); $this->assertEqual('/',Filesystem::getMountPoint('/')); $this->assertEqual('/',Filesystem::getMountPoint('/some/folder')); - $this->assertEqual('',Filesystem::getInternalPath('/')); - $this->assertEqual('some/folder',Filesystem::getInternalPath('/some/folder')); + list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/'); + $this->assertEqual('',$internalPath); + list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/some/folder'); + $this->assertEqual('some/folder',$internalPath); Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/some'); $this->assertEqual('/',Filesystem::getMountPoint('/')); $this->assertEqual('/some/',Filesystem::getMountPoint('/some/folder')); $this->assertEqual('/some/',Filesystem::getMountPoint('/some/')); $this->assertEqual('/',Filesystem::getMountPoint('/some')); - $this->assertEqual('folder',Filesystem::getInternalPath('/some/folder')); + list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/some/folder'); + $this->assertEqual('folder',$internalPath); } public function testNormalize() { From 542869114ad67295d61450e637b15a1f4d3ae209 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 11 Oct 2012 23:06:57 +0200 Subject: [PATCH 036/418] implement getId for the external storage providers --- apps/files_external/lib/amazons3.php | 6 ++++++ apps/files_external/lib/dropbox.php | 6 ++++++ apps/files_external/lib/ftp.php | 4 ++++ apps/files_external/lib/google.php | 7 ++++++- apps/files_external/lib/smb.php | 4 ++++ apps/files_external/lib/swift.php | 7 +++++++ apps/files_external/lib/webdav.php | 4 ++++ 7 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/files_external/lib/amazons3.php b/apps/files_external/lib/amazons3.php index a17a70ed38..2f7c8b35be 100644 --- a/apps/files_external/lib/amazons3.php +++ b/apps/files_external/lib/amazons3.php @@ -29,12 +29,14 @@ class AmazonS3 extends \OC\Files\Storage\Common { private $s3; private $bucket; private $objects = array(); + private $id; private static $tempFiles = array(); // TODO options: storage class, encryption server side, encrypt before upload? public function __construct($params) { + $this->id = 'amazon::'.$params['key'] . md5($params['secret']); $this->s3 = new \AmazonS3(array('key' => $params['key'], 'secret' => $params['secret'])); $this->bucket = $params['bucket']; } @@ -59,6 +61,10 @@ class AmazonS3 extends \OC\Files\Storage\Common { return false; } + public function getId(){ + return $this->id; + } + public function mkdir($path) { // Folders in Amazon S3 are 0 byte objects with a '/' at the end of the name if (substr($path, -1) != '/') { diff --git a/apps/files_external/lib/dropbox.php b/apps/files_external/lib/dropbox.php index 86dbdab9ca..351a2840d9 100755 --- a/apps/files_external/lib/dropbox.php +++ b/apps/files_external/lib/dropbox.php @@ -28,12 +28,14 @@ class Dropbox extends \OC\Files\Storage\Common { private $dropbox; private $root; + private $id; private $metaData = array(); private static $tempFiles = array(); public function __construct($params) { if (isset($params['configured']) && $params['configured'] == 'true' && isset($params['app_key']) && isset($params['app_secret']) && isset($params['token']) && isset($params['token_secret'])) { + $this->id = 'dropbox::'.$params['app_key'] . $params['token']. '/' . $params['root']; $this->root=isset($params['root'])?$params['root']:''; $oauth = new \Dropbox_OAuth_Curl($params['app_key'], $params['app_secret']); $oauth->setToken($params['token'], $params['token_secret']); @@ -81,6 +83,10 @@ class Dropbox extends \OC\Files\Storage\Common { } } + public function getId(){ + return $this->id; + } + public function mkdir($path) { $path = $this->root.$path; try { diff --git a/apps/files_external/lib/ftp.php b/apps/files_external/lib/ftp.php index 140a21ffe1..731581dacc 100644 --- a/apps/files_external/lib/ftp.php +++ b/apps/files_external/lib/ftp.php @@ -32,6 +32,10 @@ class FTP extends \OC\Files\Storage\StreamWrapper{ } } + public function getId(){ + return 'ftp::' . $this->user . '@' . $this->host . '/' . $this->root; + } + /** * construct the ftp url * @param string path diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php index 51c5d219aa..e4be4de5a0 100644 --- a/apps/files_external/lib/google.php +++ b/apps/files_external/lib/google.php @@ -30,6 +30,7 @@ class Google extends \OC\Files\Storage\Common { private $oauth_token; private $sig_method; private $entries; + private $id; private static $tempFiles = array(); @@ -37,6 +38,7 @@ class Google extends \OC\Files\Storage\Common { if (isset($params['configured']) && $params['configured'] == 'true' && isset($params['token']) && isset($params['token_secret'])) { $consumer_key = isset($params['consumer_key']) ? $params['consumer_key'] : 'anonymous'; $consumer_secret = isset($params['consumer_secret']) ? $params['consumer_secret'] : 'anonymous'; + $this->id = 'google::' . $consumer_key . $consumer_secret; $this->consumer = new \OAuthConsumer($consumer_key, $consumer_secret); $this->oauth_token = new \OAuthToken($params['token'], $params['token_secret']); $this->sig_method = new \OAuthSignatureMethod_HMAC_SHA1(); @@ -177,6 +179,9 @@ class Google extends \OC\Files\Storage\Common { } } + public function getId(){ + return $this->id; + } public function mkdir($path) { $collection = dirname($path); @@ -539,4 +544,4 @@ class Google extends \OC\Files\Storage\Common { } -} \ No newline at end of file +} diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index 8fcdd1f722..71c7a91ec9 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -42,6 +42,10 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ } } + public function getId(){ + return 'smb::' . $this->user . '@' . $this->host . '/' . $this->share . '/' . $this->root; + } + public function constructUrl($path) { if(substr($path,-1)=='/') { $path=substr($path,0,-1); diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index 8d402b2521..61f58766e8 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -11,6 +11,7 @@ require_once 'php-cloudfiles/cloudfiles.php'; namespace OC\Files\Storage; class SWIFT extends \OC\Files\Storage\Common{ + private $id; private $host; private $root; private $user; @@ -274,6 +275,8 @@ class SWIFT extends \OC\Files\Storage\Common{ $this->user=$params['user']; $this->root=isset($params['root'])?$params['root']:'/'; $this->secure=isset($params['secure'])?(bool)$params['secure']:true; + + $this->id = 'swift::' . $this->user . '@' . $this->host . '/' . $this->root; if(!$this->root || $this->root[0]!='/') { $this->root='/'.$this->root; } @@ -289,6 +292,10 @@ class SWIFT extends \OC\Files\Storage\Common{ } } + public function getId(){ + return $this->id; + } + public function mkdir($path) { if($this->containerExists($path)) { diff --git a/apps/files_external/lib/webdav.php b/apps/files_external/lib/webdav.php index 7c61d06a01..6ed93e9035 100644 --- a/apps/files_external/lib/webdav.php +++ b/apps/files_external/lib/webdav.php @@ -56,6 +56,10 @@ class DAV extends \OC\Files\Storage\Common{ $this->mkdir(''); } + public function getId(){ + return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root; + } + private function createBaseUri() { $baseUri='http'; if($this->secure) { From 141ff806c6cfbd349ffa232502a89a620882c409 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 11 Oct 2012 23:17:59 +0200 Subject: [PATCH 037/418] fix namespace problems for external storage backends --- apps/files_external/lib/amazons3.php | 4 +- apps/files_external/lib/dropbox.php | 4 +- apps/files_external/lib/ftp.php | 7 +-- apps/files_external/lib/google.php | 12 ++--- apps/files_external/lib/smb.php | 5 +- apps/files_external/lib/swift.php | 69 ++++++++++++++-------------- apps/files_external/lib/webdav.php | 32 ++++++------- apps/files_external/tests/config.php | 2 +- 8 files changed, 65 insertions(+), 70 deletions(-) diff --git a/apps/files_external/lib/amazons3.php b/apps/files_external/lib/amazons3.php index 2f7c8b35be..832127d3e7 100644 --- a/apps/files_external/lib/amazons3.php +++ b/apps/files_external/lib/amazons3.php @@ -20,10 +20,10 @@ * License along with this library. If not, see . */ -require_once 'aws-sdk/sdk.class.php'; - namespace OC\Files\Storage; +require_once 'aws-sdk/sdk.class.php'; + class AmazonS3 extends \OC\Files\Storage\Common { private $s3; diff --git a/apps/files_external/lib/dropbox.php b/apps/files_external/lib/dropbox.php index 351a2840d9..0f7593162e 100755 --- a/apps/files_external/lib/dropbox.php +++ b/apps/files_external/lib/dropbox.php @@ -20,10 +20,10 @@ * License along with this library. If not, see . */ -require_once 'Dropbox/autoload.php'; - namespace OC\Files\Storage; +require_once 'Dropbox/autoload.php'; + class Dropbox extends \OC\Files\Storage\Common { private $dropbox; diff --git a/apps/files_external/lib/ftp.php b/apps/files_external/lib/ftp.php index 731581dacc..dea44728f3 100644 --- a/apps/files_external/lib/ftp.php +++ b/apps/files_external/lib/ftp.php @@ -26,7 +26,7 @@ class FTP extends \OC\Files\Storage\StreamWrapper{ if(!$this->root || $this->root[0]!='/') { $this->root='/'.$this->root; } - //create the root folder if necesary + //create the root folder if necessary if (!$this->is_dir('')) { $this->mkdir(''); } @@ -38,7 +38,7 @@ class FTP extends \OC\Files\Storage\StreamWrapper{ /** * construct the ftp url - * @param string path + * @param string $path * @return string */ public function constructUrl($path) { @@ -74,7 +74,7 @@ class FTP extends \OC\Files\Storage\StreamWrapper{ }else{ $ext=''; } - $tmpFile=OCP\Files::tmpFile($ext); + $tmpFile=\OCP\Files::tmpFile($ext); \OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); if($this->file_exists($path)) { $this->getFile($path,$tmpFile); @@ -82,6 +82,7 @@ class FTP extends \OC\Files\Storage\StreamWrapper{ self::$tempFiles[$tmpFile]=$path; return fopen('close://'.$tmpFile,$mode); } + return false; } public function writeBack($tmpFile) { diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php index e4be4de5a0..141fc619e3 100644 --- a/apps/files_external/lib/google.php +++ b/apps/files_external/lib/google.php @@ -20,10 +20,10 @@ * License along with this library. If not, see . */ -require_once 'Google/common.inc.php'; - namespace OC\Files\Storage; +require_once 'Google/common.inc.php'; + class Google extends \OC\Files\Storage\Common { private $consumer; @@ -62,7 +62,7 @@ class Google extends \OC\Files\Storage\Common { $tempStr .= '&' . urlencode($key) . '=' . urlencode($value); } $uri = preg_replace('/&/', '?', $tempStr, 1); - $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->oauth_token, $httpMethod, $uri, $params); + $request = \OAuthRequest::from_consumer_and_token($this->consumer, $this->oauth_token, $httpMethod, $uri, $params); $request->sign_request($this->sig_method, $this->consumer, $this->oauth_token); $auth_header = $request->to_header(); $headers = array($auth_header, 'GData-Version: 3.0'); @@ -129,7 +129,7 @@ class Google extends \OC\Files\Storage\Common { private function getFeed($feedUri, $httpMethod, $postData = null) { $result = $this->sendRequest($feedUri, $httpMethod, $postData); if ($result) { - $dom = new DOMDocument(); + $dom = new \DOMDocument(); $dom->loadXML($result); return $dom; } @@ -248,7 +248,7 @@ class Google extends \OC\Files\Storage\Common { $this->entries[$name] = $entry; } } - OC_FakeDirStream::$dirs['google'.$path] = $files; + \OC_FakeDirStream::$dirs['google'.$path] = $files; return opendir('fakedir://google'.$path); } @@ -407,7 +407,7 @@ class Google extends \OC\Files\Storage\Common { $ext = ''; } $tmpFile = \OC_Helper::tmpFile($ext); - OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); + \OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); file_put_contents($tmpFile, $source); diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index 71c7a91ec9..dfba3105ec 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -6,10 +6,10 @@ * See the COPYING-README file. */ -require_once 'smb4php/smb.php'; - namespace OC\Files\Storage; +require_once 'smb4php/smb.php'; + class SMB extends \OC\Files\Storage\StreamWrapper{ private $password; private $user; @@ -70,6 +70,7 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ /** * check if a file or folder has been updated since $time + * @param string $path * @param int $time * @return bool */ diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index 61f58766e8..4b3f38166d 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -6,10 +6,10 @@ * See the COPYING-README file. */ -require_once 'php-cloudfiles/cloudfiles.php'; - namespace OC\Files\Storage; +require_once 'php-cloudfiles/cloudfiles.php'; + class SWIFT extends \OC\Files\Storage\Common{ private $id; private $host; @@ -18,15 +18,15 @@ class SWIFT extends \OC\Files\Storage\Common{ private $token; private $secure; /** - * @var CF_Authentication auth + * @var \CF_Authentication auth */ private $auth; /** - * @var CF_Connection conn + * @var \CF_Connection conn */ private $conn; /** - * @var CF_Container rootContainer + * @var \CF_Container rootContainer */ private $rootContainer; @@ -38,7 +38,7 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * translate directory path to container name - * @param string path + * @param string $path * @return string */ private function getContainerName($path) { @@ -48,8 +48,8 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * get container by path - * @param string path - * @return CF_Container + * @param string $path + * @return \CF_Container */ private function getContainer($path) { if($path=='' or $path=='/') { @@ -62,15 +62,15 @@ class SWIFT extends \OC\Files\Storage\Common{ $container=$this->conn->get_container($this->getContainerName($path)); $this->containers[$path]=$container; return $container; - }catch(NoSuchContainerException $e) { + }catch(\NoSuchContainerException $e) { return null; } } /** * create container - * @param string path - * @return CF_Container + * @param string $path + * @return \CF_Container */ private function createContainer($path) { if($path=='' or $path=='/' or $path=='.') { @@ -92,8 +92,8 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * get object by path - * @param string path - * @return CF_Object + * @param string $path + * @return \CF_Object */ private function getObject($path) { if(isset($this->objects[$path])) { @@ -110,7 +110,7 @@ class SWIFT extends \OC\Files\Storage\Common{ $obj=$container->get_object(basename($path)); $this->objects[$path]=$obj; return $obj; - }catch(NoSuchObjectException $e) { + }catch(\NoSuchObjectException $e) { return null; } } @@ -135,8 +135,8 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * create object - * @param string path - * @return CF_Object + * @param string $path + * @return \CF_Object */ private function createObject($path) { $container=$this->getContainer(dirname($path)); @@ -157,7 +157,7 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * check if container for path exists - * @param string path + * @param string $path * @return bool */ private function containerExists($path) { @@ -166,15 +166,15 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * get the list of emulated sub containers - * @param CF_Container container + * @param \CF_Container $container * @return array */ private function getSubContainers($container) { - $tmpFile=OCP\Files::tmpFile(); + $tmpFile=\OCP\Files::tmpFile(); $obj=$this->getSubContainerFile($container); try{ $obj->save_to_filename($tmpFile); - }catch(Exception $e) { + }catch(\Exception $e) { return array(); } $obj->save_to_filename($tmpFile); @@ -188,15 +188,15 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * add an emulated sub container - * @param CF_Container container - * @param string name + * @param CF_Container $container + * @param string $name * @return bool */ private function addSubContainer($container,$name) { if(!$name) { return false; } - $tmpFile=OCP\Files::tmpFile(); + $tmpFile=\OCP\Files::tmpFile(); $obj=$this->getSubContainerFile($container); try{ $obj->save_to_filename($tmpFile); @@ -211,8 +211,7 @@ class SWIFT extends \OC\Files\Storage\Common{ $fh=fopen($tmpFile,'a'); fwrite($fh,$name."\n"); } - }catch(Exception $e) { - $containers=array(); + }catch(\Exception $e) { file_put_contents($tmpFile,$name."\n"); } @@ -223,20 +222,20 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * remove an emulated sub container - * @param CF_Container container - * @param string name + * @param CF_Container $container + * @param string $name * @return bool */ private function removeSubContainer($container,$name) { if(!$name) { return false; } - $tmpFile=OCP\Files::tmpFile(); + $tmpFile=\OCP\Files::tmpFile(); $obj=$this->getSubContainerFile($container); try{ $obj->save_to_filename($tmpFile); $containers=file($tmpFile); - }catch(Exception $e) { + }catch(\Exception $e) { return false; } foreach($containers as &$sub) { @@ -258,13 +257,13 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * ensure a subcontainer file exists and return it's object - * @param CF_Container container - * @return CF_Object + * @param \CF_Container $container + * @return \CF_Object */ private function getSubContainerFile($container) { try{ return $container->get_object(self::SUBCONTAINER_FILE); - }catch(NoSuchObjectException $e) { + }catch(\NoSuchObjectException $e) { return $container->create_object(self::SUBCONTAINER_FILE); } } @@ -526,11 +525,11 @@ class SWIFT extends \OC\Files\Storage\Common{ private function getTmpFile($path) { $obj=$this->getObject($path); if(!is_null($obj)) { - $tmpFile=OCP\Files::tmpFile(); + $tmpFile=\OCP\Files::tmpFile(); $obj->save_to_filename($tmpFile); return $tmpFile; }else{ - return OCP\Files::tmpFile(); + return \OCP\Files::tmpFile(); } } @@ -545,7 +544,7 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * remove custom mtime metadata - * @param CF_Object obj + * @param \CF_Object $obj */ private function resetMTime($obj) { if(isset($obj->metadata['Mtime'])) { diff --git a/apps/files_external/lib/webdav.php b/apps/files_external/lib/webdav.php index 6ed93e9035..73231b6ad8 100644 --- a/apps/files_external/lib/webdav.php +++ b/apps/files_external/lib/webdav.php @@ -15,7 +15,7 @@ class DAV extends \OC\Files\Storage\Common{ private $secure; private $root; /** - * @var Sabre_DAV_Client + * @var \Sabre_DAV_Client */ private $client; @@ -92,7 +92,7 @@ class DAV extends \OC\Files\Storage\Common{ \OC_FakeDirStream::$dirs[$id][]=$file; } return opendir('fakedir://'.$id); - }catch(Exception $e) { + }catch(\Exception $e) { return false; } } @@ -103,7 +103,7 @@ class DAV extends \OC\Files\Storage\Common{ $response=$this->client->propfind($path, array('{DAV:}resourcetype')); $responseType=$response["{DAV:}resourcetype"]->resourceType; return (count($responseType)>0 and $responseType[0]=="{DAV:}collection")?'dir':'file'; - }catch(Exception $e) { + }catch(\Exception $e) { error_log($e->getMessage()); \OCP\Util::writeLog("webdav client", \OCP\Util::sanitizeHTML($e->getMessage()), \OCP\Util::ERROR); return false; @@ -123,7 +123,7 @@ class DAV extends \OC\Files\Storage\Common{ try{ $this->client->propfind($path, array('{DAV:}resourcetype')); return true;//no 404 exception - }catch(Exception $e) { + }catch(\Exception $e) { return false; } } @@ -169,7 +169,7 @@ class DAV extends \OC\Files\Storage\Common{ }else{ $ext=''; } - $tmpFile=OCP\Files::tmpFile($ext); + $tmpFile=\OCP\Files::tmpFile($ext); \OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); if($this->file_exists($path)) { $this->getFile($path,$tmpFile); @@ -195,7 +195,7 @@ class DAV extends \OC\Files\Storage\Common{ }else{ return 0; } - }catch(Exception $e) { + }catch(\Exception $e) { return 0; } } @@ -231,12 +231,9 @@ class DAV extends \OC\Files\Storage\Common{ $path1=$this->cleanPath($path1); $path2=$this->root.$this->cleanPath($path2); try{ - $response=$this->client->request('MOVE',$path1,null,array('Destination'=>$path2)); + $this->client->request('MOVE',$path1,null,array('Destination'=>$path2)); return true; - }catch(Exception $e) { - echo $e; - echo 'fail'; - var_dump($response); + }catch(\Exception $e) { return false; } } @@ -245,12 +242,9 @@ class DAV extends \OC\Files\Storage\Common{ $path1=$this->cleanPath($path1); $path2=$this->root.$this->cleanPath($path2); try{ - $response=$this->client->request('COPY',$path1,null,array('Destination'=>$path2)); + $this->client->request('COPY',$path1,null,array('Destination'=>$path2)); return true; - }catch(Exception $e) { - echo $e; - echo 'fail'; - var_dump($response); + }catch(\Exception $e) { return false; } } @@ -264,7 +258,7 @@ class DAV extends \OC\Files\Storage\Common{ 'size'=>(int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0, 'ctime'=>-1, ); - }catch(Exception $e) { + }catch(\Exception $e) { return array(); } } @@ -282,7 +276,7 @@ class DAV extends \OC\Files\Storage\Common{ }else{ return false; } - }catch(Exception $e) { + }catch(\Exception $e) { return false; } } @@ -300,7 +294,7 @@ class DAV extends \OC\Files\Storage\Common{ try{ $response=$this->client->request($method,$path,$body); return $response['statusCode']==$expected; - }catch(Exception $e) { + }catch(\Exception $e) { return false; } } diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php index ff16b1c1d8..5af317675c 100644 --- a/apps/files_external/tests/config.php +++ b/apps/files_external/tests/config.php @@ -8,7 +8,7 @@ return array( 'root'=>'/test', ), 'webdav'=>array( - 'run'=>false, + 'run'=>true, 'host'=>'localhost', 'user'=>'test', 'password'=>'test', From cfa036eaa916c1adf38fb161a16c50cd050c0a3b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 20 Oct 2012 23:54:37 +0200 Subject: [PATCH 038/418] drop filectime from the filesystem api's --- apps/files_external/lib/amazons3.php | 2 -- apps/files_external/lib/dropbox.php | 1 - apps/files_external/lib/google.php | 2 -- apps/files_external/lib/swift.php | 2 -- apps/files_external/lib/webdav.php | 1 - apps/files_sharing/lib/sharedstorage.php | 22 ---------------------- lib/files.php | 3 +-- lib/files/filesystem.php | 4 ---- lib/files/storage/common.php | 4 ---- lib/files/storage/local.php | 3 --- lib/files/storage/storage.php | 1 - lib/files/view.php | 4 ---- lib/filesystem.php | 7 ------- 13 files changed, 1 insertion(+), 55 deletions(-) diff --git a/apps/files_external/lib/amazons3.php b/apps/files_external/lib/amazons3.php index 832127d3e7..c3fa4651f6 100644 --- a/apps/files_external/lib/amazons3.php +++ b/apps/files_external/lib/amazons3.php @@ -115,12 +115,10 @@ class AmazonS3 extends \OC\Files\Storage\Common { $stat['size'] = $this->s3->get_bucket_filesize($this->bucket); $stat['atime'] = time(); $stat['mtime'] = $stat['atime']; - $stat['ctime'] = $stat['atime']; } else if ($object = $this->getObject($path)) { $stat['size'] = $object['Size']; $stat['atime'] = time(); $stat['mtime'] = strtotime($object['LastModified']); - $stat['ctime'] = $stat['mtime']; } if (isset($stat)) { return $stat; diff --git a/apps/files_external/lib/dropbox.php b/apps/files_external/lib/dropbox.php index 0f7593162e..0e82f80668 100755 --- a/apps/files_external/lib/dropbox.php +++ b/apps/files_external/lib/dropbox.php @@ -119,7 +119,6 @@ class Dropbox extends \OC\Files\Storage\Common { $stat['size'] = $metaData['bytes']; $stat['atime'] = time(); $stat['mtime'] = (isset($metaData['modified'])) ? strtotime($metaData['modified']) : time(); - $stat['ctime'] = $stat['mtime']; return $stat; } return false; diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php index 141fc619e3..7ee5b97f8b 100644 --- a/apps/files_external/lib/google.php +++ b/apps/files_external/lib/google.php @@ -257,14 +257,12 @@ class Google extends \OC\Files\Storage\Common { $stat['size'] = $this->free_space($path); $stat['atime'] = time(); $stat['mtime'] = time(); - $stat['ctime'] = time(); } else if ($entry = $this->getResource($path)) { // NOTE: Native resources don't have a file size $stat['size'] = $entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'quotaBytesUsed')->item(0)->nodeValue; // if (isset($atime = $entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'lastViewed')->item(0)->nodeValue)) // $stat['atime'] = strtotime($entry->getElementsByTagNameNS('http://schemas.google.com/g/2005', 'lastViewed')->item(0)->nodeValue); $stat['mtime'] = strtotime($entry->getElementsByTagName('updated')->item(0)->nodeValue); - $stat['ctime'] = strtotime($entry->getElementsByTagName('published')->item(0)->nodeValue); } if (isset($stat)) { return $stat; diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index 4b3f38166d..c3578b0c23 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -500,7 +500,6 @@ class SWIFT extends \OC\Files\Storage\Common{ return array( 'mtime'=>-1, 'size'=>$container->bytes_used, - 'ctime'=>-1 ); } @@ -518,7 +517,6 @@ class SWIFT extends \OC\Files\Storage\Common{ return array( 'mtime'=>$mtime, 'size'=>$obj->content_length, - 'ctime'=>-1, ); } diff --git a/apps/files_external/lib/webdav.php b/apps/files_external/lib/webdav.php index 73231b6ad8..c9785f3eb1 100644 --- a/apps/files_external/lib/webdav.php +++ b/apps/files_external/lib/webdav.php @@ -256,7 +256,6 @@ class DAV extends \OC\Files\Storage\Common{ return array( 'mtime'=>strtotime($response['{DAV:}getlastmodified']), 'size'=>(int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0, - 'ctime'=>-1, ); }catch(\Exception $e) { return array(); diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index e12027a4f2..521dfb69f2 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -162,7 +162,6 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/') { $stat['size'] = $this->filesize($path); $stat['mtime'] = $this->filemtime($path); - $stat['ctime'] = $this->filectime($path); return $stat; } else if ($source = $this->getSourcePath($path)) { list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); @@ -233,27 +232,6 @@ class Shared extends \OC\Files\Storage\Common { return false; } - public function filectime($path) { - if ($path == '' || $path == '/') { - $ctime = 0; - if ($dh = $this->opendir($path)) { - while (($filename = readdir($dh)) !== false) { - $tempctime = $this->filectime($filename); - if ($tempctime < $ctime) { - $ctime = $tempctime; - } - } - } - return $ctime; - } else { - $source = $this->getSourcePath($path); - if ($source) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); - return $storage->filectime($internalPath); - } - } - } - public function filemtime($path) { if ($path == '' || $path == '/') { $mtime = 0; diff --git a/lib/files.php b/lib/files.php index 29322cf2d0..28c8d0b449 100644 --- a/lib/files.php +++ b/lib/files.php @@ -30,13 +30,12 @@ class OC_Files { /** * get the filesystem info - * @param string path + * @param string $path * @return array * * returns an associative array with the following keys: * - size * - mtime - * - ctime * - mimetype * - encrypted * - versioned diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index e4c269257c..94e8a56256 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -478,10 +478,6 @@ class Filesystem { return self::$defaultInstance->file_exists($path); } - static public function filectime($path) { - return self::$defaultInstance->filectime($path); - } - static public function filemtime($path) { return self::$defaultInstance->filemtime($path); } diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index 3886a04182..1740584555 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -74,10 +74,6 @@ abstract class Common extends \OC\Files\Storage\Storage { return $permissions; } // abstract public function file_exists($path); - public function filectime($path) { - $stat = $this->stat($path); - return $stat['ctime']; - } public function filemtime($path) { $stat = $this->stat($path); return $stat['mtime']; diff --git a/lib/files/storage/local.php b/lib/files/storage/local.php index 66ae5542e3..ccd69e3971 100644 --- a/lib/files/storage/local.php +++ b/lib/files/storage/local.php @@ -66,9 +66,6 @@ class Local extends \OC\Files\Storage\Common{ public function file_exists($path) { return file_exists($this->datadir.$path); } - public function filectime($path) { - return filectime($this->datadir.$path); - } public function filemtime($path) { return filemtime($this->datadir.$path); } diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index f149c61830..941bc3b077 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -29,7 +29,6 @@ abstract class Storage{ abstract public function isSharable($path); abstract public function getPermissions($path); abstract public function file_exists($path); - abstract public function filectime($path); abstract public function filemtime($path); abstract public function file_get_contents($path); abstract public function file_put_contents($path,$data); diff --git a/lib/files/view.php b/lib/files/view.php index bffeb434f2..58e3ee6f05 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -236,10 +236,6 @@ class View { return $this->basicOperation('file_exists', $path); } - public function filectime($path) { - return $this->basicOperation('filectime', $path); - } - public function filemtime($path) { return $this->basicOperation('filemtime', $path); } diff --git a/lib/filesystem.php b/lib/filesystem.php index 587eb50d9e..9ce75aaf8c 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -306,13 +306,6 @@ class OC_Filesystem { return \OC\Files\Filesystem::file_exists($path); } - /** - * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem - */ - static public function filectime($path) { - return \OC\Files\Filesystem::filectime($path); - } - /** * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem */ From 1901ac8b17c99eef3ccd99edf877fd0a86737d17 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 21 Oct 2012 00:13:16 +0200 Subject: [PATCH 039/418] drop depricated is_readable and is_writable --- lib/files.php | 2 +- lib/files/filesystem.php | 14 -------------- lib/files/view.php | 14 -------------- lib/filesystem.php | 7 ------- 4 files changed, 1 insertion(+), 36 deletions(-) diff --git a/lib/files.php b/lib/files.php index 28c8d0b449..ba111bee96 100644 --- a/lib/files.php +++ b/lib/files.php @@ -178,7 +178,7 @@ class OC_Files { $filename=$dir.'/'.$files; } @ob_end_clean(); - if($zip or \OC\Files\Filesystem::is_readable($filename)) { + if($zip or \OC\Files\Filesystem::isReadable($filename)) { header('Content-Disposition: attachment; filename="'.basename($filename).'"'); header('Content-Transfer-Encoding: binary'); OC_Response::disableCaching(); diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 94e8a56256..0dae774feb 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -440,20 +440,6 @@ class Filesystem { return self::$defaultInstance->readfile($path); } - /** - * @deprecated Replaced by isReadable() as part of CRUDS - */ - static public function is_readable($path) { - return self::$defaultInstance->isReadable($path); - } - - /** - * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS - */ - static public function is_writable($path) { - return self::$defaultInstance->is_writable($path); - } - static public function isCreatable($path) { return self::$defaultInstance->isCreatable($path); } diff --git a/lib/files/view.php b/lib/files/view.php index 58e3ee6f05..a59ad8105d 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -195,20 +195,6 @@ class View { return false; } - /** - * @deprecated Replaced by isReadable() as part of CRUDS - */ - public function is_readable($path) { - return $this->basicOperation('isReadable', $path); - } - - /** - * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS - */ - public function is_writable($path) { - return $this->basicOperation('isUpdatable', $path); - } - public function isCreatable($path) { return $this->basicOperation('isCreatable', $path); } diff --git a/lib/filesystem.php b/lib/filesystem.php index 9ce75aaf8c..ea0a289c8d 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -257,13 +257,6 @@ class OC_Filesystem { return \OC\Files\Filesystem::isReadable($path); } - /** - * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS - */ - static public function is_writable($path) { - return \OC\Files\Filesystem::is_writable($path); - } - /** * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem */ From 6f1fbf97f7678f7a9a8b2e92d1cdd24fc5710d17 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 21 Oct 2012 00:27:55 +0200 Subject: [PATCH 040/418] some namespaces fixed for the shared backend --- apps/files_sharing/lib/sharedstorage.php | 25 +++++++----------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 521dfb69f2..e354314cc3 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -37,7 +37,7 @@ class Shared extends \OC\Files\Storage\Common { /** * @brief Get the source file path and the permissions granted for a shared file * @param string Shared target file path - * @return Returns array with the keys path and permissions or false if not found + * @return array with the keys path and permissions or false if not found */ private function getFile($target) { $target = '/'.$target; @@ -52,7 +52,7 @@ class Shared extends \OC\Files\Storage\Common { if (isset($this->files[$folder])) { $file = $this->files[$folder]; } else { - $file = OCP\Share::getItemSharedWith('folder', $folder, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + $file = \OCP\Share::getItemSharedWith('folder', $folder, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); } if ($file) { $this->files[$target]['path'] = $file['path'].substr($target, strlen($folder)); @@ -60,13 +60,13 @@ class Shared extends \OC\Files\Storage\Common { return $this->files[$target]; } } else { - $file = OCP\Share::getItemSharedWith('file', $target, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + $file = \OCP\Share::getItemSharedWith('file', $target, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); if ($file) { $this->files[$target] = $file; return $this->files[$target]; } } - OCP\Util::writeLog('files_sharing', 'File source not found for: '.$target, OCP\Util::ERROR); + \OCP\Util::writeLog('files_sharing', 'File source not found for: '.$target, \OCP\Util::ERROR); return false; } } @@ -74,13 +74,13 @@ class Shared extends \OC\Files\Storage\Common { /** * @brief Get the source file path for a shared file * @param string Shared target file path - * @return Returns source file path or false if not found + * @return string source file path or false if not found */ private function getSourcePath($target) { $file = $this->getFile($target); if (isset($file['path'])) { $uid = substr($file['path'], 1, strpos($file['path'], '/', 1) - 1); - \\OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => \OC_User::getHome($uid)), $uid); + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => \OC_User::getHome($uid)), $uid); return $file['path']; } return false; @@ -89,7 +89,7 @@ class Shared extends \OC\Files\Storage\Common { /** * @brief Get the permissions granted for a shared file * @param string Shared target file path - * @return Returns CRUDS permissions granted or false if not found + * @return int CRUDS permissions granted or false if not found */ public function getPermissions($target) { $file = $this->getFile($target); @@ -99,17 +99,6 @@ class Shared extends \OC\Files\Storage\Common { return false; } - /** - * @brief Get the internal path to pass to the storage filesystem call - * @param string Source file path - * @return Source file path with mount point stripped out - */ - private function getInternalPath($path) { - $mountPoint = \OC\Files\Filesystem::getMountPoint($path); - $internalPath = substr($path, strlen($mountPoint)); - return $internalPath; - } - public function mkdir($path) { if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) { return false; From 3ff0772a05c70592360c7b11b280cc4cf45a385e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 21 Oct 2012 00:31:32 +0200 Subject: [PATCH 041/418] add getCache and getScanner to storage api in order to allow storage backends to overwride caching behaviour --- lib/files/storage/common.php | 50 ++++++------------------- lib/files/storage/storage.php | 70 ++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 72 deletions(-) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index 1740584555..de02c0d5d8 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -20,7 +20,7 @@ namespace OC\Files\Storage; * in classes which extend it, e.g. $this->stat() . */ -abstract class Common extends \OC\Files\Storage\Storage { +abstract class Common implements \OC\Files\Storage\Storage { public function __construct($parameters) {} // abstract public function getId(); @@ -123,64 +123,30 @@ abstract class Common extends \OC\Files\Storage\Storage { * deleted together with its contents. To avoid this set $empty to true */ public function deleteAll( $directory, $empty = false ) { - - // strip leading slash - if( substr( $directory, 0, 1 ) == "/" ) { - - $directory = substr( $directory, 1 ); - - } - - // strip trailing slash - if( substr( $directory, -1) == "/" ) { - - $directory = substr( $directory, 0, -1 ); - - } + $directory = trim($directory,'/'); if ( !$this->file_exists( \OCP\USER::getUser() . '/' . $directory ) || !$this->is_dir( \OCP\USER::getUser() . '/' . $directory ) ) { - return false; - } elseif( !$this->is_readable( \OCP\USER::getUser() . '/' . $directory ) ) { - return false; - } else { - $directoryHandle = $this->opendir( \OCP\USER::getUser() . '/' . $directory ); - while ( $contents = readdir( $directoryHandle ) ) { - if ( $contents != '.' && $contents != '..') { - $path = $directory . "/" . $contents; - if ( $this->is_dir( $path ) ) { - deleteAll( $path ); - } else { - $this->unlink( \OCP\USER::getUser() .'/' . $path ); // TODO: make unlink use same system path as is_dir - } } - } - //$this->closedir( $directoryHandle ); // TODO: implement closedir in OC_FSV - if ( $empty == false ) { - if ( !$this->rmdir( $directory ) ) { - - return false; - + return false; } - } - return true; } @@ -209,7 +175,7 @@ abstract class Common extends \OC\Files\Storage\Storage { return $mime; } public function hash($type,$path,$raw = false) { - $tmpFile=$this->getLocalFile(); + $tmpFile=$this->getLocalFile($path); $hash=hash($type,$tmpFile,$raw); unlink($tmpFile); return $hash; @@ -283,4 +249,12 @@ abstract class Common extends \OC\Files\Storage\Storage { public function hasUpdated($path,$time) { return $this->filemtime($path)>$time; } + + public function getCache(){ + return new \OC\Files\Cache\Cache($this); + } + + public function getScanner(){ + return new \OC\Files\Cache\Scanner($this); + } } diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index 941bc3b077..b1ec0bdb50 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -9,40 +9,40 @@ namespace OC\Files\Storage; /** - * Provde a common interface to all different storage options + * Provide a common interface to all different storage options */ -abstract class Storage{ - abstract public function __construct($parameters); - abstract public function getId(); - abstract public function mkdir($path); - abstract public function rmdir($path); - abstract public function opendir($path); - abstract public function is_dir($path); - abstract public function is_file($path); - abstract public function stat($path); - abstract public function filetype($path); - abstract public function filesize($path); - abstract public function isCreatable($path); - abstract public function isReadable($path); - abstract public function isUpdatable($path); - abstract public function isDeletable($path); - abstract public function isSharable($path); - abstract public function getPermissions($path); - abstract public function file_exists($path); - abstract public function filemtime($path); - abstract public function file_get_contents($path); - abstract public function file_put_contents($path,$data); - abstract public function unlink($path); - abstract public function rename($path1,$path2); - abstract public function copy($path1,$path2); - abstract public function fopen($path,$mode); - abstract public function getMimeType($path); - abstract public function hash($type,$path,$raw = false); - abstract public function free_space($path); - abstract public function search($query); - abstract public function touch($path, $mtime=null); - abstract public function getLocalFile($path);// get a path to a local version of the file, whether the original file is local or remote - abstract public function getLocalFolder($path);// get a path to a local version of the folder, whether the original file is local or remote +interface Storage{ + public function __construct($parameters); + public function getId(); + public function mkdir($path); + public function rmdir($path); + public function opendir($path); + public function is_dir($path); + public function is_file($path); + public function stat($path); + public function filetype($path); + public function filesize($path); + public function isCreatable($path); + public function isReadable($path); + public function isUpdatable($path); + public function isDeletable($path); + public function isSharable($path); + public function getPermissions($path); + public function file_exists($path); + public function filemtime($path); + public function file_get_contents($path); + public function file_put_contents($path,$data); + public function unlink($path); + public function rename($path1,$path2); + public function copy($path1,$path2); + public function fopen($path,$mode); + public function getMimeType($path); + public function hash($type,$path,$raw = false); + public function free_space($path); + public function search($query); + public function touch($path, $mtime=null); + public function getLocalFile($path);// get a path to a local version of the file, whether the original file is local or remote + public function getLocalFolder($path);// get a path to a local version of the folder, whether the original file is local or remote /** * check if a file or folder has been updated since $time * @param int $time @@ -51,5 +51,7 @@ abstract class Storage{ * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. * returning true for other changes in the folder is optional */ - abstract public function hasUpdated($path,$time); + public function hasUpdated($path,$time); + public function getCache(); + public function getScanner(); } From 01594b8610ce4f08e28010994d9cbecda4c52e35 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 21 Oct 2012 00:54:34 +0200 Subject: [PATCH 042/418] remove chroot from filesystem api --- lib/files/filesystem.php | 10 ---------- lib/filesystem.php | 11 ----------- 2 files changed, 21 deletions(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 0dae774feb..05dd698ab4 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -281,16 +281,6 @@ class Filesystem { } } - /** - * change the root to a fake root - * - * @param string $fakeRoot - * @return bool - */ - static public function chroot($fakeRoot) { - return self::$defaultInstance->chroot($fakeRoot); - } - /** * @brief get the relative path of the root data directory for the current user * @return string diff --git a/lib/filesystem.php b/lib/filesystem.php index ea0a289c8d..20b5ab2790 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -81,17 +81,6 @@ class OC_Filesystem { \OC\Files\Filesystem::tearDown(); } - /** - * change the root to a fake root - * - * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem - * @param string $fakeRoot - * @return bool - */ - static public function chroot($fakeRoot) { - return \OC\Files\Filesystem::chroot($fakeRoot); - } - /** * @brief get the relative path of the root data directory for the current user * @return string From 2522c25af726a8487ac13855e8a035b990cd69a4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 21 Oct 2012 02:12:58 +0200 Subject: [PATCH 043/418] use OC_Files::getFileInfo and OC_Files::getDirectoryContent as high level api for the filecache most apps would want to use this api instead of using the cache directly --- lib/files.php | 108 +++++++++++++++++----------------- lib/files/filesystem.php | 73 ++++++++++++++++++----- lib/files/storage/storage.php | 7 +++ tests/lib/files.php | 87 +++++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 71 deletions(-) create mode 100644 tests/lib/files.php diff --git a/lib/files.php b/lib/files.php index ba111bee96..8d402c0001 100644 --- a/lib/files.php +++ b/lib/files.php @@ -41,69 +41,67 @@ class OC_Files { * - versioned */ public static function getFileInfo($path) { - if (($path == '/Shared' || substr($path, 0, 8) == '/Shared/') && OC_App::isEnabled('files_sharing')) { - if ($path == '/Shared') { - list($info) = OCP\Share::getItemsSharedWith('file', OC_Share_Backend_File::FORMAT_FILE_APP_ROOT); - }else{ - $info['size'] = \OC\Files\Filesystem::filesize($path); - $info['mtime'] = \OC\Files\Filesystem::filemtime($path); - $info['ctime'] = \OC\Files\Filesystem::filectime($path); - $info['mimetype'] = \OC\Files\Filesystem::getMimeType($path); - $info['encrypted'] = false; - $info['versioned'] = false; + $path=\OC\Files\Filesystem::normalizePath($path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $path + */ + list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path); + $cache=$storage->getCache(); + $data = $cache->get($internalPath); + + if($data['mimetype'] === 'httpd/unix-directory'){ + //add the sizes of other mountpoints to the folder + $mountPoints = \OC\Files\Filesystem::getMountPoints($path); + foreach($mountPoints as $mountPoint){ + $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); + $subCache = $subStorage->getCache(); + $rootEntry = $subCache->get(''); + + $data['size'] += $rootEntry['size']; } - } else { - $info = OC_FileCache::get($path); } - return $info; + + return $data; } /** * get the content of a directory - * @param dir $directory path under datadirectory + * @param string $directory path under datadirectory + * @return array */ public static function getDirectoryContent($directory, $mimetype_filter = '') { - $directory=\OC\Files\Filesystem::normalizePath($directory); - if($directory=='/') { - $directory=''; - } - $files = array(); - if (($directory == '/Shared' || substr($directory, 0, 8) == '/Shared/') && OC_App::isEnabled('files_sharing')) { - if ($directory == '/Shared') { - $files = OCP\Share::getItemsSharedWith('file', OC_Share_Backend_File::FORMAT_FILE_APP, array('folder' => $directory, 'mimetype_filter' => $mimetype_filter)); - } else { - $pos = strpos($directory, '/', 8); - // Get shared folder name - if ($pos !== false) { - $itemTarget = substr($directory, 7, $pos - 7); - } else { - $itemTarget = substr($directory, 7); + $path=\OC\Files\Filesystem::normalizePath($directory); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $path + */ + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + $cache=$storage->getCache(); + $files=$cache->getFolderContents($internalPath); //TODO: mimetype_filter + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mountPoints = \OC\Files\Filesystem::getMountPoints($directory); + $dirLength = strlen($path); + foreach($mountPoints as $mountPoint){ + $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); + $subCache = $subStorage->getCache(); + $rootEntry = $subCache->get(''); + + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if($pos = strpos($relativePath, '/')){ //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach($files as &$entry){ + if($entry['name'] === $entryName){ + $entry['size'] += $rootEntry['size']; + } } - $files = OCP\Share::getItemSharedWith('folder', $itemTarget, OC_Share_Backend_File::FORMAT_FILE_APP, array('folder' => $directory, 'mimetype_filter' => $mimetype_filter)); - } - } else { - $files = OC_FileCache::getFolderContent($directory, false, $mimetype_filter); - foreach ($files as &$file) { - $file['directory'] = $directory; - $file['type'] = ($file['mimetype'] == 'httpd/unix-directory') ? 'dir' : 'file'; - $permissions = OCP\Share::PERMISSION_READ; - // NOTE: Remove check when new encryption is merged - if (!$file['encrypted']) { - $permissions |= OCP\Share::PERMISSION_SHARE; - } - if ($file['type'] == 'dir' && $file['writable']) { - $permissions |= OCP\Share::PERMISSION_CREATE; - } - if ($file['writable']) { - $permissions |= OCP\Share::PERMISSION_UPDATE | OCP\Share::PERMISSION_DELETE; - } - $file['permissions'] = $permissions; - } - if ($directory == '' && OC_App::isEnabled('files_sharing')) { - // Add 'Shared' folder - $files = array_merge($files, OCP\Share::getItemsSharedWith('file', OC_Share_Backend_File::FORMAT_FILE_APP_ROOT)); + }else{ //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $files[] = $rootEntry; } } + usort($files, "fileCmp");//TODO: remove this once ajax is merged return $files; } @@ -145,7 +143,7 @@ class OC_Files { set_time_limit(0); $zip = new ZipArchive(); $filename = OC_Helper::tmpFile('.zip'); - if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) { + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==true) { exit("cannot open <$filename>\n"); } foreach($files as $file) { @@ -166,7 +164,7 @@ class OC_Files { set_time_limit(0); $zip = new ZipArchive(); $filename = OC_Helper::tmpFile('.zip'); - if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) { + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==true) { exit("cannot open <$filename>\n"); } $file=$dir.'/'.$files; @@ -446,7 +444,7 @@ class OC_Files { $setting = 'php_value '.$key.' '.$size; $hasReplaced = 0; $content = preg_replace($pattern, $setting, $htaccess, 1, $hasReplaced); - if($content !== NULL) { + if($content !== null) { $htaccess = $content; } if($hasReplaced == 0) { diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 05dd698ab4..b7f8483fbf 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -164,6 +164,43 @@ class Filesystem { return $foundMountPoint; } + /** + * get a list of all mount points in a directory + * + * @param string $path + * @return string[] + */ + static public function getMountPoints($path) { + $path = self::normalizePath($path); + if (strlen($path) > 1) { + $path .= '/'; + } + $pathLength = strlen($path); + + $mountPoints = array_keys(self::$mounts); + $result = array(); + foreach ($mountPoints as $mountPoint) { + if (substr($mountPoint, 0, $pathLength) === $path and strlen($mountPoint) > $pathLength) { + $result[] = $mountPoint; + } + } + return $result; + } + + /** + * get the storage mounted at $mountPoint + * + * @param string $mountPoint + * @return \OC\Files\Storage\Storage + */ + public static function getStorage($mountPoint) { + if (!isset(self::$storages[$mountPoint])) { + $mount = self::$mounts[$mountPoint]; + self::$storages[$mountPoint] = self::createStorage($mount['class'], $mount['arguments']); + } + return self::$storages[$mountPoint]; + } + /** * resolve a path to a storage and internal path * @@ -173,14 +210,14 @@ class Filesystem { static public function resolvePath($path) { $mountpoint = self::getMountPoint($path); if ($mountpoint) { - if (!isset(self::$storages[$mountpoint])) { - $mount = self::$mounts[$mountpoint]; - self::$storages[$mountpoint] = self::createStorage($mount['class'], $mount['arguments']); + $storage = self::getStorage($mountpoint); + if ($mountpoint === $path) { + $internalPath = ''; + } else { + $internalPath = substr($path, strlen($mountpoint)); } - $storage = self::$storages[$mountpoint]; - $internalPath = substr($path, strlen($mountpoint)); return array($storage, $internalPath); - }else{ + } else { return array(null, null); } } @@ -302,18 +339,22 @@ class Filesystem { /** * mount an \OC\Files\Storage\Storage in our virtual filesystem * - * @param \OC\Files\Storage\Storage $storage + * @param \OC\Files\Storage\Storage|string $class * @param array $arguments * @param string $mountpoint */ static public function mount($class, $arguments, $mountpoint) { - if ($mountpoint[0] != '/') { - $mountpoint = '/' . $mountpoint; + $mountpoint = self::normalizePath($mountpoint); + if (strlen($mountpoint) > 1) { + $mountpoint .= '/'; } - if (substr($mountpoint, -1) !== '/') { - $mountpoint = $mountpoint . '/'; + + if ($class instanceof \OC\Files\Storage\Storage) { + self::$mounts[$mountpoint] = array('class' => get_class($class), 'arguments' => $arguments); + self::$storages[$mountpoint] = $class; + } else { + self::$mounts[$mountpoint] = array('class' => $class, 'arguments' => $arguments); } - self::$mounts[$mountpoint] = array('class' => $class, 'arguments' => $arguments); } /** @@ -522,15 +563,15 @@ class Filesystem { static public function removeETagHook($params, $root = false) { if (isset($params['path'])) { - $path=$params['path']; + $path = $params['path']; } else { - $path=$params['oldpath']; + $path = $params['oldpath']; } if ($root) { // reduce path to the required part of it (no 'username/files') - $fakeRootView = new OC_FilesystemView($root); + $fakeRootView = new View($root); $count = 1; - $path=str_replace(OC_App::getStorage("files")->getAbsolutePath(), "", $fakeRootView->getAbsolutePath($path), $count); + $path = str_replace(\OC_App::getStorage("files")->getAbsolutePath(''), "", $fakeRootView->getAbsolutePath($path), $count); } $path = self::normalizePath($path); diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index b1ec0bdb50..853e8ba519 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -52,6 +52,13 @@ interface Storage{ * returning true for other changes in the folder is optional */ public function hasUpdated($path,$time); + + /** + * @return \OC\Files\Cache\Cache + */ public function getCache(); + /** + * @return \OC\Files\Cache\Scanner + */ public function getScanner(); } diff --git a/tests/lib/files.php b/tests/lib/files.php new file mode 100644 index 0000000000..9cb5276845 --- /dev/null +++ b/tests/lib/files.php @@ -0,0 +1,87 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ + +use \OC\Files\Filesystem as Filesystem; + +class Test_Files extends PHPUnit_Framework_TestCase { + /** + * @var \OC\Files\Storage\Storage[] $storages; + */ + private $storages = array(); + + public function setUp() { + Filesystem::clearMounts(); + } + + public function tearDown() { + foreach ($this->storages as $storage) { + $cache = $storage->getCache(); + $cache->clear(); + } + } + + public function testCacheAPI() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $storage3 = $this->getTestStorage(); + Filesystem::mount($storage1, array(), '/'); + Filesystem::mount($storage2, array(), '/substorage'); + Filesystem::mount($storage3, array(), '/folder/anotherstorage'); + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $storageSize = $textSize * 2 + $imageSize; + + $cachedData = OC_Files::getFileInfo('/foo.txt'); + $this->assertEquals($textSize, $cachedData['size']); + $this->assertEquals('text/plain', $cachedData['mimetype']); + + $cachedData = OC_Files::getFileInfo('/'); + $this->assertEquals($storageSize * 3, $cachedData['size']); + $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); + + $cachedData = OC_Files::getFileInfo('/folder'); + $this->assertEquals($storageSize + $textSize, $cachedData['size']); + $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); + + $folderData = OC_Files::getDirectoryContent('/'); + /** + * expected entries: + * folder + * foo.png + * foo.txt + * substorage + */ + $this->assertEquals(4, count($folderData)); + $this->assertEquals('folder', $folderData[0]['name']); + $this->assertEquals('foo.png', $folderData[1]['name']); + $this->assertEquals('foo.txt', $folderData[2]['name']); + $this->assertEquals('substorage', $folderData[3]['name']); + + $this->assertEquals($storageSize + $textSize, $folderData[0]['size']); + $this->assertEquals($imageSize, $folderData[1]['size']); + $this->assertEquals($textSize, $folderData[2]['size']); + $this->assertEquals($storageSize, $folderData[3]['size']); + } + + /** + * @return OC\Files\Storage\Storage + */ + private function getTestStorage() { + $storage = new \OC\Files\Storage\Temporary(array()); + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $storage->mkdir('folder'); + $storage->file_put_contents('foo.txt', $textData); + $storage->file_put_contents('foo.png', $imgData); + $storage->file_put_contents('folder/bar.txt', $textData); + + $scanner = $storage->getScanner(); + $scanner->scan(''); + $this->storages[] = $storage; + return $storage; + } +} From 33cabcf590401763609570a86f7bc7540dbf1fc5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 21 Oct 2012 22:04:45 +0200 Subject: [PATCH 044/418] postpone the cost of setting up some of the external storage backends untill we actually need it --- apps/files_external/lib/ftp.php | 2 ++ apps/files_external/lib/smb.php | 7 ++---- apps/files_external/lib/streamwrapper.php | 26 ++++++++++++++++++++ apps/files_external/lib/swift.php | 29 +++++++++++++++++++++-- apps/files_external/lib/webdav.php | 23 ++++++++++++++++++ apps/files_external/tests/config.php | 2 +- 6 files changed, 81 insertions(+), 8 deletions(-) diff --git a/apps/files_external/lib/ftp.php b/apps/files_external/lib/ftp.php index dea44728f3..e76eca0be3 100644 --- a/apps/files_external/lib/ftp.php +++ b/apps/files_external/lib/ftp.php @@ -50,6 +50,7 @@ class FTP extends \OC\Files\Storage\StreamWrapper{ return $url; } public function fopen($path,$mode) { + $this->init(); switch($mode) { case 'r': case 'rb': @@ -86,6 +87,7 @@ class FTP extends \OC\Files\Storage\StreamWrapper{ } public function writeBack($tmpFile) { + $this->init(); if(isset(self::$tempFiles[$tmpFile])) { $this->uploadFile($tmpFile,self::$tempFiles[$tmpFile]); unlink($tmpFile); diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index dfba3105ec..4382f63003 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -35,11 +35,6 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ if(substr($this->share,-1,1)=='/') { $this->share=substr($this->share,0,-1); } - - //create the root folder if necesary - if(!$this->is_dir('')) { - $this->mkdir(''); - } } public function getId(){ @@ -54,6 +49,7 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ } public function stat($path) { + $this->init(); if(!$path and $this->root=='/') {//mtime doesn't work for shares $mtime=$this->shareMTime(); $stat=stat($this->constructUrl($path)); @@ -75,6 +71,7 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ * @return bool */ public function hasUpdated($path,$time) { + $this->init(); if(!$path and $this->root=='/') { //mtime doesn't work for shares, but giving the nature of the backend, doing a full update is still just fast enough return true; diff --git a/apps/files_external/lib/streamwrapper.php b/apps/files_external/lib/streamwrapper.php index 750bdbaf4d..bc1c95c5e8 100644 --- a/apps/files_external/lib/streamwrapper.php +++ b/apps/files_external/lib/streamwrapper.php @@ -9,13 +9,29 @@ namespace OC\Files\Storage; abstract class StreamWrapper extends \OC\Files\Storage\Common{ + private $ready = false; + + protected function init(){ + if($this->ready){ + return; + } + $this->ready = true; + + //create the root folder if necesary + if(!$this->is_dir('')) { + $this->mkdir(''); + } + } + abstract public function constructUrl($path); public function mkdir($path) { + $this->init(); return mkdir($this->constructUrl($path)); } public function rmdir($path) { + $this->init(); if($this->file_exists($path)) { $succes=rmdir($this->constructUrl($path)); clearstatcache(); @@ -26,10 +42,12 @@ abstract class StreamWrapper extends \OC\Files\Storage\Common{ } public function opendir($path) { + $this->init(); return opendir($this->constructUrl($path)); } public function filetype($path) { + $this->init(); return filetype($this->constructUrl($path)); } @@ -42,16 +60,19 @@ abstract class StreamWrapper extends \OC\Files\Storage\Common{ } public function file_exists($path) { + $this->init(); return file_exists($this->constructUrl($path)); } public function unlink($path) { + $this->init(); $succes=unlink($this->constructUrl($path)); clearstatcache(); return $succes; } public function fopen($path,$mode) { + $this->init(); return fopen($this->constructUrl($path),$mode); } @@ -60,6 +81,7 @@ abstract class StreamWrapper extends \OC\Files\Storage\Common{ } public function touch($path,$mtime=null) { + $this->init(); if(is_null($mtime)) { $fh=$this->fopen($path,'a'); fwrite($fh,''); @@ -70,18 +92,22 @@ abstract class StreamWrapper extends \OC\Files\Storage\Common{ } public function getFile($path,$target) { + $this->init(); return copy($this->constructUrl($path),$target); } public function uploadFile($path,$target) { + $this->init(); return copy($path,$this->constructUrl($target)); } public function rename($path1,$path2) { + $this->init(); return rename($this->constructUrl($path1),$this->constructUrl($path2)); } public function stat($path) { + $this->init(); return stat($this->constructUrl($path)); } diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index c3578b0c23..55c2c3e0ac 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -17,6 +17,7 @@ class SWIFT extends \OC\Files\Storage\Common{ private $user; private $token; private $secure; + private $ready = false; /** * @var \CF_Authentication auth */ @@ -188,7 +189,7 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * add an emulated sub container - * @param CF_Container $container + * @param \CF_Container $container * @param string $name * @return bool */ @@ -222,7 +223,7 @@ class SWIFT extends \OC\Files\Storage\Common{ /** * remove an emulated sub container - * @param CF_Container $container + * @param \CF_Container $container * @param string $name * @return bool */ @@ -279,6 +280,15 @@ class SWIFT extends \OC\Files\Storage\Common{ if(!$this->root || $this->root[0]!='/') { $this->root='/'.$this->root; } + + } + + private function init(){ + if($this->ready){ + return; + } + $this->ready = true; + $this->auth = new \CF_Authentication($this->user, $this->token, null, $this->host); $this->auth->authenticate(); @@ -297,6 +307,7 @@ class SWIFT extends \OC\Files\Storage\Common{ public function mkdir($path) { + $this->init(); if($this->containerExists($path)) { return false; }else{ @@ -306,6 +317,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function rmdir($path) { + $this->init(); if(!$this->containerExists($path)) { return false; }else{ @@ -343,6 +355,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function opendir($path) { + $this->init(); $container=$this->getContainer($path); $files=$this->getObjects($container); $i=array_search(self::SUBCONTAINER_FILE,$files); @@ -357,6 +370,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function filetype($path) { + $this->init(); if($this->containerExists($path)) { return 'dir'; }else{ @@ -373,6 +387,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function file_exists($path) { + $this->init(); if($this->is_dir($path)) { return true; }else{ @@ -381,6 +396,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function file_get_contents($path) { + $this->init(); $obj=$this->getObject($path); if(is_null($obj)) { return false; @@ -389,6 +405,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function file_put_contents($path,$content) { + $this->init(); $obj=$this->getObject($path); if(is_null($obj)) { $container=$this->getContainer(dirname($path)); @@ -402,6 +419,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function unlink($path) { + $this->init(); if($this->containerExists($path)) { return $this->rmdir($path); } @@ -415,6 +433,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function fopen($path,$mode) { + $this->init(); switch($mode) { case 'r': case 'rb': @@ -458,6 +477,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function touch($path,$mtime=null) { + $this->init(); $obj=$this->getObject($path); if(is_null($obj)) { return false; @@ -472,6 +492,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function rename($path1,$path2) { + $this->init(); $sourceContainer=$this->getContainer(dirname($path1)); $targetContainer=$this->getContainer(dirname($path2)); $result=$sourceContainer->move_object_to(basename($path1),$targetContainer,basename($path2)); @@ -484,6 +505,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function copy($path1,$path2) { + $this->init(); $sourceContainer=$this->getContainer(dirname($path1)); $targetContainer=$this->getContainer(dirname($path2)); $result=$sourceContainer->copy_object_to(basename($path1),$targetContainer,basename($path2)); @@ -495,6 +517,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } public function stat($path) { + $this->init(); $container=$this->getContainer($path); if (!is_null($container)) { return array( @@ -521,6 +544,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } private function getTmpFile($path) { + $this->init(); $obj=$this->getObject($path); if(!is_null($obj)) { $tmpFile=\OCP\Files::tmpFile(); @@ -532,6 +556,7 @@ class SWIFT extends \OC\Files\Storage\Common{ } private function fromTmpFile($tmpFile,$path) { + $this->init(); $obj=$this->getObject($path); if(is_null($obj)) { $obj=$this->createObject($path); diff --git a/apps/files_external/lib/webdav.php b/apps/files_external/lib/webdav.php index c9785f3eb1..470a80f8cf 100644 --- a/apps/files_external/lib/webdav.php +++ b/apps/files_external/lib/webdav.php @@ -14,6 +14,7 @@ class DAV extends \OC\Files\Storage\Common{ private $host; private $secure; private $root; + private $ready; /** * @var \Sabre_DAV_Client */ @@ -37,6 +38,13 @@ class DAV extends \OC\Files\Storage\Common{ if(substr($this->root,-1,1)!='/') { $this->root.='/'; } + } + + private function init(){ + if($this->ready){ + return; + } + $this->ready = true; $settings = array( 'baseUri' => $this->createBaseUri(), @@ -70,16 +78,19 @@ class DAV extends \OC\Files\Storage\Common{ } public function mkdir($path) { + $this->init(); $path=$this->cleanPath($path); return $this->simpleResponse('MKCOL',$path,null,201); } public function rmdir($path) { + $this->init(); $path=$this->cleanPath($path); return $this->simpleResponse('DELETE',$path,null,204); } public function opendir($path) { + $this->init(); $path=$this->cleanPath($path); try{ $response=$this->client->propfind($path, array(),1); @@ -98,6 +109,7 @@ class DAV extends \OC\Files\Storage\Common{ } public function filetype($path) { + $this->init(); $path=$this->cleanPath($path); try{ $response=$this->client->propfind($path, array('{DAV:}resourcetype')); @@ -119,6 +131,7 @@ class DAV extends \OC\Files\Storage\Common{ } public function file_exists($path) { + $this->init(); $path=$this->cleanPath($path); try{ $this->client->propfind($path, array('{DAV:}resourcetype')); @@ -129,10 +142,12 @@ class DAV extends \OC\Files\Storage\Common{ } public function unlink($path) { + $this->init(); return $this->simpleResponse('DELETE',$path,null,204); } public function fopen($path,$mode) { + $this->init(); $path=$this->cleanPath($path); switch($mode) { case 'r': @@ -187,6 +202,7 @@ class DAV extends \OC\Files\Storage\Common{ } public function free_space($path) { + $this->init(); $path=$this->cleanPath($path); try{ $response=$this->client->propfind($path, array('{DAV:}quota-available-bytes')); @@ -201,6 +217,7 @@ class DAV extends \OC\Files\Storage\Common{ } public function touch($path,$mtime=null) { + $this->init(); if(is_null($mtime)) { $mtime=time(); } @@ -209,11 +226,13 @@ class DAV extends \OC\Files\Storage\Common{ } public function getFile($path,$target) { + $this->init(); $source=$this->fopen($path,'r'); file_put_contents($target,$source); } public function uploadFile($path,$target) { + $this->init(); $source=fopen($path,'r'); $curl = curl_init(); @@ -228,6 +247,7 @@ class DAV extends \OC\Files\Storage\Common{ } public function rename($path1,$path2) { + $this->init(); $path1=$this->cleanPath($path1); $path2=$this->root.$this->cleanPath($path2); try{ @@ -239,6 +259,7 @@ class DAV extends \OC\Files\Storage\Common{ } public function copy($path1,$path2) { + $this->init(); $path1=$this->cleanPath($path1); $path2=$this->root.$this->cleanPath($path2); try{ @@ -250,6 +271,7 @@ class DAV extends \OC\Files\Storage\Common{ } public function stat($path) { + $this->init(); $path=$this->cleanPath($path); try{ $response=$this->client->propfind($path, array('{DAV:}getlastmodified','{DAV:}getcontentlength')); @@ -263,6 +285,7 @@ class DAV extends \OC\Files\Storage\Common{ } public function getMimeType($path) { + $this->init(); $path=$this->cleanPath($path); try{ $response=$this->client->propfind($path, array('{DAV:}getcontenttype','{DAV:}resourcetype')); diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php index 5af317675c..65127175ad 100644 --- a/apps/files_external/tests/config.php +++ b/apps/files_external/tests/config.php @@ -30,7 +30,7 @@ return array( 'root'=>'/', ), 'smb'=>array( - 'run'=>false, + 'run'=>true, 'user'=>'test', 'password'=>'test', 'host'=>'localhost', From 707bd68bb4e77b4184b578699d508750653e2d42 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 21 Oct 2012 22:05:29 +0200 Subject: [PATCH 045/418] automatically scan files when needed --- lib/files.php | 463 +++++++++++++++++++++++--------------------- tests/lib/files.php | 27 ++- 2 files changed, 265 insertions(+), 225 deletions(-) diff --git a/lib/files.php b/lib/files.php index 8d402c0001..a5eec1f2a6 100644 --- a/lib/files.php +++ b/lib/files.php @@ -1,35 +1,36 @@ . -* -*/ + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek frank@owncloud.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . + * + */ /** * Class for fileserver access * */ class OC_Files { - static $tmpFiles=array(); + static $tmpFiles = array(); /** * get the filesystem info + * * @param string $path * @return array * @@ -41,19 +42,25 @@ class OC_Files { * - versioned */ public static function getFileInfo($path) { - $path=\OC\Files\Filesystem::normalizePath($path); + $path = \OC\Files\Filesystem::normalizePath($path); /** * @var \OC\Files\Storage\Storage $storage * @var string $path */ - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path); - $cache=$storage->getCache(); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + $cache = $storage->getCache(); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); + } + $data = $cache->get($internalPath); - if($data['mimetype'] === 'httpd/unix-directory'){ + if ($data['mimetype'] === 'httpd/unix-directory') { //add the sizes of other mountpoints to the folder $mountPoints = \OC\Files\Filesystem::getMountPoints($path); - foreach($mountPoints as $mountPoint){ + foreach ($mountPoints as $mountPoint) { $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); $subCache = $subStorage->getCache(); $rootEntry = $subCache->get(''); @@ -66,43 +73,50 @@ class OC_Files { } /** - * get the content of a directory - * @param string $directory path under datadirectory - * @return array - */ + * get the content of a directory + * + * @param string $directory path under datadirectory + * @return array + */ public static function getDirectoryContent($directory, $mimetype_filter = '') { - $path=\OC\Files\Filesystem::normalizePath($directory); + $path = \OC\Files\Filesystem::normalizePath($directory); /** * @var \OC\Files\Storage\Storage $storage * @var string $path */ list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); - $cache=$storage->getCache(); - $files=$cache->getFolderContents($internalPath); //TODO: mimetype_filter + $cache = $storage->getCache(); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); + } + + $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders $mountPoints = \OC\Files\Filesystem::getMountPoints($directory); $dirLength = strlen($path); - foreach($mountPoints as $mountPoint){ + foreach ($mountPoints as $mountPoint) { $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); $subCache = $subStorage->getCache(); $rootEntry = $subCache->get(''); $relativePath = trim(substr($mountPoint, $dirLength), '/'); - if($pos = strpos($relativePath, '/')){ //mountpoint inside subfolder add size to the correct folder + if ($pos = strpos($relativePath, '/')) { //mountpoint inside subfolder add size to the correct folder $entryName = substr($relativePath, 0, $pos); - foreach($files as &$entry){ - if($entry['name'] === $entryName){ + foreach ($files as &$entry) { + if ($entry['name'] === $entryName) { $entry['size'] += $rootEntry['size']; } } - }else{ //mountpoint in this folder, add an entry for it + } else { //mountpoint in this folder, add an entry for it $rootEntry['name'] = $relativePath; $files[] = $rootEntry; } } - usort($files, "fileCmp");//TODO: remove this once ajax is merged + usort($files, "fileCmp"); //TODO: remove this once ajax is merged return $files; } @@ -113,12 +127,11 @@ class OC_Files { // get next subdir to check $dir = array_pop($dirs_to_check); $dir_content = self::getDirectoryContent($dir, $mimetype_filter); - foreach($dir_content as $file) { + foreach ($dir_content as $file) { if ($file['type'] == 'file') { - $files[] = $dir.'/'.$file['name']; - } - else { - $dirs_to_check[] = $dir.'/'.$file['name']; + $files[] = $dir . '/' . $file['name']; + } else { + $dirs_to_check[] = $dir . '/' . $file['name']; } } } @@ -126,170 +139,171 @@ class OC_Files { } /** - * return the content of a file or return a zip file containning multiply files - * - * @param dir $dir - * @param file $file ; seperated list of files to download - * @param boolean $only_header ; boolean to only send header of the request - */ - public static function get($dir,$files, $only_header = false) { - if(strpos($files,';')) { - $files=explode(';',$files); + * return the content of a file or return a zip file containning multiply files + * + * @param dir $dir + * @param file $file ; seperated list of files to download + * @param boolean $only_header ; boolean to only send header of the request + */ + public static function get($dir, $files, $only_header = false) { + if (strpos($files, ';')) { + $files = explode(';', $files); } - if(is_array($files)) { - self::validateZipDownload($dir,$files); + if (is_array($files)) { + self::validateZipDownload($dir, $files); $executionTime = intval(ini_get('max_execution_time')); set_time_limit(0); $zip = new ZipArchive(); $filename = OC_Helper::tmpFile('.zip'); - if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==true) { + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) !== true) { exit("cannot open <$filename>\n"); } - foreach($files as $file) { - $file=$dir.'/'.$file; - if(\OC\Files\Filesystem::is_file($file)) { - $tmpFile=OC_F\OC\Files\Filesystemilesystem::toTmpFile($file); - self::$tmpFiles[]=$tmpFile; - $zip->addFile($tmpFile,basename($file)); - }elseif(\OC\Files\Filesystem::is_dir($file)) { - self::zipAddDir($file,$zip); + foreach ($files as $file) { + $file = $dir . '/' . $file; + if (\OC\Files\Filesystem::is_file($file)) { + $tmpFile = OC_F\OC\Files\Filesystemilesystem::toTmpFile($file); + self::$tmpFiles[] = $tmpFile; + $zip->addFile($tmpFile, basename($file)); + } elseif (\OC\Files\Filesystem::is_dir($file)) { + self::zipAddDir($file, $zip); } } $zip->close(); set_time_limit($executionTime); - }elseif(\OC\Files\Filesystem::is_dir($dir.'/'.$files)) { - self::validateZipDownload($dir,$files); + } elseif (\OC\Files\Filesystem::is_dir($dir . '/' . $files)) { + self::validateZipDownload($dir, $files); $executionTime = intval(ini_get('max_execution_time')); set_time_limit(0); $zip = new ZipArchive(); $filename = OC_Helper::tmpFile('.zip'); - if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==true) { + if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) !== true) { exit("cannot open <$filename>\n"); } - $file=$dir.'/'.$files; - self::zipAddDir($file,$zip); + $file = $dir . '/' . $files; + self::zipAddDir($file, $zip); $zip->close(); set_time_limit($executionTime); - }else{ - $zip=false; - $filename=$dir.'/'.$files; + } else { + $zip = false; + $filename = $dir . '/' . $files; } @ob_end_clean(); - if($zip or \OC\Files\Filesystem::isReadable($filename)) { - header('Content-Disposition: attachment; filename="'.basename($filename).'"'); + if ($zip or \OC\Files\Filesystem::isReadable($filename)) { + header('Content-Disposition: attachment; filename="' . basename($filename) . '"'); header('Content-Transfer-Encoding: binary'); OC_Response::disableCaching(); - if($zip) { + if ($zip) { ini_set('zlib.output_compression', 'off'); header('Content-Type: application/zip'); header('Content-Length: ' . filesize($filename)); - }else{ - header('Content-Type: '.\OC\Files\Filesystem::getMimeType($filename)); + } else { + header('Content-Type: ' . \OC\Files\Filesystem::getMimeType($filename)); } - }elseif($zip or !\OC\Files\Filesystem::file_exists($filename)) { + } elseif ($zip or !\OC\Files\Filesystem::file_exists($filename)) { header("HTTP/1.0 404 Not Found"); - $tmpl = new OC_Template( '', '404', 'guest' ); - $tmpl->assign('file',$filename); + $tmpl = new OC_Template('', '404', 'guest'); + $tmpl->assign('file', $filename); $tmpl->printPage(); - }else{ + } else { header("HTTP/1.0 403 Forbidden"); die('403 Forbidden'); } - if($only_header) { - if(!$zip) - header("Content-Length: ".\OC\Files\Filesystem::filesize($filename)); - return ; + if ($only_header) { + if (!$zip) + header("Content-Length: " . \OC\Files\Filesystem::filesize($filename)); + return; } - if($zip) { - $handle=fopen($filename,'r'); + if ($zip) { + $handle = fopen($filename, 'r'); if ($handle) { - $chunkSize = 8*1024;// 1 MB chunks + $chunkSize = 8 * 1024; // 1 MB chunks while (!feof($handle)) { echo fread($handle, $chunkSize); flush(); } } unlink($filename); - }else{ + } else { \OC\Files\Filesystem::readfile($filename); } - foreach(self::$tmpFiles as $tmpFile) { - if(file_exists($tmpFile) and is_file($tmpFile)) { + foreach (self::$tmpFiles as $tmpFile) { + if (file_exists($tmpFile) and is_file($tmpFile)) { unlink($tmpFile); } } } - public static function zipAddDir($dir,$zip,$internalDir='') { - $dirname=basename($dir); - $zip->addEmptyDir($internalDir.$dirname); - $internalDir.=$dirname.='/'; - $files=OC_Files::getdirectorycontent($dir); - foreach($files as $file) { - $filename=$file['name']; - $file=$dir.'/'.$filename; - if(\OC\Files\Filesystem::is_file($file)) { - $tmpFile=\OC\Files\Filesystem::toTmpFile($file); - OC_Files::$tmpFiles[]=$tmpFile; - $zip->addFile($tmpFile,$internalDir.$filename); - }elseif(\OC\Files\Filesystem::is_dir($file)) { - self::zipAddDir($file,$zip,$internalDir); + public static function zipAddDir($dir, $zip, $internalDir = '') { + $dirname = basename($dir); + $zip->addEmptyDir($internalDir . $dirname); + $internalDir .= $dirname .= '/'; + $files = OC_Files::getdirectorycontent($dir); + foreach ($files as $file) { + $filename = $file['name']; + $file = $dir . '/' . $filename; + if (\OC\Files\Filesystem::is_file($file)) { + $tmpFile = \OC\Files\Filesystem::toTmpFile($file); + OC_Files::$tmpFiles[] = $tmpFile; + $zip->addFile($tmpFile, $internalDir . $filename); + } elseif (\OC\Files\Filesystem::is_dir($file)) { + self::zipAddDir($file, $zip, $internalDir); } } } + /** - * move a file or folder - * - * @param dir $sourceDir - * @param file $source - * @param dir $targetDir - * @param file $target - */ - public static function move($sourceDir,$source,$targetDir,$target) { - if(OC_User::isLoggedIn() && ($sourceDir != '' || $source != 'Shared')) { - $targetFile=self::normalizePath($targetDir.'/'.$target); - $sourceFile=self::normalizePath($sourceDir.'/'.$source); - return \OC\Files\Filesystem::rename($sourceFile,$targetFile); + * move a file or folder + * + * @param dir $sourceDir + * @param file $source + * @param dir $targetDir + * @param file $target + */ + public static function move($sourceDir, $source, $targetDir, $target) { + if (OC_User::isLoggedIn() && ($sourceDir != '' || $source != 'Shared')) { + $targetFile = self::normalizePath($targetDir . '/' . $target); + $sourceFile = self::normalizePath($sourceDir . '/' . $source); + return \OC\Files\Filesystem::rename($sourceFile, $targetFile); } else { return false; } } /** - * copy a file or folder - * - * @param dir $sourceDir - * @param file $source - * @param dir $targetDir - * @param file $target - */ - public static function copy($sourceDir,$source,$targetDir,$target) { - if(OC_User::isLoggedIn()) { - $targetFile=$targetDir.'/'.$target; - $sourceFile=$sourceDir.'/'.$source; - return \OC\Files\Filesystem::copy($sourceFile,$targetFile); + * copy a file or folder + * + * @param dir $sourceDir + * @param file $source + * @param dir $targetDir + * @param file $target + */ + public static function copy($sourceDir, $source, $targetDir, $target) { + if (OC_User::isLoggedIn()) { + $targetFile = $targetDir . '/' . $target; + $sourceFile = $sourceDir . '/' . $source; + return \OC\Files\Filesystem::copy($sourceFile, $targetFile); } } /** - * create a new file or folder - * - * @param dir $dir - * @param file $name - * @param type $type - */ - public static function newFile($dir,$name,$type) { - if(OC_User::isLoggedIn()) { - $file=$dir.'/'.$name; - if($type=='dir') { + * create a new file or folder + * + * @param dir $dir + * @param file $name + * @param type $type + */ + public static function newFile($dir, $name, $type) { + if (OC_User::isLoggedIn()) { + $file = $dir . '/' . $name; + if ($type == 'dir') { return \OC\Files\Filesystem::mkdir($file); - }elseif($type=='file') { - $fileHandle=\OC\Files\Filesystem::fopen($file, 'w'); - if($fileHandle) { + } elseif ($type == 'file') { + $fileHandle = \OC\Files\Filesystem::fopen($file, 'w'); + if ($fileHandle) { fclose($fileHandle); return true; - }else{ + } else { return false; } } @@ -297,29 +311,29 @@ class OC_Files { } /** - * deletes a file or folder - * - * @param dir $dir - * @param file $name - */ - public static function delete($dir,$file) { - if(OC_User::isLoggedIn() && ($dir!= '' || $file != 'Shared')) { - $file=$dir.'/'.$file; + * deletes a file or folder + * + * @param dir $dir + * @param file $name + */ + public static function delete($dir, $file) { + if (OC_User::isLoggedIn() && ($dir != '' || $file != 'Shared')) { + $file = $dir . '/' . $file; return \OC\Files\Filesystem::unlink($file); } } /** - * checks if the selected files are within the size constraint. If not, outputs an error page. - * - * @param dir $dir - * @param files $files - */ + * checks if the selected files are within the size constraint. If not, outputs an error page. + * + * @param dir $dir + * @param files $files + */ static function validateZipDownload($dir, $files) { - if(!OC_Config::getValue('allowZipDownload', true)) { + if (!OC_Config::getValue('allowZipDownload', true)) { $l = OC_L10N::get('lib'); header("HTTP/1.0 409 Conflict"); - $tmpl = new OC_Template( '', 'error', 'user' ); + $tmpl = new OC_Template('', 'error', 'user'); $errors = array( array( 'error' => $l->t('ZIP download is turned off.'), @@ -332,19 +346,19 @@ class OC_Files { } $zipLimit = OC_Config::getValue('maxZipInputSize', OC_Helper::computerFileSize('800 MB')); - if($zipLimit > 0) { + if ($zipLimit > 0) { $totalsize = 0; - if(is_array($files)) { - foreach($files as $file) { - $totalsize += \OC\Files\Filesystem::filesize($dir.'/'.$file); + if (is_array($files)) { + foreach ($files as $file) { + $totalsize += \OC\Files\Filesystem::filesize($dir . '/' . $file); } - }else{ - $totalsize += \OC\Files\Filesystem::filesize($dir.'/'.$files); + } else { + $totalsize += \OC\Files\Filesystem::filesize($dir . '/' . $files); } - if($totalsize > $zipLimit) { + if ($totalsize > $zipLimit) { $l = OC_L10N::get('lib'); header("HTTP/1.0 409 Conflict"); - $tmpl = new OC_Template( '', 'error', 'user' ); + $tmpl = new OC_Template('', 'error', 'user'); $errors = array( array( 'error' => $l->t('Selected files too large to generate zip file.'), @@ -359,78 +373,80 @@ class OC_Files { } /** - * try to detect the mime type of a file - * - * @param string path - * @return string guessed mime type - */ + * try to detect the mime type of a file + * + * @param string path + * @return string guessed mime type + */ static function getMimeType($path) { return \OC\Files\Filesystem::getMimeType($path); } /** - * get a file tree - * - * @param string path - * @return array - */ + * get a file tree + * + * @param string path + * @return array + */ static function getTree($path) { return \OC\Files\Filesystem::getTree($path); } /** - * pull a file from a remote server - * @param string source - * @param string token - * @param string dir - * @param string file - * @return string guessed mime type - */ - static function pull($source,$token,$dir,$file) { - $tmpfile=tempnam(get_temp_dir(),'remoteCloudFile'); - $fp=fopen($tmpfile,'w+'); - $url=$source.="/files/pull.php?token=$token"; - $ch=curl_init(); - curl_setopt($ch,CURLOPT_URL,$url); + * pull a file from a remote server + * + * @param string source + * @param string token + * @param string dir + * @param string file + * @return string guessed mime type + */ + static function pull($source, $token, $dir, $file) { + $tmpfile = tempnam(get_temp_dir(), 'remoteCloudFile'); + $fp = fopen($tmpfile, 'w+'); + $url = $source .= "/files/pull.php?token=$token"; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FILE, $fp); curl_exec($ch); fclose($fp); - $info=curl_getinfo($ch); - $httpCode=$info['http_code']; + $info = curl_getinfo($ch); + $httpCode = $info['http_code']; curl_close($ch); - if($httpCode==200 or $httpCode==0) { - \OC\Files\Filesystem::fromTmpFile($tmpfile,$dir.'/'.$file); + if ($httpCode == 200 or $httpCode == 0) { + \OC\Files\Filesystem::fromTmpFile($tmpfile, $dir . '/' . $file); return true; - }else{ + } else { return false; } } /** * set the maximum upload size limit for apache hosts using .htaccess + * * @param int size filesisze in bytes * @return false on failure, size on success */ static function setUploadLimit($size) { //don't allow user to break his config -- upper boundary - if($size > PHP_INT_MAX) { + if ($size > PHP_INT_MAX) { //max size is always 1 byte lower than computerFileSize returns - if($size > PHP_INT_MAX+1) + if ($size > PHP_INT_MAX + 1) return false; - $size -=1; + $size -= 1; } else { - $size=OC_Helper::humanFileSize($size); - $size=substr($size,0,-1);//strip the B - $size=str_replace(' ','',$size); //remove the space between the size and the postfix + $size = OC_Helper::humanFileSize($size); + $size = substr($size, 0, -1); //strip the B + $size = str_replace(' ', '', $size); //remove the space between the size and the postfix } //don't allow user to break his config -- broken or malicious size input - if(intval($size) == 0) { + if (intval($size) == 0) { return false; } - $htaccess = @file_get_contents(OC::$SERVERROOT.'/.htaccess'); //supress errors in case we don't have permissions for - if(!$htaccess) { + $htaccess = @file_get_contents(OC::$SERVERROOT . '/.htaccess'); //supress errors in case we don't have permissions for + if (!$htaccess) { return false; } @@ -439,50 +455,53 @@ class OC_Files { 'post_max_size' ); - foreach($phpValueKeys as $key) { - $pattern = '/php_value '.$key.' (\S)*/'; - $setting = 'php_value '.$key.' '.$size; - $hasReplaced = 0; - $content = preg_replace($pattern, $setting, $htaccess, 1, $hasReplaced); - if($content !== null) { + foreach ($phpValueKeys as $key) { + $pattern = '/php_value ' . $key . ' (\S)*/'; + $setting = 'php_value ' . $key . ' ' . $size; + $hasReplaced = 0; + $content = preg_replace($pattern, $setting, $htaccess, 1, $hasReplaced); + if ($content !== null) { $htaccess = $content; } - if($hasReplaced == 0) { + if ($hasReplaced == 0) { $htaccess .= "\n" . $setting; } } //check for write permissions - if(is_writable(OC::$SERVERROOT.'/.htaccess')) { - file_put_contents(OC::$SERVERROOT.'/.htaccess', $htaccess); + if (is_writable(OC::$SERVERROOT . '/.htaccess')) { + file_put_contents(OC::$SERVERROOT . '/.htaccess', $htaccess); return OC_Helper::computerFileSize($size); - } else { OC_Log::write('files','Can\'t write upload limit to '.OC::$SERVERROOT.'/.htaccess. Please check the file permissions',OC_Log::WARN); } + } else { + OC_Log::write('files', 'Can\'t write upload limit to ' . OC::$SERVERROOT . '/.htaccess. Please check the file permissions', OC_Log::WARN); + } return false; } /** * normalize a path, removing any double, add leading /, etc + * * @param string $path * @return string */ static public function normalizePath($path) { - $path='/'.$path; - $old=''; - while($old!=$path) {//replace any multiplicity of slashes with a single one - $old=$path; - $path=str_replace('//','/',$path); + $path = '/' . $path; + $old = ''; + while ($old != $path) { //replace any multiplicity of slashes with a single one + $old = $path; + $path = str_replace('//', '/', $path); } return $path; } } -function fileCmp($a,$b) { - if($a['type']=='dir' and $b['type']!='dir') { +function fileCmp($a, $b) { + if ($a['type'] == 'dir' and $b['type'] != 'dir') { return -1; - }elseif($a['type']!='dir' and $b['type']=='dir') { + } elseif ($a['type'] != 'dir' and $b['type'] == 'dir') { return 1; - }else{ - return strnatcasecmp($a['name'],$b['name']); + } else { + return strnatcasecmp($a['name'], $b['name']); } } diff --git a/tests/lib/files.php b/tests/lib/files.php index 9cb5276845..d978ac3fd1 100644 --- a/tests/lib/files.php +++ b/tests/lib/files.php @@ -67,10 +67,29 @@ class Test_Files extends PHPUnit_Framework_TestCase { $this->assertEquals($storageSize, $folderData[3]['size']); } + public function testAutoScan() { + $storage1 = $this->getTestStorage(false); + $storage2 = $this->getTestStorage(false); + Filesystem::mount($storage1, array(), '/'); + Filesystem::mount($storage2, array(), '/substorage'); + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $storageSize = $textSize * 2 + $imageSize; + + $cachedData = \OC_Files::getFileInfo('/'); + $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); + $this->assertEquals(-1, $cachedData['size']); + + $folderData = \OC_Files::getDirectoryContent('/substorage/folder'); + $this->assertEquals('text/plain', $folderData[0]['mimetype']); + $this->assertEquals($textSize, $folderData[0]['size']); + } + /** + * @param bool $scan * @return OC\Files\Storage\Storage */ - private function getTestStorage() { + private function getTestStorage($scan = true) { $storage = new \OC\Files\Storage\Temporary(array()); $textData = "dummy file data\n"; $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); @@ -79,8 +98,10 @@ class Test_Files extends PHPUnit_Framework_TestCase { $storage->file_put_contents('foo.png', $imgData); $storage->file_put_contents('folder/bar.txt', $textData); - $scanner = $storage->getScanner(); - $scanner->scan(''); + if ($scan) { + $scanner = $storage->getScanner(); + $scanner->scan(''); + } $this->storages[] = $storage; return $storage; } From 56c7ee799cb13f7a4db60663f60b76cdf223614b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 23 Oct 2012 16:34:58 +0200 Subject: [PATCH 046/418] use strtotime on non-nummeric times in oc_filesystem::touch --- lib/files/view.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/files/view.php b/lib/files/view.php index a59ad8105d..18d9193035 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -110,7 +110,7 @@ class View { */ public function getLocalFile($path) { $parent = substr($path, 0, strrpos($path, '/')); - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); if (Filesystem::isValidPath($parent) and $storage) { return $storage->getLocalFile($internalPath); } else { @@ -124,7 +124,7 @@ class View { */ public function getLocalFolder($path) { $parent = substr($path, 0, strrpos($path, '/')); - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); if (Filesystem::isValidPath($parent) and $storage) { return $storage->getLocalFolder($internalPath); } else { @@ -227,6 +227,9 @@ class View { } public function touch($path, $mtime = null) { + if (!is_null($mtime) and !is_numeric($mtime)) { + $mtime = strtotime($mtime); + } return $this->basicOperation('touch', $path, array('write'), $mtime); } @@ -331,8 +334,8 @@ class View { $mp1 = $this->getMountPoint($path1 . $postFix1); $mp2 = $this->getMountPoint($path2 . $postFix2); if ($mp1 == $mp2) { - list($storage, $internalPath1)=\OC\Files\Filesystem::resolvePath($path1 . $postFix1); - list( , $internalPath2)=\OC\Files\Filesystem::resolvePath($path2 . $postFix2); + list($storage, $internalPath1) = \OC\Files\Filesystem::resolvePath($path1 . $postFix1); + list(, $internalPath2) = \OC\Files\Filesystem::resolvePath($path2 . $postFix2); if ($storage) { $result = $storage->rename($internalPath1, $internalPath2); } else { @@ -342,7 +345,7 @@ class View { $source = $this->fopen($path1 . $postFix1, 'r'); $target = $this->fopen($path2 . $postFix2, 'w'); $count = \OC_Helper::streamCopy($source, $target); - list($storage1, $internalPath1)=\OC\Files\Filesystem::resolvePath($path1 . $postFix1); + list($storage1, $internalPath1) = \OC\Files\Filesystem::resolvePath($path1 . $postFix1); $storage1->unlink($internalPath1); $result = $count > 0; } @@ -414,8 +417,8 @@ class View { $mp1 = $this->getMountPoint($path1 . $postFix1); $mp2 = $this->getMountPoint($path2 . $postFix2); if ($mp1 == $mp2) { - list($storage, $internalPath1)=\OC\Files\Filesystem::resolvePath($path1 . $postFix1); - list( , $internalPath2)=\OC\Files\Filesystem::resolvePath($path2 . $postFix2); + list($storage, $internalPath1) = \OC\Files\Filesystem::resolvePath($path1 . $postFix1); + list(, $internalPath2) = \OC\Files\Filesystem::resolvePath($path2 . $postFix2); if ($storage) { $result = $storage->copy($internalPath1, $internalPath2); } else { @@ -550,7 +553,7 @@ class View { array(Filesystem::signal_param_path => $path) ); } - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path . $postFix); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path . $postFix); if ($storage) { $result = $storage->hash($type, $internalPath, $raw); $result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result); @@ -585,7 +588,7 @@ class View { return false; } $run = $this->runHooks($hooks, $path); - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($path . $postFix); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path . $postFix); if ($run and $storage) { if (!is_null($extraParam)) { $result = $storage->$operation($internalPath, $extraParam); From 5a3d6805a2613c4f55daa971e112cc77f17b060f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 24 Oct 2012 15:52:30 +0200 Subject: [PATCH 047/418] cleanup OC_Files a bit --- apps/files/ajax/delete.php | 23 +++--- apps/files/ajax/move.php | 13 +++- apps/files/ajax/newfile.php | 2 +- apps/files/ajax/newfolder.php | 2 +- apps/files/ajax/rename.php | 14 ++-- lib/files.php | 136 ---------------------------------- 6 files changed, 34 insertions(+), 156 deletions(-) diff --git a/apps/files/ajax/delete.php b/apps/files/ajax/delete.php index 57c8c15c19..ae6158a05a 100644 --- a/apps/files/ajax/delete.php +++ b/apps/files/ajax/delete.php @@ -12,17 +12,22 @@ $files = isset($_POST["file"]) ? stripslashes($_POST["file"]) : stripslashes($_P $files = explode(';', $files); $filesWithError = ''; -$success = true; -//Now delete -foreach($files as $file) { - if( !OC_Files::delete( $dir, $file )) { - $filesWithError .= $file . "\n"; - $success = false; +if (OC_User::isLoggedIn()) { + $success = true; + + //Now delete + foreach ($files as $file) { + if ($dir != '' || $file != 'Shared' && !\OC\Files\Filesystem::unlink($dir . '/' . $file)) { + $filesWithError .= $file . "\n"; + $success = false; + } } +} else { + $success = false; } -if($success) { - OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $files ))); +if ($success) { + OCP\JSON::success(array("data" => array("dir" => $dir, "files" => $files))); } else { - OCP\JSON::error(array("data" => array( "message" => "Could not delete:\n" . $filesWithError ))); + OCP\JSON::error(array("data" => array("message" => "Could not delete:\n" . $filesWithError))); } diff --git a/apps/files/ajax/move.php b/apps/files/ajax/move.php index 8b3149ef14..f872783122 100644 --- a/apps/files/ajax/move.php +++ b/apps/files/ajax/move.php @@ -11,9 +11,14 @@ $dir = stripslashes($_GET["dir"]); $file = stripslashes($_GET["file"]); $target = stripslashes($_GET["target"]); - -if(OC_Files::move($dir, $file, $target, $file)) { - OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $file ))); -} else { +if (OC_User::isLoggedIn() && ($dir != '' || $file != 'Shared')) { + $targetFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file); + $sourceFile = \OC\Files\Filesystem::normalizePath($target . '/' . $file); + if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) { + OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $file ))); + } else { + OCP\JSON::error(array("data" => array( "message" => "Could not move $file" ))); + } +}else{ OCP\JSON::error(array("data" => array( "message" => "Could not move $file" ))); } diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index 4d73970b68..f94d984d5b 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -82,7 +82,7 @@ if($source) { OCP\JSON::success(array("data" => array('content'=>$content, 'id' => $id))); exit(); } - }elseif(OC_Files::newFile($dir, $filename, 'file')) { + }elseif(\OC\Files\Filesystem::touch($dir . '/' . $filename)) { $meta = OC_FileCache::get($dir.'/'.$filename); $id = OC_FileCache::getId($dir.'/'.$filename); OCP\JSON::success(array("data" => array('content'=>$content, 'id' => $id))); diff --git a/apps/files/ajax/newfolder.php b/apps/files/ajax/newfolder.php index 0f1f2f14eb..c36c208455 100644 --- a/apps/files/ajax/newfolder.php +++ b/apps/files/ajax/newfolder.php @@ -19,7 +19,7 @@ if(strpos($foldername, '/')!==false) { exit(); } -if(OC_Files::newFile($dir, stripslashes($foldername), 'dir')) { +if(\OC\Files\Filesystem::mkdir($dir . '/' . stripslashes($foldername))) { if ( $dir != '/') { $path = $dir.'/'.$foldername; } else { diff --git a/apps/files/ajax/rename.php b/apps/files/ajax/rename.php index 45448279fa..a2b9b8de25 100644 --- a/apps/files/ajax/rename.php +++ b/apps/files/ajax/rename.php @@ -11,10 +11,14 @@ $dir = stripslashes($_GET["dir"]); $file = stripslashes($_GET["file"]); $newname = stripslashes($_GET["newname"]); -// Delete -if( OC_Files::move( $dir, $file, $dir, $newname )) { - OCP\JSON::success(array("data" => array( "dir" => $dir, "file" => $file, "newname" => $newname ))); -} -else{ +if (OC_User::isLoggedIn() && ($dir != '' || $file != 'Shared')) { + $targetFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file); + $sourceFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $newname); + if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) { + OCP\JSON::success(array("data" => array( "dir" => $dir, "file" => $file, "newname" => $newname ))); + } else { + OCP\JSON::error(array("data" => array( "message" => "Unable to rename file" ))); + } +}else{ OCP\JSON::error(array("data" => array( "message" => "Unable to rename file" ))); } diff --git a/lib/files.php b/lib/files.php index ff9befa89e..7ab7c89201 100644 --- a/lib/files.php +++ b/lib/files.php @@ -252,77 +252,6 @@ class OC_Files { } } - /** - * move a file or folder - * - * @param dir $sourceDir - * @param file $source - * @param dir $targetDir - * @param file $target - */ - public static function move($sourceDir, $source, $targetDir, $target) { - if (OC_User::isLoggedIn() && ($sourceDir != '' || $source != 'Shared')) { - $targetFile = self::normalizePath($targetDir . '/' . $target); - $sourceFile = self::normalizePath($sourceDir . '/' . $source); - return \OC\Files\Filesystem::rename($sourceFile, $targetFile); - } else { - return false; - } - } - - /** - * copy a file or folder - * - * @param dir $sourceDir - * @param file $source - * @param dir $targetDir - * @param file $target - */ - public static function copy($sourceDir, $source, $targetDir, $target) { - if (OC_User::isLoggedIn()) { - $targetFile = $targetDir . '/' . $target; - $sourceFile = $sourceDir . '/' . $source; - return \OC\Files\Filesystem::copy($sourceFile, $targetFile); - } - } - - /** - * create a new file or folder - * - * @param dir $dir - * @param file $name - * @param type $type - */ - public static function newFile($dir, $name, $type) { - if (OC_User::isLoggedIn()) { - $file = $dir . '/' . $name; - if ($type == 'dir') { - return \OC\Files\Filesystem::mkdir($file); - } elseif ($type == 'file') { - $fileHandle = \OC\Files\Filesystem::fopen($file, 'w'); - if ($fileHandle) { - fclose($fileHandle); - return true; - } else { - return false; - } - } - } - } - - /** - * deletes a file or folder - * - * @param dir $dir - * @param file $name - */ - public static function delete($dir, $file) { - if (OC_User::isLoggedIn() && ($dir != '' || $file != 'Shared')) { - $file = $dir . '/' . $file; - return \OC\Files\Filesystem::unlink($file); - } - } - /** * checks if the selected files are within the size constraint. If not, outputs an error page. * @@ -372,55 +301,6 @@ class OC_Files { } } - /** - * try to detect the mime type of a file - * - * @param string path - * @return string guessed mime type - */ - static function getMimeType($path) { - return \OC\Files\Filesystem::getMimeType($path); - } - - /** - * get a file tree - * - * @param string path - * @return array - */ - static function getTree($path) { - return \OC\Files\Filesystem::getTree($path); - } - - /** - * pull a file from a remote server - * - * @param string source - * @param string token - * @param string dir - * @param string file - * @return string guessed mime type - */ - static function pull($source, $token, $dir, $file) { - $tmpfile = tempnam(get_temp_dir(), 'remoteCloudFile'); - $fp = fopen($tmpfile, 'w+'); - $url = $source .= "/files/pull.php?token=$token"; - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_FILE, $fp); - curl_exec($ch); - fclose($fp); - $info = curl_getinfo($ch); - $httpCode = $info['http_code']; - curl_close($ch); - if ($httpCode == 200 or $httpCode == 0) { - \OC\Files\Filesystem::fromTmpFile($tmpfile, $dir . '/' . $file); - return true; - } else { - return false; - } - } - /** * set the maximum upload size limit for apache hosts using .htaccess * @@ -478,22 +358,6 @@ class OC_Files { return false; } - - /** - * normalize a path, removing any double, add leading /, etc - * - * @param string $path - * @return string - */ - static public function normalizePath($path) { - $path = '/' . $path; - $old = ''; - while ($old != $path) { //replace any multiplicity of slashes with a single one - $old = $path; - $path = str_replace('//', '/', $path); - } - return $path; - } } function fileCmp($a, $b) { From 39adadd3e3e50dcf3bf577a22870aaec52f63052 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 12:30:25 +0200 Subject: [PATCH 048/418] move the cache api from OC_Files to filesystem(view) --- lib/files.php | 92 ------------------------- lib/files/cache/cache.php | 5 ++ lib/files/filesystem.php | 27 ++++++++ lib/files/view.php | 92 +++++++++++++++++++++++++ tests/lib/{files.php => files/view.php} | 23 ++++--- 5 files changed, 138 insertions(+), 101 deletions(-) rename tests/lib/{files.php => files/view.php} (86%) diff --git a/lib/files.php b/lib/files.php index 7ab7c89201..422e7f4ffe 100644 --- a/lib/files.php +++ b/lib/files.php @@ -28,98 +28,6 @@ class OC_Files { static $tmpFiles = array(); - /** - * get the filesystem info - * - * @param string $path - * @return array - * - * returns an associative array with the following keys: - * - size - * - mtime - * - mimetype - * - encrypted - * - versioned - */ - public static function getFileInfo($path) { - $path = \OC\Files\Filesystem::normalizePath($path); - /** - * @var \OC\Files\Storage\Storage $storage - * @var string $path - */ - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); - $cache = $storage->getCache(); - - if (!$cache->inCache($internalPath)) { - $scanner = $storage->getScanner(); - $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); - } - - $data = $cache->get($internalPath); - - if ($data['mimetype'] === 'httpd/unix-directory') { - //add the sizes of other mountpoints to the folder - $mountPoints = \OC\Files\Filesystem::getMountPoints($path); - foreach ($mountPoints as $mountPoint) { - $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); - $subCache = $subStorage->getCache(); - $rootEntry = $subCache->get(''); - - $data['size'] += $rootEntry['size']; - } - } - - return $data; - } - - /** - * get the content of a directory - * - * @param string $directory path under datadirectory - * @return array - */ - public static function getDirectoryContent($directory, $mimetype_filter = '') { - $path = \OC\Files\Filesystem::normalizePath($directory); - /** - * @var \OC\Files\Storage\Storage $storage - * @var string $path - */ - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); - $cache = $storage->getCache(); - - if (!$cache->inCache($internalPath)) { - $scanner = $storage->getScanner(); - $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); - } - - $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter - - //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders - $mountPoints = \OC\Files\Filesystem::getMountPoints($directory); - $dirLength = strlen($path); - foreach ($mountPoints as $mountPoint) { - $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); - $subCache = $subStorage->getCache(); - $rootEntry = $subCache->get(''); - - $relativePath = trim(substr($mountPoint, $dirLength), '/'); - if ($pos = strpos($relativePath, '/')) { //mountpoint inside subfolder add size to the correct folder - $entryName = substr($relativePath, 0, $pos); - foreach ($files as &$entry) { - if ($entry['name'] === $entryName) { - $entry['size'] += $rootEntry['size']; - } - } - } else { //mountpoint in this folder, add an entry for it - $rootEntry['name'] = $relativePath; - $files[] = $rootEntry; - } - } - - usort($files, "fileCmp"); //TODO: remove this once ajax is merged - return $files; - } - public static function searchByMime($mimetype_filter) { $files = array(); $dirs_to_check = array(''); diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 5ef49246ea..9b55666f95 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -8,6 +8,11 @@ namespace OC\Files\Cache; +/** + * Metadata cache for the filesystem + * + * don't use this class directly if you need to get metadata, use \OC\Files\Filesystem::getFileInfo instead + */ class Cache { const NOT_FOUND = 0; const PARTIAL = 1; //only partial data available, file not cached in the database diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index b7f8483fbf..d735cf8626 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -609,6 +609,33 @@ class Filesystem { } return $path; } + + /** + * get the filesystem info + * + * @param string $path + * @return array + * + * returns an associative array with the following keys: + * - size + * - mtime + * - mimetype + * - encrypted + * - versioned + */ + public static function getFileInfo($path) { + return self::$defaultInstance->getFileInfo($path); + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @return array + */ + public static function getDirectoryContent($directory, $mimetype_filter = '') { + return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter); + } } \OC_Hook::connect('OC_Filesystem', 'post_write', 'OC_Filesystem', 'removeETagHook'); diff --git a/lib/files/view.php b/lib/files/view.php index 18d9193035..aaca1618ac 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -645,4 +645,96 @@ class View { public function hasUpdated($path, $time) { return $this->basicOperation('hasUpdated', $path, array(), $time); } + + /** + * get the filesystem info + * + * @param string $path + * @return array + * + * returns an associative array with the following keys: + * - size + * - mtime + * - mimetype + * - encrypted + * - versioned + */ + public function getFileInfo($path) { + $path = \OC\Files\Filesystem::normalizePath($this->fakeRoot . '/' . $path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $path + */ + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + $cache = $storage->getCache(); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); + } + + $data = $cache->get($internalPath); + + if ($data['mimetype'] === 'httpd/unix-directory') { + //add the sizes of other mountpoints to the folder + $mountPoints = \OC\Files\Filesystem::getMountPoints($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); + $subCache = $subStorage->getCache(); + $rootEntry = $subCache->get(''); + + $data['size'] += $rootEntry['size']; + } + } + + return $data; + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @return array + */ + public function getDirectoryContent($directory, $mimetype_filter = '') { + $path = \OC\Files\Filesystem::normalizePath($this->fakeRoot . '/' . $directory); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $path + */ + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + $cache = $storage->getCache(); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); + } + + $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mountPoints = \OC\Files\Filesystem::getMountPoints($directory); + $dirLength = strlen($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); + $subCache = $subStorage->getCache(); + $rootEntry = $subCache->get(''); + + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry['name'] === $entryName) { + $entry['size'] += $rootEntry['size']; + } + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $files[] = $rootEntry; + } + } + + usort($files, "fileCmp"); //TODO: remove this once ajax is merged + return $files; + } } diff --git a/tests/lib/files.php b/tests/lib/files/view.php similarity index 86% rename from tests/lib/files.php rename to tests/lib/files/view.php index d978ac3fd1..6e7608f596 100644 --- a/tests/lib/files.php +++ b/tests/lib/files/view.php @@ -5,9 +5,11 @@ * later. * See the COPYING-README file. */ +namespace Test\Files; + use \OC\Files\Filesystem as Filesystem; -class Test_Files extends PHPUnit_Framework_TestCase { +class View extends \PHPUnit_Framework_TestCase { /** * @var \OC\Files\Storage\Storage[] $storages; */ @@ -35,19 +37,21 @@ class Test_Files extends PHPUnit_Framework_TestCase { $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); $storageSize = $textSize * 2 + $imageSize; - $cachedData = OC_Files::getFileInfo('/foo.txt'); + $rootView = new \OC\Files\View(''); + + $cachedData = $rootView->getFileInfo('/foo.txt'); $this->assertEquals($textSize, $cachedData['size']); $this->assertEquals('text/plain', $cachedData['mimetype']); - $cachedData = OC_Files::getFileInfo('/'); + $cachedData = $rootView->getFileInfo('/'); $this->assertEquals($storageSize * 3, $cachedData['size']); $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); - $cachedData = OC_Files::getFileInfo('/folder'); + $cachedData = $rootView->getFileInfo('/folder'); $this->assertEquals($storageSize + $textSize, $cachedData['size']); $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); - $folderData = OC_Files::getDirectoryContent('/'); + $folderData = $rootView->getDirectoryContent('/'); /** * expected entries: * folder @@ -74,20 +78,21 @@ class Test_Files extends PHPUnit_Framework_TestCase { Filesystem::mount($storage2, array(), '/substorage'); $textSize = strlen("dummy file data\n"); $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); - $storageSize = $textSize * 2 + $imageSize; - $cachedData = \OC_Files::getFileInfo('/'); + $rootView = new \OC\Files\View(''); + + $cachedData = $rootView->getFileInfo('/'); $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']); $this->assertEquals(-1, $cachedData['size']); - $folderData = \OC_Files::getDirectoryContent('/substorage/folder'); + $folderData = $rootView->getDirectoryContent('/substorage/folder'); $this->assertEquals('text/plain', $folderData[0]['mimetype']); $this->assertEquals($textSize, $folderData[0]['size']); } /** * @param bool $scan - * @return OC\Files\Storage\Storage + * @return \OC\Files\Storage\Storage */ private function getTestStorage($scan = true) { $storage = new \OC\Files\Storage\Temporary(array()); From e63e246c4894aa7f72feb550d98747890838af0b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 12:37:49 +0200 Subject: [PATCH 049/418] fix problem with normalizePath when there was a double leading slash --- lib/files/filesystem.php | 8 ++++---- tests/lib/files/view.php | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index d735cf8626..2b51131b20 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -595,14 +595,14 @@ class Filesystem { if ($path[0] !== '/') { $path = '/' . $path; } -//remove trailing slash - if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { - $path = substr($path, 0, -1); - } //remove duplicate slashes while (strpos($path, '//') !== false) { $path = str_replace('//', '/', $path); } +//remove trailing slash + if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { + $path = substr($path, 0, -1); + } //normalize unicode if possible if (class_exists('Normalizer')) { $path = \Normalizer::normalize($path); diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 6e7608f596..adbed5a18b 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -69,6 +69,9 @@ class View extends \PHPUnit_Framework_TestCase { $this->assertEquals($imageSize, $folderData[1]['size']); $this->assertEquals($textSize, $folderData[2]['size']); $this->assertEquals($storageSize, $folderData[3]['size']); + + $folderView = new \OC\Files\View('/folder'); + $this->assertEquals($rootView->getFileInfo('/folder'), $folderView->getFileInfo('/')); } public function testAutoScan() { From 7ef0ffe8ad8f95b023f95f80e7b91c2a3ee50c67 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 12:43:23 +0200 Subject: [PATCH 050/418] add View->putFileInfo to the filecache api --- lib/files/view.php | 30 ++++++++++++++++++++++++++++-- tests/lib/files/view.php | 7 +++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/files/view.php b/lib/files/view.php index aaca1618ac..094a7d92a4 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -663,7 +663,7 @@ class View { $path = \OC\Files\Filesystem::normalizePath($this->fakeRoot . '/' . $path); /** * @var \OC\Files\Storage\Storage $storage - * @var string $path + * @var string $internalPath */ list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); $cache = $storage->getCache(); @@ -700,7 +700,7 @@ class View { $path = \OC\Files\Filesystem::normalizePath($this->fakeRoot . '/' . $directory); /** * @var \OC\Files\Storage\Storage $storage - * @var string $path + * @var string $internalPath */ list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); $cache = $storage->getCache(); @@ -737,4 +737,30 @@ class View { usort($files, "fileCmp"); //TODO: remove this once ajax is merged return $files; } + + /** + * change file metadata + * + * @param string $path + * @param array $data + * @return int + * + * returns the fileid of the updated file + */ + public function putFileInfo($path, $data) { + $path = \OC\Files\Filesystem::normalizePath($this->fakeRoot . '/' . $path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + $cache = $storage->getCache(); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); + } + + return $cache->put($internalPath, $data); + } } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index adbed5a18b..051ae25162 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -72,6 +72,13 @@ class View extends \PHPUnit_Framework_TestCase { $folderView = new \OC\Files\View('/folder'); $this->assertEquals($rootView->getFileInfo('/folder'), $folderView->getFileInfo('/')); + + $cachedData = $rootView->getFileInfo('/foo.txt'); + $this->assertFalse($cachedData['encrypted']); + $id = $rootView->putFileInfo('/foo.txt', array('encrypted' => true)); + $cachedData = $rootView->getFileInfo('/foo.txt'); + $this->assertTrue($cachedData['encrypted']); + $this->assertEquals($cachedData['fileid'], $id); } public function testAutoScan() { From 8bce661e4ddedb611d5873c578203dfda631ce7e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 13:23:15 +0200 Subject: [PATCH 051/418] add search to the filecache api --- lib/files/cache/cache.php | 21 ++++++++- lib/files/filesystem.php | 2 +- lib/files/view.php | 84 ++++++++++++++++++++++++--------- tests/lib/files/cache/cache.php | 20 ++++++++ tests/lib/files/view.php | 45 +++++++++++++++++- 5 files changed, 146 insertions(+), 26 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 9b55666f95..7e0c5cb21f 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -251,7 +251,7 @@ class Cache { */ public function getStatus($file) { $pathHash = md5($file); - $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); + $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); $result = $query->execute(array($this->storageId, $pathHash)); if ($row = $result->fetchRow()) { if ((int)$row['size'] === -1) { @@ -267,4 +267,23 @@ class Cache { } } } + + /** + * search for files matching $pattern + * + * @param string $pattern + * @return array of file data + */ + public function search($pattern) { + $query = \OC_DB::prepare(' + SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` + FROM `*PREFIX*filecache` WHERE `name` LIKE ? AND `storage` = ?' + ); + $result = $query->execute(array($pattern, $this->storageId)); + $files = array(); + while ($row = $result->fetchRow()) { + $files[] = $row; + } + return $files; + } } diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 2b51131b20..4031c0c5b8 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -548,7 +548,7 @@ class Filesystem { } static public function search($query) { - return Cache\Cache::search($query); + return self::$defaultInstance->search($query); } /** diff --git a/lib/files/view.php b/lib/files/view.php index 094a7d92a4..ee95cce0c1 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -110,7 +110,7 @@ class View { */ public function getLocalFile($path) { $parent = substr($path, 0, strrpos($path, '/')); - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); if (Filesystem::isValidPath($parent) and $storage) { return $storage->getLocalFile($internalPath); } else { @@ -124,7 +124,7 @@ class View { */ public function getLocalFolder($path) { $parent = substr($path, 0, strrpos($path, '/')); - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); if (Filesystem::isValidPath($parent) and $storage) { return $storage->getLocalFolder($internalPath); } else { @@ -150,7 +150,7 @@ class View { } public function readdir($handle) { - $fsLocal = new \OC\Files\Storage\Local(array('datadir' => '/')); + $fsLocal = new Storage\Local(array('datadir' => '/')); return $fsLocal->readdir($handle); } @@ -334,8 +334,8 @@ class View { $mp1 = $this->getMountPoint($path1 . $postFix1); $mp2 = $this->getMountPoint($path2 . $postFix2); if ($mp1 == $mp2) { - list($storage, $internalPath1) = \OC\Files\Filesystem::resolvePath($path1 . $postFix1); - list(, $internalPath2) = \OC\Files\Filesystem::resolvePath($path2 . $postFix2); + list($storage, $internalPath1) = Filesystem::resolvePath($path1 . $postFix1); + list(, $internalPath2) = Filesystem::resolvePath($path2 . $postFix2); if ($storage) { $result = $storage->rename($internalPath1, $internalPath2); } else { @@ -345,7 +345,7 @@ class View { $source = $this->fopen($path1 . $postFix1, 'r'); $target = $this->fopen($path2 . $postFix2, 'w'); $count = \OC_Helper::streamCopy($source, $target); - list($storage1, $internalPath1) = \OC\Files\Filesystem::resolvePath($path1 . $postFix1); + list($storage1, $internalPath1) = Filesystem::resolvePath($path1 . $postFix1); $storage1->unlink($internalPath1); $result = $count > 0; } @@ -417,8 +417,8 @@ class View { $mp1 = $this->getMountPoint($path1 . $postFix1); $mp2 = $this->getMountPoint($path2 . $postFix2); if ($mp1 == $mp2) { - list($storage, $internalPath1) = \OC\Files\Filesystem::resolvePath($path1 . $postFix1); - list(, $internalPath2) = \OC\Files\Filesystem::resolvePath($path2 . $postFix2); + list($storage, $internalPath1) = Filesystem::resolvePath($path1 . $postFix1); + list(, $internalPath2) = Filesystem::resolvePath($path2 . $postFix2); if ($storage) { $result = $storage->copy($internalPath1, $internalPath2); } else { @@ -553,7 +553,7 @@ class View { array(Filesystem::signal_param_path => $path) ); } - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path . $postFix); + list($storage, $internalPath) = Filesystem::resolvePath($path . $postFix); if ($storage) { $result = $storage->hash($type, $internalPath, $raw); $result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result); @@ -588,7 +588,7 @@ class View { return false; } $run = $this->runHooks($hooks, $path); - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path . $postFix); + list($storage, $internalPath) = Filesystem::resolvePath($path . $postFix); if ($run and $storage) { if (!is_null($extraParam)) { $result = $storage->$operation($internalPath, $extraParam); @@ -660,26 +660,26 @@ class View { * - versioned */ public function getFileInfo($path) { - $path = \OC\Files\Filesystem::normalizePath($this->fakeRoot . '/' . $path); + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); /** * @var \OC\Files\Storage\Storage $storage * @var string $internalPath */ - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); $cache = $storage->getCache(); if (!$cache->inCache($internalPath)) { $scanner = $storage->getScanner(); - $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } $data = $cache->get($internalPath); if ($data['mimetype'] === 'httpd/unix-directory') { //add the sizes of other mountpoints to the folder - $mountPoints = \OC\Files\Filesystem::getMountPoints($path); + $mountPoints = Filesystem::getMountPoints($path); foreach ($mountPoints as $mountPoint) { - $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); + $subStorage = Filesystem::getStorage($mountPoint); $subCache = $subStorage->getCache(); $rootEntry = $subCache->get(''); @@ -697,26 +697,26 @@ class View { * @return array */ public function getDirectoryContent($directory, $mimetype_filter = '') { - $path = \OC\Files\Filesystem::normalizePath($this->fakeRoot . '/' . $directory); + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $directory); /** * @var \OC\Files\Storage\Storage $storage * @var string $internalPath */ - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); $cache = $storage->getCache(); if (!$cache->inCache($internalPath)) { $scanner = $storage->getScanner(); - $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders - $mountPoints = \OC\Files\Filesystem::getMountPoints($directory); + $mountPoints = Filesystem::getMountPoints($directory); $dirLength = strlen($path); foreach ($mountPoints as $mountPoint) { - $subStorage = \OC\Files\Filesystem::getStorage($mountPoint); + $subStorage = Filesystem::getStorage($mountPoint); $subCache = $subStorage->getCache(); $rootEntry = $subCache->get(''); @@ -748,19 +748,57 @@ class View { * returns the fileid of the updated file */ public function putFileInfo($path, $data) { - $path = \OC\Files\Filesystem::normalizePath($this->fakeRoot . '/' . $path); + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); /** * @var \OC\Files\Storage\Storage $storage * @var string $internalPath */ - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); $cache = $storage->getCache(); if (!$cache->inCache($internalPath)) { $scanner = $storage->getScanner(); - $scanner->scan($internalPath, \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } return $cache->put($internalPath, $data); } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return array + */ + public function search($query) { + $files = array(); + $rootLength = strlen($this->fakeRoot); + + $mountPoint = Filesystem::getMountPoint($this->fakeRoot); + $storage = Filesystem::getStorage($mountPoint); + $cache = $storage->getCache(); + + $results = $cache->search('%' . $query . '%'); + foreach ($results as $result) { + if (substr($mountPoint . $result['path'], 0, $rootLength) === $this->fakeRoot) { + $result['path'] = substr($mountPoint . $result['path'], $rootLength); + $files[] = $result; + } + } + + $mountPoints = Filesystem::getMountPoints($this->fakeRoot); + foreach ($mountPoints as $mountPoint) { + $storage = Filesystem::getStorage($mountPoint); + $cache = $storage->getCache(); + + $relativeMountPoint = substr($mountPoint, $rootLength); + $results = $cache->search('%' . $query . '%'); + foreach ($results as $result) { + $result['path'] = $relativeMountPoint . $result['path']; + $files[] = $result; + } + } + + return $files; + } } diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index 177cf1c045..839e06e93f 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -111,6 +111,26 @@ class Cache extends \UnitTestCase { $this->assertEquals(\OC\Files\Cache\Cache::COMPLETE, $this->cache->getStatus('foo')); } + function testSearch() { + $file1 = 'folder'; + $file2 = 'folder/foobar'; + $file3 = 'folder/foo'; + $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder'); + $fileData = array(); + $fileData['foobar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file'); + $fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file'); + + $this->cache->put($file1, $data1); + $this->cache->put($file2, $fileData['foobar']); + $this->cache->put($file3, $fileData['foo']); + + $this->assertEquals(2, count($this->cache->search('%foo%'))); + $this->assertEquals(1, count($this->cache->search('foo'))); + $this->assertEquals(1, count($this->cache->search('%folder%'))); + $this->assertEquals(1, count($this->cache->search('folder%'))); + $this->assertEquals(3, count($this->cache->search('%'))); + } + public function tearDown() { $this->cache->clear(); } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 051ae25162..fc872ea5e2 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -87,7 +87,6 @@ class View extends \PHPUnit_Framework_TestCase { Filesystem::mount($storage1, array(), '/'); Filesystem::mount($storage2, array(), '/substorage'); $textSize = strlen("dummy file data\n"); - $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); $rootView = new \OC\Files\View(''); @@ -100,6 +99,50 @@ class View extends \PHPUnit_Framework_TestCase { $this->assertEquals($textSize, $folderData[0]['size']); } + function testSearch() { + $storage1 = $this->getTestStorage(); + $storage2 = $this->getTestStorage(); + $storage3 = $this->getTestStorage(); + Filesystem::mount($storage1, array(), '/'); + Filesystem::mount($storage2, array(), '/substorage'); + Filesystem::mount($storage3, array(), '/folder/anotherstorage'); + + $rootView = new \OC\Files\View(''); + + $results = $rootView->search('foo'); + $this->assertEquals(6, count($results)); + $paths = array(); + foreach ($results as $result) { + $this->assertEquals($result['path'], Filesystem::normalizePath($result['path'])); + $paths[] = $result['path']; + } + $this->assertContains('/foo.txt', $paths); + $this->assertContains('/foo.png', $paths); + $this->assertContains('/substorage/foo.txt', $paths); + $this->assertContains('/substorage/foo.png', $paths); + $this->assertContains('/folder/anotherstorage/foo.txt', $paths); + $this->assertContains('/folder/anotherstorage/foo.png', $paths); + + $folderView = new \OC\Files\View('/folder'); + $results = $folderView->search('bar'); + $this->assertEquals(2, count($results)); + $paths = array(); + foreach ($results as $result) { + $paths[] = $result['path']; + } + $this->assertContains('/anotherstorage/folder/bar.txt', $paths); + $this->assertContains('/bar.txt', $paths); + + $results = $folderView->search('foo'); + $this->assertEquals(2, count($results)); + $paths = array(); + foreach ($results as $result) { + $paths[] = $result['path']; + } + $this->assertContains('/anotherstorage/foo.txt', $paths); + $this->assertContains('/anotherstorage/foo.png', $paths); + } + /** * @param bool $scan * @return \OC\Files\Storage\Storage From 7ad8bf3156a794e47ee223efd2e1cf9191cc8a97 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 18:07:01 +0200 Subject: [PATCH 052/418] move filesystem test case --- tests/lib/files/filesystem.php | 110 +++++++++++++++++++++++++++++++++ tests/lib/files/view.php | 22 +++---- tests/lib/filesystem.php | 110 --------------------------------- 3 files changed, 120 insertions(+), 122 deletions(-) create mode 100644 tests/lib/files/filesystem.php delete mode 100644 tests/lib/filesystem.php diff --git a/tests/lib/files/filesystem.php b/tests/lib/files/filesystem.php new file mode 100644 index 0000000000..363426511b --- /dev/null +++ b/tests/lib/files/filesystem.php @@ -0,0 +1,110 @@ +. + * + */ + +namespace Test\Files; + +class Filesystem extends \PHPUnit_Framework_TestCase { + /** + * @var array tmpDirs + */ + private $tmpDirs=array(); + + /** + * @return array + */ + private function getStorageData() { + $dir = \OC_Helper::tmpFolder(); + $this->tmpDirs[] = $dir; + return array('datadir' => $dir); + } + + public function tearDown() { + foreach ($this->tmpDirs as $dir) { + \OC_Helper::rmdirr($dir); + } + } + + public function setUp() { + \OC\Files\Filesystem::clearMounts(); + } + + public function testMount() { + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/'); + $this->assertEquals('/',\OC\Files\Filesystem::getMountPoint('/')); + $this->assertEquals('/',\OC\Files\Filesystem::getMountPoint('/some/folder')); + list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/'); + $this->assertEquals('',$internalPath); + list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/some/folder'); + $this->assertEquals('some/folder',$internalPath); + + \OC\Files\Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/some'); + $this->assertEquals('/',\OC\Files\Filesystem::getMountPoint('/')); + $this->assertEquals('/some/',\OC\Files\Filesystem::getMountPoint('/some/folder')); + $this->assertEquals('/some/',\OC\Files\Filesystem::getMountPoint('/some/')); + $this->assertEquals('/',\OC\Files\Filesystem::getMountPoint('/some')); + list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/some/folder'); + $this->assertEquals('folder',$internalPath); + } + + public function testNormalize() { + $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('/path/')); + $this->assertEquals('/path/', \OC\Files\Filesystem::normalizePath('/path/', false)); + $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('path')); + $this->assertEquals('/path', \OC\Files\Filesystem::normalizePath('\path')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo//bar/')); + $this->assertEquals('/foo/bar', \OC\Files\Filesystem::normalizePath('/foo////bar')); + if (class_exists('Normalizer')) { + $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("/foo/baru\xCC\x88")); + } + } + + public function testHooks() { + if(\OC\Files\Filesystem::getView()){ + $user = \OC_User::getUser(); + }else{ + $user=uniqid(); + \OC\Files\Filesystem::init('/'.$user.'/files'); + } + \OC_Hook::clear('OC_Filesystem'); + \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook'); + + \OC\Files\Filesystem::mount('OC\Files\Storage\Temporary', array(), '/'); + + $rootView=new \OC\Files\View(''); + $rootView->mkdir('/'.$user); + $rootView->mkdir('/'.$user.'/files'); + + \OC\Files\Filesystem::file_put_contents('/foo', 'foo'); + \OC\Files\Filesystem::mkdir('/bar'); + \OC\Files\Filesystem::file_put_contents('/bar//foo', 'foo'); + + $tmpFile = \OC_Helper::tmpFile(); + file_put_contents($tmpFile, 'foo'); + $fh = fopen($tmpFile, 'r'); + \OC\Files\Filesystem::file_put_contents('/bar//foo', $fh); + } + + public function dummyHook($arguments) { + $path = $arguments['path']; + $this->assertEquals($path, \OC\Files\Filesystem::normalizePath($path)); //the path passed to the hook should already be normalized + } +} diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index fc872ea5e2..980c5298fb 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -7,8 +7,6 @@ namespace Test\Files; -use \OC\Files\Filesystem as Filesystem; - class View extends \PHPUnit_Framework_TestCase { /** * @var \OC\Files\Storage\Storage[] $storages; @@ -16,7 +14,7 @@ class View extends \PHPUnit_Framework_TestCase { private $storages = array(); public function setUp() { - Filesystem::clearMounts(); + \OC\Files\Filesystem::clearMounts(); } public function tearDown() { @@ -30,9 +28,9 @@ class View extends \PHPUnit_Framework_TestCase { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); $storage3 = $this->getTestStorage(); - Filesystem::mount($storage1, array(), '/'); - Filesystem::mount($storage2, array(), '/substorage'); - Filesystem::mount($storage3, array(), '/folder/anotherstorage'); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + \OC\Files\Filesystem::mount($storage3, array(), '/folder/anotherstorage'); $textSize = strlen("dummy file data\n"); $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); $storageSize = $textSize * 2 + $imageSize; @@ -84,8 +82,8 @@ class View extends \PHPUnit_Framework_TestCase { public function testAutoScan() { $storage1 = $this->getTestStorage(false); $storage2 = $this->getTestStorage(false); - Filesystem::mount($storage1, array(), '/'); - Filesystem::mount($storage2, array(), '/substorage'); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); $textSize = strlen("dummy file data\n"); $rootView = new \OC\Files\View(''); @@ -103,9 +101,9 @@ class View extends \PHPUnit_Framework_TestCase { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); $storage3 = $this->getTestStorage(); - Filesystem::mount($storage1, array(), '/'); - Filesystem::mount($storage2, array(), '/substorage'); - Filesystem::mount($storage3, array(), '/folder/anotherstorage'); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + \OC\Files\Filesystem::mount($storage2, array(), '/substorage'); + \OC\Files\Filesystem::mount($storage3, array(), '/folder/anotherstorage'); $rootView = new \OC\Files\View(''); @@ -113,7 +111,7 @@ class View extends \PHPUnit_Framework_TestCase { $this->assertEquals(6, count($results)); $paths = array(); foreach ($results as $result) { - $this->assertEquals($result['path'], Filesystem::normalizePath($result['path'])); + $this->assertEquals($result['path'], \OC\Files\Filesystem::normalizePath($result['path'])); $paths[] = $result['path']; } $this->assertContains('/foo.txt', $paths); diff --git a/tests/lib/filesystem.php b/tests/lib/filesystem.php deleted file mode 100644 index 6e7b4fb781..0000000000 --- a/tests/lib/filesystem.php +++ /dev/null @@ -1,110 +0,0 @@ -. - * - */ - -use \OC\Files\Filesystem as Filesystem; - -class Test_Filesystem extends UnitTestCase { - /** - * @var array tmpDirs - */ - private $tmpDirs=array(); - - /** - * @return array - */ - private function getStorageData() { - $dir = OC_Helper::tmpFolder(); - $this->tmpDirs[] = $dir; - return array('datadir' => $dir); - } - - public function tearDown() { - foreach ($this->tmpDirs as $dir) { - OC_Helper::rmdirr($dir); - } - } - - public function setUp() { - Filesystem::clearMounts(); - } - - public function testMount() { - Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/'); - $this->assertEqual('/',Filesystem::getMountPoint('/')); - $this->assertEqual('/',Filesystem::getMountPoint('/some/folder')); - list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/'); - $this->assertEqual('',$internalPath); - list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/some/folder'); - $this->assertEqual('some/folder',$internalPath); - - Filesystem::mount('\OC\Files\Storage\Local',self::getStorageData(),'/some'); - $this->assertEqual('/',Filesystem::getMountPoint('/')); - $this->assertEqual('/some/',Filesystem::getMountPoint('/some/folder')); - $this->assertEqual('/some/',Filesystem::getMountPoint('/some/')); - $this->assertEqual('/',Filesystem::getMountPoint('/some')); - list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/some/folder'); - $this->assertEqual('folder',$internalPath); - } - - public function testNormalize() { - $this->assertEqual('/path', Filesystem::normalizePath('/path/')); - $this->assertEqual('/path/', Filesystem::normalizePath('/path/', false)); - $this->assertEqual('/path', Filesystem::normalizePath('path')); - $this->assertEqual('/path', Filesystem::normalizePath('\path')); - $this->assertEqual('/foo/bar', Filesystem::normalizePath('/foo//bar/')); - $this->assertEqual('/foo/bar', Filesystem::normalizePath('/foo////bar')); - if (class_exists('Normalizer')) { - $this->assertEqual("/foo/bar\xC3\xBC", Filesystem::normalizePath("/foo/baru\xCC\x88")); - } - } - - public function testHooks() { - if(Filesystem::getView()){ - $user = OC_User::getUser(); - }else{ - $user=uniqid(); - Filesystem::init('/'.$user.'/files'); - } - OC_Hook::clear('OC_Filesystem'); - OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook'); - - Filesystem::mount('OC\Files\Storage\Temporary', array(), '/'); - - $rootView=new \OC\Files\View(''); - $rootView->mkdir('/'.$user); - $rootView->mkdir('/'.$user.'/files'); - - Filesystem::file_put_contents('/foo', 'foo'); - Filesystem::mkdir('/bar'); - Filesystem::file_put_contents('/bar//foo', 'foo'); - - $tmpFile = OC_Helper::tmpFile(); - file_put_contents($tmpFile, 'foo'); - $fh = fopen($tmpFile, 'r'); - Filesystem::file_put_contents('/bar//foo', $fh); - } - - public function dummyHook($arguments) { - $path = $arguments['path']; - $this->assertEqual($path, Filesystem::normalizePath($path)); //the path passed to the hook should already be normalized - } -} From c22a723785f80671548b89c543e9163c2fff9264 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 19:07:29 +0200 Subject: [PATCH 053/418] add file permissions cache --- db_structure.xml | 47 +++++++++++++++ lib/files/cache/permissions.php | 85 +++++++++++++++++++++++++++ lib/util.php | 2 +- tests/lib/files/cache/permissions.php | 47 +++++++++++++++ 4 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 lib/files/cache/permissions.php create mode 100644 tests/lib/files/cache/permissions.php diff --git a/db_structure.xml b/db_structure.xml index e0b9dc11e9..e420a9f0e4 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -175,6 +175,53 @@
+ + + *dbprefix*permissions + + + + + fileid + integer + 0 + true + 4 + + + + user + text + + true + 64 + + + + permissions + integer + 0 + true + 4 + + + + id_user_index + true + + fileid + ascending + + + user + ascending + + + + + +
+ *dbprefix*group_user diff --git a/lib/files/cache/permissions.php b/lib/files/cache/permissions.php new file mode 100644 index 0000000000..e3fa63c464 --- /dev/null +++ b/lib/files/cache/permissions.php @@ -0,0 +1,85 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Permissions { + /** + * get the permissions for a single file + * + * @param int $fileId + * @param string $user + * @return int (-1 if file no permissions set) + */ + static public function get($fileId, $user) { + $query = \OC_DB::prepare('SELECT `permissions` FROM `*PREFIX*permissions` WHERE `user` = ? AND `fileid` = ?'); + $result = $query->execute(array($user, $fileId)); + if ($row = $result->fetchRow()) { + return $row['permissions']; + } else { + return -1; + } + } + + /** + * set the permissions of a file + * + * @param int $fileId + * @param string $user + * @param int $permissions + */ + static public function set($fileId, $user, $permissions) { + if (self::get($fileId, $user) !== -1) { + $query = \OC_DB::prepare('UPDATE `*PREFIX*permissions` SET `permissions` = ? WHERE `user` = ? AND `fileid` = ?'); + } else { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*permissions`(`permissions`, `user`, `fileid`) VALUES(?, ?,? )'); + } + $query->execute(array($permissions, $user, $fileId)); + } + + /** + * get the permissions of multiply files + * + * @param int[] $fileIds + * @param string $user + * @return int[] + */ + static public function getMultiple($fileIds, $user) { + $params = $fileIds; + $params[] = $user; + $inPart = implode(', ', array_fill(0, count($fileIds), '?')); + + $query = \OC_DB::prepare('SELECT `fileid`, `permissions` FROM `*PREFIX*permissions` WHERE `fileid` IN (' . $inPart . ') AND `user` = ?'); + $result = $query->execute($params); + $filePermissions = array(); + while ($row = $result->fetchRow()) { + $filePermissions[$row['fileid']] = $row['permissions']; + } + return $filePermissions; + } + + /** + * remove the permissions for a file + * + * @param int $fileId + * @param string $user + */ + static public function remove($fileId, $user) { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?'); + $query->execute(array($fileId, $user)); + } + + static public function removeMultiple($fileIds, $user) { + $params = $fileIds; + $params[] = $user; + $inPart = implode(', ', array_fill(0, count($fileIds), '?')); + + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` IN (' . $inPart . ') AND `user` = ?'); + $query->execute($params); + } +} diff --git a/lib/util.php b/lib/util.php index 137766d012..a2e0422c97 100755 --- a/lib/util.php +++ b/lib/util.php @@ -86,7 +86,7 @@ class OC_Util { */ public static function getVersion() { // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.90.0. This is not visible to the user - return array(4,91,00); + return array(4,91,01); } /** diff --git a/tests/lib/files/cache/permissions.php b/tests/lib/files/cache/permissions.php new file mode 100644 index 0000000000..4d47929a3e --- /dev/null +++ b/tests/lib/files/cache/permissions.php @@ -0,0 +1,47 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +class Permissions extends \PHPUnit_Framework_TestCase { + function testSimple() { + $ids = range(1, 10); + $user = uniqid(); + + $this->assertEquals(-1, \OC\Files\Cache\Permissions::get(1, $user)); + \OC\Files\Cache\Permissions::set(1, $user, 1); + $this->assertEquals(1, \OC\Files\Cache\Permissions::get(1, $user)); + $this->assertEquals(-1, \OC\Files\Cache\Permissions::get(2, $user)); + $this->assertEquals(-1, \OC\Files\Cache\Permissions::get(1, $user . '2')); + + \OC\Files\Cache\Permissions::set(1, $user, 2); + $this->assertEquals(2, \OC\Files\Cache\Permissions::get(1, $user)); + + \OC\Files\Cache\Permissions::set(2, $user, 1); + $this->assertEquals(1, \OC\Files\Cache\Permissions::get(2, $user)); + + \OC\Files\Cache\Permissions::remove(1, $user); + $this->assertEquals(-1, \OC\Files\Cache\Permissions::get(1, $user)); + \OC\Files\Cache\Permissions::remove(1, $user . '2'); + $this->assertEquals(1, \OC\Files\Cache\Permissions::get(2, $user)); + + $expected = array(); + foreach ($ids as $id) { + \OC\Files\Cache\Permissions::set($id, $user, 10 + $id); + $expected[$id] = 10 + $id; + } + $this->assertEquals($expected, \OC\Files\Cache\Permissions::getMultiple($ids, $user)); + + \OC\Files\Cache\Permissions::removeMultiple(array(10, 9), $user); + unset($expected[9]); + unset($expected[10]); + $this->assertEquals($expected, \OC\Files\Cache\Permissions::getMultiple($ids, $user)); + + \OC\Files\Cache\Permissions::removeMultiple($ids, $user); + } +} From 6db81afab9d68ccd50c1e164622252e47ae76c2f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 23:05:02 +0200 Subject: [PATCH 054/418] move some stuff to the new api --- apps/files/ajax/list.php | 2 +- apps/files/ajax/newfile.php | 12 ++++++------ apps/files/ajax/newfolder.php | 3 ++- apps/files/ajax/rawlist.php | 2 +- apps/files/ajax/upload.php | 2 +- apps/files/index.php | 2 +- apps/files/settings.php | 2 +- apps/files_encryption/lib/cryptstream.php | 2 +- apps/files_encryption/lib/proxy.php | 10 +++++----- apps/files_sharing/appinfo/update.php | 3 ++- lib/connector/sabre/directory.php | 6 +++--- lib/connector/sabre/node.php | 4 ++-- lib/fileproxy/quota.php | 12 ++++-------- lib/files.php | 2 +- lib/files/filesystem.php | 13 +++++++++++++ lib/files/view.php | 1 - lib/ocs.php | 4 ++-- lib/public/share.php | 15 +++++++++------ lib/search/provider/file.php | 2 +- settings/personal.php | 4 ++-- 20 files changed, 58 insertions(+), 45 deletions(-) diff --git a/apps/files/ajax/list.php b/apps/files/ajax/list.php index 568fe754c0..92091f4213 100644 --- a/apps/files/ajax/list.php +++ b/apps/files/ajax/list.php @@ -32,7 +32,7 @@ if($doBreadcrumb) { // make filelist $files = array(); -foreach( OC_Files::getdirectorycontent( $dir ) as $i ) { +foreach( \OC\Files\Filesystem::getDirectoryContent( $dir ) as $i ) { $i["date"] = OCP\Util::formatDate($i["mtime"] ); $files[] = $i; } diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index 5f3f3d88f2..38714f34a6 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -65,9 +65,9 @@ if($source) { $target=$dir.'/'.$filename; $result=\OC\Files\Filesystem::file_put_contents($target, $sourceStream); if($result) { - $meta = OC_FileCache::get($target); + $meta = \OC\Files\Filesystem::getFileInfo($target); $mime=$meta['mimetype']; - $id = OC_FileCache::getId($target); + $id = $meta['fileid']; $eventSource->send('success', array('mime'=>$mime, 'size'=>\OC\Files\Filesystem::filesize($target), 'id' => $id)); } else { $eventSource->send('error', "Error while downloading ".$source. ' to '.$target); @@ -77,14 +77,14 @@ if($source) { } else { if($content) { if(\OC\Files\Filesystem::file_put_contents($dir.'/'.$filename, $content)) { - $meta = OC_FileCache::get($dir.'/'.$filename); - $id = OC_FileCache::getId($dir.'/'.$filename); + $meta = \OC\Files\Filesystem::getFileInfo($dir.'/'.$filename); + $id = $meta['fileid']; OCP\JSON::success(array("data" => array('content'=>$content, 'id' => $id))); exit(); } }elseif(\OC\Files\Filesystem::touch($dir . '/' . $filename)) { - $meta = OC_FileCache::get($dir.'/'.$filename); - $id = OC_FileCache::getId($dir.'/'.$filename); + $meta = \OC\Files\Filesystem::getFileInfo($dir.'/'.$filename); + $id = $meta['fileid']; OCP\JSON::success(array("data" => array('content'=>$content, 'id' => $id))); exit(); } diff --git a/apps/files/ajax/newfolder.php b/apps/files/ajax/newfolder.php index c36c208455..e26e1238bc 100644 --- a/apps/files/ajax/newfolder.php +++ b/apps/files/ajax/newfolder.php @@ -25,7 +25,8 @@ if(\OC\Files\Filesystem::mkdir($dir . '/' . stripslashes($foldername))) { } else { $path = '/'.$foldername; } - $id = OC_FileCache::getId($path); + $meta = \OC\Files\Filesystem::getFileInfo($path); + $id = $meta['fileid']; OCP\JSON::success(array("data" => array('id'=>$id))); exit(); } diff --git a/apps/files/ajax/rawlist.php b/apps/files/ajax/rawlist.php index e0aa0bdac5..1cd2944483 100644 --- a/apps/files/ajax/rawlist.php +++ b/apps/files/ajax/rawlist.php @@ -15,7 +15,7 @@ $mimetype = isset($_GET['mimetype']) ? $_GET['mimetype'] : ''; // make filelist $files = array(); -foreach( OC_Files::getdirectorycontent( $dir, $mimetype ) as $i ) { +foreach( \OC\Files\Filesystem::getDirectoryContent( $dir, $mimetype ) as $i ) { $i["date"] = OCP\Util::formatDate($i["mtime"] ); $i['mimetype_icon'] = $i['type'] == 'dir' ? \mimetype_icon('dir'): \mimetype_icon($i['mimetype']); $files[] = $i; diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php index b6b8c7f7b4..2a784d6169 100644 --- a/apps/files/ajax/upload.php +++ b/apps/files/ajax/upload.php @@ -49,7 +49,7 @@ if(strpos($dir, '..') === false) { for($i=0;$i<$fileCount;$i++) { $target = OCP\Files::buildNotExistingFileName(stripslashes($dir), $files['name'][$i]); if(is_uploaded_file($files['tmp_name'][$i]) and \OC\Files\Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) { - $meta = \OC_Files::getFileInfo($target); + $meta = \OC\Files\Filesystem::getFileInfo($target); $id = $meta['fileid']; $result[]=array( "status" => "success", 'mime'=>$meta['mimetype'],'size'=>$meta['size'], 'id'=>$id, 'name'=>basename($target)); } diff --git a/apps/files/index.php b/apps/files/index.php index 192cd2696f..4676ebc602 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -44,7 +44,7 @@ if(!\OC\Files\Filesystem::is_dir($dir.'/')) { } $files = array(); -foreach( OC_Files::getdirectorycontent( $dir ) as $i ) { +foreach( \OC\Files\Filesystem::getDirectoryContent( $dir ) as $i ) { $i['date'] = OCP\Util::formatDate($i['mtime'] ); if($i['type']=='file') { $fileinfo=pathinfo($i['name']); diff --git a/apps/files/settings.php b/apps/files/settings.php index 52ec9fd0fe..30463210f7 100644 --- a/apps/files/settings.php +++ b/apps/files/settings.php @@ -36,7 +36,7 @@ OCP\Util::addscript( "files", "files" ); $dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; $files = array(); -foreach( OC_Files::getdirectorycontent( $dir ) as $i ) { +foreach( \OC\Files\Filesystem::getDirectoryContent( $dir ) as $i ) { $i["date"] = date( $CONFIG_DATEFORMAT, $i["mtime"] ); $files[] = $i; } diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index e98013e1f4..c429838264 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -167,7 +167,7 @@ class OC_CryptStream{ public function stream_close() { $this->flush(); if($this->meta['mode']!='r' and $this->meta['mode']!='rb') { - OC_FileCache::put($this->path, array('encrypted'=>true,'size'=>$this->size),''); + \OC\Files\Filesystem::putFileInfo($this->path, array('encrypted'=>true,'size'=>$this->size),''); } return fclose($this->source); } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 7b7bafe73e..49fb30ac29 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -59,7 +59,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ * @return bool */ private static function isEncrypted($path) { - $metadata=OC_FileCache_Cached::get($path,''); + $metadata=\OC\Files\Filesystem::getFileInfo($path,''); return isset($metadata['encrypted']) and (bool)$metadata['encrypted']; } @@ -68,14 +68,14 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ if (!is_resource($data)) {//stream put contents should have been converter to fopen $size=strlen($data); $data=OC_Crypt::blockEncrypt($data); - OC_FileCache::put($path, array('encrypted'=>true,'size'=>$size),''); + \OC\Files\Filesystem::putFileInfo($path, array('encrypted'=>true,'size'=>$size),''); } } } public function postFile_get_contents($path,$data) { if(self::isEncrypted($path)) { - $cached=OC_FileCache_Cached::get($path,''); + $cached=\OC\Files\Filesystem::getFileInfo($path,''); $data=OC_Crypt::blockDecrypt($data,'',$cached['size']); } return $data; @@ -113,7 +113,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ public function postStat($path,$data) { if(self::isEncrypted($path)) { - $cached=OC_FileCache_Cached::get($path,''); + $cached=\OC\Files\Filesystem::getFileInfo($path,''); $data['size']=$cached['size']; } return $data; @@ -121,7 +121,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ public function postFileSize($path,$size) { if(self::isEncrypted($path)) { - $cached=OC_FileCache_Cached::get($path,''); + $cached=\OC\Files\Filesystem::getFileInfo($path,''); return $cached['size']; }else{ return $size; diff --git a/apps/files_sharing/appinfo/update.php b/apps/files_sharing/appinfo/update.php index e75c538b15..51048bd178 100644 --- a/apps/files_sharing/appinfo/update.php +++ b/apps/files_sharing/appinfo/update.php @@ -10,6 +10,7 @@ if (version_compare($installedVersion, '0.3', '<')) { OC_Group::useBackend(new OC_Group_Database()); OC_App::loadApps(array('authentication')); while ($row = $result->fetchRow()) { + $meta = \OC\Files\Filesystem::getId($path, ''); $itemSource = OC_FileCache::getId($row['source'], ''); if ($itemSource != -1) { $file = OC_FileCache::get($row['source'], ''); @@ -70,4 +71,4 @@ if (version_compare($installedVersion, '0.3.3', '<')) { foreach ($users as $user) { OC_FileCache::delete('Shared', '/'.$user.'/files/'); } -} \ No newline at end of file +} diff --git a/lib/connector/sabre/directory.php b/lib/connector/sabre/directory.php index 3df85b2bbb..d4f58527d2 100644 --- a/lib/connector/sabre/directory.php +++ b/lib/connector/sabre/directory.php @@ -93,7 +93,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa $path = $this->path . '/' . $name; if (is_null($info)) { - $info = OC_Files::getFileInfo($path); + $info = \OC\Files\Filesystem::getFileInfo($path); } if (!$info) { @@ -117,7 +117,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa */ public function getChildren() { - $folder_content = OC_Files::getDirectoryContent($this->path); + $folder_content = \OC\Files\Filesystem::getDirectoryContent($this->path); $paths = array(); foreach($folder_content as $info) { $paths[] = $this->path.'/'.$info['name']; @@ -178,7 +178,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa * @return array */ public function getQuotaInfo() { - $rootInfo=OC_FileCache_Cached::get(''); + $rootInfo=\OC\Files\Filesystem::getFileInfo(''); return array( $rootInfo['size'], \OC\Files\Filesystem::free_space() diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php index e7e83507ea..2095c956e5 100644 --- a/lib/connector/sabre/node.php +++ b/lib/connector/sabre/node.php @@ -95,11 +95,11 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr } /** - * Make sure the fileinfo cache is filled. Uses OC_FileCache or a direct stat + * Make sure the fileinfo cache is filled. Uses the file cache or a direct stat */ protected function getFileinfoCache() { if (!isset($this->fileinfo_cache)) { - if ($fileinfo_cache = \OC\Files\Filesystem::get($this->path)) { + if ($fileinfo_cache = \OC\Files\Filesystem::getFileInfo($this->path)) { } else { $fileinfo_cache = \OC\Files\Filesystem::stat($this->path); } diff --git a/lib/fileproxy/quota.php b/lib/fileproxy/quota.php index d120f30999..cd9a2f4a19 100644 --- a/lib/fileproxy/quota.php +++ b/lib/fileproxy/quota.php @@ -57,7 +57,7 @@ class OC_FileProxy_Quota extends OC_FileProxy{ * @return int */ private function getFreeSpace($path) { - $storage=OC_Filesystem::getStorage($path); + list($storage,)=\OC\Files\Filesystem::resolvePath($path); $owner=$storage->getOwner($path); $totalSpace=$this->getQuota($owner); @@ -65,13 +65,9 @@ class OC_FileProxy_Quota extends OC_FileProxy{ return 0; } - $rootInfo=OC_FileCache::get('', "/".$owner."/files"); - // TODO Remove after merge of share_api - if (OC_FileCache::inCache('/Shared', "/".$owner."/files")) { - $sharedInfo=OC_FileCache::get('/Shared', "/".$owner."/files"); - } else { - $sharedInfo = null; - } + $view = new \OC\Files\View("/".$owner."/files"); + + $rootInfo=$view->getFileInfo('/'); $usedSpace=isset($rootInfo['size'])?$rootInfo['size']:0; $usedSpace=isset($sharedInfo['size'])?$usedSpace-$sharedInfo['size']:$usedSpace; return $totalSpace-$usedSpace; diff --git a/lib/files.php b/lib/files.php index 422e7f4ffe..6a063f216d 100644 --- a/lib/files.php +++ b/lib/files.php @@ -146,7 +146,7 @@ class OC_Files { $dirname = basename($dir); $zip->addEmptyDir($internalDir . $dirname); $internalDir .= $dirname .= '/'; - $files = OC_Files::getdirectorycontent($dir); + $files = \OC\Files\Filesystem::getDirectoryContent($dir); foreach ($files as $file) { $filename = $file['name']; $file = $dir . '/' . $filename; diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 4031c0c5b8..b3c92f3855 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -627,6 +627,19 @@ class Filesystem { return self::$defaultInstance->getFileInfo($path); } + /** + * change file metadata + * + * @param string $path + * @param array $data + * @return int + * + * returns the fileid of the updated file + */ + public static function putFileInfo($path, $data) { + return self::$defaultInstance->putFileInfo($path, $data); + } + /** * get the content of a directory * diff --git a/lib/files/view.php b/lib/files/view.php index ee95cce0c1..e9b583f8ae 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -451,7 +451,6 @@ class View { array(Filesystem::signal_param_path => $path2) ); } else { // no real copy, file comes from somewhere else, e.g. version rollback -> just update the file cache and the webdav properties without all the other post_write actions -// OC_FileCache_Update::update($path2, $this->fakeRoot); Filesystem::removeETagHook(array("path" => $path2), $this->fakeRoot); } return $result; diff --git a/lib/ocs.php b/lib/ocs.php index 645380ddba..c6bcd2c06e 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -590,8 +590,8 @@ class OC_OCS { // calculate the disc space $user_dir = '/'.$user.'/files'; \OC\Files\Filesystem::init($user_dir); - $rootInfo=OC_FileCache::get(''); - $sharedInfo=OC_FileCache::get('/Shared'); + $rootInfo=\OC\Files\Filesystem::getFileInfo(''); + $sharedInfo=\OC\Files\Filesystem::getFileInfo('/Shared'); $used=$rootInfo['size']-$sharedInfo['size']; $free=\OC\Files\Filesystem::free_space(); $total=$free+$used; diff --git a/lib/public/share.php b/lib/public/share.php index 7a9a087d1b..dd6c841c99 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -285,10 +285,10 @@ class Share { // If the item is a folder, scan through the folder looking for equivalent item types if ($itemType == 'folder') { $parentFolder = self::put('folder', $itemSource, $shareType, $shareWith, $uidOwner, $permissions, true); - if ($parentFolder && $files = \OC_Files::getDirectoryContent($itemSource)) { + if ($parentFolder && $files = \OC\Files\Filesystem::getDirectoryContent($itemSource)) { for ($i = 0; $i < count($files); $i++) { $name = substr($files[$i]['name'], strpos($files[$i]['name'], $itemSource) - strlen($itemSource)); - if ($files[$i]['mimetype'] == 'httpd/unix-directory' && $children = \OC_Files::getDirectoryContent($name, '/')) { + if ($files[$i]['mimetype'] == 'httpd/unix-directory' && $children = \OC\Files\Filesystem::getDirectoryContent($name, '/')) { // Continue scanning into child folders array_push($files, $children); } else { @@ -768,9 +768,10 @@ class Share { if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') { $childItem['file_source'] = $child['source']; } else { - $childItem['file_source'] = \OC_FileCache::getId($child['file_path']); + $meta = \OC\Files\Filesystem::getFileInfo($child['file_path']); + $childItem['file_source'] = $meta['fileid']; } - $childItem['file_target'] = \OC_Filesystem::normalizePath($child['file_path']); + $childItem['file_target'] = \OC\Files\Filesystem::normalizePath($child['file_path']); } if (isset($item)) { if ($childItem[$column] == $item) { @@ -881,7 +882,8 @@ class Share { if ($itemType == 'file' || $itemType == 'folder') { $fileSource = $itemSource; } else { - $fileSource = \OC_FileCache::getId($filePath); + $meta = \OC\Files\Filesystem::getFileInfo($filePath); + $fileSource = $meta['fileid']; } if ($fileSource == -1) { $message = 'Sharing '.$itemSource.' failed, because the file could not be found in the file cache'; @@ -1063,7 +1065,8 @@ class Share { } if ($item['uid_owner'] == $uidOwner) { if ($itemType == 'file' || $itemType == 'folder') { - if ($item['file_source'] == \OC_FileCache::getId($itemSource)) { + $meta = \OC\Files\Filesystem::getFileInfo($itemSource); + if ($item['file_source'] == $meta['fileid']) { return $target; } } else if ($item['item_source'] == $itemSource) { diff --git a/lib/search/provider/file.php b/lib/search/provider/file.php index 0d4b332b79..456e12b5ec 100644 --- a/lib/search/provider/file.php +++ b/lib/search/provider/file.php @@ -2,7 +2,7 @@ class OC_Search_Provider_File extends OC_Search_Provider{ function search($query) { - $files=OC_FileCache::search($query, true); + $files=\OC\Files\Filesystem::search($query, true); $results=array(); $l=OC_L10N::get('lib'); foreach($files as $fileData) { diff --git a/settings/personal.php b/settings/personal.php index c73a3dd370..5a267ec284 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -16,8 +16,8 @@ OC_Util::addStyle( '3rdparty', 'chosen' ); OC_App::setActiveNavigationEntry( 'personal' ); // calculate the disc space -$rootInfo=OC_FileCache::get(''); -$sharedInfo=OC_FileCache::get('/Shared'); +$rootInfo=\OC\Files\Filesystem::getFileInfo(''); +$sharedInfo=\OC\Files\Filesystem::getFileInfo('/Shared'); $used=$rootInfo['size']; if($used<0) $used=0; $free=\OC\Files\Filesystem::free_space(); From 7d6da68d532fe1b1299f3a724e70b8ec3960ec8d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 23:25:52 +0200 Subject: [PATCH 055/418] prove some compatibility with the old cache api --- lib/files/view.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/files/view.php b/lib/files/view.php index e9b583f8ae..04b7dca8af 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -712,7 +712,7 @@ class View { $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders - $mountPoints = Filesystem::getMountPoints($directory); + $mountPoints = Filesystem::getMountPoints($path); $dirLength = strlen($path); foreach ($mountPoints as $mountPoint) { $subStorage = Filesystem::getStorage($mountPoint); @@ -733,6 +733,10 @@ class View { } } + foreach($files as $i => $file){ + $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + } + usort($files, "fileCmp"); //TODO: remove this once ajax is merged return $files; } From 99534271977ef5a4fd35ce119d6627bbcde45096 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 Oct 2012 23:26:12 +0200 Subject: [PATCH 056/418] also use new cache api here --- lib/fileproxy/fileoperations.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fileproxy/fileoperations.php b/lib/fileproxy/fileoperations.php index 23fb63fcfb..47ccd8f8c2 100644 --- a/lib/fileproxy/fileoperations.php +++ b/lib/fileproxy/fileoperations.php @@ -29,9 +29,9 @@ class OC_FileProxy_FileOperations extends OC_FileProxy{ public function premkdir($path) { if(!self::$rootView){ - self::$rootView = new OC_FilesystemView(''); + self::$rootView = new \OC\Files\View(''); } return !self::$rootView->file_exists($path); } -} \ No newline at end of file +} From 3eea7aac1391184e00a92c8801c9f0b54c43bb8d Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 26 Oct 2012 23:38:36 +0200 Subject: [PATCH 057/418] first base on navigation rework, less obtrusive, icons need to be updated, settings moved --- apps/files/css/files.css | 4 +-- core/css/styles.css | 15 ++++++------ core/img/noise.png | Bin 0 -> 951865 bytes core/img/places/home.svg | 43 +++++++++++---------------------- core/img/places/music.svg | 41 +++++++++++-------------------- core/img/places/picture.svg | 38 +++++++++++++---------------- core/templates/layout.user.php | 14 ++++++++--- 7 files changed, 66 insertions(+), 89 deletions(-) create mode 100644 core/img/noise.png diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 0b886fc3fa..1e25ccd36d 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -82,8 +82,8 @@ a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; } .selectedActions a img { position:relative; top:.3em; } /* add breadcrumb divider to the File item in navigation panel */ -#navigation>ul>li:first-child { background:url('%webroot%/core/img/breadcrumb-start.svg') no-repeat 12.5em 0px; width:12.5em; padding-right:1em; position:fixed; } -#navigation>ul>li:first-child+li { padding-top:2.9em; } +#navigation>ul>li:first-child { background:url('%webroot%/core/img/breadcrumb-start.svg') no-repeat 65px 0; width:65px; padding-right:1em; position:fixed; } +#navigation>ul>li:first-child+li { padding-top:68px; } #scanning-message{ top:40%; left:40%; position:absolute; display:none; } diff --git a/core/css/styles.css b/core/css/styles.css index a6c1050407..b29133bf79 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -58,7 +58,7 @@ input[type="submit"].highlight{ background:#ffc100; border:1px solid #db0; text- /* CONTENT ------------------------------------------------------------------ */ #controls { padding: 0 0.5em; width:100%; top:3.5em; height:2.8em; margin:0; background:#f7f7f7; border-bottom:1px solid #eee; position:fixed; z-index:50; -moz-box-shadow:0 -3px 7px #000; -webkit-box-shadow:0 -3px 7px #000; box-shadow:0 -3px 7px #000; } #controls .button { display:inline-block; } -#content { top: 3.5em; left: 12.5em; position: absolute; } +#content { top: 3.5em; left: 65px; position: absolute; } #leftcontent, .leftcontent { position:fixed; overflow: auto; top:6.4em; width:20em; background:#f8f8f8; border-right:1px solid #ddd; } #leftcontent li, .leftcontent li { background:#f8f8f8; padding:.5em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 200ms; -moz-transition:background-color 200ms; -o-transition:background-color 200ms; transition:background-color 200ms; } #leftcontent li:hover, #leftcontent li:active, #leftcontent li.active, .leftcontent li:hover, .leftcontent li:active, .leftcontent li.active { background:#eee; } @@ -95,14 +95,15 @@ label.infield { cursor: text !important; } /* NAVIGATION ------------------------------------------------------------- */ -#navigation { position:fixed; top:3.5em; float:left; width:12.5em; padding:0; z-index:75; height:100%; background:#eee; border-right: 1px #ccc solid; -moz-box-shadow: -3px 0 7px #000; -webkit-box-shadow: -3px 0 7px #000; box-shadow: -3px 0 7px #000; overflow:hidden;} -#navigation a { display:block; padding:.6em .5em .4em 2.5em; background:#eee 1em center no-repeat; border-bottom:1px solid #ddd; border-top:1px solid #fff; text-decoration:none; font-size:1.2em; color:#666; text-shadow:#f8f8f8 0 1px 0; } -#navigation a.active, #navigation a:hover, #navigation a:focus { background-color:#dbdbdb; border-top:1px solid #d4d4d4; border-bottom:1px solid #ccc; color:#333; } -#navigation a.active { background-color:#ddd; } +#navigation { position:fixed; top:3.5em; float:left; width:65px; padding:0; z-index:75; height:100%; background:#30343a url('../img/noise.png') repeat; border-right: 1px #ccc solid; -moz-box-shadow: -3px 0 7px #000; -webkit-box-shadow: -3px 0 7px #000; box-shadow: -3px 0 7px #000; overflow:hidden;} +#navigation a { display:block; padding:4px; background:transparent; text-decoration:none; font-size:10px; text-align:center; color:#000; text-shadow:#444 0 1px 0; } +#navigation li:first-child { padding-top:8px; } +#navigation a img { display:block; width:32px; height:32px; margin:0 auto; } +#navigation a.active, #navigation a:hover, #navigation a:focus { color:#888; text-shadow:#000 0 -1px 0; } +#navigation a.active { } #navigation #settings { position:absolute; bottom:3.5em; width:100%; } +#navigation #settings img { width:32px; height:32px; } #expand { position:relative; z-index:100; margin-bottom:-.5em; padding:.5em 10.1em .7em 1.2em; cursor:pointer; } -#expand+span { position:absolute; z-index:99; margin:-1.7em 0 0 2.5em; font-size:1.2em; color:#666; text-shadow:#f8f8f8 0 1px 0; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; -webkit-transition:opacity 300ms; -moz-transition:opacity 300ms; -o-transition:opacity 300ms; transition:opacity 300ms; } -#expand:hover+span, #expand+span:hover { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; cursor:pointer; } /* VARIOUS REUSABLE SELECTORS */ .hidden { display:none; } diff --git a/core/img/noise.png b/core/img/noise.png new file mode 100644 index 0000000000000000000000000000000000000000..8fdda17b5e36b5a1aacc09652bc93126a1ccb8fc GIT binary patch literal 951865 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

JMP25%&0tuP^jwa1K^E~edce|Ka_(?c-H!1zsF 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 z zf8??nRUTMq(>-r_?I{#K-k8)GFWQxp;6D_0)HzS*OiLfgM&UuU>KN8PHV^8BLpHCO^iDRPY%rO z3_;RV=T#U})kPdVYS>Xi}95Q8$+W(EQ0yi7ElboZ?H__}zchd~M5Tg3*e=i4?2TCm>;cLD#+xdgj|`lGn1c!w&`qzUC~ghmO0O zTVy)EUcV4>s=@x-!MM$tky(EI*CeVqsRDOFI77+m(+?r896~MlKE4blLj|7_KAnJN z1)r;e^Pr07v+cd%WBuN@y8ctxb^9zcy)$Mq zJ#x%tN?n#^Ecwv-c)H6Y%RAHEYFyl_6JEcJ&G$A^FmU|5w`A1)9lO8$?(=Cy>69PY z7dGsiHqBVUForSOlG6*_$)@nbv>*yhlqcO&p?`l9f=b+|s4Z!fLF&W*y!!j6-~@-@ zFYb5kkt6*uCsZFh))(@z7NY-$yiX=#fv=fyMWI2=#p5gK>C+`IX&=IQ+R)I(XlSHl zz!)13zd}lK8R~aEwHq`bGn^uzEKm7;aQVoVmKH4mxhH01{MY*J!{V1rjI-Bg8T|n& z{s>#`F-_E4?$>k{!1X%Xh;%MM#d__anLw-cK;hYJuApWP)E=#L4eh+2qqQr>z8KCoGA6$U7HNxGZs%NRZv;H{f}%K%*XsZp zjw&+l4Ft2C7cZ7Yo;tMv{6|k(FPOq(5h(TpZtQV()|XG*uji+qk7HOGV=?9+S9=1k zCZ2EwhGVdDPlP58hVk6+`n5hM?13pv`OYs)x=jym3V(DXFu5f{V39?S@nLileg1W( ziqm0HxAMiSc_0JAO`QlPiN++;kfGER7{0bMPTdEN{3Ogb=vkr}fIO{n)26vt1%EAK zE#4u{$Pb=YRvK@GfA#X^%lLav!`YbT$_4M}E>bA=eW~MhF+zxW@*xRgn+AYS83>32 zTAF*#Qc&($8`K&7vr-1?sbavUT8|(}&Y)cc+t`vy1i#QM?a5yL32Y znZqF=xuQFTK4E(9Q79xCW1?+EF55he8rr)3=T&)7iTU{_n7bxSPS0qTDT*iHC#Fa_-#8w)K=;N1A16| zH2la9Y`A2lew$*o(?q9|6d58)hy$tyfo7CrM&sSY@Whk@FQNY4iP@8IQtL4kMTRdN7y5mH&o(D^b-z{ERwCoL<-^ z+43I}3{uc*_u_d=7}kWsfb0YPp$zV)A7-3^*7)4jG9V()QooaoOdeo)DKB*^fXUNi zAt5!>u(s?M7`qmam$jYl4K zr!08RVp#_9s22w%0Dz0IWYJZTXB_<Mx`M1cSVBjA<+$0p-%?g@hIu#*&$XPu6PLRR3;rT=r=gVX} z%u3yfp+^PHCOJOU^Ajt?_Gm(kSPYQXi;IUwM`m|ZwUod|o7jI-;U)zz7E?QK`K1Zy8iF9CW|4sT0U5v(r#ArLw_#U^3Vc5ltb&tpwEEjqBq9 zw$7dPV}63E9-(*s#_4YfM;CO<4BAtCkwYwyk}2#? z)iGnb>bO-)+PimHiO5U6kH8b~eth@p=FFRjs91*+RtyWN7Cc&UH!u;{z(`FOKYk$k z1MpKCK&x@tT-Eb1tSTz;_t)O!}RIoOaPvC0XJ9Rl8DN!9BFZma7t4JOt2htw2soKno{C2O1$e_H;V`mqL6K6WtsS> zn6bSPV~m3dfbP(MRvyz9gY{gfDBE)Z4=obPozEbbV$xCtwAG2NVbAHpP=5-dn=b&O zg7dF%>V5$*<*a>o?5P6xO$R>`c8`s8Y|#K1q=0#`1+9jKlri3#gn2#Oz$jfXG*y#8 zC~%+vc3K0np}hG(*zJ&Q^XG5N^cnLiM4Q0}VsPdly6kXd9i$WHFuOik<*G=S!Gj+)A_gvF?j!7JUKB;EsLjY0wvruz$}{Nb!q`9|9k17+=~FG1>;ELVX4f+<(_W zGt|+#omVxO`O`8DxjgQ=4qTwkJg`BzA4k96 zL;=bDw|GaAg$vBw=_d*1AT~z! zSIl?Y^o;#4)P03aEVhtKeTwZD0nSqe8C?aK!H)Ki^58#|E2Cgu7=EPB-rii-Wg_RT z2@Tx_2&Z=_(B|>lX5cCIegb(T)H#PCgfv24Fb|B)ViVQSNk;Lgf9=knicm5F(+_)& z!(hfuZg80F7DQ?f^;app!a~_d+sB}_gma|f{;8QbOZYk{`3W}hC5&wvpJiTL4T~3# z_6q(7i@brBr}U*|0yA(Or`I*F;I%(ZdeY(|bk>+xhT?Z##< zs_UL#e1wp;7~PdRvG)8GLEkyVKL*-Q2K~aAyvu(mBi8=?^LyIZ1+`cSTw;!jUA&Rqv(;gnj5&Cc{*(yIkLx0~8rutp7}~5OU~G?VAo+;! zY(G0mahg}Gnyav&5wtQP)TV)$1|?yxv9SuQ`&wl75&aGoq~mlsgzH(KM(F|zGzv|d z8KOpkQS8GPeNkMwF0*xELMSJ#*MVzY2O@xQ80fFF@k}+l%9e4`Exa$w!DyfNoY|g( z2gZ32ihmiaL&uW_db~wZS;a~}5BM_*GK?-j?Di?jJyy~Xbn&wrM=VMN6Chyffzvm= zSKu^Q1+(ckUyEX9QpAxXw+Zoa0{-CzTy+Lje+*YCjU%BIHKs^pnJ@9G=BlnozZ%XY zc(9a2p4U~zjx1;HFSKkZR>%Fv$EEWub+LM+2e86An=>%&?AgGT){1z4#kdj^UW#h3 z0V1lvhDg*T>F`k@RmdaSnrMpHYsh0;Sv4yuNJdp&-E#3O8{iXq(Q7znO~wsevR6ZnCnrJpGOoDqq-Sxp0}85$~c;kQN}d9D1_<#R%)F z$of$2y%~9Q&Uq$ae&cPjs?kQ88sm9^#9BBBH@1kzT4d-n9=ikWYDsUAn?Jfb;m z1Ir91F-4f0D^2l;&!D@&_^S^6`*i|F7$_xBD%%o5B-&TPLq{_D4`x}47EDD`v-b@& zBqB~lN?I=;;oB&=yi&%~rI4jS=3o&i;%G_EE}dYO+R?kqeN;Rd!2LUef6>f^_L_K} zQPO$=Rjvt$xz(`mgYh_x>-^J+(SfCQ9iH#T28n7+S+;U$flVS&iL6z6DYcFSKVGMEwwM3ov)*E{sS$*laEIKc~5@ z8&2*1Q`8N}i&;l~zac}`k1`3xRr`o=rcs*YqVTe;%xgYwcHSz5+t4q)1kNRNXOjoU zN5XH_8H0jW6dC=4YM+bIx-8!HKR!IUFHFs}X|@ZP{^hcAO;I?;{(;GFjG+K%gDZ!B ze(je4fN&A|`d?y26+@Xk`n(jZB)-ETc!n)P!!iqthYEQ8GYce0PuL!>qdZNy`^F=l z8@>gH*g2W_PUc*5uYBuWlay$s?K?Z54KN+XGVr0_#^V>fn5#>84 zr7@4`Fy{_(76NkbWW(>#ru~6uf6LO!O7`>9jJf<2J!;u&@J6mAB>1`U%`aWPti#EV z6Xv*)!-waQ3#Yh_C2qES`QXdAn&7SOLQ7!E=k_dc?<&awYrBM8djq~kXngxT$DFFM zv9aX}VKnYAf+<#ez)u*>+TdEDJeVC$irC~~!#d!R3jjS^@Te^O?wz!A7+MC~Tuu@~04pFVy3_yUIInyK`bc^Yzk7<6E^xB&q=I>ia!Sk1|t0G|+m zO#s+Kq!$NkZ2fZPLFu^Gb5hPgM{7cEUg-7f*R7~KzHvZ#0A$rsYL6vP#+hFuPKQ~_ zL5=|pJ$YAbhD1bw73qDr;sK@m&kla%Bd*~2+($9E@@XM>qcr6Sz7tF75k!!}1!!HB4c!zK3epOMV7%M_(8#QPB*a)b6CA?6xjM)IQ3ZBFZqKt^ z6k3v>_>zh!#w`8IH@NCDf$vbs7yv6hZ*EBASWFzRAL8BZVr z4pQPIxm)8#Zc1C1us)rFv|Wnk;5-`3zI)Au)^OAlraudZ)DzRuNE;oHY&$G5@N zX{JyfN93KowW5egL3(3UYVgxU*j)f3T_kQI!JK!<*6E*;Q`JVf(VNE<2 z_5s|=Y;5^|@}u*aD56bS4Iy#;b-Y1I3>k;T>dZ`3n4fqSh=t_A+HUivx@?8^PzPvC zqaTb6yi#0GI03kEGB=ff_;#I-c0YuC0{2imB}(-Ha5MRJ5`<6TOZL)s zcr{gk*(A?SWH92JD!Io?hV;YKZ4?!jtF)W*@E^R=ZC$g_9 zozmU$gJO(#@d1T9x`Psl#OuX6e}L~0>dGo;Y4)SXj{R@P&z2%=ZnVnE%ZJJ%Amjd- z!WO@8tC4~P0eWH?cy%kJ{dj=ab})mSH1%I&|Lur_lG2k04{kyED?w}!2dGz1H=0E^ zpU&<|WJVgmLzsr049thy3^4nXAQDek*dQoGJg`z=n+h^1lliS^7xulWt80Pv3kPQD zH%^5?Ft-rZ_Ri1Ge~LSD9}dWd9;jBK2~C6$UIP;93zega0(T$fOc}3h7*)c`{(j!@ z-BUq)J!u^X$QyN_R(t%kY0n+uF9&fBRgq)N%(D-n74}L-WJsGKfW^z8`*@BIWPM$z z~e6 zd1Mbq(6gHjCdR)(hYqd6M9G(11{Kl)^I;*uY{OVIJ(p>BKYse;C*45`GUT3ROn7f_ z>29O=#nmZrX}fnpF$ha$9hVl$X?!9z)`rDrt;QtiJ=DZldR)Auo!=eA>s z(BD5#gb;=ImX+2bRDuaX4h#KVK!B_hJ|%7pOA;Qrv{5G2Uq?74B89;KI4)L01v4}< ziiPi%(sO{8u{Nre1ZJGVvPDd77=f1K!H^8qrqNB?IU>RC>WfdxME3}(41Ncf5L-Rv z$(Pf7Jc~Cu9Lz)&E1OV?o0D9m`Jm)_G!}-4>!in9rx{={?UHcqS|rSJ6_{(7F^r%E z%I5d9z$#ZF5tBy;FohYQMIL)n=}6hx4JD^I^ie%9Px>>WU{Atz>gUE#>eQmC<%cc?CyC zFLLD-%Nv@va$^Lu0j8d}{HHGn4^JoKb z#6{sJPW(e?rwcxfSWBS2dMVO=MPpEX_4AEd`rTD>v;nLvZAR3roOh^tpVSiMyWZKk z>G+(}QGnWYyZ*GTdT6Ur&FH0!1!sj=iX!bbNZK22oM$Y42(Nu^X*AlvjL^dKqn}*f z8S#a=3JM_OO<+Ptnn?*Qfei27owm;nJH)s%XJqk9d4qh+{pHJ-h%bng9Vmh_K2Vbc zV|L0dI3bp=mczwt%M32mTTbC&mccZ#3 z>uDaQz2*Sw9a*Yx5EOgj^RxMOe!dSAnQVVQzuugNrHsyVp!{7NM!yWES%6tfH}K$x zOEJ|1Hq7M%1E6P?AhHnZS8sp+*(Llw1EnO_0Ap>=J&FKN3g-B`%X7L%sj0X>m9t)K z9)_RQ{DBTOldV*eE&S(YRH$>7p0_0RY9GjuJwCP}+MZ3-kFiLYNJmi#9yt^fGeOhf z@!{BN)?co3s?E51!ml=gjik{N9kCemhEs-ox;@Mzd`YfxzE@i6#E*V@A~@KKARz#l zV>{bhC*X41Kp&WJ?p!wx!d_Ga+*~%K7XKz;whSVUH2mnDfF_$@LsIY9A~GQ^w%+R4 zrY9~Ll)E!8vkLk#X_@}4vNGV?=U1=V;1Stzc}-)e4XsC4xG?sRlT(FHn)7$N>alUl z#;kL3iDd{x*ZT1;pUx>TFh|1`W$0uTS$))f9ufEJ^CS{IpsESsrbYlUD<)Ab7f=ZH z<>1O>1n5o?BV23L{`u!Hcla}6ky(h3RUu21*|J?B`Gr7#zO07kVvIWemk zCG;;&J1DK^$;}rff>v?0ecXm0f;T+S6iSLajL!$X+Grzkv2tmVvSHDXW}FYJcNn0{ ztWonK9tzJy;-ZV7S%a7FVtqBh;aCHN*;C2y%McQ8iS@K+%4M&VFIqU@fkw(1yNHCN zaEudRx{_C8)kyUL400!0Z;vVbGbM=^L((3P1#M1U{SwgiL~hptdXR922ZBh|m+vg` z*44GS<{1)>gHzV_ALHU4xfF4|5J%h*f>9zzw=cxF2}CmLQLF?oGJld~$I}6iWjfC7OKS>c9!&arqC-fT` zE2oY}Mw()FzURJ%oCtX~Ra5W)wmSd`M@&4gaPmg8+uf+uJ3(laU{elk_XgIkNDbZ?=aV$xSCajPk|ji-Z^~VN}7c46yrh6ZHqve)`BE7L(03nlGz!A zj40#)VVJcr2<-*KXVY@r`4~_tOC&&hf)->7A#mK$IjgBXFyZ3F@%zC*>}+CU!Gd#- zmM}77ST_LB)OYiiEu+Ltmk-%8gi*XZeQ;k*G|_hC^+qMEb|&JbmGu@5m|4OH?_#fG z!QNAd`!+)KzKs`78ACF2p|~_tAoetYzR`;S0md)cl%&mqA^=}L7!Ym*)6o9t(S!jk zuJ+#^Xm!5{-hA-Dfp{cjRX`t0m_-A)HK6!6V7JajiwMZgXkC|6+S%2$PB@1PpQ6~+ z5#m=!IDh`&2wD9D2*mwmaj{Kj&!q+gBhg?K?eHGnzE1EwR(uC=5 z)w&B$FRY=!NhDs9LtyJR#)dR#XC6FQi22Y4+WlsPZf8I8Lmwl(cp%zYm~ngY!42iW zdB`Zvlh#jA`UN-sCC&v=od26zfBo7ItH1qqd{Thp$W31NLDW*~h%Z>O zWJwqRHBFM$C#=CAx@2_E)2O4NVR-RUSVJ=t9WU%|h}LubiP++{f}u49%^L0_g}OIy zz;Odyot;%+DGw#%7Fu=p?T~l@s}>0P1#I_GOh)gavY&&;f3bUHM?Lw4!$Ks8a~rwO z_h137Lh1H5^9LP@S=5Z35f7cU;ENbBOepV7T+pDY z$95u`Y^MJVTYhm%M~N}wTxCWbz(@<#0vC#&Dec)ut-x3d{8%y}3c%j1g3~raSwTUX z{SnQP_nnzWp_f$0$lm+*&Nnu;CD|z)6F6&3O?+yz%!Y-y;Wh%UZ?<)7_F|HAfL(j^ z#CvOBU|EIo#DU>X?|rvDm~BnU(|F8|JAnf+A1^Cp90)X}5Nba}e3DHD1_3uQ z#C+euI8uiNJsv{GSvRDQ-)T2yfvVl38YjUj{|DViqPfQRrQXPe{-~wz&KE&!Z^)Qa+NT4?pmovHNqE8mksvDtT*{DNEaJXm)7g=N+M$3s#aIBMOU;JQvW5)|3KR7G!;?cy8zmA6shX`ECm2+2 z7d~&$r3OL{URA(W4aWpaD71DkOnuK<(4?S!$)tK4vpOu$ooK~tC~9RPITv!j7p;!i z9IJMh6)Xe9h78d~OtX690?oRT7O(`Hs+GGZ%w}9Bo@L#zN)r`aPS0@K9;J z%klB5ECBrpyv_F_aF&P}e(ay=Du}KU{Phv#G-TH1&%@zrZ_D3b{+ZoJ7oxh>*DWBu{Hz zI!9>}Q+mJUKMRWM z9TaZEDXwJ@f*6y8Qh*w1Bb{nQaZ!;wkdG>_npXK2ROcu7D6iolZz0Y?j9dY@o$mkJ zc`D-ZO;6r!e9Lv$rN3V;O}qoFWe4a%>omhfI%Wz-mm(Z?F|=%B^ z>@wRmg$dIF@C*@q{rJU;d$a;`Y0_l5@OQ8dAF}x0QP#9!>b#BHCkI<7SqyiAxAF!o za0aC_&I0hb1Zd;FWy@?NSH^pCb>SX?gbaWpSXNavnr!iDj2(Y8HL3I*q*F(R(n?Z2 zVkba1lkU{X9fj-=`tCdA0Q5YG%B||<%Ti2bF?A04@us-nE6}H_3zPN|DsZRduIMJ!kIp#=F%{nyl|zo! z7z+!joFVz#nuu#uNDfKagrn#&SZKW^y4?q81$67t<1$Kv+xPGHWOixJ99>XxDW2nDM@vJ<82r@;uCn{}wx94CFXIH!MV45> zJ>FT80&uwjSHuIqC?B_T7VTxFJc5r{i~?#^=RA!YH*fC#EU4!*POV1`v55W9h4bky9~=rgNDZjl-Jm{g15Gtvy!bd%BzXk|hZU*z zzFD9W#rN(xZk;yxCNhnm%(E&vp<02sUvR1rW;mZA$7qGFFh!?M-qr3YSKwaGVOWI4 z)dOB3{vg9BZP%n)9ig<~I;}^E(Ew?vkbB@fEOsAYj;X|+kz`#!(H1I399(>a3>EU1 zeQ2-a=)hM}uitZ6wJKd&PvWD}1gq$)pd%JzZWyEc^N5^EzQk7)2B_#p_(<9%X7sURf;ODiH~^R-*2@@!i&OO2)JF5hdP<4sczC8 zpllw2Vzc}qn8Fn-i;&6#|H^Q2t;T7Hv_IVN;;f z8SriFxm$fC)$MP$+oLzcr9p+!6DVkjX8d~)}gaB_L-7Rh}nAWVv}2?ex`M z&z(JcK+MeGF$vm`l;r8i1t;a;zw6RrzXnb;>Lu+RpO{#|rrzPLYdap%2Z&G;Xn|k} zt^-(z1(VwXq%Fdo{9&2BJ{cBPXscx8uIxVznl0*PN5~QCD~K^cGr9}PR11&1B7Gr z94_m*9>c(B>X})1aaIa;4bCgB8vt{K#iJqf+u@i1kOORr_xet*sT4*=;KFOk!pojc zz*r7lFhlM@%tbd-u%&VCV|uhV+47tQ2aV$3!98|ET3JE71W3<3+<(;f{*d2EJi0dZa!?6NKR8 z?jw5l5QYGy^OH?0M+*g7gI=K|f>3+Pbf8uW5BV7r4KtBsE*S_<8myng8Pg^b?*j@howwQGe*u zovA84xr~%C3^ZM?6uxzQ?% zqw?yGHcc_oou6cJ1^tjbo%6-BXJ4a?*(5wPUVrhVWCdik6wn9?Gl9W^T4>=&AZ2}J zW70~95N5i%l8E472^j+m0Da$b6|LI*@#&ihw3G7*0p;n+U{0kit(O9O_!ej9CJnh+ zqE?~F(M7wtwj{I2lxMzg>TcVEySyqzhUcv?BA|j7x&>u=6 z;U8mZc!2JB;9XrNoRZ$KlMjN_cM^=O*RNlj;hDvi)p0wKIA57ouY_?G#B>QEYA;ZV z*T8xo9hEu*Ek%3MEjlpG6QmbGiey$1U~FQND$sU7xzYP!Q`7JrqJ{UjQqN~J5Wsd- z6GU~UE4ONNCAX9XPLW}`0Cg~57v)QlM~E$FHKayh_2&lRkR zxJvSgE@~RGbG7S~#LG_>4hYYr+e4#qF>2If^UTKo7ZtW%v~uiIWzoL~OOun7 zMFrxTX0l-09ReblC=_(7P}NjmND+Tf4zigHdq@+S+=2c3J92ae>_W$|oo0KPwRIBS zW78SPR&q6DQGls%V;nFY*%3^%mZ3<#Lf^haNi3ghYN`MVt2S)dEu|qt4iPzqC$<+p zNZO+GFoj1pq)(q&_=-xfCqJUAqCyGT3Ks;@YVO9(n{ipuQ|#aW9Y{42wuvGd1rg{T zI0_FPG*f}&nrF+$XOaam$Hc@AEs!sr=Ueg`V>q1?nQd2dZWW;(Tnj>Z078-Dc52ww z=~c?uwIqO!XMqS~(HGK&wCG~w!AxR5MyS=jqjFi^T|skV5qE5 z#FPyq6HrD`6X)L~@(P>HddLt#Z0o@fpFJxEwC&0aDuiN8kMaMWY1kA#y}WjyF|Wc| zYrri=Z3>2ZvOYOVe%>@i+bFewip21;fYbXOy|kOOUNyBNl;~URbrWD9SJV0P{TRkb zp1DrG0VNEtW(wX*OeP*zRCGi}7$Dpt-1(3u^r;aA zwYiKANvOGpm!^1kT_id9sH^OP0HcK*cpbmiB~Ety7=*q4M%vN2@_SPl!QgJX#ezfP zEd&6G1WkkYE~0vpd zz7ydcDaI^x0uCtF^HVc%2V-@1940duI$>ZuF zY?o;}@206^rYob)spXtr)xhj!gIu1QhTH}!r(nR;VU*oY0K+m;5(3sbOXLxl)YJCG z163&w99WEwBQz7{^%C6bXdpz-NU+U{q$eLv1HvbR!%o7cuLv%g0OrFGz$_VaG#0r! zv+WJ6hdNG#?qV*(klZg}wHA8KQG^(42YtnT3`_~B4CvmT*VI7kD7e?+k)}fUYxk~Q z?GgLwav5No4l&8d{O|0Ft$!zNy7~9lCZqWyRlWD7=-q0O_?Y&J_v(1ZH*l$Y_25mm z8IN>6EMLFB+bZ{^flJ(@Eeu@@&CPVGc9|BJ6~*8Hj2^}KfLvP)y9 zObHh#bI|_%wUoGeK>w`heO$n9fW=POaN(UY@YM0+_0Yhs+@K%v&<^T`3iTZ`tyb=G zSD5_#{v93DtD@Q9!=RZrfBW`ksBEn8m+veIrdG_S&U?4zpoY>!rvveEad{_W)z&~5 zpM=CKoAGg+#h7eZr!d6(Ghi`DvIvEMl0^VJbo@S^+qR{#4eEf6azKX~kOA0%c3p#i z`Hjv>gp59ij~UPLUvlr>fAkICUTc@a8SX*}KL*^zY~S*WGMxXSwW!2(B}Xu4L{{kJ zV~%?GJTG0gEXo~5wbPzl3Cx0WPBL$t#BIupOpGHOLXh4ZSY*cdRZK)F`=0Ks zktA+a&~qaB0Z;RZY;?1TiR>=r=N1ZsavER|+a zs?s#gq71D3JZs2J5J1AmI|1-3GAbR;0iVeo9#iG|mcGGBYrs2LAe3)*4lt`Kv9jN9 zr*tYRYTlP=3ur3cC|nCUH^PBUB#20uMawal&b*(S>z$}Jr;gbs(xN0a#3fva4TG%ObJDn#ELz(((Z(_8#blt&CS(X=|Dt#UyP7HPK#zVo1^CC_>T9f&Wgih?xt6VTIteE&WMVA7Q*)t9=yXX)L$9jLx2 zp=X&XoTjvJq`t1@1LOgpj1w#NVK3?g?>bm@G(i)26tRO86-Vx5F;E}dm~fnh<0;3$ ztRseAKfZoFjOT6~SEdyS2H#+%sw2P@E>w_D;-Prt1M38yOHOVSPQtK`+B4G99#pwx zT%K)UKqEH*A7^RpsZ-}Nfx#)r5g_{rK1lc3OakgcLx_Bve5zFA|v=7$YB}j-It8N@f9`Xj3JUW*kr+KY6ma?D6Byc$fI9 zn)HUf;LVLI4Z~ExzXHl`1nLkKTGz3_SLg_fqoDLsTKe%L*y>c++rF{p*n zbcJ?Uc4L&xuY*$p2qp)3GB* z+(h#X+H;h`D4mK@nl8W=i|NTQe?Sq?@+xACv;hQGTN3@1w`K}wbwICPdb?hkm2nnBvb+WNCq>Ec+gPgM=7^HNj$=qDGFOoI{p`;EdZF=6+&Bsa?q`;qH)04^$*vAO zd+wb1F~?4nC6}&WpZ*sb^B#N^{(!lS2dKl#D|tNDq{HN8qZz!~>FG)F-MS?wCeBVp zoZ7=%S7pPdO>4bXWYM<$VV>45K+mAsU>Ff!UJabKja$*2Kw&YEYD8qbCVbMGW5(ch zFm%q-*IlB;VA*t%NCp76qJoDNXTnk;=x5A0Y9~R@5kpRD7_o>q<72|iVj^gC61uW2 z7@+7;W-mh^#Qmg1N@VxXA3k)W>IrAg;hk%%3v@e#To@+sihx`rS4TME+Eq?Hw}z_; zYQzjsuQSgr%PlsYBnbbje7g45Q+4#cMJ|1y0U?> zO+YduC<$vhn|yBHzU>B9vIE@{E--dDJsSvL~9L0%CF%7Vy2wI;G^@bK8=ZdO@FK z0IDgdLJD1d7vfG&n(j2l+$^`Ce~6vn#P=`dgZCr9h2y@^8Uyxs_4T9VWM!k_nRdQY zP>=_|s$Xfur2V<>HfEr`fLvdMD0BSyL)=BG`W0H@uD!4%qbub>2Z`hoz0h5ZV5jbA z)!4z3LeAf1QIdrLE^SZogohQi1XHDOm~#)X7O3Xk=Ta@vuS+v7<-bVy;n8Q#7)Mf^ zfk%)1r}sW=7*lZv2?p8GM@c;9=$sj(6(TrvMHq5~tNp%kVfi(5mY3Mmt}N+e=vo{*Gea^}g0K`4cj@Nyyq7(_|=$=#++S7GN3MX^eZ-!KdJI{oF+3nC56mvA>*pY2K_+<2tPd9yNGA|g1OWUh9_%iFK1O~alP<=on9bqEBo;6I}@!P{CxP* zZ(UI4z*_0O{|+igKYqn(RH0y_GUTLmKn2kmugn=Hp{fHF6jcB8P^^JvAPK5{$Y~uz z$MT71;tFfLm4%H3$%HYh9@#Xt+%n+; zM8?TnN@7e|EQM3S?!SNR$rgIeT~!8nS_rj#7Z+!5%#k?y3?~5ki$q6XoQ`B!shDj$ zxlSIqjk1_vxE#1Xzc5%_q9M1^>X;*JKx^(zEtpqdT3U`k@O2YId7!nQkXbg-wRYwE z>r-{9F@bCY+HC-}YYR6`n2c1BqsMXhuwgl{3aYs30wAY3z%@F;$rg&B&z4i}Sq6zf zxpbCQ3`!@0AbzK0(mZhwedP=-f`f1!&}~3h*A9!U6-ZPKeWgC2_A}5BM}Ge4(Sfhi z)6-)h@Xc288>=;M{~xqR#Z*=~f-3#4R91#(CKzYgo53aWx2wexz8Xd9= z6IKZdpclxw-53$zPtef=d`U24zqYhEC=VJ0*gLO;ri8k$4nUyo{rmUw+$-3;pP;== zhVm&!4o{1U_Th?dOrx)jZ9TTM0eCM$JB`6GxQ*Ro3$NJzn4>RNUC?$0anb9Wqk5PK zZPk@OFQH(KrV!PRqz$M1okum`eAG<*cl*KR>^nd&?PlncA>{jaG&wN&ge~ zjPQc#&-qAmTghnRZb5#2S1HMDp2}$Usyl?eJkFYUCiIgjFxFP3b;9CiBBfp;y&s_| zlki{_4F@y#udtvpevQ+7O=o!b;QkTb- zYv7ATS7?e0f@tepAThKsn$5eh{m1~><* zxr136bdh58O-GZ?ovXz{R1DJXxEJey)}@(OE9_ppK_3{6o0V1A4dz7|0PsxWB$lSy zcdC8!W`qt!!6GuGh{Wqw3tKM?y8MIE(x4TzRec5x8jHXJ>Tf43JZh-P0;tKppgCBD zhu-SzT8lCaH+!OSy8=(fS77DWSo^ zx|1ePUcwYu_BRsOVA@_RQAIj4`+wR-LbzH1uo{^L3fslU4w_xKkP5-;tprg8QG7lS z)(dw1OsJ5>TVAMYgqxaz$?nFWVw{$i1rAN>vOc;ClN2~oeYNN##LQ2D;*sH7l7}tv7}nqp91gcC5_d3MTn}|t_D}9muF?s<%MM*#ufB%n&N*IEy(M1wQwqzTeObgVU*UlH&U17{U7lF(e1y_1 zk(;{}Z!be;^3{}AZ|QEEm~vS0=tmtd?#soTju0*4OLKGD2i=o^~U4G^DX;ELNY@>}EN^fnXc3M19t6Zmy=(Jgmi zem4Suu8!u&7GK>0%9>}taT2ry8iI_ghls_AdS>4D@84s&9;0|%8%qc$fF^*u{l|ue zYNwL0+`r7VZ4g5E)@E5;d~$-xUpbhrKTPjD=7{+H4I87l-~9B#eP~Ao$ae=kt;)3# z*whskwwF>7;E3MV8w8&84?&wU~exKn|9FNs7rd;GmIz>r>g5Vh_vnyCyZckhLD z=xhqTC78BN;_RvW+1~hb> z!k^zU6P*AHzLF8gTmhZonDqdDB!q3pEPOsi{Kz;$KxHIle3=M%>lA^ZjHCx*GCYf= z(+1GgLX%~S>Aw&{0)Y8Z?RSRstb#KIGb3Ak$Yc12BB|O7Z_sEC)>!`7V)pFWi(#L) zac=CPtgM7IybS5q{gWJu8)$pO<|n43W(j%XtRx-chJ2)`1j& z_%u;{wDT3Pi8SFT>PXZ7T5G^ThA#1+X;05mhkWND)@m`!MI&NAYS<-CFD zIB*DMIzDLeY~YTN&l?xa{4LGK>q^0t&Q#h9lhp-^hUUlFpO=WAs9{IU!@qBKB#y!f zr%z*gS_SMTq05?ps=Px-nH~!Cey>8&t|lZTbf6*^20a3WcCAd2f)nG-(J~uVKr@(2 zLN7nNGDtK~-hC*^JoryWjAM+62j#_PF>lg_cl8bF-7LWS1Yko-9TFzPszAfJKX!DJ zcs)aSjpC9iw=Jq_89ezcrS%N>X$jCpV#N4?uenmdH^0al;%w;2#t*W-x_NBj-MhV~ z?f#=og8c(VvF3Dpj&zyW%iIzUe30Y5G&Kb$uef>A23Fz!1iKtBq;JJ#GQ|TEolmKn z340x{Xj}TO1BmAD78QBpA+W^K@-3CX8EW-~oHb`Da_1vcHM6jg=Jkkq7fru6&v_1n zM^t-8Q>RQp=GX=2=813)+d0#d03CFpxKXH**hl6QSMVjW$Ez4HYm^SevhH|C5 zxw+Z$Q3VWd%G;5BJmOwSX02CavB`vbUmb9gC3s9{Nf`$+aC=`oHl$!(1j0>xO^-GS zoVN~@u_uKo4=kO!T|cPSlZu47ObXKDSWZ-H@e%pAZ$~5Js%ED|;X{5vP#N&2D`Q9# z3-J8Bva)q7k}>@MSG%}0&o;kQ&*gi8V?+~8kUz(!CeP7r_H6&(Yc?EWa3SawBN^dWUBLq(06?G0m3+mzhoT!8e6dnr>&Os;{%6R9K zW_a=js@`Y{Qy8)48~JN}iqq~v>=R`>Cs#%GrAsZ(fQIBI$PNGY>fe!FL@QG&leGy}gCen=5+Rf2pYQam1%?E&QQAWo4>W7zAEKot9p9N|S)1gxUad z8N;H}OP!n&OvWAGpk8dxcmR_xr0mjg#=d_yx_P*$jMA;H{?u+K)XRI>S*Bo>D-b(~ zWA6o;VACj_z9P09){!alN$x098-6{eua@fn{=4`R3X=av)0v0mxUOydMM!E1Eh?dr z5Gy3fRH+o9600&bp$Mgxd8i~!qL3z3DpROTnKh|YG?*fklp+)%^Y^>=aeRO5WAD9| z^uEt?-`8+n=XqYI%#V&1Ta##BkKsm5m-U!UPfx_URq{$363Hl1fG+=fD8J+u{G33r zvj9<59h$m*(2CWo=Pz?}vnM(ZM=yTj7eEXEsSLf_cjgDFib0Vek6s{MXp^;_9Om~{ zR$f6D6dD{{>k$|I4y`M{B9*{&n){k=NiQ`pP%`#O<%DhXW+ z*on07;K6^tlKMA-;nUdH_2Kqil1kN584a=5{1jYIt&THh1#nt!N>BHkJ(- zPF$qBs6@;51>p$(MVT%^&N`g^S3Lr}R<2q#4&%SDOvlqFIq;8J1TvAzDFtK~thl@I z&B5e`L24cmE&-P_FO~jo%a)n&V)`=}{kL0Q2G7Zk_RUp4)~e>KkK!bxs3-ZBzFuC5 zOwc+QTuobkH=z!i_Ssh(pX>AqIZlY`wCvpF*-(=h8LZ+sYw;IoL8qXQU{1H6g1VG! z)(xwhTU1$|OajE6zKXz7sA6A%Xw)!T*W@T`N7TQA^CfHt=Bdg%F_-s38o5caom_VikuLiG&4pbN)1VR>rWUF(Y?gBfzKtW;WSuK7=O0{5{ zd?PN7%;HTr=i0{d3t39N|HzTEP#xalfKLP3I_X}HZs~8h09)vb_lm1Q={+f;J^<){ zHA=1`+J>Eg8q=f$^{68LS@KC&UOwvms%`0PK&OH0j=_WTKD_ zLu^&)&Z4EGcrGKLvNiT2tWkpqk=QQsyh_2SxwtQZi_zKc1(=N@=ncWP%9<@=$PSA| zHOM?mQ=nrmfVc4M8eyGAB4`y*bTGq3sN?TR7gi8uUtp1E38j7wE@F__l1VsflO`o# zS>`6%6vlitJlO*2fD9N(oT~(}zoZmB3j|qR@DUe|+?N&Z?oFsL#Vmg4ff?1b&U7Rr z=*$dJ4B1E->o5}UPJO2#7OrxFV1TSb(V=k>=`!}?tmys3iQyQulKx}RTQjL-V6a0V zAh!z(!^rbx6f%v1Z0pOjZKO~d#>YG1oEIbvVHccRHmes7hO>`NgGmas?f2KA*~jM0 zo*fN7VoV-wq`=+BrK}+~D$3^7f-+PJqk~BhOCa)gr9j(<0YmstoRc0%B+azo?^fwA zII?S3ci*2K-Vh@zYC(bCzBT5ws$XIstYJphfv`MmXg7o)t(A=4}gIzl+?tk=KTf{PZ`Jh@wr;Ll3rW! zEodX=oCIb>a{Lm=MUlN`)jW9>Va&q>Vj{f)oQFeucJDS5a^rm6@WB9(Ss2?q2RUTi zPJVw$y*->FWz8m-47=(L7$Hx_y}Ji?8CSEjtq~M(8fzTRKAMTg!4GkHo0@_l0sPqk zo>By_kG(A?z7v75t!gFc1wgp%QSq=!@Zlbr zHiIIK%csE*rUU=P9t?eD*|NEkXSWFvHl*`o>}LERjB+@dv0$v9{_Hy2^6curUI40S zYx|x$bEX>^J)DMGXUUBku2U(Z3UBxmC(oW8%F3Y52+$oMYM2WU3bJ6*ld7t!#+DW< zT8&T8uk7iD1*uqU4`IvsNtPMJjh1~|f+7pFT)0V{f|2r*Qu*;(ZdN&c*FVw$<+O&P zpB8HuE}V}ti93qW^MQ!@ z@k9d(-UrCEHlViJeyODgGlU$9`f3zwE@Tv6Z(=kErxKRmeU+4zL!VvS*cWP{Gu!Ws z@h#OYSj(Y32r zH~&ka(};dV2(E=;yYP^U$15AVBL(cq{$`WA!rvjLA34>TWTYL35)#A^3qeaI4t@0Z z0nFB{@hyW0H18v&O{q&}D@#soh&?fs;tdqr`5=AMAf;MxA14?ZR?kqIOv_?(y^udF!H{v7QQERT{8L3X1q@u!J=rZqxS8-13Dse)x(FJ^IAdza;NNn4o zvtCCQh2(y%%JR^V5a-@f&V00cuqEg6HfoO@+2$f_@#D*aw!C(ni>FL3x5IGihE^kJ zl22z;>AheBG$%VGARxd1RR3vpb(@s21%~S};Pi+Kri>r|s07t2_B%FU1aa)0X-s+a zb~CMn1@Q&Dj1Zi;jpr~L!(;V8EqI#Vy-xvxJ1v{nO6Un@Xn~(hk^tOD?v4!QoKBrR z`>$vTg$?yIYRYT)ydt}j5kvdYx zZM@t1`ON`WT9CmiDmvL-bpd9>58R;$G>}Me&33$04CM8i)}&St|TIJvQh58p+o z;sv|$8?8{f@YC9(G9kC#U=#(^Z8BvKLU0nh7I;7tsF4pwM}_2^ag@ww_n|d<&E*T09iVT;Z0;Bk;JHo`%odi2z$i;x z4dAPf1pq_8Rtm+!k3X%AFs~LJSuUjM-(XW#RB$2AigZ7gKyj7WT{htPz10=2Xg8Fz zSHUuYpns!Gbr{p zFs>S+t{xD+d-nyl8+CDBv!>T>B(j?r$+NpwRvZPmz|e$#@DryYnXox1gtoAqfvGBt zWkSA5J91;7+|~bidDS9yF@|$E2nn9SpJ&$Q@PUjts13qqZHHuX0Od96u!r}z1I1LC? zSmfJm|Jk-H8CJRrU!KasQkY+wX!fB}N+@G%J0nCq{j0lWFkxs34c=J#gSE+>pCL56NL*n#+be8E$gCuzZfEry9YZXD?40BH(gH zyp_BjqM0r(S4Sq|37btPa{R;zXJ)Q}A1BSJ)ElWG9neVXY<;C5qs<~_HZ_9~6@?P_!{x`O_kHtUW4x;}dRxzsCKCdHuMD2wURXOKo zv{--w2FZYO&+CnYunrR$Y1D)X4!YRJJ?FU0Q)Kut$j~=<^Y`{)2lwySq{{RW#8zC3 z+;5=7`A(kn!Cuk{tf5pl{2f@%K(aA8((zv=E4K6J-|iy8Li{CN&uW#z9BAu)7}B@^ z-~#&Tg0P|c=rIxPlovDhy(UQ#fTKoQvuyl3*TEPykur9NmzwXLfHhX1r&QlV*yF)A z;%HFuC*Ytl)bBCO@iMR^nvrBJG=lQ_dU~yFuyyVelCql(S3Ghc_er>_4}j^3TVdMd zhF+^)GPg1b=gk zY#xN3Uk6FI7xp?UYO1Rzf1pA+4+k1zsv{$`*Kgj)@ZqL0@o57-D}+@hR)dton#uq# zSbXY1C6;Nt=E+RAr!yr+#iP^o_N`cNXU54`#)oX+SBz)AptFo2oizlNtxU)udh|F% zOEQ`br#qeP+5A}t1{JX3@OrD6GX-&?VBY-sh7?r`kcH=vYt5LPMwT>qVLCaC)e?ut zuQ|-d_jAX04;cRuP;&WRV(=nQ{df;-z{&8HKpsv6&;q?#W66SI#tD0}9gY>mvMMYc z9s{8W$(28Lu28@=&^j(Y@80_oo8L$6Hh1;$_0WV0`FgC@lYZ8lEj!8fS0jX}g*_%^UdME!nV#P68+_)~g{ve#)iK>);3 zYGLlQ3fSqC9?dU9kOGLP&tL>v&{ook`+=(i;%996wsw@E;T3$go-Dq;sVfBD6ms1w z{({R?i+6CCYJz^F0|)*M8+kq-1&kJ8-OaOdx!WNmj$5BsptXsEoj5bDhk zn!WmLCvI00BPg-6`2@`ItB~6_P+L6YOJuPLYYPEX@EtJpH~$G<@f_7+2+O*KejZ-k zjM=lhaXJIYJx8I&wKEqf00qVEGu#lr0b6V9T3p5MFt)7Jw9Ex~MAx0ALz5CT)G9_| zVq#Lu?=b;n33fc)D6+=ZLbxbE%lw3%-nEDHFknK1wyb=PRt{370BgFXlv0E_4A576EOK&h?OZPDqEV@k5mMJSLl8|4% z&@Eh~@+R|3Q2{DoHypk7_s`4qM0Qz_4YW>s_wDQJLm;PLw0r*ixtIu6&|iF_d@dEv z+r0R-036z($$cn7WD31iAq#Li0x0^ES>L1zMFzuJ3k!=xx~tP9CyB%qOFo^FkAjek zRxL^}c~F-*GdL*V|C4ZZDb9I(N- zlW7O#F>>DlNv1a5@F!Gt4&y9Uqhc9UXLXPJy%Yiyfd({?F=xP zFPjf`Sbv7NyM2n6WC)s z5Kw@#BHFz(*paM8cg%DmU7&!JP3mZ*pjmeR*3l7I)bQ~J`OI+2l*Mc@D50UAKMA!s z1ld|rt0_Gi-@{l(A_0HaWCJ2G-A+v3g&oo!h{S@BeF*EF6{ryr+zn-FZa86rQm*$x zH$CNIJbf+1fDsryn`s8(m+mqR`#>}jW{eAoB_=USnVKl@-sZ!mB6Bz~mSKSzl_@bBTW_uL`(2tlHFswX=f``|O{?@M zc12il_Y-bzZtI!UZ051E5N;V<IuhB)b6t-YZ~XPl~`>UMkC(8$UFz(o>}T53kQYot_>}@vwKtjxMZtl^*GMUYb?U zrohY1B;-+Sj-*C#=4q93Zr(t}fUoDDoUC5OL;QxgeKeYt2CP!MVxt3#Vi^w2!q#LW z?Y#4R4wS5UU*4-x9slcJw(6IR>D ze@PJP8S23-9tO2H^AEE>P>BM@6Mso=^^x+wc;P}1NO+CEk@78|8ct-Q=_9gLan zVJ-A6R^;(j;Af_|2>GRX=+OO9oBaf((uP~f=Z@|hVj2L{uWox{KNVNj^-Z5=)I512 zL$!5_dz<(0%%UIfHoLA@9KSl@9Tof+5R5Dy?Kz#2;bf2L495G?H+}s2b(auJv0f?{ zb%%?%I%gmfj2Jb_oR!h)!1$_y*JBV7?-AFH%q}9|PD+m~NY@Br535SEscwv}tOnk_ zD9k}s&l754*hJFhB-36#Z1z>g9cmj)z6>MP#KDs{y%H99(Pouj@1TOL>yb)VC@K$vOe^E+#ihu;RpG4=~jbK<#29SfRaEH=090uABFJgWG zk8-7ZoIZ)u1ET8Nh6Xd{K*or&dox2N8|(vvGa`i(&QuxpP_`;Ft~0odAy*quZ6wwH zE_k#pX;!f;KaaJ?=N*XqF-IeeTFpXkpy7TW3=#F?cSGy#OED8P03$Qye*JFKf6o*p zURp|uJs6V}iQ?glZ9jJ7{E?UMlIaBjZL&mSh}M98M9ZM2_CkkY%PfFdWxhyoVzNdg zs)OxcK`H4XCY;4I0B*t?18~!whJOozTc{?6g}OkzH3RcMJZ5R=Wtd!Jm3Mz3eqSUP zcL32p-Eyg9rDb&pQ2Lr0ZiI?M9Wt2~-%41ON=x@(=9ae~Z557+Z{|1M(?8 zbk`fm?(~;ro31tV18_x83Ck zImnw^C^~D;lpY}Err7mwv)*iE>DhbOinr+(-z+$N?!Ev0k@KWhdao>aCKD>tudAko zL+30t|CVz%r*8Q*X?y;sP0zl*y>hk$b%5A1CqvzT(Ubksog_6(E#d)i4l%f5{p5(Z zwY7;PaTDg_9JP3Ey){+o@dqnDqTPwWOr{xdr#sO+ol79NUg|@KPUkIj=YFT~euN#7 zk+7O#RMdrAfdWM4`HihJ*$Eu{wy`k?W>VOZ?Z1_%22xU~5)Nxxu5fb;yqJ}hOg$$> zK24s8 zjOeA2HzM|JFU+)`1jRFWxtp68ee7c_FK*ZkPlX+`!oaK6FE&{lJ?kxM-gMg&uk0Ni zlL0*Q08UO&FUKN;W988&rg@(4KYS>Jz-q<1DcgSi`Yp@%u3-SUk%u@06$Ud>b*39S zH=i|E{`v9Zj}@<5Co~z#@HnRG1CX4i*bPs@45B+vL;yoc;ILuI+H87M1Yo;M#e1B- z5e?JiVf?K`Fu~_=h)eT0Frz^aO7)z0{oMq^?hrW=7^WH$u4`ZpOG!!jfefDkj}|+- zRS(Rl!-l1h$i0B+9bu$Xu(Ki(Mdj)8f&a)$7twRXAl3^ZG#ZnY`-CQ=&fRwgk~*%Q zD*DR1z39SGbEJ zGwUuTl?Z>s#M9Gr1N+zhP_u@SHpfUACvKTC7}Jox5d2-W5kiPuXE|riBZ{n0;9NBv zKpU8v3=s9~SF(~X(&K!=sUtRWc%D}2t;)#ER7*H?DE7vUDJeH^E@3C?T3=j#0#S`( zWYrb9b?$po(vGlxBLjqSB@RXbJPSu{g&7b)K2I9di~2(iQ}R;FnKLyQ9yAg7tu;r*EJtUkJ6s|3H=X<) z{+OUT4TJNF{_YJrAVKOE2>l9*miaKI@537Ge$~4^NQjDIRk@P%rD)vrF(-tE+lx9x zjsdyq(4pOFgmO1d8sxHaWeIUUlJ3XtKf^^wa-424bSotqye2AEAV(9s8eX{TRid#I zhR$$E;y3vl-9o)v@}8Inq1RUY4F^1)LOjJQ0LPql=eUx$u8#f{v9< zP1=DpC79A>Ek`Gu`SzYnC}n+#%V7mB(awtN=x@ay?0pMI^(P60lbnKhj1xc{12JrE zV3Khkq8344SK`O`A$|SYwaH}YU$n^)CP}&ozMeWWnp+W?tc_JZ&2BYEr6;?pg?W=0 z3}%BmcVJ>VnsRMC+L(AmqLLuz)qLI-8sVSVKNV3}wh;naiL&8X>SwbUtv=hkUX~Ab z4Ad1r63!2HYJ@=>TCNo*|@5ps&;M zik2sXs4JL>+RYQLsXGQ%ib_hE$QvHv3inr#^9Cs1JtY!eeH4Gw7B4UP8(^`Sw5AN< z*LW;lS^%X|uoQ(!$~rjcuffJn0Q1Xl-@g4f-0?EXXfHZ2;rwb3UCx<0Z-|t!0Ziii zR6^N684KBaikFHY6s+SPYY=f5r2S(lFO^MmEZpk8>gQW8v;k=rLltZ^lyE0;_m^xt13<2jd!g`q5hf zDOt{%)c_(ko8CZ%T0j@yjs&Jw)rx zStN|lD=QDr%E(ZK zm0Da~9V_~FFn~SIeBNLqRb5W*anS7h&AtN9NFcTP(h#`Mox6|i+G|9eo&iZ0PjqQD z%RX2Z45bYYIZZrd3-C*puOGlANMWr(Lge6W9meYk?oSwx{vB4(PgfRJgiY{_RGEd> zHh52Wa;g<0-kUdX9$_jwR!sG?131nV^2~?y_rAfI%4Cn7xD3)3J==nGfK9P61uy<;P&P@NeN!uh)G(cB*WiG<(TM;plx65<9XcEsoKO-R9G34epN6)=mVo z>tNy_6%{=KKo0U9H(_nj)ZDB_tsIGHN{C9tk|6vu zr}{WKseXaq=EC+Woginuk}T`3TYt5Z@8QI#LWH_Z_iBtPLdf}Cxqi2Z`uGdK!~@xd z!^J|dnSBbqQOFJ`*P8_h96)WnmhyD@UJFe_F1pxV^`)(?6NfsP8;1>5qE3nUL&~@| zzoY=BQt>4NNq82u+yDGHQuO4B<6s4aK*2P>-usKAEULMIMYVqPJNp+ zbUGo~J-`iP>V5}X$b%+HwQQxFq0>);5qmz3fCW7g?q$2e!^0IA$CqEESzqJf(Ss^4 zn3C!$Xnc%iREogtpTIjN9_5uIn%f`-%R+iV<2H0Ez6w1LmIxajE6k5r@?D2ji7-#` zh)X*G?L}84c}%7A#ZCv|fywPs#-NPhP!J7T#+#NF1)LX^I3+TeXLu1+v`Lij$Dhse zS+(k&;5Te1+v^26r|jI>)$ZSaU(1s;z(;`N_7j^wZI}e$b}SZ&KwrSR+e|XWGKVR? zP4X=tKFksJ#`xv+CQumxkG7!P7OM`#bBCm<4%XyL>=mw%9rGuQWt-R-9i38<%(rft za^#bown8UKDe&%7Y`~X;?Yg5{v^e|dEqk=wp?fcq&;CE!RUzusT{fF)Qs7m5HeO;~ z{s~WV;nD|c_XX@8x3LeHS`652{*w0N3dWLkkTq)srJUVd!aMmJHwr0hOVWH(PVu)_TnTG~CMh>s>vl$>W+ zS-C-fUy763v3S_1%8H7|{~dk+8(ckn`U!#Cm<)JGp6F3*nbt7ZOFWkyBdT(!% z&6%vMs8UQxg)1hu-Cg0y91u=Vas*i?_b5 zPDI(jQ>Wev^-Z0~(nQf+6q4d-e4-C)=L$WeZR>}{2R(x;p+}#^WF_R0G^7YiIldBl zu}b=rejKtPLC#XVKy5P{u@*d!k75SR%e4<_7XwnAfzQ6Ws_$|-O&0S9sm3wPbt4gXC! zE5;$!!1+}bm|emv^hE95KMmQ7HLE)hGjB+*YGz>(!8(@M4S4_d?YS6`2C6k%xbX)Y+cvzx zliQd?#I{hs`KMBE$PLKj(FD-|n+T(4e40aP8X{*|Sm^Q#y!SFsa^$7;<#Ywwo^T`F zRG=6h&aC1x?nGN4(9DJo$NsXRi5^d|)4o+(s~XQvD`K!bm+k;3wGljL@OSFEYRKz- z1617*a*;l|f&0s9NGETHsD)Zq1+rGfXSQWUB2GJqRJb66%SmVX85H0!USgq+cI);U3tSsryhKyA3i~K+sLR5`K`wf57@LKe%v(-S z1_fciB1NIT8GW`RWYA*akd}Yvjts&hRkUZ*+~c}YzW(GPU08hf7?lo#a36lS2cf5l zLu>Tn{S-5a1h2kK>Lu2(sZkgu(QT|Lq>){|i0ooDWufePIa0kw;sas_kudQ6*>AlE-ejAKj?(i}1*RFG1c zrkt65ERR7%!`jQ@%BaG;MZDEWrzwXLLYk*9rdo3;;kLZY?=eWqxREKrTxeIaZ0vvt zq{#7E3Vi-hn2yt-$3U^ZMZ0az-wAMw;#dAcMn)|*3|5Gd^5Jeu5Vh-($##jL1VnNa z6YAYC@)TiC?Bqi!lY)vlNRqIatZ+E7Mj8JxbToP#)F#XanXuJ zp=l72hs~1)@q$LY6HkjJ@ob7bVUkpyzWBQGZy*B~{rIj2M=i<%@z2B8fWd1J+zWHj z1pNBg&~v(GpEfa}ijIy}=QfHJJ7)_iweR?OFIBPb|aVAUGJvcc} zLn09}^A4bfZ~Rtrz6*2HH4_Kz84BLCFS@Wk7MwhrSC~ySJ%+3UcBQ|2+(SVe2dHp` z{?G>Gm`A`IAm}$>H13oXBg?SzI8F|HmhsOrlI2E<(OtLN)ft(U_21IHV#EP7enDCGgV_3#X!Y-0o z!$tG}4NbmR?t+L{Q^;6j`Lg)jvfYVi&(7(Ur#l6U`G90?CO9Y2oFaBh)V?!m>vy}0 zfM4}kTsj3gyGLo(ao6wC#LU*hsgP&93xeP(p5h!nTm##$SD^l>(vt%Za-N86zzwR6AILV6iy(^l7mNblPuXl<=?uBEE;K*h?>i1mWh}S4pWjlg@X`kM)7ZAjhc<|y8#Y57xd@G9Fl&%7p9;7eGkV9hb?=mSl{C1(SFGhnN-4vP z5zVPGFXbAhkY`eXKQz!c-32PVKU%hnP)9I?Q4~HSm<5iOm#V<^yEEH=8rn8kEm?Ae z)wuGXi~twQzt11|x&Y2?%XM1oy`aA;7#X@fzdwcuWG%xVubh7P`e1(g+ z@S;rsQRodMrAcfk2j80XXgEc- zEYncBLPb_$u`@&zztc(N#iWC$bg)@Gr$-R5Lr{NpJ5M2c1)$}Z`GqX)P-Q}A2&|ji z_*?AdqKe+K5i!F#^4hZtEBnt|ym&Ru$NL=qQwMaBPZ#EAdqNM|?m$9W3(>I}D&avY zw507AVu%GS53xfe{4?Gf!-Sb#%5hVmSAs=aW1!GkV!=L6N`R=62Ep*MV?w+L=Y@JE zy$qL;Ort(*B>mVyrDB;`65*&qcr4(vdjS-pH)G7hUs_wm6#W~Hkubl%NhC*cpIisY z7xw*VQr+ay_;c06#%QJf=!8iLv4+lX055RQm8)0Zo?UoZ{W{?FXbp`n*SSy5t5%gQ zV(xzS+O;j1h`tp!X6x5y>piK4e-fU{dC#t3E168&p8}oP8brK>z*Gza5ZBwl_Q{*t zMQd2XfI|t=vknDfH4jP|X1x;>QlZK`0UG~QEK%7PAKw%E+ynQrbS?o(_>cpSr=}JF zr7bDR(z%Jz23j^LE}ZXkI;dU@Tjz@P!C34wN^+%MzQ_2n0tiu%TP=i0?eO72Vs*Wj zo0}$s+nu0<6l`oY`P&gNMk3C#fluzFYnd zUsaVyXpXU`OCt~TWLBvu)2ml^@_^6R_lpb#Zs^+_wT~Dpe8Nf3HRPimbmJF&Ue8_t zb@2wg?X4J#ZG;i*&eY(7g=S@Ei7bF%Jq&mo@}eE;@Y+w`yzK22#}S~ho|Wg)d*jl9 zedcBE`w%279w0hsYic1%e@COnq$M9g^?1#LB_ZtCYNbJW3{k0x6q#+I*_l9~qF`3X z5+&F`y1KeXEZFKQ*-OlnmiXZ&lmqDM(cI9Gip}!%b9TcA9*T{vcGXk1VRGxjM{AGL znt6+`B38|@lvDaVJC5E8hWudQR&24plRDGb4UmXcdR?7!8k3N1m|QOrq%GJ!tcnbq zhU`1UIc7AjD{vpQ82g1#HYO3KFrnG<7zR-zCGHhOSFv=*%lpDf7bC3`*4l#I9xvCe z3nMx+sOU0qIJIf&1@Rxu`!#7gE|T!-2g^?v>RB!J=n0cIN3Ztq=tLad#ZMkX*!hx= z7(_!o06u{OY^u3Tl22E--{LPH2)5V3*us#TbTU2ND&gS4Lvmq(^5HPm}D}4vP@?p^KQzWl?*kyrp-BWm*3zIbls~$uk1b8H^5gv zZPp;b3aLZjX3fRg46@bfzzj2lVg87(ywQ-|#dT`|J~F*wa4tMoojx|cjH#>!qnuda zfB+cR<)r3g?3q{(+oYVtQOr;Ji*J%Zj=A&t(@6Xd9x&4_hmz_a%IoS+ke+QyLWGm0lNfW64J*B4u)7k2DbFy zCYbd2pI*;SOfq~VY_TuecJ{=j7iZ;7_!L-bLb0DJ&}6I>t8)y`He)98LNxCnRLT#YKU_2z22D z&8sWp+(1MLfGj?=S*AMuRssv#4B3a^R_NtFGHd<<$bKCCRYylhIZ?bpPF6M;$L?VC zoa=aSDxt|mlr?uaL(bgg!@zHg=FaUxxz`FeUM$<$MJ?fuTpeZ%U|s;;nkGp`&;j8& zxTR5%m$T-huT)NV$zGz5FqBpA7q*cD^qtS%L==B0=nUI`eg60Og$oCiK+3^q+c{51 z`3psAB4Mg=A%$Q&i-;1zQg_^~ccgj5{Us--CF;;U&W|(fhHEj^2nH4%i`~HO+d=Lg z9?qawLz#d(J1gpQa=Nn8sTaHHUojf%fHx6I!DPYplBJC(KnoW9`}a1YN(TXb8L1dA z_(3=WC6i<(a5V0d@SB0cuw<8pwJT3n-$8=eO4-b*pLLf*WccI9kIMi8_bDMASOf*2 zRm&veB4}NP`OzG3mzCb+nJPj{I7cI${}>HF@DjJvh4dZ25wjlMFzIj`zbJy4aR&)g zY|B+c?173(FeyDeJyX#6#c;`|acQ>5=EXo_?9OPTicy>B@s2ay`@Cr~t9n_j(37|= zEidIv*ji(a)bbk)u&4CK?{yBA{KtPsL!0!6HD17sU^6TMH4c^mO>U023d=n*{8WqZ z`!E6{xI;Yfz=iTmF>Cr5Ft546IrIMZaj8C}6tT&^i$oWxUIM7FN}g_Hh5LQr+Ie0u zHfA1}@nE8^ZX&*SZy0q+LHP)PQ~wwo&py>Q63Fn*-Mhj$$2cQa? zLjlq|tRNAGNptx?pD!&A8H8Z!zUk$BD1luuDe57*C!p436lsbQ$;n4=zd;cWpxk%~ zNrZm4Cvg|2xDUe+8MY12H6ck<wI8F^~{X%`s(`m7$f3|Rb_ z2&PFlRVnn1BiT_xYVEP;+J*|pBL)Kin3b)XtncngBdUkBBj#Ho)hu9lO)h?_)_);DboAo zm6yx^WDCkmsFOYQm~V+ihHbt2Q+ua-$%)!{$gFE-RQBbAdXTZ_8hBETeeMpm@ZK ztNSNnKhE&sTbNe}LqyA{^K8JV#PM756xWjh_(rfDI)SO|T_h~Kr2}e-YOmPh`Wfee zmGB)i2-6FA+TJqppD>L`~Lmi30uGGv3w7wyjG~N`D8Qa5hF%` zMf&pt_Sy=a{f#6{UVb6!HYXqK`pIMomUS_2h;tf@??;Xh05YSohmwZ6ErDY_meE8z z%*_l2_QKu60%pjf`SXV$KfLV_J&w=xcVB6#QsATa1cZs0YUc_-8!)bOkh3!;odQmP=#gnj=y&Zogpcc!2t(@JoikG9vOb?QWuK594a>v)AbcE?2`R z4j}mUj$=dNgu&7E z#d2U%h-ohOsMSE_FYyHs{vN`kcVupTn<{ieZq@UoD_0!Z{XNZ{1WzhVrn(Ylu#c0H z)Lv2=6ak5t@y}j?dCkZE2rTLxs^426_{`IUm3!H;bF60xyGDy43?EgZG)E&mXUXGU z0gJ0g`T@)A0KM48x@9g{5}4{zA>d+;siz6MS zPY)U0PV=O&YT!KubIBh4v>SdD3mFQz zW2`(#71j$UIN{Xr_Jj7Y4!jGZn%h5!bzL~<$GA)<=pVe0x@}|*{hpGui2g7JBx)Tr zqgH}<46zok@_hvAVf5NF;XUOMu%&>P9i0Nx9#u)%GiCgigi#6iw03A)^UD= zNveHusbV0O!E`eaA%%^DLorT+iO?D3VRZq~(zeCo;j+QW$q8CTmxZGa;vGpZQHQVk zIati8+W!N96)BNq8K3q}Uv%}M8;wI7xnSDz<;OfJ&b#e4&!ak>RNJ=U6*zhm;~M!s zeM}A^rN${_KcM_qSk$<>c16+s9O&;ln{wy+)n{9tdXV+PM3*!B9>4&0OpKmKloPr& z*fc=^DAjyRb-*`Gz>Hx8tF;W6yfFT(gYB1&x5B|>?ZcSRCRMnXb7Ey6eB~sq`VukZB`Ib=Lok2$lhjfUc zp(8uuQbB|d63E{QuQp6Y!f+CWWAhCF(9YAdMXWpOfY7EGVmhW3m8}}*0WZjSST6hO zxBvOIi=K!y-$O8CbQNW-08oT+G&`);F(s^_Jj6x#Xd*0eT8N!cZo|b*eYoO+;9EJ+ zgbHsPsyORSjCa(jYLnT#0i52O1zvaAK*#QeYbZLPg^vR{FG2`Q743eC4cgKH?bON( zGVML7+g_0=*K!;aaL=;^ZdlHj3~NB6FAyiH-LYI!Ur{39QE9h$@m;hLu%lu)eZ4T% z>V#@S>|d3`Aon8!m{tV1Vw-nQ;RcmJ6MdA~`Xy9oDuV`HWG%TaCn$O}S4sf< z0c|a<0^*K8-0u%$A$N9j3+D%6XUzhUuDJD9(EExBR}yTWy`;F?WQNPq0lJiQ`Fvq9 z0Eq`reZVzQ32=_t9TGAcuM>4hqx#s86?oU!+uEwnWO5y;8A3u^$7UN(3V^VBg9$iC zS&TZ^Wk-%5#-30!YpHU|XyY$4*Ym>S#g`KFEf(zncLV+-IV9Q*->Fj)$1dj#UVRp1 zBKC-u!!Z!nxvns&=r{Jz@JRqW=u=l_!xYKKsG^w(@?&?sWI#_V?7i~p+b|XP7o`y2 zXBqYe^!`8usfipJAAaW!h>Y|>WdQ2~x%?v{EwU-4cM6~RBNKd#uHM<6srRrQuI zrQd{*cfG6_2iXX%X)Gz#iX+ejlqUfxjbb5Wp!ltn5XJOsyGh^SaFhyjb32217XZp=F;G`t zcxm;(sXXi03>?K8v|~bkx$mZb-hkiTEv0wacK5XJSTj~`*M<74BJ+_ zk_GsZKZD`A=^GeGBR1+Q@ngKzb10tstY`?KUyH|TyPcQP1aKliZ&95Zk=`@J8jl~} zKd)SbLrQ&fbBM4jv_isvUsPuc7KA;1`m~;_ZxEpBm#qDqUSB}}A2D2jZElfzOaM zvvp+$P~ww0ayFvgpNlCRP-<_Sk`>n??W9apK&>QG*S4XZmzoZtvYlq4ovB+bpVtvu z(E~1&3>5G-x@`b<8R#wDsLQ5<_C#`3ib#O|Twfl+C~+?+z_pn0mO`Mub&y>jz5m)d%!^7OcmDMUpFMDp2KUDmry+nerM-g{;Y2zn@=C$5wRdli_=y_|h$JR|yj> z&G@*uRw|Y+m|U83Fc6&euSGf|_TKO|B!FtDska@SeM~S`Y~YddKo_;t*B@P!<<{dX zHIFX=$QNv&f=%+)cz96dYjP-oOU-R5X-hfvZ9p=S{BKVj)P`bh-U?6^l~?B_%z;CW z962%s&#fyE2OW_8*UBF@Wq@5<r1NyyVWabb zif5UWaSH6sCxj!5Bx_xRh`C(ca0+JoY%jGpb#D_13jE`G5Z=RVb(fG>U3AlcOp$V09-zb1&p-ku|@uQa; zGIHdsA1o`q%_yrC>wme>WNcLFC$FYJgEs~sRT;F@ijaC+=B~P`b`kP zz&L6tvwSJ5P%Fw6p{lK>>gZ=IZ_BO zBaDQ95EiOMF$8{{1@S0K;3v$)jVO*FGPM!qZ>vn%qUgYZ60VR44j5^*Gt5X5IgmRH zDtiYV+;Z|!k$3%VHF(-MvcRRfya^gyO?9=Bwt@^FlcmFw06j)HP8l&%>%~;MX$5a_3*fhfPH=g_jU&LJO5elXYXO#KRlJoA7pb1G-AiXhZ z6y9S)U=aubT2Wb<9Np&*3X>DqPUpx=7329d4)Fu4RCc^4`y^1Hzat)u#si3@>(=0T+iXE1OS1CmT)6`b82Hp35OumTeCE$iCcDG@kjzgf(F z3*@C5dV-QjPtY3mVNxhl)xilkj+j9>c)jJdxr3l(la`j=ESM+DpnMT~FMI$ZuL47{ zOJ4ZQ=U|Lb2lq6BlJ;qJb@2eI1k+U@82 zXA|H5bMvPQLW5q>7Mfj{Bt51=GyRJ{Fvk^Kmr+D2%4#{*l$1krv!mr8N9!pEh;_nku-snSzRuTPDSgRw1pPHmHNJPGj5Th8@CTq*U((|j z%7+i5k6CMP0-R6+V%ZXZQEkeRUf`P(n5c)FA59V@6^cYWI6{QYETzSAC}@ISfQi!t z;T^{L55a~0^uo)wUa--8xF)(|#zf&z@e|&099Ol7l;Xm~)|@v8-BgL$`?v)T4x`1m z*3&axEQ>P$2|^?PliAk+H#awV7)mVw-Rqs4{4@@h#4>lbzSP)5#~98&qdeKlzE z1}G^iCW>*!Rr-w4Joo|{WBdpG*nD^#|9rud1x9a%YzzF5%bHm)CQd%^tlna{Hzp%1 zOP%?NCMt$1d^5awMsfJFrZS79L~N$$h$YC?Qj^!h8F*>%7Yw_(1anWo@Tbq7O~zs0 zvukIL;e#x4Od{Ptk#Iw&LCL1d&8GqIaD#_21Vfn>7aYar(OQm|H5kchI}72jg;6T zwo=3#K70lwqd#NH4l1)0LegKXAqyWXn}^r>Zm~F^QG`lN!-d!16EH9hMvGz{#B`#b zoLJrr!yBW;-po&&f$xKu$0YOoXf`1)G(!hdkT~hamn{Tu`a}oP#;0nnnlm+0sN~?1 zB>;GGW;*k1N=aj$=_>hKl=P4LdQv9Q>((s;OfI54z1Paqo<_;A z31^~rWN~Af$3c#l*Dkf7^WDhF``;)U(a41Y7iT(jf$l*f(Bwe>w}jPQA@qNQRVF`P z;JIV8v>Hml<16U)X9Dh15v9`}oPji;1(GD1Wo$PXOs}QSc}1#70VIK(Nwnyrj1vfw zW3weVq$hhg{)D3`Hg&-zGZ94j2*uGR9qrBg0MuMVVL7~IT(X<*?usiy0`#C*s?wJ{ zEXwd)o}JiraFkSbOaMA`?Kn@gNYMbe z70euR*b<$K-@Au0QqZ2L!$4t#a0GcqWD^ntFN) zSV10TN-qrSJy8qF%F1T&1a|Ovf5>+0@>MSEDdA(C6@S3K_>J@-`^JsuVu_ADr{Xi{`x{nzF+JD@D*8H!2y(z)XBjhL zb8v!qF5?J^5i0R4ENIh|f}9#R!?CY+lJ z^R?ve-5^TQ4npCsp+kqp^Uy_B{!8K%5gOVDP^-=&Dn-a{dIYH6NKCxt4cu(bvI^vc z-$;Iz06a7UM(o<$+X<*2Syv1ZwKo%r{q+VIe0-zXI?Z1`ULs*T$&5|>_+zk(A_^g6 z8sBVq94}dy#iYFeAfSNfqVg4W6cWY3U_mj{cPneLPuZ6y=nDle%SfD|vJ{GrkC(We zK6^aF34EGf(K`I(1SL^~D@s(WZ)}~&4Gxz`J`wgnJu*NC0t7chOS&`OD#CIS{hL4I zzCs``qRwo#5q80<^f5nq17Biqz>{yxc(2VqwvSfTE+F~%1#qsVinmRpG`mYA=t8|= z|CO@a>oEnfbdsc3Xow%*1B@1$!N_b zjGz#ka!waq13$wJNA5ZkhOJC18XygPX4qZ|+r~~_>NBM+re7*PrE(Jm8KEB_@me>j_uJS&hK0hLN+VK0714g z0L;$HvbB^q{X;xJ26$IU4`Z;hm}NJ2uEyiXj}^r}FRdX%#xUHtl%-R`;OhDAWIez*CKl7_V%1kr5U6Do=%_FfVrhWM4fo|%;k${LPo066miuwIz z!uV*){aR9(3C_9anS9{Nu@qg8YG`u4f>{#iLoRC7$IytTzpSk_qRZZc4MZP7%d65W z+cMM5k4Xtd{S{yeE7*ds?z&agKtn!Jq5tA=7-&oTPjk}X0rI^ItFYzofWUs zC>1#A9%n~#I~Ty5BZw1okm^fR( zKVHV>Qm_nn{tf6?BH)ze**}&hOzRF<<=ra;uCyZoSM*N zD;^RliDvdE+ zM~gRO_yKhuPo!{_5a|@0oz^-9UzzS&(gEZ{+x-x|V6dG=WdJQ?ygOc2lp@+Va=)h5 z*J0hN0NG%l&gc;!5Bpn+f#*iP(071C*|mIZeq}VC`eUIFrr-GQZFK78%hmkqw%cHL zU!aG;t{exKMV78J%5uhxsMFx}L}gN^yiUKKhV=gwvO!mIA$Uc9TJ`K%S-jN{HEnGR zO29&h-5C;zCMlUMmO|nPx~CC`oiE7d;0Ku6a?6$q7a?~k<62_nYF?KK2xwRxd6a6* z|M~O9$R$HIvZB%z9nqjX-QUDXoWUK|@^)4tC2(B4cs4(v?6GFeC0JrJIbf%SwcR|5 z1XWP6w0}m9oF|%iRN^jTiD|H@dbc3wB)DR1NxXRW)TzFdZ(X`binv!r&|;hUmgQhJ z)+F6B66Q`kkVZityG=qCf;JVr&Gu^cKyz~0LoesRWC}R|g|x8V7eomsl$S3JBA5Y{6w_k>c|?MaWfISht>6Uw z!W;(ldcIhBFkyl}6%R$PXj4}sal%`DtJw2mn`xhp;nWA3;hk}Dal)6QGar8%s4A;F zOc*d$vSO?!+1rgJ1b$76BPML=qoV4W>d_c%Vi3`)h4gyRx#aI?|Xz?$bfdAwm zbw?DtWCo+5yn}IEB8XuYxtG(i<2mNf3$Vqm;OHhldOH#d&JILEJ@AnzT9+T(d&mXH z-4r?wM)FcDF}W0oM-G~O6J!CK@FWge^M0l6*+&{+4?&PF%E^qwP1SAwq*(5=fQNF? zUPnM5pjD?fLi(VNy+a!lC}7`DU%uS_-;Hc&=pmbD4+CxkI}N_Jwe>~=>CLkbfG%DG zO&SAoF}35Xb!YWjT(Y(>Q22?hyViH4=_kj_Mhet#Vp%s3`P7}|k7kB4)FZ@Pq z+lHYs1w^orOFXWrv0}7hy?F6^CKPJc3aNNw{*CY_6D`DdgowsWTj~Ia0uE1HKl}03 z^=D_ONA9If{K@oAd)&AOjoj2PbP*RIa`dDS|H-QvINtNI5tRajKoGPxW0v-~Asxip zewE(Di3=)NEPyjgB^{FjO?W1mcqim9d)nbUd3n>gSD*d%@8AC$P7ykou9QcyU;-@* zMjQdK3R`q;nH=HxHe(}OJxqU}uG#+Y7D_mFHL3Ju>jp!&*BJ24VgE;3a4zPlx1#KDiW9T@HoVPWUf%loQtqTzYqrh^_Vb?kjhZ1k=Ij za<6}resKvl7jQN;oE3Ujy88ku@?V^XgJKMqOAVI797W*2jAXy4LN|-zu!b;L7QXB= zfH0Jhtx#UgmPn+j_JwZfDXRxf&CHzjX4SLN;Tkqro5%nzia}!K(FL5YokGLHj2UG` zA2?9JzPyjH+rn|a!ppOSsBbEy^R)b0fJMuZBsGhQM_Whd6ZBh}rhm*x z`F<>LP|ZBvSvb;xPVL1Ud`gW5I1joFFe1rq+ixY;p^zDUPC(?&; zH5C7A0bxHzqm~Td5ryt;d!=_6SB*SD4|g!d4Z?sg z$CH6>FVcxe&z=pjKRSM%5C!qJz*wDw3PHXj`RDWBKi!&s{ICSIaD~%*lYw8T zVCeY|U^*n~a6ZK3Oq^scK5;X&QIy6D?^XcVKv3dHR{s%w#>sK)Jr9G&wX;x+qS5U? zY}omAG`e;%c8iH{wOsZ2W!IsQMbO@j-GAgr1X3?U3hT;yg@tJt*#T9V(H$|M)S9O`@*gz2EluYL3EllA_g7WZ*z^ zD>xm0|B+ATuo%QddTytR*b0GiEnHrq+?I) z)$v1-9}s))pGO@LjGw^m6%9`LlwgWNremz zJ3hUCuSAE=8u*wnU3kvTd{*A2~|4vVH*@IiTPt!Zd6IgJSm4b^5@^s%wI`Q7>r z8!DO0WYxm_fE7Yv2QI!!`}>KehxNc>tWnoe7_ppCBGD|@&`eAwe-QdFq@LmbQ$`?D zK96nNNHuvcDl#Y7eIu)(%l#L(RiJCx+dwyl*MAE`Ci*u~PrgH-ChkHbg893x}0HQNIh0Sc&swu~F*;O~V zIy$-m6bNj78FQWjb^8*ueEPAmv;@_BrwM4Xr}K}G5lM>Hd#R7lC2*J6tb~O2Or7s= zK@o9HJJ^g5LeG&wU!4GFA%kxmX18RSkI!2iOREr&^~WVyE(WGPc5zW;0Zd}}F_K!; z(%sEsCR6v-ct_*PjwO>O1z&54h2l?|fmk?9qCI);J!&rglT*d8*8+dLil0gy?Bh#Q zR02z~o)V2ictr{2dO)b9_DgJzxOfhMuRR?WWmCssP|mL+!+NF_ASwRL8F{#x!)_w? zs-@?93I5r>W(tDoH5w;^ z@51Qal>~~Gb>M)y_PBAHWgos@V-+`h`t&WZz_t^YzBX7(5jO}HT#G>PR>5sKblXFl zt+xgAExb}DSF_+EKc)Bbv^mFMi?-23d=zd}Y?AljPL?NfJsaqHMgf4!ckLR({Ih|b zh`pi|@BB4f^z_{ozv)ynbztM0z?pH=H^aF~3BT zGJ~$7*oUqd;X>Gt&##KH(3-%TB=*pH)BsFaCeAwlq)}?&%XrUB=sbXU3PEaYc>8uD z!`&P({H3?iPS}eyC^$+VDFENu_cIEQe+d7-ja(hp?Fh(LeBR3_umcz z(h-zG=c}uhe`eqg1eaa~K>wc0)`^!fm(?2M^I429w3G%2-qIJpk))Um8F}0YerNe{SM_OeaYqDXK4%5D zsIIOKfX#-Yqxk&X%L=qWB2!Vs?JyoV(JG4$ezW@=(IIEhyF$m;!ez1#>W$kd=5o0n z&=^O;Yj6GeGl2&ElFQj#N<}3Lw`$%o+2Y3ac6Q;na5li}k_Qq^_j2XhwL{pxzNHdm zqSQ*|HoOGb^%(iU;LgE@BSwufsbjWTKFusP4FbkLC-axb$o>Tof`TDZ$TX+ovqjjVBqBF@S zrp0w6!G#{sZ20g!(Au6estWIo?UQz@1@Q%k3uJ^*XfdV78XZ-^lP9yq`~{r34dcyy zY@8AQZGq2x3(5q|^KE78uRx#y0FnD101M;0bS9h}Fbl?GP}aVjY%tG(GUxu3D->YRxs)g z=Ry&2rUM!QXw9IQUd!s~Eg32jC?mLF?7#*k8lBs2amNVD-bEl!C(oUG3tZEcz$chU zc92DK44?nAIjQb+X{RWo&%9sV zxV{3yNePsa%^1)4LWJ)X76eh|J3B6sKI+60dAK+#qQ z)njjfJuhY{sAcgBwq`>!#l=-34WJIHi%S zp++uDU8aFjG5#aCvgSL<0g$FUpG|#1>pF z9%>JcIaTD5W=?0;zzdec*z(bNpNJ)kVpDC3ZnTW4?KdRY_iO`JjbHae-VebX`iM|& zP`2M0mtdHs)3?2+4cd+rlTNm0pOMS$>*vhK(VH++#wi=T6!IacCV9LcO6w zcLB`kJ*lWDz(5QsrgH(Sk$5VW4yQy_0DoxlzQV+Zg|Zt1v+bkMemP!B*_+=r@kL3(cgA9>NL1fdFl36Ww3f!_){Sau-qD3W3iMD^l3X%uxI~@A1W08O53mi6fQoQQpQe19g*&-a)`Z8D@+Z4Z)mOF z0t;VbAVbX{VydHPP6uJd!f9xPkK>q}nAjvpw<)Ji1@k4Q(q1}oHTvU!DZ}^FpZ9n& z`z|AIq!rg0^u6fgyr4f4HWUUVAaY{A;i`NS+m*YwZ(IMvr~5f~yW)2QY8$bAaz&80 zmzjkHZTQZK{kCgub719Udv;b{u$Cjuey|X+2h#af(IPN%8Zv^9;#{mlZ!E6Xpm%G& zvugRFF$T1 zU~B*;B6A6wOP!<|L7WL>AetYa83t zfw0ChOx#W&&y=Y-kyPbBAipPgh*wz zlgl&og`T|#9O8i^ro({qRFTLA-ly6*}d%H!;`#~(?QCD z3m18?ARQ_jY{Jl>jz=vvlbDop1^}Z?AgFo{Wsm>6)z{^CsoF*oMN63+ zoWtC8X&pvU&72lpbXgiv)56M(G)fUJR9sZl128lZ!wU>Cm8sY^8u>{`BGgHhnrdrX zO@~?s_AD#W5~#G2g(7n4LeUu4(kduyYW-$SzdKcIx9S-a=)f1lx2q$W9%gPiR|Up6 z-DWyFr{LkU1Y6j4qU|KnEQbf>v0%ZgcG0?_%p4}&rKPKBTJxE=UZQ8hFD88Ts#VjV z+(shi4xpGWrnRCd7g|r87>y^gF+@#u63(g+$%o&-|44AdV00g2+rNfBnjZS*H}2Qf zada9#z=af`S?&X-GZx!7T#o&L&V4y?F$WL+Yl;Uk+BzS1TGYLzULg{RF9Ucc)ZB1B z_RWoLa$INqd39=`=TtM;yyZQkQ(uI;G1rSoY!qb4iJS&)pdB+z(V!;?OUz(+x?2+T z4n#!-37sksVGpkEXd-D9g|9C82|4KI-li_hR}c%@e;Kx8$8Ru2+hHiCU%`KDgu=HS z?684}&=&8n?O-_QHCmBWcV-jJvDZZJX^It=uvLOY{S{({Jhi`_(8^*X3)6Gl?gTwL z*2RRV^b{_F6T;-XOUAVnGl&WTso*3?{C`+XTcYDMr4LZKQAFfp0K)}ce+ zK7AA*^>t-hv2s$*;16}Nb|Sidt1ebWP}xTofd||Y&X*DR76Qm*REiiXDx@`hfSj%J zy*%dK+<3i?_D#=1QE)CJS$`0-LNwQJcu)<3>~($qZu!3B4~cZdOB>tPMx0nW3YVjo z8B4kyVg_jb@k58(F#$+)0!W)!#$&fJ&)K;%4kfRV>WljUc%&DVizcTVo24g$39)1Q z_8h2nv835|l#=IErtCl{lh_X;@g?VqwLW=EtmHEw^B;D%hauEIoNC zJ(+`a{8?4{Ysl~z5AWni{~=U{GGmMB77IKrpzobqx5|9R(G*gLk;Gi=ZqxLK&<`K331Mo1NAktj+`@)%ItteNe1y1{FXt;+JJrqg>-tMbVHKsJ19Wu*XeLyJ zrRgEMapnX&0xD%;f{65MY}|Zxq6!GJn zMZR&jT*IBhttyc9ij=_l0auT*jCaFVT-qhrjj0^_>05A> z1nCDKrz9E^A-^z+sIf<;eWw}v%@=9GA0H>RSj0pOENB5s1}~P;&)p1DHS(!#L9})V zU5wc05>)9WC_n*RLOZ4;XS70^3FQv&2db-r?H7c{-0_14+bx(eLyE2>j|E8#0lmc! zG5tGwMu;9i$9kxA}iC(D%!13i3fSuG`YQa_Bn@!X- z{Io_H%6Hhe_$zwdAM|Wh05iF~MrUb7-|`NLa0gTtw~#mVl97?YI&9V|*tE+SDYXm? z6b9+*Ynb8~w;Y#@h#C{D<$Ct+U9^Egfjp={Rs_wM`=0<(IY5_*x2tMNSy?}}i1yH! zu$jN-r?E9~?b=)_?5E;YM}+;0uJICMKldfNRUPhA3hze+!i2`T&9#D zl*bk9YR53!ZnaB#&)j>8si2=kLPVUdg(4hUW4@UL&TQvA!wb-{`Hdq*yhk5Y5W85C z(EZJyJ9qMr=H`BU3IK{#KVSX*v!sn9^O3~6P$BUFH+F!#v>1r31y$(V%w^?+CqR~T zAq1DP&hr%V3#LF_a&aM7mVEgB7--=}T3UA$9p)h16(HHkh@%YI$gD@;>WpgEm>%IA zoA~YcD(JvsoyhBByZq&~UfAV~TR!UGau#l9RszPe5x8$yBF?K2ZuHt#a_*p+=#@;a~BB34+p`|%O*Y$xhH+>A!vl+NZb%miTA|lozgIA$` zIAeUV5~=ndW(?sK`A^^%x0l2NqkP)JzD6CgN&tPeCw2(j)%|Rha9y9kp`ljnB$+Ce z+L44bRIDJB;~fj zF#j71KYU3;ZNzTZTGQAdm%=|o#qLqK6J)=U-0 z^yKJvqfpM(^2N{K4<6EzopyE>TB!o|5U+_CuK4e*C9KFDySHsiWh6JDk+*?Vx)ibN zn6YCg!?8D^YkvwKg*?%@q6Ub(g_)#aG}81m0c7a%1orii#!t1gvqWnxf`ad^2=PAfUR+?)1e|>GOsHmt#C|NRF#LZ_G-0X9OC>bS*8#3%r5_ATO zJ`GaccHH})ate#n*>r#6ZI$6OcEaK*lL9l3IoS;BkH-){vypF{LTWRQ^kFZ4X#COx z-AJjWx0Pe@;gp<|6eltX_>Es=Wn`2I5GXHQUj(!Vn9f@QV*CmvKBY9zCOV{P?YMz^ z3eD+i`gZT$j&gL6cR@J+MwWV6aXffS2=4BaWS5kF@4(2(*fmVi9Jlt-{b$dvViYAs4NJA&`0L~7jl3wY#m=2c zz=I`viTH$6&=?9zX>Ljj$BB!&n=F;fJv3CE?_`HtQ z@iCBVTDlHgDMk!#IZQ$F^bU-q`r?ahVWX)6!6FtDOITT+tC>b7W27^=?1G}2njQz{ z()H`}FhWfL^7SQC3jTf-6z|^hoz`LwpFr)$pXX8*aqq%^ffhOh-np)=9863ue?N)D z6Fh@Rvq_A;hp{p>La2Y?(k0k=D+R^W5!NSj+*SZB0Uad(>bonx#MVsp0&+{SlM45YAG9PJ&=AxWkt#ii>ym8RZ=-;<`m1$1sG( zGs17b{sR_~$8c|h52BS~h$$*Q;g+V2IB+9_famd2J=9%Vl=M^>K|Q&m7W28BakL?6 z-jEY!1;^;vPhK1cZl(|l(9p5SpEq%~?V@!VT#BHK?chCr&K}L3ewkm_5G8#F<|y{r zTkBy}om1cC$qgHf|5-BrFv42Yf5+{Ie`^bS)B!)ak%^KBgXjQ`)3BKC3Jp8knx>oF zFO6TmL;|YL-K{ak64j_2XCckh#ALJZh+u4wq?R=b%iy+Mw)be!dZQ?>K<~l^SY3oV zFz#*nU+s|QSQWOfBjxf^laPst_LIcQI0mAtEReqXXI(oDwtl~;sDrX~9L$F5RHlHO z`}ZC2#R>In~N6YR_jnS7#C|!rZ?M1RAyMkhEd_;w{+mpwSr$H`n03!Rq za~^mTysOumBgvFBynY@)YUl#SHU(5iI0tY)L}Nm%jX!S<6;InjQACxglf4T&%G#D` zDft(Cl@NNN58x`FaMPSaUI3%Di043uydQ0C(IR7CmhzzMHU))+IlR>J5=AV*Dw>)C zguO8%$!Nq@OKhf2-3ps`&ZbS9Xxe$aGQ|)z=n12t<&;nyLn&nWG)gXLpxuMD^x0#* z?%r==HpF2=xE-=kI!SLyPfWZ8a;iYUcN$dbOyc*F%IYOji=8Z(SZMUr3-_R>&Z4WZ zAw6~xjcXXA84fjpg8XlL;6z6lJqB|x-E%~R3ys^&gf%zS__`mSExDWiwhjD?CGZ)t zb(N_6+rdyn*RNl%#{|x_TuMdHq~h?k8?)9R!s$8<`k#YGM99&Q;iS@6g1j`)n-uWS zSnFo=3_doW!Z~ojfc7kIi0Z<TGS%;E1?LW{M%CUftJ zrJkHJUkIJ?r%uT+YUe_I1z?`;>e_%OgZH?NzcarJt|{EXd^Sac*gLqt$Xbznk}7}& z$0AOq_P)hy_}2H}z!)ALo-OcB`ej=3m;G_pj@|_13z#sVmT-BLp`xr*H9X+WtyKMqO#_|;xa;#g}>93qO?}a(^urP}4 zad6<@c%gMZY~mMugC2Y_ue~c={&cu2wT!Ia_!0)Q54z9mRKVn0$tc?ymIKVVW2}gJ z?*@$n!}=q}8yAbx{eR zC1r>FtR%Dvulbwa3+R`{M6jT{eh0@P-!f+8kRg*%`Hm^4Aj!Z5Uc+YcUr3+qbGCxv zM7IH|ex!U{XRUdI*V&Zuu?4hz71fQTB`*tIr6^jGXqwY95oyO?tIFzTP~7PM=Kp23 z5)$j(hcRL!4^3n!h`-O3I;Lx6CLJHv?xdr&p%1^nOUJ5dt^jVeVIT-32SVj` z)&s%3n{tAM1XS{oX=xY#Jb}R~5v<@h!&Yx>v$-qM<&6BWo4y2!Q2!^sy? zJyA`#T@gGR5rCx%Qdx^6>)y5{9FYTB>De0Zb zW!cVVHJw-d+I`O+bI#6M$#KX)+n!eGEfl!5yaz8#A|^vrsAT8678C%}CtYr= zA2TUh7weO*Y<;uQ5`g9q!1H|+2vfS@W9JVB5+rI^Y`lM zHE?R_n=LWvpDT=dPhf94)WG2FoPE|}8OezD0J0vEymmB&(ca6JMSmio7=XWG1r6;v zFe*b!+S}@cXMaof&*E#0i1jtTGk~Qm0s897qef; zp@6TFNWKH4I)enA27M8C;|c)A99|>dWdkc<_jwRmd@))wV+@9V;>!_!2CQ5_2@4++ zahxmM`E`@eFBao9CAU!R z&XY%v>X_$k_=Q@P_P(KbnUGsQ3O(Btwm^fzbpnqhGeCN|0=~ zP@=!Fl~cMte^^I}$ztPPB2nBFk8s)qC1F*E+Gq=jf@_dm<9Pj5Ve1wgf@(#WA1&LX zGOrOoEET0t)%@9D`=aCd^w?{}d+J>!o0QuuY+W+x1jbVvf6&c&fd1}pew5>~=# zFxiaQ1F;pkajU8BNAQg_7mo}hU6%N+%phbc z6`F6}2!{-iH*cVUR@_YcSUdgT0EjrX0ed?0YR_OuUcpCPj^`V@ z+&aR!&Mc|+O=VMkSirvmNs6V6WzZ6KT6OjCVzm<)>FE#go7#Gv7Y8%sW@S2vRl<&{ zw~+v?C&+W03KS+wh?J-jFg$y!c1%`;s9yl0>NOtLO5D+-Q{fJdyW^R$ z^0?kzC8=QB4Y0Gbd!%gL?!bw7MyzcdO?$vUp`*!#o#&D?%}-DOE-^mrd$;D#)fnND5`{LjHq5{l);AR-PpkT#s zo@gw8v(@QxOW+wOtX_~h^nk!-IoK&D=X!BPuc~`z(XdK7JT)%{T8QgJ?n4w$8Nl(j+8RsW@?Mra_eUPO) z0u)_=F5(1pMm^8axL?05&mhu{aLbmo?w_lA+EFhN1)Kqe#)pIANQBIb^9%mzT$HW= z<#yY)ZHk?wQeQyjEr=}{EET$hT4usH7ozeH9G&(Yh00bCpb@xX;E9=sqxzgn-o5a> z@PEtk^4_@dMine7W!vary6j$4pgRH1eHH}#SU$d2_U3lEl!ZnFI}PlR#CTW+hA@tx ziBqLsqOvzPHzzYrMgo$ZXOLLN7VZ*gu_H?t96WVlC9PvImd(G|%+ard@pFs(Yc-Ah z{cI+RP)jer}zp_?6c@dH1a$1^=nQ^o(@WBaAK(o;oY^7 zc1apx?&1b-;FnOBY|it!{7fyZyVynG%oxPw@eE%QrlRDXyLR=*Y)^jNhM#*O>A;Mv z9}3zq5kH+g0Vh19UwDGx=dWMWS#RQhdt@b*Nk@#1 z_5)_XL>J)exS>PuzkK+xJxh)+g0>2I&>ZAy$()jdgGf#7ANBEpmm0=ERTHa48{8<(K#~)hJ4cw2J```USP9gz>^0H>_|h z(15RaoEbvo0B5=!8&yAo*N=fVGO2w1F@ zDW(e=4=s+>G(b0=$%L7+W>H(l7E;i15r1J7&B%GmoQ85>l#LDTWg_weEJFS%B_a@- zfd$+Df047CXcRXBh0L3kOD#8Umbg8TMU||6wL#`Av*A@VO0Ye4NhI0y&gPix3YvsH zJHruV@G0c`SwQXFg9RW7tlwy7a-nz`fu*T3z(&GMH49Iu<8~g$kXHyZW#+_*ozkE< zm(VssJYEhj;I;6)G{n_DkOj>Q)+@x-x`H*zEZDj8nvL#jNPn26DE{Ox6-$hIBt$v< zphv8oX3`_3^7Uo2al1})=w+yOl0ih<+Q~66F!KW+bUfO?$4sSgt)x5#VU1{yE_w{g z=F7bIZ$ZO-=y!!;=?aULk32O?_G2DE0v+)}{6%3IDHoa>tR<_dsF*!OIs5^y{v`I9 z(@}$5W`@uVbN>sP;R21>WKW?Kh_i}a+B!~Ps~Fi1Vn!g%_oH4~=_&h*&;y`p}};%<#xNcVf$@9315k8yj1< z9+!rradF%6$=bp_pC$g6=Tk&iF%~;56DLfJ7s_Zl7&)_l@p$!|M!5C!X|FID= z;se+c58n6>f~{G|13Jn?>vDeH)lO8-86d7!bfc>L%7P7aoez@$*)^D+V4JS3{(u1! z5dh_&xiP1(LzNj4gdJ&rcH`{`xakuH3(#MX<)9chE&j9 zvbn#l5(y5}-7P6}iXv5x%)Y|Fea*KfIs!pZ zq0u!EBfbgz6$G*G==D$X1M;XPY&xD|UQ-JFNz4?1^$(NeCFTsz1gfNPySM`st%|bI zCDmvNPaq8l;5a~5yCbaV7sygnBzL52tHS*oLUu~n|AxYUfM`AVI zhlCY2&tD3^R zE+c8H{UMX?3bL|pq<%LuExbN`{)Q6uxC=r|!R_HU_+h>9?l_{>eQ6{$BWeubwh{#- z<(J#ca;yWWku|SL1oMzL_5UtjupHhPl-6Uo5b>7Wk<(KwxPlR z_b%~I&cshGGKpxWmK<;y*3wBT zZpqQ2^{8v-`Lg)lXIqWTT5T5*`CULLRPH}}4 z{UN9#$56hEh9VvKZ4A`X514lIf1RF8knMK1pI%7S3bEp?r_&~k*j1^ri~tZ=mZ?4o}$2tTkii%p8>+ zpv&X+?ILQ#FVEk;gsFL0D-B)C`5QmO zq4U(z;T5yMQquGqKKIP93)Y*^)0^s6yiA>HjEQSCv+AaAdqBn zCEQ}kavRVimPg^obzs9iUPf&nZ(>q$$vv;r4-ScbL`^ibqy3fMkA-JUe??ghCTKVI z5X|fiT4EHeHZj1|%){8&_!yikA=CJWmU=j3xf?`DePZHqLRnbC4>?=rb2*RgU*DVe zwop^@Hy|xfeBm?c*Y^+2mENP)E%NkyN+4Co!9#`+9cv}J4N7`0V|w=vo#Z7<vEzZ@r(Xia3!!K`)Obq0ms3`jX_yyC#qhU&8;=MwQJY(6ro z=uIwDcq!g1wUB-bnBSImm!=WZZ$_Wd0(DXXg>z5^?duVjPTsllWrb`t zE1A~E>@NxhO}ac3`;`t7EeKWH@DcwD_536$9b*YhB|+oSoBH}e7hq(;DCyp(&jSJ^ z^KcdA4N!tYHvymdG5)o(BE5SGZW};*QbEf>j}nL1QUWljuB}ol`$9Ja^sK+83K+kb z?tNRS*9>wT=5FK$w1!bE^H^ez z@nY4+UB?WSJ~why05ui7hVS_sa@YV?J3W8FejM&wW&V@CUy|=9>{eXqrPeUjX7U{m z9XIp)s{kgN0k z+dz_?!&r;v?U16>f|fA3d>P3et03Qn3;&&Km@(jvs)G)^c7bw-VCXrFTEj|9-o#1dnsZ4-#_D0%IYJz z(Z+Im$QRU_rLS_jk6EBO>B#MI8AdePRYOzL0q4Q@Y>;-|_6UNcq|5PE4%Yf#^K-nH z>PNp?-|x)1bC1uF{|SeAJ-ebi)S>2#VC1P02MpYL_TGPU;$Oa zyXeInu^!!=8r(X6Sp66Cs=b&IR9RQY><$mN)RIlOnZ2eCZUo$%$vp{iZ71O&wG8X4 z910c*b?*VeH{4x9S5t*e;~< z@;*d%=FJvz70FD(gvN5aReNHc(iu^PM$`~wBEJ-}TU-CC8z8~-$fy*1 z2d0WE$B$2VU0rRV(0i2p85r{-mH3X|q2c&B^pp$TMr;tPTV>ds^5joZa4rD-9pdXD zpxp*ImxJG*b_uT~9q@8jEN!{%*EvF)_Iz;2wvFvzKN^rA7zcaPT+WKbuc2I^mVlW# zyqtx=l+i##u$qn6(_Zgmd@eDGD8gkFl{!S6A$V^rYTu#5Jam^22v0c1wP6orKN{L+ zJNdk5Uh3%!)D0`Z=(f>ISSW0*Sx8!$NWvW#_(ekgwhoA`lpnR4JfvD`g|IGBgG}%o zt_UKh_uMWOv{QDt{7gi$$SynDU5&eScL}piGA(=!gDYBFhvk=_&Eu{;4xDkie3=FO znGsYk)Gprujn{L-5e6|xJt8 zKgO0X>xphamYMS;E1ekz`}@((=A6REt=CN`c1{6Q`v>&hK+* z{DuRV!MvtS`h$iHfForN6grh^)y#EP4e?^qANZMQ#?L;ZbbaLNt0tOJ0Ln!Dwg-u` zyy)U^A}Psjuw&rV6e~GPG$mstfautB*SCjo}xFLp}1MPO15x;Twkqk+;wY_u`{PUg{Ie( zZHl~5uM_}&c2!XDbW>A->wM@YW2qOM7#sLHyJ0Nr!2^9zT)YGpz6`|teJ7UAW~;L7 z@-s~sf_57DmG~pqv*n;3+@o-IfMC#_(-=&h2_f6$L=TDi#+*z$q!j|&y5nZ(h-ku{ zNkNc*i~(B_6^Vf}kh~V8t96v+5?+xO0IVG@bLY+zx*q0BOJLj{OnZ4>nGnB#rCmmc zpuuA)0fI z#k-uSXJus_K;}AMyLPRG2{H;4J`C`J{y~OiRVfYE2spSO#nyQRrT)l?rK`Y6*E4S{ zW=yG>eb%M_rzhu+o`xxL+fM@z*REG#kbh-;UQQEJ4-#R3yC+STzsNcG+5EX1+{ws& z`@%pm?^OFbGN?!Kl8(Xo=Oi=XHVp+Cq5C7}R%gbipee~c_=FplEnV8#P<@wB z!rtW+(K4>*zPJK`!HMm|CN`6*@_C$|f$?7F)Ieku7TjCjBh<5SXqYNq ze0WN}>|X?osLMTDyYX+^>i0Vw+lzEz5iimV_bf`dp$X|6H7(X$%Z(PIAH)AFN5{>S zlx$2XzEPwuK}Gop70qMe!jxeJ^XC&{-A*D3YsB?)KL5`Jph)(Ls40O4uQe^@5j(by zX2m-gQA-iqlcTbk{bXkpDZNlOkovGtYz6G16RS9Wc2{3~VuvN7hRleoz6c-MVak+= zbnW>IXhRC&#lh<$J))vM;EJz?4ERwl!(0ajd}bQFp%ZwK4OrdbE+iw2DCg`;(&^W) zCtrR)US1uPuNDNyj$wb^!0i4U&xO4(n<5E^e~EH?^VO?kzrpX9r6ksgnKdy{8fUy% zE;=fz-RI|7d%S&o!q{pR@yCx`W{dz0zEtTQLg+1PFFlM#sVR2u^@vo^% zvNc#MjswC!fByXJO*H=j*oeJ|JKH^-RS3cj10)nmv@0no zwxqUvHv%Q7@E%o1m+%N2u7#^s02Gy@rPklnoBjl11^g3OAKC zbXHm1D=`0cPI8MKLXTel_;EP+eCMQT^?@X#gA6annMJj|#1K4YJ|wuIF?QAb)=GJX zO>l-vpti;_0h9vs_1d@C0&+8!FyWm#cI*sXy#abwb3GfQH%kx9>Pnk2K`{imOD@&& z8{@XGmzUSDJi`RaurWH0oovj&)(jDq=tk5WvP(*!&l4duk32I(NWl+81ql?e74mtG z1n$26`Qzgb9@<-myL*DhdWa~q7%G6T##S7OPoj020MHhRO6mj{)G_>l%yDU~Me;2h z@3?Kiz=4wmbMGlyxjhi&;AKq`)W>IB60u-I$0*a|7(#NHu-`$V8UYH*f7ndsr{jS_ z4Y22zo$2JHeE`YdK$RUyaBjrVAIObTMV*q$=Ut+)cVh9SYa8tmQF9XHyq>mcB}1wT zKnI9Tay)X$FF2m0BU%cCwn_iDKZ`C;MBYQ8wAP<@<dSxF>#L#)PPAJ@Nu*rkCe( zmBJ5~5T0?3B0uwVIYNDjL?Ye{F7)yAxZ4ST2FXd|vh0p|X?viG1{ehGOL{ z^x4_$OJnT^2&=*f?vyp1uqB|64F-g$fTH4z7__lVaUc9vJ;)Juo#Fc9#ZWRLD*7KESjyax6NT=e)XX~uZHe%di9!J21i4^Gq(Eo~07{>>7O zw1D3Ju!#3%OCtgS?m^9Ip^#%&oCvXH2)=74uz=Xh@yhnO+yi&Z*K3Z*FePrq$0;5x zcRb@RQr`Z3)zr4Fcd|344Oq4{ulf+&?JE3UK5-t8gJ%4P9}DP73X7Ub#tVEeyjjL3 zQc%C62z<@vFIcQym93rH2T8_k`eW8i=<;9anO7iY;p=g&4pctX)nEzUvBjvFHJIKu zk!3Y;z@9X0*m2(_(|$8ePNA%{Js|6Z7HH2hVeTj-24m#&h%L82Oo3=R((D&6=8a~f zZc5o(_w{w?dMKs)C@Q2}&uD^4o6gPs0GewPKe0PYx($$f6^SH=x{=EzJWvRLpuI}N z6Kv4t9EXD>oNPytP@U`NiV;I+I0u2Wu3qTBg=zO?cw>tRry)9Xx^Qb$mSpeJ96L*0 zRrN_43`??17*D@i3$XK(0`WumJ3>&K2!0+<2C*WYcp4B-BnfS@!JEG|Hbx=C@qm9H zj4`-5@Tur-`{T2rcKA#d9YWC z5-Hz{=tEFrBrwAWUzvymJ>kPdF3IuoWllS8TjKO=DR#1=vA>Tjr1AUr75$pU@at9ary=Z z542R}UQ3Z=16F5cP0;xF=kBK zNwVi+<2Ztl-(0gJ%KmN+qs02-~M_pybsw(>WM9=|lB`i;hG1H*py7aokNgdrx}6 zBYg4%jm`%>M=pv&YI|PyzI{Kpygz+DlPde3X@vo3%78sWD>4db<{L=yEPmC25j77P zg)+$sxUiZx{3w2`*hf!fQ&UT|@Q3h$-T7nCh-Ii6zW$AnKXvZhgB762Qx4wm#Ii_H z-7L_Mc2z}4r`|U-D5E`3$9vBO{=rSiw$;!8y!I4^gocC^z_V-#-t+dxpP&B*et%#7 zTCn{nK$EEM_5_lh5{wELI3fa>dj$bPL88U(_heapqFR^*+0P{lFFz|~Vfch{cbFYt z6$Uk*s0Y!)wc$qtCdtx)|T99JD_%g`9JVWPMe3 zJ8z~b;ULmq*?J5w)fDkU^&RD{_A$UT1&FztOQn_nb~y4b4Gho} zGe7PW!<+t|!a8orz6VasPjj#u zg`5&Etdy&ri`1={ais+9K_+a4V}P*lfEjut8a)f;j>M)C9$y8@^K6RRByz2e3&&v^ zi)Rdldr?lz!1D@C@Gfh`5L)9P{;4CT9YNq?dloxSf_8v%S^*=8TmXM9wyAa7-RG=x zbc}3Z8jC=ebuu$k4SXe=jnZ>olpGecEgZ|~Ov)8>(jrFj58RBWpsEuPWBlji(}`cd z0LD-(^n*r$iJa zWa9PfnsEA$LlW40a>ihH`k@w%Z593IWy<|Z;Y#+1&$WhNC8EAraeak^*KZ9>kyg6y z?Z9%8M~@Ce#jGn7yxdZy*j_D4n^VP`7FAY11Fh-LGRmG7v)B9YYsu>lIXmn0TtaoN zlgpfV{*Sn}G-;+GkI%&O>V>+JlFmz*cjJc+Jv&-Yub`vE7esS9u%3tqF~{o6Y~;uo zjL6eW|gM51FmO)ttO7FLm(#2oZhOfk!N_FSu(2jRoansrCmeQ+K}A%E@w zDJ%WTl>_z;4n5mT1ji$L7KzQR>;Vekqm%fo_dOdw*|vMzbH zoYlo%Oc?`Kug;@VB;RQJF}H|rO*ksMF~F>+OTjbQ4O!N=Z@dnjrR}pD&HAbAsD^tX z$3JzYPhz{8;c`}2KCgkJSr2EknJOB|O2-2jKj|pD#eUq}OTE1l1*uSUdj_tV=Ia6j zSxQ_V$+ien^6swJ0!vTRP%Ht^eGL&x_W6q!V@Q;wu1Atrq>Pq%3qR8ixbZV~F1j=+ zbgnD1T<>AabDI+zAnfDWf{Q%&JBIQmt3Mrrt@{vg`)Bab6R0Yyc+FEe;u;tT@oRm< zXW>s5pC_vUXs*+LL*YqesH#JQl`vpWVR>ccAV%UvAb3i&NWgvrzOTLhjqmFzTg|(z zFehu779IKGRF#y1nInVv9>e&}B*G>OoICcwUhKs3b9$YG6SSK{|yy9J(%@JEzUnS6Ri+ z|3(in4f;j`WBFzpH-b&siA#GU4eF2Y1S~rnD=X=W508iX!PH)fCOsUM)fg_gQ)kbd z(Zx4aH?nY%6-EhalFb5MoSVLA`wFDF-y-&RK8rk4fY=a^qi{3B*yW(hx ze%)x(roY8xf-%+gP*XbzWSY!srjTavE5wOrknC|FKdzWDVJWy3Zz7FQEm=ETTX*_X zZIvDK5Sv5_T5Sntg5BuaAhu0|Rgyx4g+7FsJ&5WG*{*0u*=UF}-7Cz3IE&MP7-*$D z`A`ig<^?qKfXzoQhq5VAHn-Ro%vj0tDS7_+caiiwIlx}=3=XE?`OpzNL<;d9cd0iF zLAGpqQzlOq%a2LiVl&LxKmVY3Zva9m$6&#mYmtO^9 zVvnYrrHLJ^*f;HW+z!Sx_XjQZT~MF-;R#Y!NCNkG<7*NLgb>WJ{zC1Rv5a?2oG~~B zDv#hoc9WB9&m&eWN>9M!?i`B)e_k2@)#Y1mghV|5XVV}32~K+r1`qa_7@=W{>161G zf`Tg1d}3psi!C48za*q8B;7;+aUp_htJ>>h5b1u zvJyN2TwP|(I#G#`WuUS(F^lu)Ht%lw+qU0a(MqLv@5$f~&v-5}F?;voO7#rmD+r-? zUS|4D$TgHt_kUw2IxVbmCiR2k{h|*Fc3D~3mh~L6jm!pe|R*?#);J%Jqe27a3~c&yHyEEEY_gr215CN>{1 zI#V&-`%5FJq+hg_h)&c0Ga#IX>htVCFiwa{iyuR~Ujr=;z043&bo-A4tQi=dz|dJG z&_{M&Utq1AeBqOSBhj{=mpZ{vz6Y&90`5P-@77GhIKUK|VIN60Yo%DY%qLHxs$<=6 zG?UxBokYqP6a|mvGXwG)nELL>Vr_KcwNF6{XUZV+;ui)Gt@JXmM&`DY;PJlK>iiSY zQm@!{+rx?1bwHF&Cg8hBot`x~CYVS115(U&zL))&Y#}E$^@b9N_rM3Lq3Fv zYDR2#JSa*ZJ+gXJTf2=^LmPhLHwN6DFfBk$m$Wk(C*1PlvN>e@en(UiHf%tUv!BFB>jThc7@l71lr5Q8$aI<;ZBxAOR0rE zWs#uS2uXqX|K=H}64MvP1r$=W9<49iTaU_54r2evXT<>q1cKq#Y2k`c`3-) zI)?#!N){Qrqer=8s6X*^`6b5FZA8T#7>V{&SR+hgo4H*}b~WbNiy4WlaTyR%^p8J$ zhh1{`9h0X6{m!M#%!(qP%aZ{X*MdF5!5{=|Z>(%xIBezk z&m<7Mq*OKX;ZmtlY=tryXwo6!k0JRjT&g4ib(j+h9 za1sO0`W#E||ZlEAW>{Ll7*nN>0ERZowS@?)HD!OgHg* z)UxRiR?pSE@}sZMe>HvndZM!-PZXgvdiPH^4b+16FD zX}xF}?+a0P5P0f3)`qQ!LdD44hde+ouQlvD=FxNfmr>cmjI)bBj8n#KTjP9qE%0 zGyjJIb%0gb1-k)zx`R2H8|-fcB|3y3ChM_E1PrO1z~D$2a^_rUQeg6FVBc5Zpsm2x z?HrRq4o|XlXWxHrECh@Qm_NVIR(IjGI(#i~UIV{60pyRFP@e#A4aDAW3C8^}L+DdG z(I^!1N;wE9YXo?UVE_H5(zh^7SzsqIJ!x8w>0t#1LPP$CfWl)Oq>tFTdW-ycVFBdE ziTaAwKp}6i1MUO09006@q(8y12WxA`Z{ULb38v*p^%7dlLjv`?fbAWRXCW@r0B27~ z_-&Sl(IDg`D>&Y4WRCw}N7xxe=MF`!8PS9mD*{iVE<27n{NNH_-&)?G9xq?Mj4k!5 zB`f0sF)Q2!htDzkz3?8D$5UF*qd1v63cc0@d6Abo(G$UMY6KX+%jKGEm-L#8btn6?;pEyFvCvW^LWu%p9)R*$WmFSDPrX>Ei}$KF7kT zg?a2Lr>c4Wm4MD;NFq4Ml!d5zg+%g#(leBa8|v?!@$i_RrZ4WYfA8K}*>2t5-PGD; z1Sbf>?Qwu)AKG9K%AwBxdFEE*toQ=Xs28LDV}L6|l>Km+v{=n`hG|Jf)aAWo^6ct7 zeR_vE`xZ0v!NixFETlk~#nh<&lfslJ0mg8jGt6^7-0Q8sE9UGDD&sU~AWyOYF`N!*U^ z^PTvzh00rMVa^g-5jErTrx!Y}TCrjRs6!p!kO(||ji?^kYcOsdyRy>cgsddcfTPa=B*A0*aneZfLLp}S+30(9OH`i>~^CWPLUxTj9=i& z{hR;nKJm+Y+xxYzZeCw?@Q%D@r`TSn9(9#(-(GH7&s43G%DwlUG*WX&_}cs5-_AnJ z(UYoik!Rz88$a=QJGipPuvq9XwJ1U2)`KIXKosYDE-gX9_qgaX4#82jpKEYU<|qS$ z2*@Bt5W90J3sdw}8WFNb!XBrNUF4CMl$0oDvRaIbiRnK~e$b2m=K`?A=qW}$VF4rN zE)(9TKa}+|TuUbWrMO$IBjVgosJ4@m`U^MLQc#3Kdcz$ebsA$%l8_0v-0W&_Syo>D zQCsu=Twt9EjHK3z(go9}FN`^Sc)2N($(4XEU<;P4f#eXS{jcv6Hv*9_XJc5E9|Nrs zM*a(hI~#am9y522T3D}fW@hFvozMo{T+-JpP#D0^m64G-*j<_f9dk~W>jO^M?zxm1 zJn<*9`>f*u3ChE77F&(L1esv@5AmyK$&$VlhYArG9z@rBb6xWTNMfBN5|(m3r4rxY zgmb4b6`A?hZu`RwWdn7L{EdBw_!m@OFY&sP_~6s0%C*Ix8r=<*Tp;vg)_&;(@aIfN0u{yzFerGc;;6 zx^pvGQ!V4HBkdeE4ieod*a3%F=P&~7u}cbG-B4jcN3sx`Q2p=(p(1F(HLZk#dw`ar z*ihcg#Kh|o?rDtpdQ5rdiXja^pG^R_S-hZmK;4@$v|Yyo$c8Jdz#mzR zFm1>N;RLYXg-h%%pXY_&w3LnOL%NVy4%@$Rk&)&Qp%+X+dP7R1V=)DM+@oa|VFPVC zVJKfiGqQujiA#GP*>jdG5tn269C$>AA|Z&C___(Xx^N6^LUXKhlUoUSe!gJr3h-07 zBtheHqe&5juRMaY&7m<|*tM%G?G=s^Cm_7s7HbDO`v6eTT5%qbq85?zw|(%SL23Ae zw4R)!Kk>GQQ8Vng6y!;Ma6^x=ASXD{m!kWcMp)0#FjDx;Vq0p3Z5g+m5vtHVpm2-c z_>XB^@O>zZpzgz*(?i(xPokZ8RYmW|R;Dk$iPv-a`0HZqMye|+N?5d7Z2J9mE9PHY zUm**<2=Z9{_fPXb{y-?g^n=9gF>r`mXa_2Rzs@3tvlCHFV#5!MurotiJ+Gw8byou! zdcg*+1}O#v8;Ny9hQ9Xh!-q=0c)T6Kk(GdgaQ9k#Avt*~#R>Q21w!>|Sd^X%g#heD z?(~ThC)yDM$G`dX+>IOaJ(n)E{X{V9i%ZK7E~mNs#D>=qe2Q@p`o!rtXqk3`uB6ZC4dY-5{77UAY9&uRP+%I)JHWV+sl#=QX`X2_7RV%$_-{hQ5GB zYtzHO0|;os*IT5M5UA20WlaLkoEcMfqBgTxJ$2^HPCwr_V0 znfD5|h0mWpmE(fx(??UYC+#e&_z7kXWw0E32?aYsr8n94@9!EmWXNzgwE;6umkS@V zol}yx@H+N~_VM944%XMw^l!IBPKBOg(($4yO zc@?B`L{DS##79Ya&9yw8|EjL1M4Tb>doCndVKsa9Lf-=*Bk4di3C5y;&c+>V0GZvx5)QVQ&)ctuq&mPx|gm$(%3i`#YF@@ zPsdp4ph=tK>3IU2;>y2p2rAeD)zf?<%IMOlO}Clj!Tm*uRE(uB(nO( z2k)EZ`w5GeGeo;4;4u4Euqhzi9@yAYEwd2tk`#>Zk#@=TIA50SFj=|Jg)(hB@E5osW3IHUm*&;z!QrWh4VqF)*{#XYj^8z&- z!qNlzZr$=^6f~;f;kUB3zl9kvR$HL|A*?RCVZ+(w|Lh~{U<>C%ERKb5rkU^8z=f0S zsF#UI^Bm+kI6ET}rlss;jT?**!UsF+60YDifOt6H(g&fPG8cJuSJtKl2Qpno>^PF@2&vl(^&O*KK@AoYC{oU?oH+KGlTxgs`f&;!PrP4#6 zqgad_vO0d^gc&#Iz%fIHm>6vNVA}freK{hOWtXx0=jY`G;T8!a<0F){Xifu#SeN$% z**V=Cb!FdBV`tg^p|U!h61A-4E{=VRv(-m=PXj4$Up3d&X+i#6AXC~E*|?31OOkZ4 zw>usLlsl94baadf(w-36FayJ{l9JLM{(6fD1tXZrUM!0aI^i^8sK0>jwL*c!bqAPl zKmsaE)w}UY&ma!V#S4f=2OTrLwc4-0mTzMGXK6`E^A=9894?B3#)-1cAGe|a><9Cn z%uE19##u9w!BL>Y2#^)8U%QrpBCQLGc&8J4EEx>;4b^rL75gDL2tcq>?(PU4frxx| zMO!~nA)M5ZA}3%>Kc-uV28v;BV;TOEd-G;*4xG6R1+taD$q9d%Yu7MF?HZJ}_~p-7 zKBH0eEF>e00MjL)IX+%qQ5+o6LhoD1HquVD@6Vv!Nix#XfgJnAjHjIkcRHOj=M|%h z*0UgAv1Sbc<~o|2Q&upg15zy3p!|(Rj1;@eSl(jF0%&OUsz8_qP1L?|P$5jKyr5KbczPaqMMB1-~nqsoPHQ-p$q!nPiXipu5V zZg6rk%)s%8*2b&~f%G^vH5vBlOc%xEcmiJWQ8<4ACBf+++-nNLIYpj(U(S{|Dx?m& znlBp@0wI2t+5yykQEJrj+4l2IhtcWHaDMaWh5&@)fa(XnGn=K!2SdAv(TJ8fG_-4}x?b*+<= zffNqx;GFd&m7 zn<%j(Xg0JMK+n=Yg9%II@5k$VCuxwk#fSpMI z(k_m{cstp08Pvo-K-@1NL$<-&c9SyoGCl$w-T6xyfSSpLw;2jU>nTOdNZ6wx>^jcw zE@-LkV7wPOX#r}ri})A5y=8OOwLZ^T#Sctx;TX+8!f2X;mL>eNaf1<|3 z-xn-M#)PhNStL&I@H@9|=`fT~ z8G$bKtgP$m)u%+ZD~WD0h?lIGq|=w7v>Hmq``H@~RCtJ^rMV9&@-}pj3CSm>`wgb8 z+gWLz+~b`h41*{(8jCo@hs1aVy4h1Q(mTJ7R$7I+4(p8+ChhfPw+rm>7{5XX%#??R4>r7XA!+s}H@Y>q6NR@R)T_$wGB6)ul;Q({VS&wVb_a*cMSISorFnP%8 zOti8z!#fCb772F6u#ZrQ$6F zeUe!h^yPW?&)o@9=Du6P_vhJh5>fG1~di{0$)?L1+h90JBF#1bBy25*18@!gn=vPVqK z>jm|i3v!&#ry;c3O3Y#!iIS;;QrkobBLy#rCkFSK2`56Qz==3XdlO;fjG*EY=avU% z>Q?LnFS#l1lQ2FLw5XZ&!3@lNT$7m`AX5mru7Yr@_<(x??~~dRd;8NhS#IIB%Cb%r z;tGHqhln(`XFjIDiup-y7!<#PU`@4*@kAm;KxQQgH<4oAY793zcbx}MEEo=3n|#xs z1b-+|UTaf*ZUqUW2}wG3>{uNbhbpqh36mzR;o6ZD_9Zw9}?HzP+1C>RSRF z7J`^*JHdr+;tJ~1LsE~mO+*(wX>QiQH!IG98kF$P^cCm9qX-gV5OyIaB`Aa(Rifc& zgMeJ(AKEywW5_j$q=R(k^tlJ#Wg~AkpZ6nzn22Bx!7r16NT-R8t{<*-8`@yqv15Zk zK#3ktgl)0Ot}R;*_qh|ussY`GjHnW>bAgYYJ$r>8N4GBxCZu){KK(rp5S;IP%xj0jpw5T|KB0yZNwke+jjgRR@S7F%BEOMQAnLyX)O2vp{l%DPw#Q~V+lPpZGUlZ(7W10xg{gu!9gKL} z7w^L%4Cjzy>5CVl5qbgun}DeG0fXJH(2BjjF?LuhN8lA&4|Vo3=FciUU0rD)o!>bW ze87p?SUI7rf;vvW?|^t>d4A$Ml^`_iq$oMGd-rY;TX`OYjE=z=fuh4uMMZszt@#*6 zU5HN^%J|3+97(~@#yYsY&?1F_`{nEE>Smy8yoUGT--LwR9>v~&k)JQ3vO54qA^(KT z{{i6BR}h}96cXQAeF?br;h1XAoU6iWEp7!j;fB8bk_Y-=A!uVVZJ#14!Azo2JVIH7~yOd8;!n zAWV}!fA~<HbUE)5oh>>D(fxy*IW~%nlw&skPlJ_ zQj*Bn=mL{j!Y~UP8nY|>IbXyT#M!&<0M@Ocq+U-!@RM%60#MSGQ)M+ja~GMSU3{>a zTqaiM?kDkAVvy7-A1oDe&3R0h-w=P|#8dM9dlDvpkYL>moU96y9i?#ntQ|JlF@%1~ zmW1ls^78WjX121!hg;4;4<#bpp?=U?UT?xwu$4T$By1BFQ;rp<}9R0t>+T-&&$J6F2yC7-&T zsfFT3I8^J|#APHS#(UyJBZEMSx+57Ne+%*tQ)OA{&u<^E{u8)nD{h<|29=?Mp2-E! z3-LxG?|l-J24gJLSVI5Uo~krNW#mH0HFLQ4GP|aqD;0~PBsYm9Rx{f}xbJd^cRY*M zR^bW#*1_Y)%k~eSS`>ic{Tm&92NXYUY-<4YR_^-`oSu=yddt9-Pv`&%n@G$=jGES`*>^a+N3_M!-b%tLV+#Q$HdJHOxt zQd%*kc_7!yaT4fvHRubQ!ethHJoB_SA=>RHQ6_RE7U#@f;(K>{DLp)V!h4!6|;7TXJM27k7 zGH-IDbNF3iWSAv3t1o~%b)0kCX=)01`U9!0i7E)Dj8g?|KEL2>16oj19#tz0c?j*h z`m9-h*l5UG5mkdy^#vop7!`Rzg|r)6xXR5S8t({ws-GS?gd6iU@HV9Fd=BV=huYa=V2Ev?oaeNxLtR*kPEbw29>XJWgGGAJ+X0&^E+EX(;`Lc4!q7 zmr%qXM9Ial0t>z;(@2znHSf@q`-z~5_pPmEWY_=2i1tyVN5}dok%iFAd*Et|gYA*0 z)*TMzVF*@w9n~6{^&&*@IFQ3?V9R;jw$Cdnlw01ub(^qHhxp`nZUz-`lhkO!EJ!p|KpWps%r^pjg9jyJ)< zkO1B_02NXyGF}aO7e8bsk2r^dIKiVq2zb+Di1|uik{z}jgTpbBHQ|=q`1FI=Z;#ki z9h8R(T!Rr7YWehCrSLjhGiFT0RpE-5ELz;rjHJ5=uQQ&6C}pJojCnhj?1q4G{hBq- zd~qgSxd6S*!pJWd<_sKKgqw8;h0NunueQ|RP@JnpA`CP0i~!H38yHwq5~)xxAX}`5 z6+y(EjJT2RM~PlV>IY2OGKx4)=v@&j;>Xyv_teydS`nG&&xhdnbV4Bp7rivp*oa)D zOb|t~v3qeYkJo1s;W&;ppt4cz9UVh>%~8BDkq$Rv;lhP(P$6e&oVXh|Fdiwo9CHQO zffh#-MmlHQcr>G8RiDoqE^l8^XwbDv%FGa2lmfIfAxesDIF&l&l7@oJ$??o_!W-J0n(jD}O zRBQxH%m7=M%IV^XxbY?+p7z+WM$2q%jR0y_`r|TLh*V-Z-~=Y$Bsk@KNUnBhp{VXh zqbk{~C7=Ov!OAc!S50^Bym61N3ar@`U8Z}in@9mpphhr?Q5!B(5XYnK0jUN<`O5^n z#TdPdY_@Hi$tX;FCWrq*5|$}{IKrTODuiP!-FDQjUA-7bJcusoG1;L<-o1Vu+R z8KMtz1?%QX{$Lrz^<-R=;qLD4Ra>wBi4P|UQNvA&VdOeWM(*W*?&mty;Zkv8+yCn2 z@81;7c4ZRz?vV`F@u48v2A@Sb#Mru)mgzv!twe2^Od+S^ zTQUNqAMi^QIG>p4x@Yg+AVk=j!On3)7W0bJa{*W0N%CEY_MCKL+Q!isPk#LJkz7{x z5ec#)3~LEX4?0_$WT5_7PDG9&xYvHsGeE#;GPsO?_2yzS(T@y=Gug&V-NGnWeg2xr zwI=t)i00mdzydFNUd6B;TzZAPm_0Pg84N0pK{3?8N%e&}+TnPpP4KkOVs5I=&Y!=# zulD?ZEx`6ldcl4Y$@A*!0^-5--QC=Vb5k>XR2zBalaPKxO!*M5yruz0vWz|`rb}^A zvkYcJl^8Kh7NIm9c`taXRUH2PA{!hITe*?BuuzC8v@HJB6dDDF{7e_icI0W)#qQD>>iB&iaH-4_Vr2=$c z=xi~eMq{iH_3n9q?N*K{kw2~HcI%KW9U)l;1JK3-C~@6}ZCtz7g<^0%$1l@>Y5i~Y zlmucQiA6!P3iUJy6?qg))eS5!DG8(uDqgEQBA&M3HH`CI_?8#6DUvaEC#Xs0N zNYmQkaPMK`EPD>V>d5`&0jt%@-|>J7;Q+{2Di#&MDT(B6QBj|*V4h4-%ick%s8Xc1{~N&WSmGTn1I1^A0qK^H8^Y zk&iF3Rkr^5s)d$O^S9p?5u4KEEQ+agyVOSMU|DqL(X9*sBrqtIm4rhQ{7ap&T@Csh zoZZ~axZkp9sF{70Pv6rB-SQf$PwX6dB+SV3+m(!F1VNAYSA==j~$jS zjX(F-Ummc|tyCRv-@G{mzEwz`00u&_jc~=?s6n&ceS!kqdnWiOnEvYby^r2Dj4;9Au9e>wA$_)|??2K9`^P z0XJHc$Uu)Yh&k6Ks7HJxk~Uzid>D%Fuz}WyBE(=_+P>RwaI#@qLNux>V&@vE#ap>% z&oCqK6~)2WW674>Dsiv22wM6)49|b3j-cOZ!$@w1>xGcf9iAB2_T)J^Ar?ML2#N*2 zwwVRhB+?E)qw>!KvT7s%!jy^%1!km(gS}N!B8$ANjvI*K2^@(%0QxHdCc?ml47u?h zs{i`yMqMmx@RNC$P!@@IQ^E|Tx4s|}lhKcwldrE8|5sAe<4L~#& zIJ6DD6Zyz}2!`}Svu7Q1dbmV_=J){T>rAlPH)rR^huO)S0U+-H+!1^q*UL4BTX#`Nce@*Ju_dL+1#P z$%@dKTDtwmjCIMiS-Nz9$O&NJTTf=h@t}&tl$6l`yU}RPjKthD(^UHycC8CzCK%|v zlDSFHblUZBWVj%j(aUNhlr3JoXi+6uYg6OzC~#h>ac`HS&qVpg$QYYWI>m`VpWCqw zF*og(NpgFg4wg1@+$K-@_1Ck&z(u0WLBA45w*+@Masww_D;sP}tXniKTOt+cD(>G< zwxBiWfe(Ks42`4}z`bxCe+`SvAi~&?`uMJ%T z*LyKTbu<772xfkOteM20{(yONxxz(yxYxXS;RW=@r97W%T*M`mePYzvSO$|>oiT8b7_#oO!#V=5YFM$%5MeeUCiNs_5rLTJcrdL2>eU>}hGN^0#0B_%Nr z!FKEQ6*bSF{|=O3$7rGT-gvHDP?xAHhAjy)%sr@9+Fz=tz4}3Cm`9m7`eYA8t{;#D zhp{C*kpZ;fV!Q;xbhol{@SXDV&7V3t-k^T#1;F{0BM?1$e}f(0<8Z7fbbkJ9g4BLF zFX{-)j1BmBK8t9=DC-%>nKD{0AF`s`w z%BJ=H7l9&@L1%8oK!VHuVP1kvfgz{3kQ&Rez8n}Mj+eC*h-w!_Pd(!Z@#wrpTa38% zNCxQF4YI6Wz`zV9W%$m~sk=|myWF6kNaN%hF=vMXVeyMC7=X+Vvq$ycQzD^1c|x&e z)_wU~2(B;^7kgr^WuT2XZpWFa3PcC0Dgu{Xu?r2KvN?z1@(ZK(N zoo9f!EqgE+UG6s$yoXQotZ@PrDTBO?fOS8|{G|gl4JV#r?%l+h)kofJI7!!l6IuWM z`wUs--W_0C@?*yQA_VUuWnU$I@oj_y3I$q^pbu4`59!UC;A`&kDh_dWvWKf#Gi6-w zXW(gq38O3K9G!|zF_vOYJ;*Sy^V6sPBtdRqF6l6Lt_|kW7z8Vua=hw*kxwq~xW>)$zYHeSqv22VaP>X8MD=kRjbEZZ^CLIJxtRfm)3rv98*r1HxNi=I)M+R zx-UrqF6Uiya~&6&=Z&@Z|N51Kpf^=-i)`2Nv|O=*Xn9&PV^A} zq^4%JfBrm;dF3-`j9zp5pS7Pjjy*S(5p5+YHX41903{GLV#^)%mA6bYve=y@X~rIg z!$;X7@JM6@Y*r}tUaWplENB;I^3{-y(4S&=FPC61@W|7Ma&MQF$pa7!24oLG#wC%| zAq=E)hY!=@RjinD9q1xzKrG}t zh?yuoByE(t5ot>=Mv9=ndY~H$NqVS!3fK>P+h)LfPo1N2&~=u4?1-ckC#v1$e75cQ zmU~4uT;T=@=P9M|oj*YN+{e-{EpW{E-_|J*Be%~;)|5#N4>akCh}D_LB_j_&v%&oZ*ZXt{)`4F+!vzdrPCRKD{k8QM^ZHG zx97t^!)s(m?%g{UuhuxIQ!yimF{~b_kF9Z*gT)n7e+#{;0GLFz0HVBrh%EWhBSy&4 zE(z^17D_`hSe$XsCvb}!@eOCOI!Yx2S297+wefacp{|@5PJ^MM9`^>E{`5TnsgE zkPFiw%vq$%hcxJ~_4AXzj1S}jhjIXEUI`?F;zU@$Fda_ZX+xxpSjk4>Jps81n=d`j zD5$0J5yVgMUx#C$MtSfAJ@;@%UH9W$2D4b~;9#-Z-94Xzq;+RvBtY8e6Ud{WKlX4^ z=xS<~<#t?CL-aFh`0ya+aQD=b*@Y5K4Wo$@Ndqz>+`5jnU&hi3-Fp2QR+9aYqS10v z&b-|ra7EKm@x)69=kiqbW7O8bd(op5{s|Kzd;|+UJen&I zc=G>}R%LE`Xtkob9ylp=5LNdhGax;rW73=^CsWUzYhe8KM@%$f;P4EZozF3N46ugv zea1M^<1-oqzOg1x=nO@M8xi~CU(kqqlA5fA4=ecJo!!rx!Bf8?`I?VHj$EJuD5TuB zwhW(>YpbdjoVL+OVa-m!GBlscb(5l^Vgf9<2kSaq^u}m= zwFMMsQ_|F;dFgWnr<@`L@1?qHF~`si8j&*I*qygo2dx; zL3Xuscu?X0UagxAmu^Wxu>wamrBf!g$1aH69DbI%eq=8o_)Z?DDGhBXe&mlR%BFDH z7IB#Rux0}RPCbGB=ZaH_!|U!PDy1cq-eQbxFi%Q*Uy@LyHW75VVcoi3;_dKP8hMn5 z9Mi+5k|77OH=u8yJ{C&Fy^-H~G7W5+fq^FDqZ+~JK9Z#Sl9IQ}?RKmYEQ+$4npA0d z`NeF427Zsz>Y*2Zr$P|fH1iCRn)VcZ_A13N;lV8fY9$6V;S1$)1kY<5@jh(Tc78`E z&)XB)-HM3Vp2w1-xk(~ft_s>C_Ip4{?F3DxrwRSk)|M+Q*tC~EXe*W>E&<}_otdUQ zeA5EniECy(2&OByvaGa}2iJ`WsO%lq=vsLOv=L>(DVs+vq$E>th~7RI`UTOp_DuIO z#Q++_OAHWFjcjnD-7f=$Fk@qm|6}`hS1`1_`}dzAuhEE!OG{$Ua89^9udNk%I7fNN z!NgxA@*-Mczsv~k$@Mtaq!^ZW9BLB4FpXpCo0zHrl<0v7(BiOFe(HM4q?MY?MjQNPvh$Ig^U>e0T%ZHrOrXt(?w35`|va7N>o*J#)oll z6nepBPsFonu=D43XH*ZbhzjBjud|tWxK$_R0?X; z0$}XWC38bO){ru^Z98!BUj7@Zm)+a!^XYKs?q@cR`;C6Na&1E1rY(HqP z-h>i)u}q7=-Nbb5W_WMLmR-{xH-5Y-+BgLorG5KApg6v@xyXkUXkj{)!~75kB(j8d zn4>L=oo%dCd=${c2i02-UPc2g)3MlCG45<3;U<0G@)y(A_ffK49GoTjO*xS1f12?73)PMN&=^bd% z0n8JK(U07PqGmB95_%I1EDdCBeL1<~Yd>;IhPdgD2*p{sO90!~c6R?1ZoejE_xodQj3sCXf2OHeg#rE?S-7cKkP^jRZk&6drMFK0&7DyM-vQd zr7?9p6%rW)1LPDHEf_YVrK6)FIv0~_UFO@Xv1i5xE5}hoFFB40W(H_>1HY{kIea7x z&?#hHB4N3e7_WSg|NQdu-~G`4*M0r^^>6n023R`>7A5_WB?AMEm`?4AsQn0y%^;45 zQ|v`g#*X<11UM{PwCFbx5iL^8-@bmG#0^l$#Z(BZe3Wp_N{+C8SSa)vs6ZRB=K6}? z5k@Ou4za*Cd;#)QcIZ$y9+H-hjB1#2Nmu>N0saJuq+xI0@#W<(P|~5_IN)j_!ExU7 zlqrw|D?6Z!6)K3IT=mxozjs`?u%QvS4EsRz#v4Cki@cQp*fRBq0~MY;L;6vgzlrSA zm)-BXe;)XO%oH|t$d_;5Mvj%vXJJ~RfB5&`*?2`&$v|*ex$>9pvm^7VywpLAwLyhe z(dOcJA5ZuGSX9xdLSOS0McU8@U{Xhtm}?MX#K_Y|30h)UdH{@a0%6l%9QVmr9#{ebLD(BZ$MwyLMYjstT*H@ykjMlsirwx}uK9qQm+ zjA2aM_+w)^ZTz82k6Wu}w|)BLj#kGI!h@hv9UO4`xY;lQBJNnCzvsH zEO7r*LVk?d<+U6f!e&|mm_;OeI?kG8xM&{dv#L9H=Kn4m(#FwbPq!?_%Aa!348yf2 zhIK~RrJe?)`^FpkfMM+gzL_X~DF~#h=a+qpi^wRV3ufOivUVgyP$6bAUlN)+DO?(f&PqoxGC*R8&RUaJ z@GrLo3+{T*6@3TPHT&AF+o$)m4|Sd5|{B1RZK%#cOPt?ysmd#H?a`nUeu}gnZivf`wlz~Lic9`Zbi)M z6m#2qg0zs8>OoD`gpl$k$|yZf!y#hRB4$kHjDo2@1;g^9rlKOj431~d&En#Pm`D_` z)@CjR3AaFSG81{FXtr6cH&KF@(2#Y{Id=D3dP@g{@H)5MgLN!wqEnc1%v~Tgb;9dK6tzBf=f3exQJt=!q^bK7P$V$7u`i&t@KgPU! zv+VPxyB){-g8h!={e@xAPGHoLlMr?{@%B5>#+D)&HKtsQd%9sanBzEPLmpJr9^A3i z=Y}MUaafgz#I~Qpfls|Eq=_y>DlTJw0PouRRHYXEOce#215aZ*RQD%}npIr!UM$Nm zh?VLY^?|EGvmU}3#ln>zZ17}OjS8X@`2)o!>dc%)^fq3o5GX<~v&9#3#pK~_3&y4T z0(dcBQz2Y!{P^On48LSf_T8621@64_92^z6K9B{(zGZ8NAeB|!U7+3mW%~|{Jd;7z zy?uOCXnPbX^%&Rc?%^Tb&>oP63ptls{lZ>@beJW2optJDO+#h%1*+x)6Z3B zIMY>k6od8LrG9)n@$ju$h;}9mT>x163nXX`=fYn%Zj6CjZ$x5p4Le#0F}S|?@0Ome zuGJkekaHjtel#3a*#{(LmEa~>SgNv1U4vl_hLDp@Bz~7mX51z+qJ-d6Ok{Ke>TrO@ zw*kzP>`O8cw820Y<7I zw#+RYD{21&zX#9>T@)3A`WA!H1bj_8%8Vl@IvFmHR`{2-EDI4=v=w{jBs1Gc4xv-r zyLIG#Tm#-k^FVz4zka!Ar;f;cd^r3pN*}8QGUpv%e!-heWKh5N3M_>oF zWuW};mXq5yA}d2`xe#TDHYF2}vc5g`6@IqaRsnu9$OAk<pEu&YD$iOpV``u!+FWwvT^N73MULQ5za1jtsu`FNVW zr=Y0lLBhNnOkyn{`~@iYg6*@%WIJd=-^?Lus4sEVzpqksKdaFn9bMWiJ-yhb_5ef9 zHof@ym~o#yd^i`KZWWa@I&)7P5Ce44Pl?gcoJ^#En|-^sr9%UoR}*xH^6c5q{J6vP zXU=?_T-_oGzv%)q%Ch?olV1fR@cQYKCou6F3G!7KGiuZsq>W-$LoO?O92z?Z5PU{& zMci>OAYjs&H$4d7pS7&izf|uwB(MV-D=UQeBWrZ?rcFT*H%J7B(JDS!^3TJC@YrW? zI)<~5YoY%db&!!b0}$R7MjCp!@04Y7l0f9fRajBM1}o^$AV#8m~_I^K+|U{1H*DUG%C zWML?q0%+_%A&v3^tA@4s_P(8-R~2+eUlPbXj7Bb>J;LCN)-VD)jU4%dziMjQmz|vR zT*OH<(;;|JH*P|^brA&WjVP%(KC{l98$jYuK1WY8SaAYw3qeh}(rLAc_&@STawt?7 zFs?+H^&C59%-k zIfU0ma$Q!WGc5jAS=o7<+dhE%8kAC+)23}Ee^61G_%ke-!iV^huJ{w55z-=1w$0uy8Xxb1lT=)UadmLR&ZbnANFa1M%Qb+jf z#qTF(aTjmbN+p^lHTyfDBi-MxM^BuXOfxV%NO=zcyH#;fQ83o-qsWJrRyk?q3bL5? zd=c~ih}tf@e}5>%j8lNYXq!}fVf`2Lw^tCMeu6J7fP!q?-r2a}bKri^fN@OD6&VQ_ zI)}|aIs54+xPMO^xVMAm}R9X$-{_)!%l#`1JssX=wA}xU19{ z@q@r8m$!Wnj{+sxxs(*It&m)Ebal^9#q#mt^XD;)BHTb)ESoa zLr$+<=N;3Xxv{0Bq!e-0mXuFF)J*zx4j|1fhJa0VuW z5Ev|=U9=(qVC`e1>Dlz2WzU~Kzk(U@E7&g+rJ5*c1|y_W`4<~dJ~+w+F8bq`!@=EC z&^x)%>>dI^WuFCNG*`E-YU^0{ffP5 zP|nH8n|Q3 znr1QMj(fvGc{4Lp(X>I2134e2SRcFEjY!jC4=s7aUX^F(PJtO-2vS;?-0sMO+NW zjvqG&PSQ9O6(#xd^=lHWr2now$>X>FkH8lht}q^nLWn3~3kf+4V3FMAOzp*HrH5Eo z&iGJUc{2j+ju1vOkv8JQV8yWYT#70HE|%ZEeDM}KWnwJVfL~kquxfMXjiJ~xz^I)3Q%uAmkSf4ZSBxPl+r zR!=QO2k^8{+_AGQV>(}LnmowNw(FJlct(6C5we_Uibt)(CLFfXnKNe$BD9`zdqZZ;m|=A! zHr5Iq=zbbB-@W_xWt};FT9MEpGaBQIHwz1;G5KT5pHFbO5z+|yO9xt}mWGC19KpsX zo$VUA%?xm71l@G;#~gD7{> zMx;Y|oi-FIRQ>ixnKB{VJxW(5%dl*(avd}$*vX|V!r{d zB<}3~F>I5US6vT@CP$S}8&8;WWZob2XchQ?$~Roh?jdm>ePAxWK=8+Qmn^D^lYrJp z_kD>h!bNd&J?hvioDh=;LLXf0okR8yO6p)x!73yOa_sjN}J|0irXj=_=VY*}2|HkdDYhbXwliH+-mT~|^i6J7G6?S&@f7?$a zX7n&kV31Hw1_kwC#zq~&T?`{Rk3IEXz`rV?aGQhZNeVnz1sMN4ms?~jpd7{RK0kl| z8NkwU0*}UykVVnc2A!x$z%0p3UdWRz=FOYR@Xjb&lBN#<2FX{i#$&+#b*8#{kvxNz zL0t+kbQTd3Yzsit4j7DZaA$>BU$$X@G7P4pBvg^O7m zNQJ3%h8zY(V5mKybYXl3qF=4_u%Rd>Nc zsPN^br?2lkFz2U))is5lQ|U6ZBp-up-RZL{$kM_h19MF^is&}(BNI@|26DEH#fYW! z)Ku@G;G1?KIwT-9Xv>aPIPeG9qN;eAG;~95eyjw)rBCT9-^Ug%!aTI?@e8 z@oUPDM8O#$g&7F!bC7f6ym6u-^p(J+kyV|iR-;fYU}ar{HSx8#mq(Cb^DlW<1&He7 z_^d*qnM_6yA210PIE_lTh=!9lQbDiqm0G2Z)Q3R45rK%ROTeJ}2m+YoMt5rW3Cv$% z#xP;arTp><)CVGnn5MPEe^^?>IcheuAT8|O}d!(|(Ph>{n z{4D!>@i{Sac}y9${5$Sbq41-iwD=7VeCTJK^x}i8)qBynYna)ZLJ+f3N~t@IfUW=H zVLqY-jG#Hd_p;$4z57CbR|~V~_eMmRk}x)5qo=1XffxIvnO5MduSD>bE<=^}0~aKL z=Sc4SIV&E!&RRx4XMgJK%oQyV60-dsQ4E@(;5VZ!>mKAvPkAnRt;IBUb!{f&XYk2w63}gaUAQ~6IclJOn zK889v*!#~PJ+h*gH$ls)Wh5(7myfdZ@Z3w&nnNG4wj(ghPBU?T+?)?LMaT_6Z9*qu>UmJmf2o)ffdxQkSeOn$wK>$_?zzTyM#|W zG4Lg5rk0iysAn1Y%MxIlG+N5!^XKnHN+hllNjCZq08(q|U=dB}m}>t4fL}O^1fEUY z{|82zIl_QTPu;UK`GcvbagZxs zZ2`siNwr@adEmgbM2-G`;Eu4S(CsHl0QYR>Y;D9I)&tsrBhx%G;AbwZ{DoKG8o0Gqr)gc?CDJ^LM?x46HUsC`Ha^_l)8{hC7J96 z5UwpR@_WyK%p|kC27Tn*@6Wq`jQQIx^%4bjAusX;zRNtyfe;7=>z^VZQe-v>{_a{* z65gC@t}(e_&I* zlAgYwpRveamX&bRda&OP*-v~)n9{CeSlgDS+D9VI|BD*x z3a{)369hYH?N_6Y@&p0!+PLwbbFfj|*FzX5X(Su6e8q|lC^dcI9Cu8E$e^Ueo$Lb| zxDc3T=D2Y;@505=)ZYXFGR0e~%3GMWb?a7 z;09pm{M0HKn;NPPPY$F;VE0y3z1wl!Fn_^>YC<=%f%u@PnHW}rZ@>Hi4VDhI?u0;E zw2Ey277C2AM~n6z;%Wi80QJE4N+5wHo_eYrra7#}R#+#IR3gvBE0Nl{fU+80rY*xG zm{MHGLQTQstk2W(XaHMtvF0L}(G*J5q4+`gg83z#<;ZHX8=sL3CH z%ymgbL1S=3R~w%%&_=@)rKBLC<5&E%8(_eK(EEltKRSmf3?AHLp?vS(8KTJ01i?E! zW?lD2)qJD-r!T_Asjw{y6oe*}oE|GPoO(3X*BcY4myP26+zXc3B`{z+HOoiH`%8+7 z%(nytbV(m5R`t{ynZn=36zf7Hx(Estp+>S&w#2d{&VL4J;3f*(6NDy}mX)3UvVAs( ztH$0vdjd&Zxx@Yo1x9(5wD{adwlTU|xz{Z%gOMv8uVEA(vxBTr^752AfjlCgT;2SO zOQwSjg+7wy8rDk$c+oZzLm8X4@ii9$$m5#xkKbm14T3?cQpB9ksP)YE)LRGye3pv~ ziO^}Pt0*wQ8pQ1dW?UibnPJ?B0c$ByKr?YlG4pFYlU3{5190UA5nw`_JYxre%`KGJ z=Yek-I`Ij?nAJ*i^F9Vdn0^(yiRkFTqRPo{XxHbJ>#>yuJ0iygyNuz+f5ydm%n4{y zo};E#$oQT53J#FQXG5k#F8O;2z#XP%tF?g{ zE*UB(pnr8`MHLVwHE%nL_!XF*kzuB@^Y5|K)BNU`ya`?)z90d&d>N315a0E^;JBVRx1LzRa{(TCM1n7C zMElO$@2jQ|UWp>C6sHc{qw{TuB@u|Z06=myO*gMV^{`bQkZ&`fS@PMlyN8Y$vytRL z6%vr80sZh%6~H5M93CQQkReJ}jS~auB!ixA*nH&n%V&^=-%{*SWyrzMgu{|I3u5!v za7*ktaGQX5ERS0nT!sW{+ z_a87|Bn|2X6t_lLDh!3;n5|`u{Csf#&>D))QOt^2jrl=cfNt~|zH{g97GVTm2^NUw z;vtRH3csi&6HZ+`*R4}U!ca?|py+Y8Qfj!OfpyESWsaDl-IOD}2*Uu4i=wU}TBVXx z`IAU=WBIi3c(0<^G>=hR%!sjH9nY6wiZ%HR<;{hCMrcw*661U^`r|4>Ai(vyYeR?cz`YuP)N3H+?jtuSoZwl%uP#sX|jMA3%{#CP-NVpJ0? z@bB{=xF?ZzljE4~M^Osyta++(&q;$Va`!!SNy3l%0m+(%PsZw0r6{LIGu+ph;q9U8 zpIjOY*|LGjG{=q{+06*BkoncRJtVmVH2?nL3;96XuHFF|+s9aS0+KHwCDhG2RC`D;{J>+qLaqbf(DdJ$-Wc zXqv3W$(Tg|1}1)k?NdU3imZ9GWsHdW+r^Ob!^e)TwNJJ8vU(dd59_ z_86iGO+h3VPU4a#K*eEeG@}sCLCi6_NF{O*i0yBHLn?i!qsU7L9IO~F`S?%jUStxf zthf8KDD%!^g1%i+qV?p)&XWP~r2vj%n9~5xPz?kis?Z%)l#elNGNv1)RZueXc1!q- zhU^q_A2)9H@(M)0bdB?1Ev~O?T-fnq4{`-hIaz)YKY%ek+F5NG6Gn@soImd;=3CYS zJq!}caE6xVt4IgCWjdIn^Vtk#tVUoF*ys}vT^~jv0SfJ>G}tAEzCg7jkLcJUy_+k( zh@(1-RxI2`LzjqErg(FXC!_1XlQx^riD1ByK|NM zBYSlrU|{>@WzXo#!l`l<1YwHP*n)e=dcFy}}nByv%F+I6=&ovxk0@ms{;Fc)R{HRi5 z@rsH_1A6%~4(9R|u*8Bxa_zbFm7a2-3qEJjUZ!h{|W@z!)rPkE8(z-K06lUlEHG=&p*v584RGk5V(ZYU49{!aR* zFUV6bQ92HmGU_2Y&749p!6qM{VA3Q92~7o)Yc-^d1Y(~A_S8Za!kZ%#h(7!-k}a8=?MU?1Ixvn;tg~9Wl1)r9Ann~AXN8z3w0IeZr|K@=z zMxkrG3&4>sIJ?ye1LWl7bWT^dWYCi|v6cMr7ab#6Pzi7SqwCu){((0jgNX#tZFAKR z))MD$$g4pzHE!G2&eQ^`*f5J2HEd6{3gMv($$Pr7rK9LjOw50$B&=J%o)!r$>kK=GS z8G&xT27!I!H)AbA_TxB?;BF2_7rYQNdEe4)3|`OJ)k`9pl-QtCi_Z<`AQm%vEjUpo zBiDvkhHN`Zk>APXEMVAZ2H)spqAS1{dTiwA(F}j}lV()(34}qnNPRs>++&QRW^5uL zWk(N5nt*(fu+A4klv;)fdp^5Xy9rk3ME}tIRJz9ZuV1_Huv|EfxKUi)+`Q=_TeFzx zQOTqOK~_2wtx$?*x0z@Kzb@8Z1kI=0B71v9q)}@xKF0Aqeg}^KytNnX=qiqqtAv`s z^dagxFi=-a={bbbxO0tGa4#(H7i`GUutdtJ=HmLt=HI&-4s?k|O%zyQ6p=^;(%!L{ zFWTkybVp+&V&_>x|M39n55Yk+dC8fB( zsFFj;l-&KEL4gli{Olx>ImoO;K(2@p@8G65cd^z}6oUa>ZDUgzcnBbjy!V3^ofh4P zA4;r0c#MS{v`=VXJw-QI>Z%pyeEh=)pX(NCNg_pd^r%s8bfk8)QUP$Ai%=S!oU7v$ zYMe)abu`zS2?n|}#@(X~OCV=ZB<+9V(KJ9(s@P0=>unhtw!S*aNyz3 zM=l80)hT#UgV6_e!Y&&yVD4L2yXsA?h*}Tb4;1b5gy!cl6fGj1A@P+SojVDG5tD7+ z;_gmAdQy z-O{1HD$_w8$5tQ0^q}>IfV3Qf+L}3bY>$@a=7h`3rm#D|=trtTKs*6yo(pIJY5=}d zaMt>0ek+k^4mqB~)nuh}A!k)(r9=Ac${P)&L>hFX|JCKiFT4U7m#~^6%n3=@KPf2* z;5^$fz$``vNijMA*J~UF!&?4?nwc$L>(o&gJL?I+LB!Y#G7Y0l7kk5$-XFfm2#_mZ zIC%g?wj+VSmA^@!myL#c5-#tlQoVa?Q?P&HR0!g-s-__^#A*UP5QItN(Zh$k`2k7n zg(8Y1rv8R4BZOFwpu+_mWsyl5j6X2p3w~vTc9AR%E%^ABOJ;z^J{2}!G7D|qtSui3 z*K_T}LIu{cftmA&y)K&`jLiJ82rUXYOG|OVWT6A}CI4MII1u>_b9${Qvdcg@M4&vE zB#Dde&xu`kMRD^9SA$}h^F8=`F|0!u>gNRR;G{|q3*PF(2M?;?^TxyFu{@84Fi42& zX0G`863Hj-wgHffeMOFuskwP%>=7UiALJBGcU)-$2rTVI!e1y)Wib9cYmlg`FoCnR zwCbcKbxdARN7N^RVa;Wwd_ZQF!cK|UJ*tFuV3{Q=S3alVkd5Qx5K-Ja3AE)W2E`JP z7ibRIE8Y!ZDHkt3lp8!aXrPQt9ftHX;u1qB;o;;Y5mJ3p-69a!pJoot^c~+XopMai zL3og3KQR<*P6Rw;PWO-ZJ1eMOqN#5W!q-($ip*C~P^f}ZW+Kv2Qw@1X$or%Ga51`% z(K+gra{mzY-^EPLPX0zLip>n%c*4%sNPUSz;S@&NG)|Zh5?*$K#TcqY+n`lA`|a!3 z1gfC1y9@FNl&Rp(+9DXvmX>}f1cyotAuzXfvUdFFDh%9i$={_pST8H(431U@uBCxf zfwJ@}anf>YFFeAyVlBX*6;M--ieaU!W#acwp|BWAhwMvf2_{8Gpaxa&HF~9v9-5mMd71BnsUpQnaBS&t9+I`B;6^*6$vflo<#z<{Y#^Sb)_0b;DoalZN~PIH|Bp~hHR2q!q$3u17QgF^|?qd}@Hb(+M0w?J!lu z^27+9bX>GW6dZAajYcyMQA~0=LsR0)PVQv0v;o+?;UbAcxZKHKNd{c)k~g!(Qp@I0 zfk!&X5EXB_O7OWI9O_%>c}&@bZ>E;U^pIo_fO5C-7o}eTnLNK*x8ES3C<^ym5JBw zK44ZNu~$K;{ftOWzCkjr18}So$y1XKTm8M@Yys>;`FF@gd<9bwRv6do|NS2gdE+y7 zYn}rU4F>h&4YvUnY~dH8(2DZL4=78vvl#T+407VbY0!qe>n&JLD-3)y?9eG{;^Q31 zSI~COV`-C}fsTdG^{Q?5wKJvd+4qLh18Hn-}D!2uhC%c542$@Z9(y|f> zgOpQ1{uDS?es(qdLTpVd-~BFRk}5FKd0^~j&MzN%v&q|MKPMHvu6+3p(C`^QSRP9; zz(*Cl$Ott~d@(ht8k#>p1IddIJcLj;dLeJJS-jYsNv!j}Z2z}~2WR8&uSfN8X9m%L z-_RP8P-!S{MwX%t9potiDdjl@a&wljF~l6EW}GQfRI-^IN>`W%QNu%fLtI1{kM%1n zOL^kNzetBHCha1aZOBdIv@0)=pb8>2zq5$mfkJqqGo1E`tcD@%r6e?@@1tQR05s2I zz|G*ccVUQaD5+LroXtEc+*DBNKY{ee(tq^@Z&ku8`If|s402>*Sbd@35Lt-OKY5n`o#L5hmVOR zYNtj!Oo8iw$-vFkHH36+#qgUdEJW5zB|YXVq%&FUv^6pX_qcUhOG-;K`}FQTn5CFV zz~@saN^Y}y>ZEACiVLIYgTc(SAc?}4O*zX&kjhFm5$EbRFo{5TD%4m7P|&Ufz@GwJ zv2%31CDJ}YE|x`GxDFUN@FgQer#Ch=$J_0}Sels;=ZswAwQ!=KdF4j1@4 zIMRgC2SklvLAsRKFfVAT@4_xRk$QWYIN$-ZS5}LOl!w5^D^A{Y`AA+?jjK)~gg3qq zRQio#VeuEgXzVlQ#=XZtvOQckB2uk_2UIXg z!p|*oLWz-XLnUS@gerX`0cifSP?eP7*-&N-pAo{iaWoGC)g|V>j@dBYXz}*r3|l4a z{$QI72rBR(B+)6m_Bf4FJu_3CP)e48im!)PS&0GN0iV$?=t!8*Yr|db52&)9#tCd; zi5}4mzR-D^2TNt;%|`M5^oc^`^4bvK;bDR0Xb;g~&rdt1_d(*&NP(UT^=+pYKd^tl zenI+5gW_+x493+(|I5tvH9U;4C?nnZg2aghR8Gb1`yS!`vkd26 zziTQqAWMp{pLml4FNiq*6N{Lz!sy2e%3EthM%$?E%3ueEBYY58&LE_e9eksAv!dm8O=$qL}NhCCYGV1dE)7dG)%4Q2Z&(T-Qy_$n`(&60wKDS)662W>8PdrK;WT_aY4Np$2 z{Cq#u;5(4GAkbE$VT(h^^$RNZ3hv0^L57ARU!$4X)&bP1u6VRJ@kbZZT*(vu)koq4 zW^kHRR!8pQ6eJ6eFd>g*R3CpK?AV;0vsKXuvSggmtBzMwa}XLkP@z8E?{{{;xlW@y z;b5r)Qj9i^+&Dm}7u0h(AZJ-uua1PD89A^(>&uqe-i;jVE@+`t(24y8nuQkk4UBDH z<~Z`h4XGHtsK|wI8U@c9I4y{f%=qh?WJG>K1qF9lAC-w9ll^ z_u%Hwfqsj9T3c&L9rOVMcOQKHOd(wN6+kfoaH;~{h*8MXG5^i`cl#GRv2Pa=wf>Qx zYR$2dQtSC{8&STH-?dk+UakGFw%{mbQw0yKlXky}8efIPQgd5*pz0)4K3hSOk@?*G zKbp=2EXQ^2`%ffE2qA<@5|UJl42w#otU`uX2u%_~5kjaZl~PGa85@jCmS!`}gQAk5 z5}K%t5mx5!ckS={_VMoHU3+gDp8LAb>pcJGe_&BwN9f_8zb`ygYx-UB=~*wE6VHGD z-c)8CrDs|-#=5VYeWyFhGQCG8Xe{n(x1?*P-Ni31um7=MzA)Xs>${74w!aT~;J;^( zlCo=w{P6aBf8L(%yJPw9?5}UP-n)9QvA3!s_JAUs?gbr^$ljtYO9#@$_%%_u=Fw6o zLDC}9Dce!yr8uU?Il;nl&rC@c<*Z_o-GyQ-vS@32K*$H6xFq$-^(D}*}v8PlXq zCnj(DCtp~S9XQ}(pXSzMB2=^{7RO%_EyC}Es|oXib9hNYj@qhIG7nXH5|PZ1=d+NR z4iWrRPEe4i>SeZ}Q^|MS@$1Jxy!Tf@*HG>xQISZdqVcY&teo!yUA(rSAV?TXvDWU+ zNwLB!MVC(z^yAEfyLT^^Vd~747>bc``y^<1->|G;i}AM>fOHp$8f;5e+67#e_2PpA zG>Sy!?r#*4c+qZ5>Z(euLQ3r5;2wkJyi^PEm>ADnqlMRL8TKr9#(?J#JeLUxITHs? z7VH~@(t5<&etG&_qewD(VM)#S%v2 zK>CT{AkGJgr4!^i?37ll_3$ueio8ZUI?5`l0K2(-K@s-n7U?T1gC))ZHmGL-(Z+OO zRw~w(J4quW8{0lQ9|~lRpq0Ml2o|B(D#sQ~#8-Z%Ptc<&P_~K^iShrU#ma+&q)Y{R z*jv`0(ZPwfSxMG}*rGWfxxF7TctjpL^aqJVreDCfe?Zfnr>z`1=a`WUK5GWM2t1JgPhd&+zqyYqN1WP%qTUu>YG?e86w8O891Bhsks;?5%J^U zh7JO*;1xlR^Ws+ghMnjWlpQ9RS+@cW>=pf@5AG(qAVWg!~gT(ks}H~=6ND3)Dx@+{%QxCyt_b~82<@Q zTGXLK`KC&FJOeNGnN6@*gs+EGFfO_G5Yu%XJ7!EGo49O_;T4o!Q&{TWndjgzRr_#> z2~64aUB7ouWb}1lJ!818)<{9BCQ@O9o{*r*hZ@{ETeofds4lM$f58eKW;v4pv2C6J zqb^+gN@l0$^=AJkpeS_M<`g4XdzYGf44Zb02;Ds_`|ukzJ$Hai0yT-1naP4-L}=}U z9vV~Oz8luFG4tSi?&*Jo>2;aVi5JXDu-t9t7Z0aH>cVzpvZC99a#(-9Mv-YT0lnV$jjaLzGjp2qS z%MuWII-IN&oiTVds8fbkQ6br~f54B_1nL78BlY_=Lm&kh07P%b6W;wl2DX8*)^7Bs zT2RL2BN}~}B~k_{$~~gr zU_3eunCb}ti#>u0<3Mmfg}_tHRn(}_nba^mUiJ(_PyhGAg%!NHB4(UjdKQbLH_LDD zT&vKdhxy+Z7n_qd=)#ymcOj*_c+Hgx({dGZzEeDY;jYd~h!^&w=6g1RZx{jy5 zZ2XCd-)PB!Q8oV5R`+1dJUV+F~5g@?iv=ln|MuY z`RuZ3@&H=dkUR28dxHCm#bYFLD9)8JgJl%mwtu#x9^WvFLH2Sk9E8slN)?+*oD&AR zySpcs;TDvu7iM#O@*$iX@+lW9Q|$Gy3I7RU=Mlea0*L8w0|SE(G-LA-?LKzyy{2BWxP-ny2`d0JonK&u&U+D$$q}@;d?xt;EaDc^QXUrDp z(jI-FFI>R8THM@j&f$zczhHDFcVRtt)9J9TQED{Nti@85;su|Ba7G6jH$n(HfGsc? zAu_EA0)7w9IXv46k<>xgV+lQ^w~3P0RfpLaxtaor2~_yQp5DWYAW$dso*qWtau+A3 zO_}G<7heT;25IUq;CF8txjP)S`R5mC%9iA~7Vj>Mr9E1)h zfY+A8mYM`r4C+5@*c?ogYiOvBO=fLyC^mK!q?8W6YaPt%`5-pDZb9f!*K!bgaJ^V=h1*8Yu>vcxKdR zv@Khy*nS+khF7m<;Ye5moK|xXr0w{VNjqMO02 zYYq#GUSjfmjazsh*=%;Rdp3J|8uOZzgM9Z7a+-~m2S61rr=_gN3GfU7bMJ*FM+xL6 z3&aC|W)T^v!PGN#z<|R$;SA#Mvj=5UIw>v0oqc7`2uXedT}J?SVJwJ=KdogpUOj4b z3}Sa$3+GJy4!hNg%6VI63s$gLVPFki%18M%%h-8O z;l3oqb79irjL;GCreuCNG&1U2G0_m9n@&)ikh7@Mqt+734FUZO@c9~W;!6A@_wW&z z(#?rZ=pAh*@oA;Jn_Go2mZA8HcyH#PvBn3cM(=+{J&i_Kn&l#R8y{;uL0L&j4Uk{-@k%tde_;qB61<~JUi5)9Uc#31JOJ22lG&`; zOmy$5YUD`jyWHvtM?pib?EE>o5;Iroi2o6`<03sy=mwATi(6Huzo0tSu>r`WY;ItI zj@6_e$I4|FCig#qfvU8|oA_AOH}D>JxO0C-^}=fSnf8X_X2noh^AEs(KPF zP%7>@$<#^6G((MG(+lO1vVu9cg!^N9h-5h6k$m=T{4*gr%F%_5*#Zirso9T?m z2B%bt)^Ae0&mgxIOwx)tZD3gikOTg6O^C;t($EaqnqB#7Bd$a>)qp#=#%k+LRDy z3VYuEyhe0So~&;mTx|WxJG7D_;2vvjK?LWX^u=%M=TELTa#}KPGwzd{?xK8z!O*w%!=`_nE5<$X9UvW`3kLUM%1fBK+D6{tQ z*pj ze2>m0By@sBtxK*n36w27%wk?$cjl)BxWafbw$##bc;L5L`s9k&aD+vhkx@;Be-I)h z&pfcssALkDncd(g!EMs~iJhYGMR$M>GD}Ie`ThHGpTLV?TsTu(y8I8EA2DK)kZ>ew z-{Ao{v6ec0>CznbNO!Q+|HiVa18LhNSrd8Eqp-F*Sk}A*Tjv<~62OgCB$B}3`nie@ zM1)8SCrl+JW1+PjrK{W1X4b4%F@J8KAcDTifK_5~yn}31h&={Y^f}7rULd#U=<9|} z^)Y7e-ud|KmA5?WrD^a2>S?7PafzA$TjybD6^<61)@=F*G|?XP>>kLm0SL<(BdU3n zGwOctDnjAa!kaefMRW6ibl_6@k+XE*y3{dYf~$>(+pW!Sj{yG0@yZ?;^yfB1QWU?k z4PnO_If*#mL>Sbl5&vNO(N|t-2_Q48-Y7yzHv0)QiWR_}SIjiviX^n)V55zq*5uXa zZG^7WenTXRA;|=z>C=C|rfajOn>j!GY$8c^`FMpaHKe2-#~jBOE2lJQ|8(q*V896( z8U0AVaAT})g$(sCoe>snqX(;~XfGzn_B&AvT0|(dLH^r|4}!2-aHVk+(p(I(dWj6t z6^r_dnDpNqz3-5qx&t=pqqnJL+1Z46+81f3J3w?1mAM~4<2NeDGWMD<54-JMNLgjNA1v5uya?gYLZ~XH(_Wg+DIme7`hh|uFpdF99a|R$UVkp z7tU?eKvmV2`!J2sV6^|U8p0G%uvd^23hPK9K#k~r!eN=Ql4Bt z$g$Yk^nXVSumOM2T|d9|BctIAFODhrCXT`OE-r~9#*EoJ{^;F3#%jhV;H;u7>cfhu zJ8Q4|hS z?i8pi%Pw^VfEKYN8Z@gfkwNi!YK`nS5*;0a0J%j0JVN`Bds+q-TrK0R*-Pa9|& ze*d9F2i;+9ptzz|jXYd}#po-j$_>y>%ppAi5O-z(alq^sBIPC>eL`E`a9`%~;jKpX z>c%ou7m9`OC@(@HVC2;xyU5<&ov3R+T>ST-rc49~IE_?b0at>sPM|#$?%b;hyMwUQ zSuq#n*|RPDULKwgaTs*Sm?$lRlF`b-3N&xHArpubi*x);w%Tdz*JC44$_!=6i;+#& z50rDi>&sLlSmNZr6T3AgE`ZRl&*%zAtWDBWzF3gI%7bD zy08P(WY+rwF2n{ZWRj(3gS=EvMxx3FvW0mjhj&GcbL5KzaXqH9k*)zy$I$03u+wx5 zD84X_ec{LQgt^%lsgAjltivtefrRR*Hxs-B$ZW=CVsB>`Ep|VUYqkq(G}bJ#CPK)l zjrxuidU!fhEF{N=eC4%_n&AMUbJwmNo4&DmwwS`4NWUBbbqg-C8*fY=f8zm)8ZjFr z3$ZY>Wl#KHx&Xff1HwkFSkxOoFKCBr*d$)Ul-rE&Gk)m4LQ21uW&B(f;KynjjQLcc zJj7m>be~)K+_Uw0Pr6DZeXXLVA~AYR9rOXr zIL$_NH>p5Xm`N%j-XQF2iJ)Ne@q1I)2Z-n@+#;sK>22dK-8bWZao29_355y?UC$L% z^bqS>AX+5B6V1ozrbQq>Oq)8aZeGIkFq%5m;SRyK{K=By;(99o2B@0hd-uB7X!RYf zBwxkJsYA%H1OUy_#^wdFGL*nh0oji1RH~@PS9r4nu*ybf@{$v*K^M3*(<63K{9I4I zRj@Q5z#N2JwIo%r8zzw`BnKaXI=hqE`zPgzSy6$Tcr*c9`x3O@H8gYrHM!5W(sOCD zWA$AXNOuabN;d_CR6ZU(?x!iR+9#pH=QRryzBcH`jA374 zaDb+8I9;~|=B2oY+yR8>A9?uj<8HZnGlu93H0@*=$F!SZ19Nx?j_{&_f8!yol1P?=0?03= z=m>9HcbblRTF|$^bmz&){rKU745!(eOwFQ_5(AKl%}qu7Rjs0&{f!-a$jckUp?X5# zA20H-Dx729Klw9bs8sD4Taj{nh6L9IWtk0_=uxI@eV8!HCQ5Bc6t3X-j~Cv#Hr`O@ z=oR5$;W$C|!<=zxQANdmc=C-{wdujYw1?lAfhVkLuHIQ*03z3xZ}j8!X~s zZzJ~+4RmC5bU`=SPB$)G*h^NzRj$)&BI9GweF?tAI$EFr2vyjM1v4<+P0=ZVg_6U~ zM>Tv2N2>=6RhoKR%HJdI_Ag9_mWWD9y;Mo;9?Q*fGy{LtV(L~YVdp8(O^C0t)9Iha zTonob>2*Uxj)?7Bi;W=su6?(+|94N|eK+~v8*Hj6Ge3?%du>&(#pV;38OSVG7>grhsB zf%+p)RXR+)+QAV(5#O83=ntW5Y$c_$Gqd8$v5Qj8%9`6R(mj?knC};y6E^fR@CF25 zw!-kPNFM4*kz__MZ2VwJ0^W-TzjU zrOoU^Fi)`ya=HSl+xE46CAN+CPJ)|M)Yd8@%Lix-VZH|GE32&Ri^qYcFn`0@|5iKY zX*NWMZ2;7Hg@tLfOnXSm%phcIfQpKd4`|dgs(ul^?z4gYdE&cSY-J#PF1mM*TQG}m z#2PK65_jGMf=C2Fb>vLz2|&nTO2mJ2V)X;z!TE!!pJ#i5`I+76dGYksLbvXxChv^D zVGFdF6B>Z@89nv zJa6$VG8S7tK%QFo=Otw6^|*5FS^|TlGxg}$ibWdKU-VIDnE23^zBmS&yM`X{I`3T? zDE&@OPlcnNJhxtFE#9Ad((Wc;eZgp4Sb_5vxCU{!kGy zda*Gvdv0!Rmou6&#RU|^l85||3~`q8QNjkm&BbK~Te3r4B<1kg@_3!POO$wSC$K1j zWzh-71t{6(0Y=D4)L7Xj9GW#k7#RpYr7bjwHt2v?dEOgA1)8bI z1Hn>Tc~BoH*h7pW4xk(`yUpgXgcmQLzpsHWkPx*FhojsAhtF~-vPBF?lq6XPkr{T- z<9e}`+)6*RixTbg{ma;xX*(th6N=KicYDAatL5m#t-t(2Q>rEcV9~+r${2nG-F+3si3#DU`yg^VpOv5dx?rD41$H@DFx3 zuldP>!9dp1hv`6Zm>lHPMnMF@PvtUR$-<}`o>>)2LxzL{WJLn3517tMk`oY*+5A1A z9?P?pf%w*LX*9Zr0YG(~QBRo0bwKE_Fy}SDc=35%b!y-e+<}P8QP1?K*08a%GJy%8 zqHO*I4na0R36Vh#jHQCOvR~6`G1tk;G}_(}23?a%LxDcw-e9!p)j?R!YP8621hEh< z2EK+0Vm|l;(PX!=nj^kO9hd`jzlmaH$F^PY2AiB zXF2OwLCJy}wWqU0k7w(^UoxCF?biD&|IT$xe1D>)jS@|i@TEy-k93>}0XnB&GuST7 zf};M!Q?-zlbmvKM8Sx!(hx~r^`t^;%+7l8yAB`2A6Kj}?UF_=5LV|vM`me)&j6b*2 z(+@KENQAWy4j~Q1#(p-6sN{@(NlygXh-w^8*;mS zh*=IcqSTVTotf{x-|Av)udUp`tkZuQcI)sWw4`bt!uxyJt{?lE3#SQB3eZDG?$RC- zB`&-Kj7KuBU%$QuB*U3m`^~ePN3Q}ntcM=$;O?${9lOH|vraoRsMJAbS_jTTJsdE_ z(9moMX!as3zeUU} zg2QWu9o~61xYxLHV|%d?W^yX#G7p1d7!VH|fm>8#KTyabFg$N+N~D8sGss%0rd2Au zta0yYxHCD-g{)0INC3pR$CLJD0Xr?gAR!5OTURIHb|)vWKbiLpptBw9{D}YRgB?mt zSZHVh4E#^jBX^>-M6QMRybBK(U^pgvUfORJ=uKwa;|%&EJA9`Z7#i-zrN$F6kTohx z!g=9gccJAk0*ADrX2spO;SgWpUQTag0{`zg^l?xy3*KDVo2{H2wqTppMve>y(z}7e z4z`e?>3RKu_4ed+j#?w zU0jAc*xJg(YlosIKPZY(yUppYEZGog`LmJ~xr=cKSc>o6vv6^7sbKv=$z9$7Huj0_ zgfT5uSBWvt)ukJq7(H<;+tPb%_rV_X9QFKg<&38QR?sG=bd#5l1AtSYtjuJE7e>tc zZj!nh=~YDXz;_J8C$kfTxmyla$WmGhrv@29NQD(3ypAs1v-a4a>@IBl45H=9hAfaP z?kVt{wE*^?Jb&JcHQ9LPBOeqI@JNP(oqJ-u1DxE3lWXpk>(>u(Qj+l+AaZJUA6fq# zy;7vq!`LaP_x{JEZQ1|l0t_D9oxiKi-xb-9G-B9_1%Q*P$V=zq^3&=)8NVJ+!Dv7t z@(n0KMPkgb6E1=Pr;Z-IIxanZfyh4se1iVdgIX}07V8*&TjQ%&p^4g|09<6_g|>+mb~R(llikX=}<%F&fC_`e>zL4aBnoOBkcubCbPppna`H6Iu@cw)rY>Dz&S zY%e?vgRlfB5XrqOA}6c)D7pkWxzR`izTY1{cI@3K>{e*nH;bBc6a@V2_YFCLJ=7mBCfLn+dwTDIi)Ip~Nm{2CUvLP1Ih5EG-n~rp|7sr4cc>SZ7wsyk zFD{Fs-@vzEuHDb>^ChmQpC|@h*-3d0-8T{xDcA$0zFqW+vGhSwwgowYjl_|QCa!S@ zLJg95(t9Z=+{ac)p6@?a*2I{T52YS=k=7Mm6Jf4xdARq`B%6NhgWC=o>Lg z`=2=pLfp+lR0)Xhxn;|MI-_+Uz4=}aMBR^y#FBrDmsdO-&8@5#LU~07v!a!?P<9aY5Dm*mo)_QVif~3gg>AQ}q=tE}aYW z^Sw|bVoX}PB-J$}p0Nf8s{_-C15c-kPNM~wGy0GQuG-A_ZA{%}Ox+e29?lZ)HKTR$ zoja!8q>+j}dme$zHk4d7!DevJupb>2p+bx z9GXKt^YbGpdX_-nr;6y{Cc%A;EP3&OE3zL`M;$%#8(P4xeDW`ecv}M7`hSEfLqpdo zG;AfzelI#evs=ZO3QJs`<(w#|~YNdFmMO8rtR!@mOJ3gYZzp{BT zz?Uzj&J)KWAK*wOshWURFFR&S<2fft2=@U{HQ-KgPwYv!`hns)U|d(>pB6G+MNtaf z>|dbJF$EDrp74^ddji2KR%C38*A9|_CLXj)R;)NZNkhX_9~yPa<;%^2gUKH`yy*Jo zi_Bl9tbXfY8PqETHeBFcx5o?OBc>tU5E72sZ&}WGwi+hE53a($5Mz7>uCHPfF&=qS zI7pa~WJB89gB(Ui;y3t@)yhnfyN6*ZIZ;d#d~P~CDC9}|H(XU^6gF^6ixuriuXydz zXfKl0fv`!6TYxT^Ut}hG-7~KkSZ8W`_)totJ)#aVNRA;h2G*Zl! z#d(Twg)W*_3Xp`(4V&PhGFVwzgp4Zh%}?NCPGh#XewPlXls89@u77n#5OscoPKiF- zn5{Uk&~23h>QaFcbB_OMnj$ZFU>N+6__3MjAL_eGHL3WeFC86+m0;%Wm+2*t2CHiAqN$t(;e{-8+SoS~1G>+^^?8xzK2wb5*ko9R{s?J_GJ^~m2$X(gSElx1* zCO!0Tr_P?;2wSR=%!JWo|D-zLj${?I&AA1NO7}RoI)j7IuSyc2~3hZg@rROEf{Sh$O2~wZC(G!Y}SQ`tcX$KKdPrH z7VxH_S=%+lrt%n0!h{@uL&7EG%EGz9o;@XFnOYCI&a`-YWh5~m)Qzm-S!g*jXUO7c z3s>(MH+!j3#F$&Ze{DA?NlkjVT3AjrajmVBlO#hnvb3`5);%|Q6Bp_usQP=<R#AYbrT3v+XI z!P8)-e`^GPg#At5_=l_SvO+S0mbJwKPg1Io2_d=u3yCq zEq4c`mhNNz-uZ)pyR5_hw@k!9t<{j0XjO zHh(dJuiDHm(TQF^0Lft;lo~(Cj`Q%Yh=$KDQtlUvzIG`HP0OG42dtU{Si-L?{QHM}q3Pf%& zq9^jiR~!4pKbwJHcz zd{UCN5iGhjjQl+%aqRNXbIR0#@wFJV2uB=@sxY2{ug2>vGK!2ZJp9O+EQOBOBslFj z5NBS*fDt19eo989z#qYU3LZYpAXi<4L1{oM?qc_^!>W@AjCr69^byA~fr*EvQ3>G0 zPy36LB0Q&WfGy#)rz<#BEbNxy>Ht9e0zs-(l%^FgqY{E(V9AdVu2f`A9N`Ix^jA1l z&TKQ7sy_hV=+iMN;`xjDn=u>b3WyeJfcW{s+=2U=ZsxD0OOLlRY4o6fei34(F=5=e zUgJiOZpd8s^bV8g-8jXP>KO5ktI?OV9czu}Rl=Mz5&gTwKTC%!xRn{qWOMmIbwtfP)jtu;tA$yv@DR7 zt^AGse$ma`<6`&|3Gv8@6aThH@diCMm#oF!bUV>M#5o7sI7N5<6h)4siiN14Tvt+> z?2G)QX+7{L7=k3dvHfc*ztrH7)-itMDt~a*kKGD5+ro&e1S&|@$^IP}pJ4iJ#uG(| zV8H7py)CoUl`WMg%#=T-O?$RMa0~zaw`ao#pE}O5C&f&7oYdgzkOeCg*6~4Xu$|zM z0iYWGLj2d4S0k72D=aGw*|Z1nnP@PYv2UD7=V#7AHej*z-EKPAU{Cl_kw^8Mq)@YZ z?c%4wki-M~un-n`&X5*C50rJ5-H2HNb>ShX2I`3>U}=jWr(2_y71n@(({@RvzYBN`S`tLj*ZUr=_iYeisC5jZy?_lgsD!7x{U&6GdF%F zJG4YDSeG?xN?4+tqEz4KwJR0GW~P6qdZV7`-F&}&Dqcmod&*Mw>gKnDcEFAiUs+D# zCt@TqwZV3}2*Zbs>`ffmi$hA=H&=PZaU-vUEOf!b290mz0$(_Ulc`2K%KaYvYr;Zj zI=L7+fi&KWmthveM;|UBPi?0;x~4fe0qvFTl=O5*6X^tE;ld0S%ocJ!u$6C=rY&a(YDpn{I!AU14?tvO(Y1G` zT$@I(=*nC#Df-7nfJ$M)+C8mhSZG{2T5t9f|C7dsKXZHu0;*;X&-KQW}!XyV=EBLRyJ%B)zAPQABX+gVjY1L;^n+Z>B zEH}8rx^@31^Mef{VQMI*AIFXynMH^xt61U&H32waW(V|^&3^mr<`gqEC2yGRlVM=I zqNa$@;$O@^pEw29cuIT%2s;Jm4MLJBAU9owlZ{RI3V?YH$Z;Z(pfC3g+fZF#_7E=H zcBY$2Y1LsO$_c5Eaw-(aEC2j6%RR+j5s8Q^OS9dChdt_%P3woiG!g$Obh<;HGW+V4 zq++{14!v=Ju=)Op9fna_wW9E-epi#Rm(B25;l4?<@o}Q>TBrjj%9>q_A~u4J8xVmM z&n+)J+)FqJA&l9Jg@PIQM28LR`HL4%W7JFdMDL%JCRZ5Vrz+gDIIIxGje`=jg&%1S zTz--7)|CgfhkfL4c0y*fcUNIyTbY{&er7B#!rsT|(5!q%6)hveX^5Hjb-U5R~(BMPhpkY-2&d!1E+ps0=oS`3;( zaie2aq1dcriBgNoUQJmc5YK2C{n%1^hDFVhk&(0U(SQv%LZn}?LRdD4`zujffdu&4~qzhASz<$H!^e&jeC$ZwjuEdR7 zk))PjX5=iA>iInq<>cg&;n@8J$Le%Kg7oP0UAL;h)D_G+POA* z7GX+a3(0R4alf49kwk^8O-)VR2FJ!JG{GoJ^C}(X1v8DW)B8(=WA?3OmmXhQlqLtb zB>;oX06ehWzwiLPXUz)L*3ghX!+d2TUW2$hY(%)VXz9`(e@vLrhs|0XS+g4f8kb=L zv>7&F5*1?B2(55jYBm@hGA@dlRSf5wE#g|r_2OzDRSvlHydOSp(}fbYG zEXJ>YHr2pHDTb6zkrWY&POA)1i;0eH*uVgmm_5ji5`uzucG^}jK3a*0_=g-uXjb#6 zg@!hjVHNJD4$d0ED%&6KusYw85DV77Qw9e)?O{d!g*DO{m`HJ4eqSKKu&6D?^)Rxw z*5M`9cPSrUGCZH?0|#;o3JMZHDT17;3_Ys!RzYclXW~!UJOEXq)AMU%WrCb)xrMc9 z!~Wr9!YTB?xJ(#Pqth7z6l9CyruGM}14|@+sw~A#El=Yewyh8K(@v=BHYnwAp2}Z_#V&g- z!b7zBTt8t7<-Wf|H}2g2KEsYB8oD;??OV5| zK(nh~bwm#6ANQ(>LU}H#!0#__mq4uRZewkoN8TDM)rdwyP7ZKUXwZwUMvnG?g-{jL zvIgwr)a6Z=V6Eh=q9y1k+%Bodpm&P@wKUA4@f_M|HdFbO%TK&jq&U6O;Ru?s=a?Lt z;4W1&1_&>ruBi+eBK%Mj2oah4_un%EgM%xDvay2-R?ON-gPz37ugsme6^DP=sJgtYM=h3zFe>E-6VBg)kZ;dq>zM z3;z16do#9j7$rE;{;5VZZiduf%B^Z6OdGjjP#!@WxRC_4ZXh19jBt^BnY|>P_GWB0 z8+}NM^S6|9p37--CR-^q!AB)%Kaf&6UyvtV;;QPJN4U42;fiGnd@dq&8+xmFuN7d_Id&*538Q~64{pk`m;Ia`T=p1@LL z0D&1)ufKTIv49#7FO7L-6p4kuED}=&7kYJ2ZjCQJ7S_KPk?G%HM5^aetLf-i`vN%K zL;iR2h^=f9N*1_B{O+%v-8?&5-q7&g7Lv9fqNo_hTYt%@&1DE3w}&cI0G*=0MayyQCc20neMQ!;2#^a^mW z-C&PTKs`?qVOY&~#n!|`z>yaj@{4P0RlmUzsX8&Rx0UBRZ$g@Q3Nt z*Ij_ZO99D&i5w3nznyh@COd0xa61&!Cg2S#>Dty#WQI*d>6FYOAQ;+-7hRxXq1PUy zV^uXZQ5d&YuFmM7BSvuz&#W00LK&$i)6ekx4~J1aGNjRH7-nj3tJ7w(0T5}+v;b?W z1c6hvFq>6yF4tcZn}~8o?QS&O{Xx!P$`P7;g5V@2KD#TdFzQ~tx`TSOo0!K1(G**& z3BG)YI~iSWur}m;94*hw%iB*M@d6*L0wK)+^_k_Qq9vkBh%>jiXrGd^VA-;sz(&R3 zyqbJ-IhH)t)XQwFzp~SGN*Fi0gL?!CvNh{#C$z)guxxe2?4cfCsf6V$0aVZ^BBy=H zSEd4$N+jic&!oOQqmujd22Ybh7KVYMJPNOQfU{D2Y?{HN$B$ho_{~t%I$$m7vbhZcN-M3cJ=VQv&zG=B7V-EyN!pm6jzUmA z%149M!8le|`ydtbpMF@$D8R8L)U}wQ__|zb^hV2a>8miF(d8erLVr z&JLlr5%uc`p)6KtA@)4Gy=&jHs~hr1>+0SUp4m>`-og~D5jqMaCZU}hD24|(G=mR4 zQCL*}|G5Cgt`BI@ia2dEoAGa)XlPg^_SfY9<%i}whro-04D-(lm7sL=(S2mqMS0;@ zx?!f_#K)tJPo4x}rzOufM}(Ytz$&SN5F~cu0AyiwMcEiK0EN9}5VmN>=Zx;MGmP&m z;3<{L=IVS#t}ux{)92;1wI%PSXuz0Cpfm^`zM!HZfiq_$+7^(V^^P~e5co$(90eHv zC_mpESF9ys+QUtndo+q=l^1&n4J3jz7Ug_KKd==m2VZfgjG0iPhO%DUwsorvwU2w@ z1skG?Y!5!rbPLCy;WR1}aHV8x?7-AQ`cZro%b%m{+x7uwy@ftt#_D%GB#pT=%y(Gh zzwTmUEPz<#@elB}S0EqzP^qgv>kB`23VuS(fXJc1_vh!wHFW!e)3*|oZmr0;XUj;@ zI$lM8$a+A_-1Z=?o(#W84)n9#)N~KnFGaAV?WJmje~n4dUX3;SZ0N4+4J;9so{Ebz z-ohpseMr|uFjhVTHJdOX<4||ZL3bqtEZgW6D1C3+n3?6dV+n=sB@4<%0BoODvQ=XE z@zlB28kj)M(PE`j5U!D>-3G~f9@`sbo*!kfg0U^&(4l_2Xzz7Jos!AiXyo;he$|UrmSx~z#p`3W3j|mWD%6Wt`fe{N4~Nw7ivDVG*dhi zsx`p-5B>O5Ip-tAZ!j=XXRg+PlL>0fXVS2ynv2cTDD)#m4<9ZRNl^5K=0F>rLKBMU zc4eD{m45WNE1cl6R0i0eiPUS00p^r%wN{4v&50_-VfaRA~!@O zGTWg(Tetedr#RrCJ8{&ZS+wI~c)dYP8hKzM6?eeid?|DlD((obc^#?ReL9L!W$lP{ zhD#I9y`x|Apc}o*0C0{u`W|qX9afPWRSW4xH*zDfXzWXTU4Ss3;Sa*Q3@7il6Bo)C zIBE}s(tQT@6xsOCEfLb%q?DB2J8>|= zU^8bRFSTBsF*%*P3TQC)HQ~E>)0~2fCSVSUBpYYkkcfy0F|o0>Sg5pARmGfVqv?(| z`z^2k*}jEdK>V^CDMYUx?_R$y=`4|DyZ!p}w>mc1|IvH-F^N(mYss~D^l*2t!|kBCVdkNRcV*byqY0w$p z-j2jen%rL9y48auq~L^-!st;40A&gyB)W9WESyLQ$cL_<#ldvq6}4GkmFy43+G)0} za${YcAD`_9p2tOo*qZJVMZV*`c}0eqOm4z3ZoQ(I1l4<7qJ6PuV0TrhwX%ogsHFZ zR05#7!eepAoa$}@n9Sy!-QAM*?c3)8#81-3DQ3WtERuw*O=MI5fMe><_B?w7!^Q$e z{sxpc4<9_xX6V35Kv;5)5Y|pKH1RCLj|e!Xq~xy|QKyn{=Lmzs3L}%(Ht04JlP!8i zVwK&1v}Gb1E%-ZHeC*vNk+gn`1iNHAViMxnLfA2$=M#;){p;HpGTeg5rSsz|`iR#M z9`8`c&xyRPOi;nUSrvzyX_!!}l32NaW@d<>oAc)77Ww1lj~>08YGmY2Fk@u}J;Vm! zUq^0~m!EJswgTat2JgsEZI21WAF)n5TiGf?T>4-cVO+|xCm9vhF1Gp&D8%{i=5C|+ zi>H{JZ2qb%k+5ND0#sect_bROPixAqlB_>i$Gx($eUDqcEvVulH*fwa*p(i{Pmp^f ze~)FjCW*D2h%PiYIkeG9c*MsrNEd-}Ap}(BMbO1;G*YwpH%r6W5RnQvz#;&q19-$6 zM|fe__?Su(9Tnva)pgJ}@9%znWz|vtcV`j20SB1Dcgo6|;aj`XQ4fF;mIbNLm9W)O zY?Qzla(Djvx^j`5+Zh6ta${ie2nQkzm@0AXD<_*Ymt?2@?H@jj$1{uggCU4!uh+9| zv0%~imQ`6gm0&kc50UJL>Va|^C~vb*&-20**#W0eyttF;DTATLQ7r9Qmu+MM*$i8K zyppUsVowuB(KZ(8;j}h_T2(=oa)0i*RIvI>-z}pK0v@(;UzkYzZc+1QP&mpt05P1% z5=J3%J!});3D04V?vUaci&9E>7%+3jF z7>n9e8f7&a5>5m3tPv$;W$=~4MdHj2o5;jIVP0m(kLya*7?c=U-A5N|?&DBvDuywqYi+jtC{|NeZ1x`DvWOpd_ zP9iS?274X^pN;fk}(hdULFrlr<+s{GnPR=@v3SQOBT?G7P=`N0F8eM zNme1M>~EonH~_{6CHNCV_a{ErRlHHc3raD!l)mUI_hdF>>|Ry{(K`S$($LhaD|8&y|giK*2~zCS4kU zbjj2CD38E}UnR(vU33?KR3NMIOW@z=k!XehYqzGAK=7K3A_i}oJOp>Ng}K6L127-N zyeFXHOJRs%q^9O40y4h-1+YDihdLX0PV{{LOMy zUtaocu5uu)o{vOwmCM8lNA4=jC5*v4q5GJ@$>?zetHfOA7n-R=}AA z-rNaT^6mS=yiE`FgSzqa@Yp32h#S2N?c^eObp>5( zLbKY&)JRJCop0b)KN%nE@FQ@hi<$Kky7E+6lN#Pk%S(?p0@*mTSs%9~GieQ9A8p`- zGZ1-qe13JP0gLk%{E~X3KmCMD$ZElRqdHaqg1lOG!zVjw+0AQn;^($el;bip?eNBr z7xU~(d1=e4BL#>=y^*Z+?bV})35SC9(?v%or|hSsO_BO47OW$uko8dh>{(Cxqw@$4 zuhCT`F@{$?e?EtsNQPRnj)uk)8>f>ePUwb(hsU$iJqtj-n^neYHk0#Ig0_l=e%P?G zAGDDVuv86)DzSM7ktlzOQnD%$Po4~ez3G)K1{n0`wcf4!=0ZW{@n)7 z^_98$110J4r6rdm=sC?W$uXl(KEuVeIs-(?pPOwP4>XKf`VB`&1d-;U(XSI+d%oyq zP)ee`$aw9Y2^|*s@k+t6$IQisH%@_JfZI++S=P2G5*y{u@9p*vKfo|{wA%=l1-1`X%s^f1J9ar z6V29MYKji^bUu6f+Z$elj)IxCRq!RiIoXwT2RnwS*tyWFh~e8ERP&~3d=_*JXdhE1 z5a{y#fkLFvHn5knfVSWv(yeh+lQK$v05qCNk&is-J{r|gZWBOVDertS=_n5g=d~6N zOdFX1h+))Ihn*!7jXHR!qX`O-1*w>eGRy<5tUh$1NR)=OI>HamhOhe=0UV1wm*Wg_ zwP!m^gdcb+6HQfZtq1Ho3tE^WU=#_JSDP*R#6jV1Q_Ip$TCb~N(&)?}W6S3!v<*f8 z!X#GdfCjbhZoD#${%k%@!xvDV5(Wgkn~Pt{Z0iylV! zI0=imm-H;2@Vr}i7qAih<1NlNYu2rs{T$A;wwhYpduFW7+#~~;8)UdBT>v_m!cg|n z$T^|!FNeYqh=rIFqpQobE}hMQ0g{jjBSLHkgDryN5yO@7Q4nb{;e;Y$*-qt>poN0_ zueBZou#AYM~@ampjcl8+5+#l?#&w=jIGtEq3)D} zmwbLoK~8q`qi1O5Cm0$ou(Y(S!yDiVd$*4iNilDNrYM+`p`jg`K%*wswyp4RL`*Q~ z-EPQ$NHj~82MrpH4o~Y@Rh2fYypxfUYD94*;>@LR6C}(U?t+%pJQ^*}Wyy~IUn)D6 zG~lN7|0JRec1Jg^U;my+$%EAb?gFH<2kEnwsyR}Uu0iJv;I34;-|Ah;gb_2RYQ)*O z+O=>G6)Bke!orlfhfYw8RAKoge?VN$9=hYKJfY1LEMQDbVnZd3>w=F+;2JbTP0`CoDpXKlcj1bvh%nq z%#Ti3+W_;ficQ|Xh`6hnX~eBq3N(np-*TVdzkYt>gZBWeFXvg;F=kYee1e5uA8xx6 z){Bw!=)s^?azu%x0Jz}{GzYVrBVL;}1=148)#+WW0IvxnA1EhZL z)3ki-sm6>OHxcUy1z1hTp#6%y`#V}SbOI;2`J!+7e31T!eHPGnES=SOI-46X@ni6G zJ4z}=A6XOOV87GH$3AVti&^+)zXQroX0Z?7Sq(IK`Y}4`NX>3^1g{VVY40Yt9TcvL zYrdF{Br`5fLbZ>fotlJ~C?xy<icA|S1j)Z}HoLZWivXp}}48ux%2=awq8+c}O(KABw9F4xkMNl&m zE96l=2+3jx<>DR+@Enm&%;w9VGptXu{s5*}JbhdxMQ}e?augE?KTfQ=)jWrJ^K$U> zvr_KYZv^{91s-ZCCITUd-0rGRnR1;OPT@{bQ8}AK-f|gi<5p~b|7>kPA`5;p+L_Yr z4TQe3jL202dG%>*I5Ihzx=!1q_f*Q&-C1 z_YPGR#O(^|_|aX=0K#6@ju2G_d?ogF=~K&&3Su85Y-9E#V15ENp231Llab#Sx1yGJ z?+W-H?u~)A~ z#zqm7CZ2?>tZWuQ6vWu@La?OIV0S|%5s}NDs6UQNTOBkgJ~sPrE2mH*Sw4OsmULQiUry-=`|?uQATN~UR^}q;_gNx z;;IL>qB8sdTf2yHt`+HsBZS&`h;0o`O}jw^+Qpmk`t934z$ib_{O{pyU>vwY2akzZ zWe(PmgbJrISc0UU63cu6RLv!ic0NSecdStY#TaV?x5<6|`mTTn7AV~GPR}duE9;~o zFZ~Wv0oGSyq?raO1a6j5vJ9_+P}fDO*1@)XKL@QpnS$XKp_*2oz;jGEC~y0Rn3aKy?05wUWO zOrv9|yV?By{*?44So2)Av%a5K&WB|w$RliOhdL}8Uyi#dzehv+PJ}pTOLYA-ma~hkTBF=Ig*(OmU2Xe`>W$V~2K0uN_ z0FLM+Bzk%V2D#m&e!thZ}Q$1Z@ zdu9BmVgtv1cws)hy=%FSd`{bsukBxfnT0J&y1h;i!_p+S)YrDWKT&c2e$>C0E(z$+ z9`@QRW&`03wSo6si1I49Jsu$2dX8O*K7v839Xo!y(}E*@^cSu+Y$7&;CH3b`4&Z@q zrYv|u+Wh_S@8w=pML%f0u?G+CX5dklWF3u-brMi0pTLT3H=ns9z@&1CKwMR2<=tp0 zLg9SJ(&~=Ef^{iT6@=aAGQRF*4is*_q1~i){2%**QuiZgsJ~g*V;uwqF6HyQ3R>~M z)jvz6Y90fgG3#pBA1&tV2scMJHu=K6bvx8<%AlP zLYKG(+P1t@A}=+jat2;ZO6q_xlghDafOubz^^V(f{83~iuH2@+*q{5b#Brfn{83jI z_kc7{Hl_m@f6h_trlBW#|N35cfLx?_pjFd+I~;|r-~5FO^D(m@BlKRxTqynb z-+xD$%yllE>&bJ|vLK)}+tIL}Ih%_X05u{~0O=I5c?{_CYpUe2sF>>faz^m0;d70LN zlms%%@!-2Kukq_3L3g5gsua7sFdT|f`Y!Fdo_0`-r<^7p|EBaaqZwnO{B+Uf#0}p z-+ui(+8=XU<@KvqJL7}qiqbotPxGg+uHafY1w!)VF>@q3GCsv#zu-s4^W2XmXT$^AlOHThQURhAmph*i!@;9?OW| zP4tO&7tH^4xY`K{T_t_j8~Pl!+DD+N$R5z_20zzj)v9g-l$GVN$;-yEqN_xSe(w}* zWIt>%EzksQWighLoSa2UlZ~zNHuiIlE-o`VOGv`#lgp?ZrflAg+WnIN>>fXEW2T4G z*o0HKAb_gO6&ThohI}CB9LjitR856SzQWKZmmwo*!RQhEfj#I4mJq`e-oo9CR9D{! z_3!z#w8~Uci!f+?2+?;2-ysS#BtmS5iWoP9%U(P$+n3E-w!Fr_LS3rnPLa!g{P=GU z)cNHM%ynGt;bCF&5{V`bJD3wKfSlaG60{BGhOxDEm?q|eA_W!WDf5+XZlT(t&Wy*N z;2Od|5a``PURqA4RhhuTn&t8YtV`Ni3RL<69Dbssyb7lG1h+z!ehT?5v|uYZ5%tV` zmKlqOs16w?gB|soo{pxeJ=gxa);=~S8DqEAR0JIg0dNAaXHTW1+>pJyMKb${;V`?-X5DV4$b7$ZQ}~zOT7$Pe%C~8re8n z!W2}vPTY#GxU3vz&J6wu10oK4;SG#|HzB2RN6ASn^%NUfPt}WSn`w6fzX*r{;}v1~i|I`bVWtA1Rbr3>d^O?}28@ zizVML+H^#1v!jnag1RfSso81!O|Dn^-(U;3lCq_q004T9i+JxI{~fX!vpv=2goD!Mjxg6 z=Qd@brk|rEyN$cVjoljWYVXvvv~VzqLS7=Sd>cUt<}b}bQzpGvTDqHB>KKCq*jG5! z)wC8u4S=o1(+{A7i(MK1if~;C1TV)c)EkWU>V_A0Qt173F`fP+*4mRLs4y%X+NY2H zK}fbVYE@Xe-(l%K*~q8}KHNt^7eP#`<%>DW4Q4Ef#1kX|-sn8~wQW@S&NPY6iylo? zQ_B)WJ|jRK5m%44DwN>GYy`@h_@bg&-f?tBaL@hdD31Y+p^s6c2Q24iqW{nk_GHA~ z^mEl8xDxE`9C?YamnKnY|@BvH@*(D$+Q!#Bfp=gwRDlO|OYXwehaf)L0UaQ3));yEiT zMGlCN;NJnjNlQ-l>me7AgmKP2=u{{M=0AGyz?Ju?FBatX1d@2to)%HwR8&;_sFqa_ zvq1B-banp~3GR`m@qW;_ukiFJ?RiLQVNwiP>+Y@u#v2P+HV(Any_mMQ;Dq#x8Ko5P ztctpla5A~)S*HIPtmK*6K0@*Q!b$*D`rq2Rx>}4PnE#xvc>OsFA^NfAt=~93<#2h; zsA>*ud()xx-C=18X~1PZo56MHB0Ii*RcGfLz(q2EajEc zrLYJ9@#EOBW37=4RJj}8hfh+A|K>qN*-f}gv|#v(+Q)lp*>y- zLlz)C@NSgDMKDaZQ|O5&vXwImvfF6Wu~XM;M1as-)TL4c{AwSMVRGc za~(Qu=MQb*!dUitGN3Y2lB+WCtQq)}O-Lghc!Agl!D+JLT2u{k!eHcN6ToB;V=O6F zehG<*@$@dXDfT+NBrN-DY5!WXDj8D?D_Nn7`5Z5|TRW%Gk{C@T&6gKd64`zg} zSiU`|s#0Ma7ly0#EGS%tVqrqtYTWhQKT&7GzIgV^0JkYS;xSjFU}ytmf&Ue zGk?HxM^HF{_|Ba-PlMHy96wEe7;?w)@`1x(-{Cz%Q6gzbO4516wFf6!Tr9T#ho&3FN0~TiBT}R#I{BU|d!EWhw5>*^~99?LP!k__VAnj!1$DU5Sea zZ=ga)FRETh&Ck{ymNImXsKc002l&~ePj1Ia6U4Du_iAyH%Lc$V8sM2VJnSFnQ zUlGIHDUs%Vxz+XaZ!T`-6?|>Q;ltU1QGCt(XnY^bQlJp>QfmE*mj=bZ`30qX3-e z!3f6V_LfLs$V^|{+|hUHu3yhYn(CzHtbugg2K-RN&{`fG{i|v#QMqUfjf{&qv@-YV3 zs+PX*D(^fFpUWfBFhFix>Vkr`q};;_NHvDi=_8WEPI{wzxb3fi%}~hhrx;7bwRcr0 zI)&5aHI;ud(H13~_6Hz|<|6sCSfC)?L!!NK%)_{H*9PzceFDaZ`~<|QTqIADH0vj% z-SDeDNNEkFkUusP@XZ1O($rmLQdLz|9Qu&+Y?oxM{eB?Hmk~*+7J4v*!<~wjZBL4Y zE(h4!7mzrdS~=k9GjTBrs{;2@Uy8D3P7psJovplDrY8NpdUa0`rc#6@Ti?(y@bRNZ zA6e7F;nhrNl8vQh04}8maH-Lx=RwgZ0gO%7UUJ@*SKhNz%qzg=sZaEe!+F<;o5{%m z%wP-v*+_#S)F%6dfpK4u+^B1^k_AjqN4KHJoje|ZQIo+kj>t%x8X8>Gtp~zmId_vp z!na=oxjG=i+O|_}@~o@CkCFZKJ+mmSJBOMVLoR>N_Bv64l+m4SK*2Y~d3;P8nmhjPjl1wM&*)-;i=#bLhZ< z>i?cQx4N4|5F#mS0J=O0iFwM2-`{BRWHVM99llW)g7jS0x*ujkOn9zF8=mM7lI%v7 zUDVvH_2AK?)mX(_^b*RoME>6_h`0*=IRM5;ayLy73Z(+uaQkzEIW`NBODOZ8pPR5f zP+5>%Fb(bH9@g+3JoTf&dASehyksrPUjQ$@A^6jU+s6OVx#jErJ$*U{S#k~OHewvE zJFsV2(=vf6pRleO&>xsOyC=YSgtMvMi4M>yKtdDNXrvP-72oJ=r^xl+ktDnccyqGp|Q@*%?XD} zpIw+MnkkIaNZvCVBh#I#V;pl7&8LH%v*7wtd8E@OiEOH9(Nt1>A&CwmBuPF`*9CT+ zXue5H|N3hHHpP(yg%Oz@W$e@DcsVPpy=(R^7xZv0(2zf1iIsV=4_);ym+Ia7vD12dML#dLmQ5&EORyiG-Pu`^4D-L#+mQ?JK+hhzN!u@u`Q$w17^NvyNm^ zZzDHBJS@{{R4|cZitL+`aknaY=?<)k5F3;^Fajc@N(QVCu}D%Y^{qZUP9*o!(PTGr zN%sYDk7C$bt*ICsGjBmR2&(|ttsWF(yFNDb{!ZuZ4MIiOt;8^F4;}?fDvVlB08h9c z6QisuFP|f_u9w`}d4q|Fex3fRW*poaVf6uUXGhUX+j3xWI`oP{FY~>lLrjgV0W&aT z`9~f(vJY)SxOT}$WyO=Tvv={x!-W6AEtV-BxJ=s9}Ss8ub%js85bT5cR$2M0GX4(hYF`Imi**K06y(+?GW{b&kG*HJW+4io!dsG_ZsE9*prhL>! zaJRAQ%BB=7>XT22`vU)}6nd1BxMU8q!B7Er4xxig>l8^vM0Aou;Wj~6 zSIwHNKrs}6KNXfFq1-K{z>1^p(TmiOr=^0+h!R04g9)Kv($g$f#udO+0kA0|cN4}0 z3XV_)Rxv)pzPYtkTGV&6dv}O(?=O^{_q)oyP7bhJzAo?f<8u`;@v=Q+EtEQK%_m#Q zciZ;fIdrdmm}P<{SvD*0*_FF043l4JQXW?x*sb5G)WH3IR$f}_%S}2zJ^%W>_Vc$t zzASnB;_K(93}F!CZg9>=ayXU=sg#&R-)I;n{J^#E-w*ghVSf(7=eM*GWX`V10LPiC zudnfp@kKNn78n$E!$m9sa--LqdW3)bK_~oI#mV3pjN5u-(~CJEB1JWK@E?oH_$>ri zuBN!(!r>gu)N~k#y=L*LSFa9*^og|4Qpn`0{d5#vVppiL1)M$(+5`&RSKud>Bos`h zKny>5^8PvM*U9K@f{6tlG#S(R6y5_~At^G2&*17Fi!r|kNtPd(1jZ@Fab7Ko1!@#e zCdkre+E1T8PmRSRbeFBjX|zCQ6%`f7pcdLjS>%?sC>S8vLn0|9>8%+)5h6`Y<@5t! z%7s$52ZFxaZme;nV{1nJfxF$4fZb= z58J+PF$d}B`%P3XX!NfGC-qoq~D>MF!&(FPc8Nt!ex zc-7u=t;AzUtwrOM&TtMTRRXy%?1xO8Jb4MJ3UTykK@hshh^>+#1qlWd^RR^(X$P2E zA#c;(A!=e(b+xHz0T{d|VQ>d-h}(4b`M4v&BI?0lqoH=bA%KP@7(rAYv|h=zQIJl#P1%PNZ(W>;;}DtPjfkibsyphIW`YWYmR<;e5f4Z;;cK#0&nih%4c zPQ=eZ2Cun~JF~jY6O*;*7Nmt8j!)PI(e*$1If!#6gkL&3zViZ#yb6z?P6f`~-cN|h z(^&)Ul%F4H!m? z+zos6>a|qJyIj|=R}dwE_vXzOXfx8NQ3euyg`0fAI@TPIuP;4dF2M;#I7H`DqeH3G z2n%DK;XNRhKHn(d%E|hgHhOeW4~ZK0M;LDRFYu?G_wR3x#j7U9KJhc2@c2Fn41Y7U zW&oO*I<`jnNK&c^Ov^^-@%y z-o2fuSX7V$& dKMlvOg9rKfdAC92Ao+bX{u>t|{+u;iPOO1SLvt4c`JoKFuFWVY zO2i}Fk$cKtd&LpUAmW-3n}w1F)lD~Us`RAfGL<_ttzBIlDaNhYv3@JYia#MsEl=0!3d)^^b%%M9(Oy(Eq# zk|r|7^!K0dD8`?Jhc9@NMvhfeTeyA8MJm5YH~8f1{ZKdv|~@q;8IsY zk$!jqehD|$u}EPmrGn4lYIa14JY?`-YdW-4K!84y8nVk0i;9ZUDUN#ormU>jLsAL+ zH-ouaQk?hu-71nZDBRAd>*obM->3b{?Er%EGRW+3epbmiGV+i3n9tjwSRY5v)_gaSoX- zS^$?Y0Q-b+jg~zrA3Y=UXI5b*diMiTdd}Rr_iU-EmW$lB@^Za8X47Dg9rMlMq(0T4 zJF`p0j1I5m%ZE?I-6ZI@bV`x)7BW3EMqoWP=sYgpVR=KGDGoUY8 zz`jYu-E6;9Ql7e+$7p-9g$bh1YR-IO(c>D>tHo9)NU4bZLfZe&Gk@Or$2NH zEJ7z5jzi|^zI2=p2z3`~42%bqEaj~35R*VexZc;+r@cT3(nWwGaiMxDh1SU8%9*~Q zghDyOMsp=&2s*g?N1)*+UbqqL;w=GMKcQqFkxpRq-{X~>!xR69_97A^>&YM1DT-*+ zVf1h%H+KF!%JAbc%p|cCgAaH$a;Pgtz?u3{rNpDxK1iP2bk@_X*|P`b6C-wVhOTT$ z<|j^oY+y17>eb=<_ouAjGl83-)t9J%`M9NeH5dI|gjGRnuZ(e&b zk0VVs&kC=h2z<@N5vMoyk0T{ys0>k74x+IC%Lcms@{JqMhazOh zoXcqQxhWN=8RUfN1de=?J8tAvoR24>qf^IeX{E!iwotg4phc>F{CLg%cH<1r8`U!p z@6c-mo=Zz>L?v4T(Ih-UO@x2ejJw(4*N&_mBw;kOCU~>|lEFLKj9JbYp=>k{>LEEr z$C^%waF9}60iJ#PX0dCW0VrX3wrT5*4LRAloPzKZX=Pdj5RQ zKq;rMKt#Ldk2zdJ`@4o`{|E8>)5!tKK^mh(l7$01~2-HOdc*sg47bp;_ zLxZ>MC)K@MT}zAcCln|SrK=|(gj*}B^r@q+7n=0(y0Uv91%xu0 z=&pltsL2VNsH6d{MS`tAWy&#T2xoKdL3l;PjsQ2F3T3h+lKQAVX7Em6$zf?LBK;UQZg$9qJ~T_Bx%PSfygQv|5K#gIdI!( zaEq^DKCc*&?M$$aNF?T5$>YJ*pdVe2r;}*D0YvkGX;S_C1;ut0HFS;8Xb5-XBmlyX zS@K~uhGBo9F1yNHiI41RHKrBym64$z%Ay=8h0Kblr%#9D6Q-kZT7*;GeRm%xYH8j0 z7=|MR9@KE#(N^P&=JVg*aYDx)I^?nmbZ<82+%|L%%sMs~D$+3ri_^JMHlY+n05F?N z?n!mE9ZW#;pCZ4PnYI5O%ZDj~^Hs=ltzppuGYXgFVSgM%71y<&h<0)1=+PJ#nY(MA&|4(5(z8GVlBZ2m!w;xvl!p-M{GOup=V_k2nlYkMhAUKi3%NQ%p_Fp+qteQQg5awDH$ z8TEe=HNtevk-FeW`#9U&Av)3JDGrj663R8PSi~HV48GSA7R|T&yME1J4oMK`VXuA%huyB4Qta3U~AF<)J1%t+AT5T^w}iP+tpo2)&QKL_(^P1=qQ9 z+M;!Y@a#jwS5E03L#c_CV;P#2Bvd4+VzLa520d&=y8%>ogN(*tE{oNeGUchr3hjee z4)^J2V(zZ|gLNtg`uU_pB69x8bKLuq_Uskcw!zTQTp>U)3d`cz^@UivUu(Frl~@2` zB*a>617L7xDTx?AU9OUmrgFLP?p`8y7E13F1j;Neh54-5%Y?bn$G-+X%oPwF9xPDC zTEN%+K|%72B&>o#KnmHLGWG)s@6jky_M58*yxQVJU01s1cVA$Kz86^YW##3yapXyJ zdcx057)D~^&8?sJldSGzi*64 zXXB5K50BVqH9hzE3LyG9I(%{RIN<_af+mUTzyO$)D|0wC{33$l_I8&9Vi!)O#LpwN z2Lf2|cp#_C2*wEFj?_ej!I;@Syvz|mgf1e7?PgL^K@IVZ45Kij`c_7IS|$4H&+Y9i zNujQ}1>ipun`RK?33XdfrZEnMZXEElEpI>+M@MhT?(>wkVsecGI!vaqEw;1sW$;HZ zfSnCD=P4wK=0tEar*4FdABXHi5in(IVzT=JCEW!5DB$V#}o0{KY)um8| z?4)g33AjK%Ta5*>5}}|QKx9kTudmas63XKcT-H#_J_FTx z&61X}JfIq?Cz}VqHya1Z9b`>IW&FaS=yqe~pQfV|wgtTE8W53L+M;3IBxZ7$ej2IrqAdHzEY!y2e`%>H(@!&)yu#=Twq+$x92X$_} zQa}z}wG>a!hKX#-ii(=3VaS>_uA#lwww!lfCD!l2!}CXN8Es z0Ct+>fgbG2A9Nb8S;*;Z?N>K3CNYcGky+{r){z3WibYa1mNalYBAK8}Hcn3Q&v`Gu z_^fp}#zlyyiJF|1@Ww?RKR%Ywia{SJ+%)y{?6xx+guaMg?u;pkK z_yD!XFa|mzLT*GXLfjLX-CIT!dL*L?Suy6YvxfCYMBnlBEx3ssLDp*Mz-0HIa%2*2 z9aUU4I9>OlDk_NxBg49AcfDccwvy6*jZ1VG!sOkpsO4aQO#qOZIitmRlv<9h4HU+J zv!#|)HR#_o3O%+XWpU9}5~iIm)SwG;bk~c`&3ytnJ4P}11IMW7W1SoxDXFS%#3aK- z=cp$;$Oddh7T*8}vlMZ=4@g_}<`bO%KOw^mmPa^Y2R=Z#ZL}-az)7%bTq;X&;@s^n z(O{#e@Sg+hf$kC?`T*uA%@HB-8Eh&A50!m@C;L&Sj72=Om)(ttxEUPg4Oq@Iln<%= zyHj|*S`4)mScw{-7UU^`lKnQ_ z=|j5QY4EFD?l$l!%=O-jCKvmh|JXSdMf8)Gk=Om=SN3fa<1lY<# zp1>e5A}2V_9Rw88Teil7XxDO>m+_>SQ!s@gIh;MB#B#dXQP-C_SsLrH6aL0R^j9g2 zf!+7TZ1;>*5`=j8d4?b+;VG&drG+6K*!ldDG+*!&N2;o+tnL4Wr*avv{7&(6Yq)t6 za5TnYqu67aki)#{&vdXg@P@UJ5sQ0~@i+|>1Csc(lAl^8DxNFoU1+89Uo|!Pfslkb zDa|D8{SO7aWC>gJxV zKi)SP-Xo!QQwk8C&YL^8X(~K1SVec_Z}6b~L7xazRhUyTk{3Fjm>wK*nHX+`B4!hn z_$C|x8uMsi& zgVdXi+&FYSZ-=X@UgVc&u3*~BN~Hh?@EmTs(?&t;)ZMJ8kaz`FX0{rO9L5PX5pCHc2 zYRS1ykt^nbY)1fep969UO2$wG-N)F2jpSs#p@F@K7WaG0ik0F7xrMd-p1{^&dA>rl z_>;fd4>%P@dNH&u7mWW2<_A)^y56R$5fy10+_@QtLkC|-q&bxHyk=5As642u>KHwK z{CvW2H;mQMQRFS>oGc&li$6D~=lns1*47}(Y8z2jPQm^j zpo7DnUHj*L2D1YIahj+lJjJY8T9jLU5!+I}3ElesM)K6&y60bWiIiI$&Q8RzPh1cWeKR9RdBPid=8a;q{d zQ96;-d6Xz_OcnTjU{Dc6;-#vW~C zv}f<*bJy%Amkha8)qM*+lL~!Lgo5{Q2~bO~q0vZ9O4@sINm>UXd0WA2ne_@V&aqZI zaOlvbXckXfe3Y-df5YT?;!nic4z{WX-1!cmo6{koAvzo`Lu%-HFx8bA)*w-oTmV_z|mS-;VDT; z4&1MaXvhDx)k>!-c|tg*{fZTHku$RHXV%h9-vG>BPhYH{s@k9DcA4#T23bKrkn5)V z1yj*v^}#lg1>YTG6t-^(i{_cB$ubt z8#PhFTF|{aqdZ(g%j{1e99u%*J*Mo)QOspSxGzPT+J9%y3Qg5V(C%lDusPu6VA6eC0|L=oF}&HyLs696zq+A&S4Ii_H^J&{ZV8EM)*FMXo6wk#cX5*+en0Tgi)KICx8 zVlfUuVcZ+BZyZ9FW&rPIn$arZdE=;Fzbw&hbJh%CuYC4E{=jS$qxU>28W5)^OL!sRIgb-~LEV?g-ZM2~FDwAy6P@ z!D8+)gE=;l8c(^N&@{%gDUyM=Kg7nWxYKgobIwQ?dQs*O?8ewV+bFC8Hsu6ZUoX(7 zXLOK0e3K98b+Q?3z>q=!Xr%JloI5@-HUY?~W&G62YiiU8SqeUtS^~YD#-m7QTG90F z9VQ6Embx7kmCSgcKh5>jZ+oazTG$a8piJJR(>!ChU>-H&av#KD{{yH;i7+H+(jcb6 zh137I)AOxoi~}l}yC6LC*^V$@Gk{(asjl7w-22H(nFBFg)zB- z+fmH0)EO}%CpjTukAS=YYjT0e(&?daD}?`dvva@5h9;?8^YE+@3FTJA7pqE4cp)>| zP();LJl<#qoN%&=Y~WvTmCZ{<0v_U|G)y9CY;F!@I_vNPa&MATiOApSTi*AHmu7L<|;Oc zyk!ecfGF*p(G(6 zA!xM;h|Lak4sWJu%kyhTklQcd6kcF$U7X5o+K<4e6G8zA2S)`Lio;h?OqXE9m6e*D zke!5(+d4<{pf32%m7PDYet?`v#vOi#OlW;=?e(sXj*?%`=Dxx?<$}Kr_OQETH@l<* zcjbPX-!sK7b>MLMBV^QdhlYLu-)#~XI=L-$p|227UrtTc5+fO2WUU#HRPj2M!xv4+ z5hn4edvUyL@$?IILm&+WrYhgmabi>w@+S3@SM2YR9z`~4H*w?$ zM~4s;ucTmXU>VRb3_O~A8cMy6S8Ph3APVOZ8VY>~5ZsGNrj3H{3+r${rS$>`{7|Rp zGK6vCkw%i(cJMp7Tf!zGX14vH-`PM>_6;4tRYJ=6MKH#Pi_P0M!n2DZFshmDy`HBI3xA6yTuoj3pQW zZapN({5s=HLBX^K2I=~mNEr3@fW4W{loPCC1dXi_qcg?4oq%~Kr+JvghkJN=HE`xk zXOvHvnoMbNac{&6qi>-{|3dS@qucwQH1FxVem%<|9AhZv9BRYN)mh5x)~|2CbVg!M z1JZ#E%8xz1zTFlO%U8xLqy;qx2e>WbfqF`^0cB>O;E|TpkmOWQ)TWU!6;6rjCczA1kzc7LKP@ImF94T&XJK6+)Y#vn-DAb z>&hO49yrR1&W4RF0uag`bNF8b?fQakRs!xJFwg-k;RC}q%7HwuFFGTQ9YN^9K7xMQ z!Q<}$?ljRD_<{dwMok=zH?o@~j^h75Y@!)uSQGf02Y=cEnAAo`s6BSj9P}!mz zD+V++H7#MabpyAb+JO+$og6KWho01C34qfAStS_i0HTIGiYo@rJF)WDUD@&7fJxa> zs1^=$Wlh3a9LzoJB1&C`W0Ra;)=3J!5Iv%eG3RUM61Nk{XYpmz^o2f-f0DT>_$Fya zj3><|4~{l;A0<^AucyS5g%MkC7_BCA8J>iqA)e+Yk&#Ah`W7@8*g^%_tWSCO6x_B}>Z(-QN$>#MhKXMg+ty@y1SY^x;;?34>W*9PA(16TSyboU?8 zQ~AGter-(`Uw7N|>9^(?h9!^<$jD%G(7FBVg~e$6g%MGvuC|*tgUNd(?U@& zf+re?xg_Bb3IhY@~_nH{Nd@t^lA`kKnQfb5sRUZ-MB zH0kauf&U*AK_I$SWFnaSJ41Byzo?g!SwbPJ9MlPK1teKXaBxi4GM%o=Ua#k`e|qtq zk~;PH|7ig>{2UzhB{G<91(FX=@?sy=y>|ZYqFcCXQnWbgfB_! zNxL%=d6ct~B}p4Ih)zr-84&-w9~5>PJ2HZhkb|?5%sAgBV5cantd!zT9A<7|5#+A& zY9|oxUX(}#UHm&hMn(-^SVMP-F4o5-S_)=-CTGd+!FjsT8rDo%34?SWE6fqA8JT4R zC9JK`=T8~GXt<^}XsA>St|;0g;g^;|TFga0a=_o;Uk0G`Z-RPiSnN!iQs6#`XTJ&Q zkQ1)d8!PU^uK1)WUTj?tB! z5TZJD=sTcRprLSQq$O^}v6#%@Kcbd{3F?z(52sL+7gG)HxgS474*nlecpSA5rPshBm+{x( z5n^aphM@OHW$P}&_(cBz_KZ+xqnlei;7T^~{cKjQ0j6DFe<%C-m{l(k=YWai@xzC; z6!6|uL518%XE|r5qUuGnAuJtNu(1kL_%l97D}#ySxq>#azDcGGy?}-oWfEgif@nIl zmtK?=qC)uObXwa-(5XXVDTLScbG3V;hf0@K#d#s_EA3kTjq;&@Fop*Y9~O{FaH93? z+dS|?3xdnMS;FJ@@uEN;&vtYT7@4IMP)pt~D&A$pqU-`7eGkE}M@y;?KqR7^_(zHm z3oFseM6$o?piJ(to=Gzw5>e7f@bPZ6v@fU_1QgDxaCWT-2Vm&nWFw<~$OY{v1gBm> zuV;$0mtiR`lw60E0(?={+{C6u*L-NCjJk-}bA!g7&v|&6i?;$8j2X+sEz8b|%EEHrrAmY_Uix(%*#UFc3+`~-DN;F4YtS8#!XhV@EgYHiQ*06+N?pgE+uj}e^ahH67A<{0%B;NNH zqazQ^nKy4A^xmRHido3vNENyq_-acNdXRfS;^&ZAo`m8GhgA$|+H&&`NX^p_v9NQt zvU#o?yu+t$w}1BJ$rwhOG^Rir=D>AYP?$4joap_jBNc-O(;K+r!2W*X3uk-SD5Chys#R5$=gCDFkfoSeGt(S|pbr;};m{4}iFpiGOfUckh~ z6W;^Vws&BVIRn0?dlJXC^(_r~0mO#~??oc`~>`=5Afs~jAviJc4-+1F%9OE{%R zGU__PU1cV}PLPhD2~_M!WB?*i7eL{0!;rP^dS}rFV`c`pWY5cG=FlE~w%p*s1+2kM zKwGMe78ZF&1jhVk2s(ovEDr{62I-;OFiqNx!Omy8lLoKs-~GW&|3$p%=(9TbCp!v=PN0}7x1Fb=p~r#RVWqW5wl zQHu-cF+Ni@qG3}p$BzsM2&m$uRTok)Sju!d50KYBSrAg1qeokV%B*&wIp|np--`{P zH^taEq%c1}kkhT0?#J9d?U9&Ba&NssJCdLEP+DTyHN1MhrHSu8mu~o!P_6u=YeKCR z!mRReSPO*J3AE>rskzKhNc5aLYnC5{5Ghq_F)l`nNDFKW_u)~x>RC$5Q9^3-8$(#S zbC^reGQmC%L_WHQWH*PPCOd)hZ#>E#TM)*nJ37y|a@T!F--sKk2ViP5<=I)TL2V|1 zU^k?H*#|Dsv-f}jLsX4CrSxKK0*hT905eQ7FxZb_xUe+VXhGt`wbn`jdI}aOW0ieP zOl-&AA*Yg}vIjRvK2`}+#3G+LROX@{FDH%50|SO5@dhGA zg8bH8V`@-W3dbMd}&5!1|aR->2v;g9)K#YdnM8JWUot->m0T&O4 zI3waIn+9*9l5G0il#!GJ!4%FVRQAVWV@v4~JgHDi=@|-G&^8hzyc~ohc%elThk^_KDy+_)(heZ zxM0?G!J)g8xf`FU0KMs+jY)7sN&W?*>VlbEBEE+dO5`_f+_J-HtXr968w6aF!&&K$ z(bW`G{R`&Hy>nA0;FRE$vyQ&ITA-k+Dk}M-_n+bzyp7F6Lbu8mUQI@c7YCjCz#wo~ zO=gnSbmz!AC)lk{cw9WJkLopdBxfSYa;D90_;BjPA;!T`=o zg%X=(l+QjhLEwalb{;jZ(f(7~rgF~lwytnO_!AF7xH36mOo(N|78FL6JWyCwbrO&I zWzlE;YJc{ylW$P<`0+ow)2F{2%ickc+yq8+6uTBN7uN8735(7Y1FJlsDop3VOL+YD z13(#+;zS^b+$6R7mjk_bsBH)&?Q7yf=ed@r&dPwdV?vGC(q(Dim+-HdKiVB z#8giHe!DxSvUwo65v%hB@3==5;;_S*J{g-9 zuO1;ohi$-Q@zc!y-5|Oz_!W37QoaW5U>i)v1zATGxt^;LGG|9qd)mdXXa2isMso2t z^vBJV3oBqResXj@fh%CySCQ4{n#pN1`!9my|d_hk= zkZk#Izo8PUqV$-BWNEILBLO>GgPSI?dVLd4v#*8Ze6BE_3JPmQ_HpiHgXVI49My| zC;%q@Bx^Jmbpuw0a|Gp0;>D6X^s%jNw^Benx~)_KvD`g8d@=b{!x)wjqQ}UXPwY+d z48{T!8t}?3_0e|BU>=bgSF5-wr||mQ!5GRp^&Acs`*XeSd;Iou6x5tOhKXsxezqvJ z2n$^+Rp`OY#34w6OaQ_%L=?i^J9o@(ANXzF-IxizSU0|Sgc*(5U2x}|m{?(Sv3aiI z`d7jChWXQ6C&DU_e4BbI1Yr(3x{E;^UT|m)fDbh^Mw<{UoCWQb?$xV;8ydaMLR1xj zVPUtif&EUfDwywS=!D+r5FBOm%&F*$xYh&R{>xw35oI)R8n&D`0BP6YwxY3jmK2~zO8<7vcj^wTy7F{PO5rbK3B@z*A_=D{h zE&@z&YmDc-lQ-J=!;mK$@)uO79p+Ux@w8YAoKA>H<%dn(s?Xq`5A667<_0v!o;a}) z9h;K)o{dO-&MH`Z2H_e;E7_Omx;hao&B5XV;-87P`y8**klz5Nm;@W=1wl&I^BCNKkV(K}`2P6`jj*jNig$u)f0e*GGT+OppNa9&s zW^*0|Cahv}Mx!B_fUt_ZVH$cfs&~@E_R)3>LpmgsO#7Xp3Ax+zx2vl#35q@w-@#v+ zTnr~bZ-ts&3;j7-A*$CianN_Hrc7> zwo=B|q)JW8P)_B-ezWD9<2FD0la4Lz>5CNQ&J?DCrtxG>nDughPWV(_@STNOe3$ zUw6^!GqA%G6t@~`Et2tV$DN{mIWgox`Sbhr?4c7+AiuD1{uk4L+MOAIRNvM19cBN zSX@Dd^ksv%Zyjuz*Si0}flFu(lp)7tEZwuMxNd5=5II)r8qtL^rc;vmZacbc5&*VU2x4$J^4}tUGDqL=pzdt;mO6 z9B=D|x89mm!B#{q1}G3K-+u0(sMX-({Ii6bLIeXes`O7egQ65DwO-g-gpYw6CPswA z8>xhq6XWy#_3IIY>-~TcL9Q~rgOcqByNA;W{KVqU4S~zhE z5sH25G{@sLzLYXJN&!9)p}KWNCA3^UqK4~0=xt(nACiT)$xESQrnx;;mDaM(NSeeG!<8Ytm*1`|_O=xGx z8l8;Q?m5hVKa|>P^vzeiuMKBNZ8m8%5kQL}NZtW5DvOIxpFv`F)$4WSTQ1V8Sj6pZrx~- zG#MBi$Br0*TuocbbRp-*1)v3?u3AVvx1EIdkF-5jgass?JzE+@Vh*RXykc;{Ia{qj zyo4wWUx8v70%+|62|H_|X2Ns0t3PKX*a_vQkhWZ+Egf)u`?rDU>tciQY7JFr2sf@> zdy3f;*`lxl@Rlz0kf$at&ehRw2?d!R% z=w3F9?MU@S7J(oTQL*>F9X5Pj6!{!rg~QQo_WNzx^*I^?O_4K%(}i4vW^JIv^+m7V zbEVR45he2V(3M_l0TA(4I|Y(Gw%iQi{SN3XpCm@2Aj& z*zr15B-u<&E91^5vvPn)YBE7vE>Gj~j>Wo)hD(UMvVb^*sR2eS^j&M~-WOzcVW8mu6(La1ghGWRT_6#|~8@?O2m9bL52P8!o8D8}P);)@pTn`I%`O}Ne zxREZQV&v&Rciz0;C6Xe_+E{eFA6P!AeEug4KFI=Eb|69TGiQjU>Vd~dFhfxZg|4~u zGy^U9RyvX@3Kuc7M-1X>U{%5;`o?MX`9596nJFq;X!Z~AnVwL~pXR7r0x~3K51HW8 zl;(htW^&^|(C!LeHVV0naE;gGsFbhMRVw41FGcND4Rybgw`hfBlOk!&H|QGB&F>pH z^fU2Z_TnNcMYg&1V?z)Lr1nTdI#6P1?8|dvkX=uvS2+_hUP>@q9Q&|b6=2vL=)bG$-xoS zt_X)y>GmHT^0@7Ijty2~w3)Dpk3xw@F`mP1B!{(a83s=P@$|n5kgcN9PLncq1hjRe zf2>4^fV!f^R3gc(|DX#axNuCh_?(cNmVKnUf1#|JAuKmazx_7Pc-OC3F>(umf9>nn zZp1*wZQHt41@xkFic66kVl%S0( zfyu@09d|0#wTE$e)_|vO3+^A zqVhN7LQb5Ol$+f(UEfbF6{!njgl=)koHg~NtHRc3V9$p|ZWxNhXYg=+MiDigVj)`3UIBo6o7}Lu5^xBf_3pa0` zAXPiqT4Uf!BE%3yQsf2mF8YW7umcF)FtW{0TU5cWc1Cm;h^YxNy@#ot7jd}0zoJHJ za@0w~3@f7)`$BO&S;U~9IC5l?5A9SNArLz`G=3)*w;IWs{dFIYaeit(yomXTt%0nL zikOCKBnjR?*WNIm_l`~sCYy^1A3BBv{4_vJABRx9POa#gA1H4HbkoXpupVA%8YHx? zp`q0;Kz%y1*CQwG6V?3Yo08;nqI{YD;K(XlTEK@3qabk=n)3%wP z&c1D=T$>D2z`7G@mT_>>HsFe)SYjEr=wn&8U9sYxtn&DL6fSDB$$P@uvW68OeR5}~ zby24F3_FEe*#0%F2Y&qgc@;DeSYG=umIfE%xG2B3ATy@?y>ZuLbzyhH$Mi zr(F3OQ2kGGNiR_e9i>IY_$}ka~w@5ikma&{Sr8L zvR7vbg)r#o`LyNNqU^L3DmfZ_sOR>q&Wica-ado$FTDsW4Y@vhYhW-RQS}}wtdFNe z)#38I$VE8=0562dTa-2Z#OTRY^*L*G1BR(RPo61Ko0wjxuU+^F$ zaTOJlC%Jj6Nt(GuzKJzH4P(^<_ZaabunFiq17Q_8MYQcIWF^)pZ8;21KV18r;4u*? zP)kuv50cfz?b1fg{^!;kH~&T$+Z`h{XM7;Ku8^hLijy%1BW&@f{JO|(`H|a8EIuhp zNDVHKawzNL8#Zpd$WMlcYxIHw`GqVq1HVQ9aKP{A9={+l-G(%-hpAj4MUSeIQuw!y zjw*!z-B2VOp|F(=s~2&oT;Bup&e141$GM+9_Osp6;)TDr*a~hjFd?sp>gPIq52;^JS-yt~dhWqB?Y_3t4?} zfP&;WjoF~9R70kr&{*Vis^o!wy&OF-s0MD$zpy@nUup@y`0nYWM+UmRc?MW`sTcEm!w?mQ8kd9^a%wok+L zzxEv{b&)70nb+5S4s*u={74j)rEQ#;dhVR}Tx;w2$7xIVxvXC8$7%e;NTt7$p0mFi zo}^DW_0ZgR%m|Qmg6dE{y>OP0%Odo3CL;y+xHUN}^4q?BOnS@JKbC9+PXe_`9(aum zaUn4)JlpFK-O;3aig30WI1NNr!5+538l)#OQLBjbi3CU!6{R7fuO`HJCiGI>l>$6y zFoK}}PgrS~K`*bO$J+yMYWeckuD{U{tOU-i;os^HmR}}DZi^v{=9&j{=ulNrB>{L0 zd{mcUrX}TI2tjwTgB#L@T{bVF+!n3GrVn)%N9dcLV%9GL1{j0}6q>Gy z8+@6T!hRm#dahtkl2R-QXx!%G^NCT4T8Cye)<9;q5GJ$*awDzWRS76XwtxRV!n9we zKS$^(2KcG{0^k*XBcaa7LK)kF=^AmU+b(htKcW-(a_#-i`sa+0X@=N-CrijOXashR zfGwnN3xID~()=8Hfqy_G@?jY_0YZt~6=(DXmEj|1#Eo5g(S%L23W0JL-{JvB&KGQO zf#elU#_ZSaCrCpkyNOQZU^I8NGV0TDYzXvr`#J1e*(0UgDPrKII+&fGQ?%$dV@_!~ zV=55EAf~~SKahgQu_R`ra$g79dV>5>U_h)&6;a{g*}TmOKtg8Qr=4V+_wPW-S*T3k zaR@ilRgvE-O^3M`Li>DDO3EM(U&0_2p?8iW-$TSIO*cO$=**dQx0vxLq@~PM4$;%l zFcgsB!`Z{$PGf=gIKYx77`UGmgQoHj5 z^v5~4z>lPTIG18ukUz}`xPB{tR^M(YukRcty)a2!Cig9V_N-Zfq<<7M|HZk#jLdG~ zodNU6u+UaaKrfhscuy!Ht=Uq>#4i%-7C;{vPbp%&qf-IVUlBU|Oh_RP#41PK^zGAr zo9x*jRPD#Y^0bz(r6W~oOw(&maed?39-lX@PJ%0-&x4gn1W^dJ83V^{quu_}em zt_G`Vg?=crTdUyYpt!fpGz6DK11G5?p#g?w&?p-ZnvkB0+adyRjmu_wU_FyHQ z$R3fINDml{PHH#jL@uXeIzJ?sAhjpt7za?r1=4xX`|xc$Lw}-3dHRT`P?b!)RURrS zpbjQ0R;UtNY#noVHAY{CJ@0qsgVfSy$w3}7W2B7&xZ-yp$B&_ugs z4;#Gc1)HgpOvN7XT($vDCRognn5qf0SD^m z)xE}K7i~X*zQy^#(H&f53;C7j1!RS>jkxRS2eUxTW zQ#|P_Hw#RPDJX_;m%rtr$fg&$vJ2~79_4soV4yE6XC!iT!FCg;GY?!%vG#+{Cob0G z5=j*M@G;QKQ26cd$OCRSdY0qt>txTX;aVAv8DzcwP!GtMr<4?@=AW*?p^tqy5#>$Q z+6(i6aFh8FVm68}L&-`+pg}sogdSJ(DFC3`JGfp>Fn%t`T>TA|sz_-j;P>~<0GAC6 zd@B&(pu|K5DX+%BjbKA!?sk@CLM_8(;3=Ge=uNa0`=Ya(1EzNoVXq#ZzQI7V3NLA4 zZ(X=BnB_kLYJz&^G6dgVK0pi6uwQ@~+S#?rSC-v_8_!@d4Q659!ysIv%+O?no{bpw zBDs>9uddergnepja~!?jSwvwkI&*>W;=3Na zwx2AJI=Y?(-AD>-Yw_Z)Vd^!5-=eLVp8Iu}| zg6H3~w7Uer-FbF>%R#KO0Vp?9L5)68Ck0a85MUWQ0vKUAdow~!<~+r<(DRN*XF#|L zxps5je%^{T>b@0q@QAQ_D`ww);Df9ug6AEUF5rwD?UD!7B7TUM0+|akY$tkZEYPjs z4rcJPXNbdO34b95{sT>?Jt-Je9+Rg}|4Wac>2_+vI3DX00ktwfPa+|{Xdi(qEWM}> zuA3`d3C}2wx8Z^sr=gJ#2scC+o6`sm@dMC$#TUAxg6!b#y?gI)$;2`;`YF&~BPH&Q z8>I0OD`*9h^T1W#>}7y#n7y_p9epGIBQGHFbMsG^!3h}&j|7%zb9{kfBGoTqZzz9z z&b;<~<7N*JC*=TBnk6yTsh1y6hkJmAb$CRCDMP>Qc&Hz6 zZ~dZA(?cLU5aM7EdHjzy<6-gHxKW4He6t;U&W)t_JWEO(b!a^2^&SLv&hk5Cmk^h%|!`nk}V>z6Himd*Aqqv_0skTFn_jkecL2Ea3|H=K(RNg}BsadAI8Or5_Um@233IpM2(t69I%?^EWrJ#T%(0>Z!D9s1q^-5g`Drr3@cD z``0a1;e-c-@5zPijOHVXmfs8>gq=(l9D_W}kBnXV=v%+RvxttLfOG3RB#!}D*)B@q z?>xQN0399}toA^gbaIhcF<8!%XrJ$Z-_2-8QrXp=L>U{gqf_W3%Mc)*pbxmg1u0h4 z(#4BYTsLeO{Nl={LQ+4&Ib_I=i>F{VLP9%!=gR(1DJIgXWP>T-w`|?2j(Qr{xtJ{a4Cl6j!hL9j1vExw!#{haV=x@>c%z(UJZ*Z)iu=nGkj0UTw zbL%ArWX_y-e6oe`$F(!FMp?G!^>gR$ts;=c8+emM6B!2VHS&y~;I_H{`u+Ptbmz=^ zK`gnNsvrhWRR^cdN18C?P7@8_7q&9c)RwtMpAZZzJ9hU`60sJX^54*XoMW_Oo=Lo| zEppUzY&Qyx{@yKBIgAVMeRjo-qxil@XdIq(8c4Q+z?4B?s+{Iq5r%x^gt@UyXQotl z-=8R;PvC+@W+esoew$}m$7cTo>k>}^ImqUOag0K60p;*4Dj>`3TK8zdvihTZ84!%Vo{C^Kg*8v& z6ah|fZg7m~GBgc#~djGK&7t;9$rAIYv5se7ZaZ*u8Oh&}D~XBj zFQ~l|0l6lMZ-oa%hr;h&FNw@Zb@i=$mvXcWwc@(xf0u!sRzO!K;gMaaU2+j^zzF(+ zTBNIkaK@_fpQ2JQQq+@N3`M26Ws044^cyM^Ot z=2!1OrNu<-c&__O-e(N=d)32-pSx_%v3p;?eLIrg$qTeB1ecH)xWJ0ockRlR&p3q+ z!N8>;l=1;$se)IzE0H`YE)Ght)e_9Pxz3RZ+#TvHL>u*pDnph2pD~lwVighN>g(b9 zucI%x!tafO=c5%}xq=iX4jE}N`Rofhz;P^xdlaSy$k8rezaHy=T15p3p)k_-rqk}v zw`LYZq>-Uv8J%Tq`!^S60y5txoPG_cP;rz(pH}d&fnp(kN3WYM+1-t*`O(9NThf+Y zd7wk|8J!r;xN_kQ+Dum|3$ZOm?Nl+|00aHkfgHpN#A4*|6=Pv)K62o^gLj&U>fN2W zIqVbPBM}k4!>8|5fpicukrt*Vz4^QqIM7TYVg8@JmO?JYmyNgg#pQ?&y{{dI+}l7k z&_Q)~{3P?l0XLtGm=Rz?rk0(yLuTR)4imaSN4n&$96%3mmBJioj%+y9R!C+?SeH)} zChmP$10;Hn1nPBGoxU?<=iAItH2NYTvGv`% z)wrRn5K|}M`x8pV({u>0xhd`G&=L^{MngwDWW*hVTcvmX=qPtcsfZ!eeU4Co@_aU9 z(edgagCvkt&x<@nuf{uv=_bd01moTcK*OD`$-~HmclPFwqOfil>ge>Rn+_MSJHK@m zR+Ai7O$>nnG1FnPzT;C-F;cmg&BswzJs^e!hl9kOkj<0L)GiSKI${LNGnD?Bkn5N zGU$qT>(B#gfxhqx;b5yI(_GlTv?K#D4a%Ep7Ig*e_cmtoP217gp!@d=;|J>vmVyDj z0;Fs}$77G{(bps!=wV+;rgjzw5MeKKsJ1tLrRDiTHx~d*_E~66&H@+deC5p+-Y|j}@Fm_?pU~L3wI2erX)= zop>4g+D8qh*d0XtYX*O7HR!qWjTHxUWqWW*g%G~~lGz`_jXX6xxv4eDQeJ0p%*gxN z^UBAM_lXZJzCXQ-J@sxKoU#Urvoc8FefNrsM-W=Z*ihH4R4SPi(GCdHN>^uvmNf1k zHgu>JR`WxL4ykdLT+&`~J^IG>Z{w-%T2PD#6%}7RiWr$c3ToY3u%PhjBKclS(zs1( z)qQ6AEZ`oh4>ngnj}edj6xjf$bS9S#W{NbLw(syXacmFTDVJzccYU}M8hP3QSwzp@ zWU(pJV8()y$bz^BlUG^LR_`eZ3&*^sknn~yzsP0rf~D39w$=4YIs%{GZd`nf z8nm;)ytC+8A&5Xcdg8!=c`y0w@f2Igj)EVN*$PxCi_78(4x0O7 zbatD4R1%_I0h|Jho`jK+qtkT2?-v5LeHim=0=V;KY~GHn$x7ad-L>cats>8J<0d92 ze*{6?z!WVtW+cf67f_JwE9RKEM?0ay=S}7KjpWWNAS$4U6J`wAC4w_0cjy5)i{0sk z$vj_Aa2^xQgYo=gSMY&P)RsQ5px1E_{QBqRj(uEo#l~LEwokh<@OPHX0V3KBaILi` zCz}YbwwoCbgTP?Slmf1FeE&XdX6Vp|Q^?lR#t}h~?sQ6BH!UPCtY{}ErQ6V^GDuB+ z3J(wxg^HDzo(2hB0q#sjQtF$^-9{(OOtUYzn8tHbZ$~tiiolb@yaxB=K`Nd~x-~K@ z7m-N4f=0H5u7%^PoJwZVn8OB~J{9`UFW@!sc67|vJXjousLGvMxbK__dn&nh+POJ! zLi#isVvP2$SJS)VHy8|Sr`21H2kjWAHnr<4Ts^(TAU2-v1u)F7c>|_LQ(^q^7ar2P z&z~=6lB>&ujhCK6F$EHlD3z!C3_XE8s2F;RERh0e<>WLXi^gd#-K&C{i?%OMjQQbl_6I5g_jcm$qC|cCbX>@`_?h-7=Ks<3CSWzLZ{PotC?p{i zp%Ow!LZ-Bu6rnOzLM0S!3Y*xmn-NkWWolH~rY7^$M5RzNl(9)Ep^Qa%KiBX7f8OJG zj(0!%+0|P2eGTXNolca@ZV1xih4UK_Q^dgnyvV0nXbHt$0TSt)30hjN1aS8_!=%bK zJa@um0WO{+eCP@B@y)E=M!t`l;HH3kc_OjUHJn4cX$~6rN2H)#p+i-msP!=(wR7PH zfh!-wn?IfT_5GTlOk2TmyvdJff|Gkd!Xg7d;{`6`uHU~alTr?X)xLQ4@S#80&b->U zZ!HKx2Lg?lzj*OX9!WK$#Lg0LO@x1Hze2m&nP7Rvd}OWCopob=T+?hnPmB$ydb zPfuSZ*>Pn3dRaqoAX%oE>e0@+)|@=qJ_6EkoB}hwqiC9(aa(jG!Yc_J%s0t>{+c!a zy#pYy;4+RQ(8@VFOMkHrtF?#<2%1w64>r z*kEiI@PgPIdXpyg6KP4v2vShbP=xmuuOd!BNp!t(B1D#@UeA&z;%iR;q}bPQ-=+|M z6Y^-y-!qu10Uc6=>D@ZbPGL2lK;Ta&ZAT0rF`^K;l|4x;NI8w@G}BSdZG`EWhD_w> zy;VuzS{9IQ>t5gN&YZ^qM1v^Kx3=~rbHUwo5dkyJ z#teh=SEGjM1rc5gys3v$_w>1Q<|vZN3AnBTt;;}rG!E`1g2hj|%#qOj`)A%Pec|S> zUI|S{AYwgh`Wc!6OA(lWxi^>oDUP%G`sveq@KzfS+P|879#4#=OB@|_m}z}O^d*}h z6%Ry44twOLzl-wqR$7-V4Ast@cR#p&A2FKT2?1ylxBHl2XS7oeuP^m~_<^$`4eznB zLT+b1jv|r#+h9V(u#XI)(VPLHBKS0l=rxo_jv150EhrLY&HzC1vFqt~>{seVFM+~F z;k-_a;VIw}_W=Cq4&g6y2=RPxu+Sc;w#EEumxF7(DztYD^w_fk zd4CM12{2RlL$hxGL3R{GsVtbKdT@0YVSoM*;u-n1du)_fqIMy|zl8ZHP87<%umon1 zacS)7mH-^Spk`rmoCg~gzo6#^>mFVBf&DvPxK zBEE+yAV4iEd=BS<6aB~nzGremLI9Um{N(A=2ck{$gp=~7FI1A1extqcgvAy?N3Kkq z7;?BMjYZ}RE6aSk2e-YBU4s1khp><%NQ?m+GGOsUAS^l*GhTOdL)q|E^jspj_fbhn zWhTR#q}8{gkh=4z zD`x!r+ls@iks7p?T{te-*#MePi~I_+DHPoj$z`y34w61}Ud|ZS@%Qcz4GEC}&@xcS zMeJTT8Eer7ATV_<5ZZMWQo-!g?3yayy*quW!b5<1IOE2mc9HTYm1Mi1Tx{WClz~V% z(xF!{V1nEa<#S~X$|#>kPnd8B!~I5&b?YWlpobhoQ%n_ejQi*mw!#y<85xNIm5Dk$ z>PT5h6j4S@L|oL$xo<*eWGLoaikX78Oy5MfG@F>9jd;>mW4vkRPP{~*6^}Ey03+5t zK&tD!gK~gh4d#PgB86}+-N`>x_E_~D_$EPM@qK=_wN=SVWBErj5Fl+Z5j~oqnS_OB z9C%PaUL+|%m^Q3W?YSAuKwM0<{Pwq0XbV^v4JaUkQEpvfvs@5jevYQGP@Q>*u5O3i zcDd`M8;d!^38lgvpsKET^z4zlp%*`<#T@yAc)Bnig|G*2}#1D`Zsbbtu1&4ywT4zB*$`bk! zi9{DsO(66KZe<%PuQ8}&>JS5^^F%MMX9ON*1;l9ql?HQ}YSO97)8GRuIEtnIvZ$zA zbDK{t53~WEA)Wn2i8+=kZZ|?`i<>Aks=*$W%xpH1xPV`|6C>ttB#|bkge{X09wG8E z;gOa+eCT3GzKpQF%wu7bS`>`ugaSekI;DWa`RelN#9(DfB-a_Ii`sM-zW!WL<;}9v zx0H^4%;b;~=N>@^5MiNJj=nq!8L}IjOIYuhaMKo&t+@mn&v~?)Q)rwS$7RAAOGA!1 zUPz;m7%ifXAS(9&N^S%)5imV9JnP3oBgCvya_^d)HRP+g)|?T_i9RU`@Xd1v6Qs!( zq17*$PUTaF6%j6Ll9A0Om{c_(vv>j01=npuIfB=*>izpUf(iJGV!ICa*Jt7{V~J>V z;v(9q8r*@SPK{hoYM7}WPNXz}DxYz<(3Nt)mIz8lp_-muRKsM%4TyRYQW6vM+CV8t zP))i4=ns&nda=U8jS?1}3taqiDVBL^;0#?nXQcb#Q6W%AUi-(OzeT1Fgf zX`;3E-lG%$(qmGDF5j++QYn=OFF>d|UQ-!oVJ0=tLnc!mL0(z{epLV**$m3%1;~FF z#_q+X^)cJ2*GZodIf?_1;Lam}Wj`4f>YTV+$}v8W)9xR6@L&|#irN^{E*!BMb`?(q ziU3R0d@52U=66yPm%nAt>7~<7l z<~S3x7Z4Rl$%<+c2R}xT;93$g0B05g(S4$Rgr41`VPwJ#%_X47a zL5qXTLgbbsFENTZaG(l7K$n;Y{Vrr?w&E?#E_wX;4fyR+`bIGtQ?YB;>UGaA?h{Fb zeR}n(rdV_TgN)1K_*^+BmCXds_7?}@WC+cYTYr8R32^)ZXzMlrk)=e3<$xT3RdgGT z`RxeNb4{83R!L7{jdy?fHRDWi-hF zEv6iv_lYsBgRByv?;j9kbrjABvL=y3E@z-5CqM_B6kk4n9zW{PEbfGVu-~S@XJe^G zW7?DaX>*%{3@+_UaT7!y@0OJ+cWl8Hw$Y{Spr_nM?Nfp>9vX}iP4m4ce@Awromg|05B zR?5)JGXU`J{hAhkj@VlWBN9>G+{9HY>Qo@l0;W|=!J$rEPUnLpv~y&C#Xo57uxL>o z11Bo*LC+h=<-*dQi5E5nFh(e1&ARxP{G0G2RYa07fz~zID%v<#cP~L1C&&gp1;Qeha~rPcfROb zlt`lYc}I(t#`?cRpZXOg`2adhGqX-=Ozx%>Ng$Jg*ykI7FgH?DmHeFT92PEIX`+;u zNw&-xJcPorcb)k4r>7DQ!OaZHzHFW# z<$e+~88Q~al!~4m)YpuDz)9IGh=c3GXvfP+6=^GW=5reEgHXQ;j=h@wCvs!Pk@3yE zVP)ntzyAWI&n)H{f$H)OV-5%Kp_)+5C4q$3@?-)a*oTf9IkKGSkxHz`-Rm;xqOaQyEr-RRV5tsWn zF~NIy+Q?ahuyUFLJrYR}fD}c55&xiawnq|BOSy*qjac&naZ}9ZHflmc1d!Z|v4$MK zFR-cXK6>cT%HMDY&1>*t|1Mdz6CoK7*aS9o8EG--UB}VioVZ3l@>D19B;1oa6VnhK4q(L@k zu`5J1*;Di)xVK&YK(|KGA7!HkQQBv2GS)8DmrFPdilJx<14odv57`8O21~ySH&Gw1 z&4N|LF%sQ3elR!-M za@-h#U9%@>=^gUvFC5JxMs+^s`T>3VtVFM)ekdkpY)QkWjubYr8uB^9NWiTTM#(87 zr>N#j4NdT6kPj(4-?@PM8pf=*vak?ygG6p+DF@&&L5#qxAN%*uucwoT4ZHKUswx`{ zt^lDogW0E}`Y9A{c@M%XfVQSvMkIECEny%Av_*)A?2>rChI*Ezn8*?H<&F@(i2c?!(cjTc3iYWJ_$(awbXC`T~Iueap)ghxDtQ}B0tvG=kxIE z&^}!UsuNI-$d+L?kG&lw(yovYO)Pa?6ms)$ODqD8+z!~w^b1+={~_c~H=w78hvvZ= zCgW&rEdiQH;ng&YSB-M-tZhmj71cwWHCAH|51^7A#z2kROeg1Ow}Kxtw@bu`d(*FF zqc#o{CGgzjjvNjDn|%yVVQwbA;{mm(AjB@ZCnpN_9k`&!$8O>dlc$ zjn$6|!E^>bKx9~VpFUk2OXGskH3HPwg}l)BRJWbDg(pxYYGb-^CjUbXGXZ&t1 z7?RF8R>)}{MWE6zK*9#Djp(__8EiS#t{5Uwy4^p9297UU1q?9&CTA}b)jy+`>?*uW zJes}6alQb3S&+_RkQE7B9AcFs=#sOs6;@e5+twow!9V)~-}yCrU-zTwyveqL@Y@JM z3hgdt25p8fv|hh{?0iQ@E2&bR$nYZXZ@7lM3(-GWXbhsrJMf~LY2X~0T28VB0g-40 zR;1aL03SaDIyYAC+NqmFg78ue+Nz?wTnSegD!Y}a+q8Ho!{A|^RLnmnHUzT<^9+{N_h>0=Br9WuQd|t5( zGQQ2n@VSmYP}r&+qDdFtkI0bjK&>Uxk%#(oUfyl__H7u7&~OAflYh|+-9hjF4CN>I z^Py@_gT43|DeGgY(Y?gzcp~%10jy}!I?g?25I#~z=U{M9P&8&>cUu2XkTb-bzPqBU zumY_43G00-3g^GkgRY_?>PyLqz;_KDxDqHbS%l5pfmkI|_lJgdW~o@?pl7C7v`DxN z`xtEO43aza(5wbjw!tgyvDVJT?+C`!Wq*Dy($iBY0lM%=Orp@`vyk@ELOFv?mkDqM z=(so1FHMo|xt}8?8^3ikDgY4l1Wr3s4#Z?PhNx{55nbu3x^LLOy%$()o9QUD9eeCV-_>tu>5~hr0(4D=fqD$vqEU-ZC+_Pzi@LwFez zDSN`m|NYJd)(n(XLN}bw4I^YF1V$H;qn3(ZDiseu0wx169R~oETMI3BZ7%^%?DVJEZ949fZEZz zA3ScjeG1cv3qQ5_G~!9HAR%x(YytV3*%Ap2Xu;6YqwoFrvr-c*`XWcFNkVB9A+yUs z-oZ@+<^7Eqqq`SyOoRSMjmrKfkEspWy2hL`+0YvHWd??J5ddTgPBR=)JZOmt9-ID5 zZBSPU?%6@&25Sf?d5&H^TO5Pp2BukcqAq?5S&=R(5#WiQ+{Se<+fBT>6xt}Qi4*&V z&pIjii#$pohT=GprNI1R$VdeSX9*+NMoKtAu2oPh2S2Ev4Zn}8VQ)uC7Gf07)gTIPRZ5hb^=eF{zG6keo)vuz}3(?uv>@zX`DTjf6*c(0)0l zdRMd0AFA#%Z^YDa5>ZhUjs1K<6wCV?+>aWUd!2!G(}?`s!t2^o;js$wt~5d`846T~ z{AN663Of;fvg!i(hBkxt4~HrcX;FG4Pvj8U$*7L(wQg>yc$CMp{#O!ZH!|G98;IQe zvg4RfEX(nn`@#*XtD(^W)VcFEDq&}0JvrH6lw#xKWq_{5=%-SIHtIs@2eJl5VS!NP zDiDG*+bx~-rVi+Y{Y1>$M5*}WKTVJ3@8RCsS1nWxKn zmxiIomOc}4x!P-&G+jDg86d4K$sR!UFPhdi*$~b?-@8RkQA~5nP zJiy7!Fi_2F#Ia5pI<(Vk9REnaS8^?SUOj!fNLC_2u;odo_KiIl#Jrq70|v|D%1pGQpQiDm}Tp4{pw8eKJnqQ+-&!K43{&GXf!}`6Z@>K|Gr?z zh7KYpty6C58Cpa0VW*r^Z;z5uX48hvMgn&M0Ympu78n)!ABnT{ku@c z$|CQ*Evi>;V;4$z<(;{4SPm*7Qx7_;Y$P zt>(;`;4^me2K&e$$kFvjbe&~d6=!)-Cc=4M#;C z=uHM!xya3bA(7kx?Qeu2CMPN77nRRFHtq^x%cX28TXNB(ii@&^t+U%%B}QEy1h|Do zHj)qa0byzohY-u5qyh6lDQ{TW%;v|xe|`?W=2?{^rOw*TIek7kS&q?(sYdD&ej@e8 zjp@jLESTJ4%kHxy(@9_iWIOKWWKE$1Rws|r2@B00mL8MDItz{DKmgt|oS?A#`D6e8 zv_hc37TA_vNbdH6E0fJ2L+m;nS}<1qYv?A*=&`Ce@=r6om(OwlZEG|0DGh|9uP^x# zf^8o=efn`?XhaII5L%TX1jyn@&&DB8Nm(Mwz|I&3EvU@L$x6?FZBc`$_u5?yn2|?C z(EzS^f~#wdUbHv={c5_PD~R!4^3OYg+2Lnwg;zf9(xqhF%EeSq9a-zUcq|H%SZYAh zPJPh)<#LZV0OVBz_nM$N-HUQ-Z@+;9yNg0jU>@E;bn8*;i#hiE*i@G07jX4Rpz{Dq zYJA#ZK6ssfa(VO!iMJBtOhv228ucT{K!YtA3uYEa_(3?)$TxTVx?5LUo62@D1?R@8 zUdNn@sj#^#lL&szCMFUw0}Zcq8QF_4LEGRgF^`YS7Zy` zE1u64nx>Is$FAmaGJxI&D%vO5S^I*+JrQNi;ro({Ya}*_@p!b~&Emg}MVKERhX`VBsoRH64S*a(G=M@qW*I#hh3eWR|Es%{ij}}0au}+Bc zj?kZo!ZE7N zNhffVWrSU&lektW5r^yRE5q=2mgthv;twnMkg>}Be4V2lnAfJOqf<rFm%v=^ruA4H z?5x0!)sA_vn(&m-JjM{fCn=JSt;`w_BO6&}!R#gZBS~#AGC>HETKL)CNFkAp=hrqh ziOhCi^i<#Y*PC!PXbl;XOCG2OJUEK$^lR6S#vD9I(q0yYLLZn8SHKRm&VIl3Nz0+Dz|^t40#Y3h(<`YIzx zqOse=5xD@>>_ThnSW}w?%>;a9%uW9MgCPZ1$$C_cv(^@&6-9-G*+Iq{TWJScHanPX7&EuQ3GYkFk6_V6`OpP_+47XmepjMA#BLH2r`vr%M1fWlamGv`H zcY|xvKxP#P=2%Os(wUAR7?fi_aUirNM%HdYuBxdgr6M%$C5!mo=ZQHL_E9*k*$66CK^Mx z30I*ls44;f<9-ihw&12IFhO$lRH0KeH2jFr?MG1M8k88%Zc?^{;{HPxd$$5f?||OD zO<+GlZ@V}{HCfzPHg67Ny8lphm@QetO+1L{pd(+g*e}62e>$F`0pd4~_V69GfE2uS zDqmXII@1szW6c~)>Y{}4i`~Vf8@ftoEHm>Vf>%Jn9n5yhB3g++f)vI#L0cWGfxQ-U zjz97ci>Vs!NAtfpl4{sN;tjUL8~(Gbm?f=EaNg_#XnCRRm{4ptL#i&LafB{9bxgfniPzJRbEEu8yL0HUbru zgNF_YwR3OnkPY}Gur$u zEvEeP)6wvw8{LnXQ^>(m7c!LW1=vI>@c3n3e(QvQQk?GHKf1&REF{@OhxzJxm) z>iWS%jIKf)`xXl3ASWx%<;R(5*~MqyO47xP=g(&gBPyxW*tZP{;mU$h-Tf~0@E zXiE>ZQ#&ML@#IyNQxRm+J8TCwy-H^xFY(RG&(Bac>%f~4wxsLSku_AC4*|e-(*Gw5 zADt^h8~KzJjiyY06|*Qoyvu$q{W+U!DS<-Necigg!Y{@2YmLLXRdxuGomgeCQWM|R z*5(Z+LU^H_o%~;f))MEGRa8)*OI(PrpI?Xn7)n)ezI?$5sn?f z&cDN3wdV}40>LByP>;07B2K88EEz$99$=TWqob%4E>04koauz`JKEb90vMz48sLsU zlb&C)KmULRIKBj^r=4D3#1{xFz*c_6aH3F!+`|(#;u`JAJ0WwV9vMxC%LofJD00QD zfmpWbGu%TxoK!k*k9o9De@;h^u^rU7Z3s_lIPoe4S)i*>gwU!A&P7v4$0`#QOCdE1 zm(<_h9RFlgjasa1)}8ApZo$YyC49nLyf#&gD`?rlubrU~D+n$U^LuVnDC0+%@}#81 z=r1rm_^Q6sxBqx#IBHbUruzE>kz0le6+b|}XAY^NoWX@$OdAk7HxZT#m2p=LLq+f{ znNwCw%h$@dDVlCq4S9boL2e!%vY4m*xfJfTeEm9;Lny!*8lEK7SO2o57EyTET?PHP_!w#dU0X@HeSXu)sz?6+}v3_`t>n6Dtg#GE) zb`&7<9(_&;^{ofGrvpG|7t+!iUelv_6ohF6S`AA;BwRuMWCf~HRn}@0mB%s;q`fr; zeCbWOboGpvTR%zK{@O5c&eH=Wi@=A0+f6l)K^yH$S^*)S@LzX7jxEGfYkOCYpm1 zMsJhvT*B`O2WVnY-KBmKbr2fGt2pE*NtLeCVJt!0IaB!KjlfRKPTERg?bu0q90K8B zWS4K;7ziwugYHj9pu{~1m_Vyt%C-*xVhrU6e{VzcNYcq-^3^S^j_7w`{~t!1Z7t*H z8&UMe6W;J2jHCrGUmo8If&7JvPAII$0)f>wHjbP>@=!Ski3nWjNfF{gYka?JQ2a*@ zrUEYfXdG}(x++SHASD>MO5|v9DCrz7ieqJb6GN3z9AD=Kv?WG}Tb+3bZj~FZ7=sG} zx%u&$(MlU{_4f8epOjy1a6j8ijq)Xc7Nm+=B^N=17C=lbA`D>!qIbW@NwaVFZmB?8 z>Adk1nd>tdC6irznfrb%P}c%p;om?To8bh&qLt9JmiMOu!_Hs}UoLWM4XO9OO|9y- zUu|+kPrlF4*^#=uTn{9t*YL}Qnre37Uj4z5VT>A}9iZSl)y^Km*{j&xOBe;zafM1} z-gxb!oZ0V?S{`RBJiGU1!){(Myz|V&vWA;^7?r5An;|P4 zIYf%N+H*K(!g*H=p&0jL>6LCo+^#@_5sVH5?|fGw#Bb-8DHm45koZ<$2~WNveAq6> zG0Bz@fv8I@xNWnyd{)^48Tjb?HDB}(*D%uNFbc&33p5d<>8zWo8STv`$r!pxG$__U zz354Y*hbqrnlJCq-k3G!@J61!s=U8ESK^v=>sH}0UqNndBIi#h+}H%S`uY3&?-J7} zLFJOcq>|i^5Ej_GgX9#yU{GjH5?@vr_srFmVnqqhz;_k2O#puat7+v+#EKq+BVGuN zKT(1luuU8oRrdD{Elm8%! zCXuJ`&{a!bC`u;&RV^8?WmWpU$3WRnM)*qtuQ5! zj)tCXZaz0?6^jLMLqTF>&8S66PZ7J4iVjYm|7a-Uot{4Xr}X3$N`^HPx|pt_E@}Pt ze!vT|=ul*3CMWMi%F{_GT7fK_b5$+dNLjkp=IXXBEf%5aX*awcY;q=bzkg`?C> zfqOLxu+hlWG_c%F|Dkq>F?_3bQF`FcopLtT)<*YEOh4+UE>GS>BY(44xc=GG<&4y- zM!hnH@EsjJy@w$+23{0b3g~kE5xm|4D#STfL;PDc^-To5Q9S8ww2R|@K{>xbhi(L8 zpU6cwTB_th&#lklEryY$psulG2`Zb3aJ3KYr(yUs$yK6;H|(XRX7#zvXAO~j|BL3O zHrqsKQ0zIiEkUir|W<)C5*Rv{`&O;ba*Dz1zW&`lbJ1e zUufs*-@ot9Fb^B$Lt;)n$L$%4v+pP{T)D7U15z{-wY!xIoFIFVRu0tLpS>ST*-657 z;>Go^?+X#o*FVy$NlC-rQYBhZTiKm{9Kk6pCLLyyvDQK2OOu$bY<7iy;2VX)GF2W1 zjry25Wu3X6ZPk_3A z7uQv!2c1hvk+%BWe9s|s1`7QV2=hQFfGiWsqzHkSXLszqR!yl zD?FUzi%!1@-nlaXhA*7Y=gMz#NwX83urU(AMDBr7Zk9&QF>6vhPysu$v}$PKDN(cD*H(FJsi%ShO5fC7MSPUcW7JT~LsR~z{ij{stt zk?IKT@B-+l3Ie7*!@4g!Yz5lqC|PcnpAYE08mbW-Avgau*$9JT*ln zCqXDt?wF-+k5dT15b3+7LmDo2AD)wvxe<1z6_hNOrcj*00(jzSpR!D>RQfFt(~O8NE1c%~!<#ojFoi};nMBe*iAY)wm)#7F z&Yk>s<3Ud}NunlvixpxbMlw^SK&0}H5(z$J^=HVeqt)fb0M8b>#}<}t8q{buP~0V? zha-v030AiNyY$@X>Z--WSu?PpYGRs@{^#CXHJ;AX3`%4gd;5Xz-Ydu;4q(M_L{R&I zjy;mgJ?HuJqn{CN?BN6w0~U^xJBng=7aj4UMT-Q5@PM~Wa=;ZnJ_FMGkOn$<{XfBj!I+hVXMDW>PYuDB=)?o@kjc?z-pRSA2qLq?)FSmCeiLWCB z5{!rw@nbua+!;)?)NTFxd5jIRhc72Y!Xt)3CQPLv^+F#D?K3DnEai7!&|r(XLKLuf z;og6M<#{8mykMsz;=M)6#0IbiJZ8q6LI?^F`9*_&??4_0KRvz>6`U1X?nJT(%&2|( z%~uX0$Beu1U1epK7yx~ZS=Ms2*TWAKIAbNdCyHG}iH3zPb?=I7LUqJN^=uZA2 zQ)K#%_RG2m@IL|YFn_+7q&@z1 z@58lRJRQ-CR7~gTpw;PH|GCYdgql*+3a2Pc5pm}*yEl;WPNN@ju-9TF(G#A?OKQGg`T86^4#7a3%vY8@#@{gl;h_ckUI^2pO8JNM*5vDr^b$ zLqx*kHJy0J-XJM{@uv*PCvF5S;1tUNMqDH-6}11T(3(I}EcksdxRImCstG1pouPTM zMEPwBD2*rO0n%ndBQgn% zIk6i;$>xLi`W=>#Zn84(Sb#lQ7PCekilCT@6a8q1!$l`K5#81D@6Gt#I>ZOmz;fNX zb6xz^nLRKV2dXP`!75=#dUCPl@E9iRDXdT-zK>~h_b)*u1O1RgTL}z%j4Q*1>hcW$ zstjZ^e`W)sdX+1Vb4iJj{-(OHVGiujkt0JukzJt|Q<>kw{F*qFSmbZNVei!nM2tyN z=sfm7uNg7LLyc%78Hl8K{4Q^$B3ZLEVRA!UblU~pP925D{vAPXcgkS3s zow%i8%vb{^JV1SQfPpcm?EC~Y0}W6~bg~nEWF0dbQ>e8axIm!7`!LJtFqf*@qbE<| z=`C9+>6~fBV3yvE17KosHCJ`ElS&=d&+%y8Uhs*+@oVDdv`M{2Ex_sW17@3N|g z1Um<#J+c<>Wc1-8DZ+nY2KmGV@q@bY5o@V}3nPQ}@j89A60g&X9065XNwbaeNs8h% zx++c*i9OX9jP4$o*fSLH7HCdaBjsrZ-6bB?qMF(rqi8Vkgcv2X*gAY7iMtt=B{EE1S1 zuzxK9d$uOV$>F4}AA*o>Je7HmpFF|r?SF~OMHk5FdS>Q=&D7QJKmZ4FV9SZWTgojH z#cjBehhD;gd&gs92J3YJ&we9G0dKFZJ}%PEMLe>I^Fh7hhRLLHC;Z1s(c^db{axrW22{JucTKVl=qQHsCf%cZacbrIlJg2ISFnnxHr zKMCOhvuPvgWwTjsX&I|;*sUG)e_DVC@!TT`OcLXCHEU~aja8CO0|_7@=&UorLDwmb zouLK03Skf4UyjKHqIFP#aGUo?Bj31{%SBh>+h5OBezsCz394TQ#xi~fD|4ePZNu|m zOwCIUcxTF$+FtP2S`XA)TFN+iFR<3(5LjBwCdA-4eY-x_NEX*ZUQB@M|L z&$%fn5ark`dhXw~tCHE#nWs+eV(+yJMiGkQ7?*|;1xc8BLI?VslLQf8hVWHHh!-{M zX)X`Z)<5dZCDfE#lD86?2iO#ZQ4b)&T~5qLdjxRAwcHzyXWT*!dy8 z>`v)!LTS4aTGW*9;LYv!Tm7! zUaUBdiUdM35D>K$;FJR95zDI$TyWSv^N!$^F(2Q&S=kQ%eTfW9XV$o2^$1m$g8lZu z+K~a?pgw+lZ;l&tFkG%DOJ?CRDa22Z)mJGpyTv@HCk{jLgs!u{C4OKE>K}mps@yEl z!HAw9KFjpyd`7}+!1iduXAQ$=psK2>HK&Zbu6c`f$asogR6e>Y!BP&c^iS)`~& z)9-9~sNbDZ&YJUqc_T&~EGy{N18K65b2PA+HgKhzkZakDoM|fk#c2#?2g&$^XX-{H zSwgT6GUGf~Jt>W75~4LTFabw?9eu%kQ0(&|Xhyy~kNKE#)Aq6{Nl^ zVU!aDQ6HwOKZTY-04EXo6IgRP57pALPz_e4z}+GvdCT3cL0KrA%y*XWXftzMym&R~ z-$QE%AA(E0FB*$jGeO7k>fA0zr_Tx&3w1pz=pBp z*{QR$AZC@fFxhD{Q(+%6yaXuq9muUQ{$E~{KL}bSPEroF-ppwkht(koEG8St4FGeJ zwX#U3DMu6blT!D^SJ2zvMIDHTcJ6QM|_f*BJ3(Tko`1W_|* zc)5^GwQ{W#Q;z=w*fSUW`3IW)V&QvIm)9f;^UW-qgH^QAC0xz=pvv#jIO$tPh?r?X zVwOL9mILmOv*NUol4%L3Py=Vv#Ty?n%qmL4Fzl{Eoe$L1+(A-Id3Z{sQ~p_{TaS#$u#H?N=UxyI)VXDD#M}Xi54jj0Td#)9_ zz6*aJ&w#jChRXYIgxLGTNBDrZ&mNT1o|omuRfyvJynr#9D1A%mD|>UMPaHY&u5p<@ zbEdqYudEj@mcx_NP1VrH0FvbHZo*vu-0I(k)khB^YX*gf3w&8ERhJpND~UgnClc!s zFO+bySD{9(4cFQYYx80P>9(&)#q@da;>Ii1n9aeb+ph`B_h)@F3ZfpA_NdDuJfS>3WX8Hi2F(i#V!+<;mqnVnlN$V z6hzi983MR^-CutVB`lG=gkWyU1%h#yyJAJcIMzl7i8}H&bH?rJ2-a7JY4<=!5j>0m zqW_@_7)WI@b~0ABSDe*t6z)w_HumTPI|zFT2)Y-eFVX4QLtFLl-#-hAq$hpg8y1n= z&|Y0OvT4PnCe)A{-!?R)p*Q)>j%((TpCSj6!RU_I<#82e0m>un4p5hmU@Bk&r$!_y zqX_EGX%Yz|Vx196*zlg8IqB-YzOYm1s-8c7EXE-<@|^F8PpMDQopkNmQ2~1i9dvLaQGxibgze`R2_*NS;-(<5_dllDcC}VhQ8Jvl)YMqI$y>j6ON% z94>_X6v<}7&NCOHkMjF8L5XFgqgG+o$v|IdcxaXxvjz)^Zq)uwLG=YKHu?%X9JGc($~)%rKxEwa;5799>lku!%I*= zOT{TuLDgi#RmV-==^{60NBV#R6xQL8dfveIu<7GM;$<;jNjOD3!5O=;g(zNXXNC*cCAVNz5%N#ZJcm-T<4j2p$ZHUF6yoP{yNHwUfiMm zwMULjr^?M|6vA0f>L3owMo=%e&B*Q50WtvGE0K_uaNu8|vg?a39K2M@M?>BtrdOJw zPL27FWv~(&ONxIm=MP|}IJN2^1Psk=7AfS)Vtna~iCW|xAeB2fH+dtqOq00imSfYU zRk_6Z{Q>@|8L?J25OJccR0WM@1K^r)I_HxRBf_$a^YeFZq0tx6bPo6EI|^=hAZOsK z9JG+(G?mt9vII?e05A4R{z?JMfD3;aaw8G+!q|sco|mz`PG4<({YOHwtUY}AOfeGy zz55SF>SZ8gd4fd8Nemz%Ro}Iq0S^d}CI0H#=*S_MZ3MIqIt(}$%ytZ*>rW!qXdr54 zS~W$I?_LTO)=vbdFa#Z#MQ#oo7h~XMobKJ?asaYGtPbAx0;oE?w;E@AokX{%i7*QpL7U**ATJ9mrtJ> zYdj5PsD7GXo*tW^-E|Ks36O#B$fGy|j1^YyNH z?G3dBJxS*8iA5?z%MwEaj9;lQX!37f%74gt0^+sBkOHXu-3(2ap@_SPoZ6mBZw|Fk z0C@Iq@U<4AfmIQ&kyLv}bd_^)`x2UD8Wb3)1Xx?e3J(wo4)XpS6*m`g-5reAK3(C_ z8H2$W%nEP$y~gl6VGwpggW!reteSGHh_3bwtLF~#p%(7;%e)0K&-XQ1dE}#|qF>un z+2Y+DWJL|6E1pT|Si!@ny$(ggG+FH|P80R{>f ztV=EG$FRbE=t-avL&%XSrEeB~W04A_60Z<}?>*<{9jznf3A_9B znY}B`X_MwDl{tCx6J0D*V~5yc5oE z99+Q|Op{)+9e|H>I0AQ~7hLGC{$);#;nHJxSTWK%h-$NQ=gzj=dAy5Z6Ne9%gJPMU zVw=*8RO<|XkXCf=4`8QAwyqTQ(g%ak9?XB}D5AJV8XCSr>y#oZDc{ldrr)`=v{Eri z;Y5wWQSkhU)HY~j(-83R{wgWm66t3uDLLB-#6Z(ozme+3b#kc5N!yfx{_1Iu*SrBn zM|_q@yWzfmeKN}4q5kR$q&<_|xFQFmWFR6}bW7DVnVx`=kJeo}z(+4(+FcH^ofZp1 zoSH#*A7iCT;R|eByY_S|SH@uexD1bBL(KMXs;$*~#!6(RU+5Zdta@VZUJna8kM8k7)eAQ{a@FE6EBMMm=)eF!sqKrj$H}uzW=MCM7?N zi+J-oh+>8A>Ea*TK_aTf3N}Vp94GPgu3ah6>p>Sffw7SK zq*`e(h9LtAWC+y(SKp_YgHcBLyaZ`W@}ko{qpdmv*ZOkN{fBQ!UE;g+q^%ddPA?|P zy*dB%-5hG6T0mW>sK1-wON0bD4(8)xN{S^l+yWTANHhY4pr4Ya&DXY*TU8BF&iMDl zNld`%-lIo70B;kmcM?Fu3iMbvCTnWSfJ{2`++8`+zpx{`{ngz9vSTX^)-{uyAR|>W z0;eRrEdoc}8$h4YT)_O>EGOMld~4d3ztZdi`7^GxR1;JbjOiYPB2)};{yr~SuhR}> z^WS-1*UZh$pGhQ9_f|2=`wqZy+Kyk_gi?aJB|(J8260OFK^zOVJ%EZ85QcvX8-IpPZ$Oe%qh1*xOKr$PEqnOo%I+-(wjdTN1-FSOyW=^HrkoV zbB>zREq_wW_oFHko_K-u1(7y$a5OX!cjta0jE#9yhd)i5hrKfN^VhFIL^?8E?>7OB z{q>LDyKJXZXBm+R%T>D5&G|XEWIWf*E4PV9H87KA=#1i)Qy#ti=CJ{WK9>{DpP!cw z+cg{6aI$TR5juZhB-wDSE|`~=a0#M{N`Wda0roz1Z&d)w`VyX=qgF330IE$tnKBd& zEb)v_RhRdf?txECt#BFRTpL(nAvVof5LP00#sS95FSknR;lqcQ`4u-46HN&+I$Fl{ z-O62%O*5sziPbBiv_NP1O~<`8m;WN?b_T<5-;q)?np6QOx{ykW7L-Qg`7e!Etx6IV zo1mB=_kU6Oj;2=WjY!`B1v%v^x~$>wSi6Pw8w$v4+l@g?2{jVKB#o71M<1FM2XU3n zN|}Sad~!R08noc7g8#1w{EaArMc||hxaH0eYjKr zCceLTv*Ah#VD9{s1qTVjn~iH5eq_xbPxEr}jK2V4i)-o;YdI2-O%iS)3r3d3#(RXg zu@l7$1^jL#Oe+bf_W=p{$vJwKo7^AgbTq}7JDbC#VbfJb#!6A3>_DFmO{IM_CT2g8 z-d#9&co@TkUy3nJkAG6FMxr{CNIVI-pBTfyBId&PnkZWQRTm@ zrX8XOr{=-EO*%jwU~n^9Dh=)o1Xd=ci99g|p<#w;K$^H8-4 zDt2!hYUIr*GQLwg{6n3w9VOh|+qccCIVB42-8)oeux=p4L9wm^6O1pq5`s3+sij<(3uBjz9$E34|mz3TNBT)|M3~ z89|L^HP?>HN`+CIw}1#IPM^Nir}5E97k@&raD`7WnJx{)xR(FhA@maE;}mL8Q>k;BmI5CX3%xupH!hMD<5d*aPXc2)6VW zBdLT1!5IUZ8YvsARPOwM^eSbIU#1uTz@lEp{Z^3$8mN!nXWj zuTQ~1o9y1d4}k}k5rQZh+`oSabl~2>GhUgO=_~EIEqLs|MlCwyi=sObk8LWsF-JrW zofubN&6f-yBGAcd*a78W=VAEUaXd~Z9B(yA2HND)aragCVZL(arAyE0MkIWsu?THl z&|{Wj3%rlw`3eapn5?zOsVJZV%Y5OaYXLr-LWgkY{~L%NPx!M9q|Y}>h%zX{KA`xC z>_$0a>PzrH`7m$jC^j^@11UFM7|`zqH94N3HhF$<9edCb6`+i$f+pNcZ3}7rR#eIN zaiP&Iw{WcMNR%L?sM^e&Vg35_KmbGKS?C2Ql%Vj;H|q&jgjug5)osKQuQZ~ zemD-`r|-UcQl}i^+XJ+8nu4}fL8`zWcqeHQ%)9veuGCYV$oW#o<(Pweye~BV zL)>Q?{_2vDc+PTb+EUNI{|-V6*Fwv)3SCtMAopDug^wyKDg=ucy8`|Oi+Ded8u?$# z|Dk_U?16Tiyxj)gRLY{$@4-EQFqNYMV99KBL8PG6L$?~QAc{<2+A!1 z1??lOcnAkmHYzZCU}Y2v?IzaUW>8$aqRBc!@X>FYj%JYS)S0HH1u*5+oVbtp3oTSo zhBVsjs6Io74SNVU>xiO)_q3B=7S0iH8OZ80XIBB6PmOhP1`_wYoz5WQ6E;3$7SU2R zzc{whCix>}rY_Xn?B!LZ)oXVd@c&DYHfIcJBS6lFV?;|MrBSr+p0>6ROSsX;A-03b zJJiO_-vWKt6$R!Igap})gZcjXb1rc1ZNA-X?#I|{C(os6Etq49ZYM6n-vD!|=$Cq^8Au@Ua~b?Y3ieK}bwOh^ zFe@sMGi55thXvd_URsu2QH>W+|6ZUD4gh&yfDz0PWbGSGl>Qv9dDng6& z+-dE+{=m7(18{Q>7V2?eigA_^ghPz_3h}-S>ln8WJxwV4ekP;7zp-?#3n9&AV!?r$ z3Ru@#DEM;8o;bGBzUYj7h8yu*pqGvd7n-4^+)eVwB;fkZ2%zoo8#H+um`Ws{03$mC zriG(8$5ft*{-q=R_6MwT4-9`6e~R;b!w5TO4%=VO$k@0A2}{4hgL?qM9{|9<&f@de zvTUTHmV>7)=M}$XzSff#6hMj8!NgszvW!3oXixcY?C{}yYH+hw{8FbxECG(Bb?evH z+eZ!^ZWWaUGI^QEm!qbdegOS`FV`g#7O{u1s0&;(Dl7m7R&j9TaMPtwf4suWjw`$h z=a+2c$_h^MQDS^2qFTKOt$Tt8HI6f4)Z5HXa&n(x`p%TPJ%Yi?paAShDvYg9fkm8Q z*q6)HBSO@3J1?(bprRbwGs!Pjz6e$P4TzNrOl^vEAcbOtM;fUv-%M38-`H5@=&@ru z)BgEp#bDe3Khdv=aXEzqk1T)s_;DO7rirWix^6O{#>&tw1 zYS15aHXe`z#T*BRum*_G{Cj)`d&p5Hb0FkOA%N_o9S+YPxw;=b4H6APpNAoa#R|1^ z%f^klVgQ-qfQfrik{=+7jlwk#YmIGkSs@S8hDZL&RWAleA9uo(UPe^{kWgzV8!NuOyK%xR^+q>-iVP+c` zKs9~1?A@Zdm@MKQ&6R$CBd5vXR%r$LPrQ2dNDQq1L^KgzH0JMlCdL2$`6b608BK%z zf!op$20Rjhi!;yf!Q9ia|EC2I)5uqHwtwN?Gh*;#maYO>MZ-TbLf6dH)Q*u8>hdzp z2$!(jq|%{XH8nLAp&=W*yrg{Pv0}VSJH^)jqB_`|20pF2^z}7lKly{~oLdI>`jIP=suEuv#th_mIl|q}Tex^}kLBsEmPc>9RDtvxd3vVyGP7w97;i@` zWcqUAg+CKZ0H1h#;$P2dNj`EDvB1wODpHOZc*T3NR5q~e#3+S-89DNTk-{SVeLC_t z_h0-&si<*z?)`CzW)fYY@Z{ZB3mnv&AfIXp#Byb*+dw!n0FXYz4;Qt;sF?b+-0t^_ zkyjJ@@4LZ_6C!t%nG-yk)VDAw)gYwqsmMoV)#Ke?vOQ|S}- z5eQR+g>wB)4EVQ*n%qw|Wf;CzW^bM?K-j| zsX_{{k~GA_9H&#XS!7MWF;Mi{vSl^kK{n$8n#f>?6QPdMIO|j>pm?_1Gg89epg%DI z?lk7P`~fS;9=s~7l-BwP6-lC?HG`QCOb|;Ls002|B^Y{2@A!CQG+7p)iHbD_ieP+k zv~OYx1(Ku3Wo5aPSbO*kg)}kOXrNngt_q3$Qv%~vA_j41+i!Yx!3a;-yaL}?+%VNx;U$J-&1^;>lqTks63rTy{ZrDP$t;{acYDT*}QkkDwM~qG|7NZ1f#5dh`VaMa6VTv#PJ#Zs_Xj`fgC$ zIgr$iDfCKJP_el@T`?rGk;N{6cUh@!<)5+Buu2O3pGC?77u+0#B_@DBkV7@(gx<=o zKdTP!rku0|4W}D&iMARhBZ;Ldf&4XPueyT71;V7un%R^>sZK_eQO9p$;D$uv%UN0qDcr*c}9BF5^_}Ll?dw!Nq^{v+s%YN zRSEyK0;1+x(seh?7Br|<{riql|Pt-*Ia8!Ognq8khf3}hL594;3LM36(_J2*; zn?`LnAE8tEql${%>zP_tw&ubZDjn}FTfRFXM#=M1!z9*sv_s$~0@zq+&cq;|hKt4{4qbYwa@SA(ql& z3g1$B|5&6Q{@G4WfO28TW9*bojq?} zzaHJYrzx9BX>RGpdru{Nb&$Wh4ED4xnhr7F8%r-eAKVSlcpxY9?xD)`K^rEXdvmIH`}jz`qY0Hv{)c()m#6_x1Wcm?WsEx4gw z;bE4Nq!Pr1@zPnpzZnOE_`=h8CNnZ|V!q;+AF|NmwCyJx360l2-rPu27)R4+Y!zd8 z<0qt57BC$RYb7Gc_w<&gK*Z_je{^zni-GAX!L-)!VY~qofIg=^0i6;FvnLmf3<3-P zO%kSW{niGsdv~OF>nOGcsRVcTQp>M22t`x4=Y-+*H8Yh0MP8^L>>cPx&5qw~s~>PQ z2CK_AVbUK6`uPx*#71nZ#w4%QqntlSGAsKkLXAp!sAaDaWCX2Hxzkai1a|>2UVQIf zcQ{~2hTvqS>-?$NKjRZ<@h;GV4Y0-E5NlQ*U4OGD`9|#1&piGBD56mWZjBZ*ArK3Q zd1F=7u32vS|1nYo_2mLy+$dbq_LLF(y8xN)G?i3k!iP zF*e46YhZ3OftT_{>0eNNfdaP@>*a^Cm0RrliOtt;%sl=0F8i{LD)b3kqn*l27s}cg z8|^bT4Va*_uMO?mW zesY~VALHb6XJB;*RsyYWh2tb8MNw_~ zwnx+PXk(XLHh+E%k;onc8O!mxq$JE03bct`gg!%-^viD=z51ck3}rO10ZB5FtS%B= zYy+mWwIdbP44f!A2E7&oVkS>^<|Fp%-+va8gC2~77`|Fp<&p5FA;ZgJ3Og!X6lt7C zqu{=a{@%2y1T>=@J|c^YMt9F`8If1atL;sDSptTX!@mwhF|!BQE0tSx7!)V{m?zFq z_M>5-mIig?CKx|A5Z&u=E&S|Bex;+3oRg+O=~pezF-VHzT9(B0?8Zu(fPO|1=C@I2 z)F{z@Zxc-)CmrZ`9&a?1O>&;EbXUZR5cX9CUQ~+F5n=4Jr!QZ2AM21QFRIu)Ko%xP zlDvaj_zuNcPxcI>fhJ@!vN|a>)!GiYR@=jEw>UK=-lZftB!CrUGHzP55C z_i-c3SxzBW66~yo+=yf@nSZ`EE(DgH%@<$WuAX?j*|vVBpZw^G`dTsQ2Tf zK9!ahK6lnEJv!K}1Y~xQWD%|2Rj>cPVqtO;WFb0|8n$4c{{8QI|MO1`z3Vq1FmZ7D ze5+X9Oeqiu+wz=q!IKhV8|70SYuyCXV=VGED9xYPW3PZ=tOb8tPZUECjlVCn`K-xa z)p@^JFa5C6oy*Krz6GK2nM)Q@A)98^Oi8v1QF$i&A>4C$k5P}95V zXWmj;1VDKj083t8cK##hN|u+}a*?Y<==3JmQP%6%uWjt?c2Se)vbp?7ghhp*`2oYH z`;m)x^71x;xjC~^pfYo49V!U!V1P@%mzV+Hkd)Mp2w9|a12?*WEs8;qf*=+{wJiDR!>^&=-g4Xr1I-S^jO|J9@CnY^j02|k;K3ikjL3qD zmE`Jj@j5arrU1g%39zJ{tFIM^eiv)Ylv0;JbCH|S8$r4z(O7^iUR>@k==nYU)dhqi zk%YlHq{jGs>T)b1LWV0MkVAF}JzXAK&W^1!0I@Cv)8Oyu8lyqtDv@nm&B*92#*;sK z^(vG}3fBCmy`rtm@v1vhd|DGOQS$25he6E10leOYa@C=Wf9g1ejwr#qaqGr&s%B7_ z8Eu+!{0#Y=NEwu{ru-thNt1cJRFK&i2Hw%QEB!y3&O9u~wQc)1g-WO-Ayh&Lsicx2 zg(4JbRie^7N-ASTiqJr`h$bmx$&{ipqzI*uWr{)>zu)<|kE8X{X zoyU0``>_ww9qNrnWz~2qlxFXoeIk`>aV#dM_83Hn3|jJmkkjuJ0G{!#X`$FEbvSAo znC(lP*>XYu$17~2mz^aw;Q{wp315tnYIr?cj#UPIYq;c203}8`W#kdQa-;!F z55h=0y?s6BWA@p{^WaKv{tiU+9cxJkv0rvo9nyEghFsP#5k$bhnHVp%4Ho9-fty+t z&pulI87ZL__u;cFw=Vq&(pJ%Nkl@!gTT4j;)LajmQL3;ECJziHKmlkKRyMovNjD-p z0h$Z<)@o!deuwouq9!o+W@FAWgf|)g_afG`?SQE(E?m4gl?BuJp3)knWo60}CC*{E zu~fl{Mr7hbUnx9b6Oi!OvVi%@q&`J3pjhZd8jc&+BIpecP^nv4m*lLwxQLNQjS+Qu zh`KiyPtut)yNTE8Oj+y<;N26GiGVm@CTR+&f1T#uqmOcs9lv*yfsBW+fvC~bzdiCo(3!^4rDbiPV?q!t~) znm~chQyNsc5$8xdsGPCvANYQIvtVu5Tl$KIZS7hFTa05eG?zW41s!WQ@8TPXKv`Va z#wc?k>M;Wh>_NjX17b%rHH-V$8?xMTYZ{ctR1RTf;k0=1nYI^y|Ge??@=_SJFh6+V zr2s|3kYR>p;!mwZD=qXepIG&=OYubOD|E!4;g&pyc^}O5>xp4I}fe@>E&1TN)cK|G7-9=E}@3D6j=ZboDko^v|3* zd*%UF$rkHc!_okbYHyBrV}#7Yfxe)^U>2vpQrhhHmA`*BE{FEi%#Dmt?A#<1lfDcK z%h~g2OA2j>h-l%CPNo~0M;-jecOpi#nPQG$D!YFBS+|1ntc$PguUPjX&gs_ zNWyKFEZIYA@lobO}?MfmUpZ>h6ZU z@D@p|H5b}s#8E>&hW?e!ditS`QjT0b7R<#TLp-2|SD1tE#iW2>lZsy9E^zXwEB?@f zu4ASj4s841sk2u)$`>1PqwV0H?6UzUgK_M}>d1o<01SKmM_FX#yycDzV^kl;$ID>Q z4OR?M-%GbkpOlMn_wWr(Ou|3kQsKqDLl?=g-)xk3a3`n9_Bva8h!iE2j55LnwGppIO2X0UafPS9>VVdGZdSL}Ir(XEkECr^D zO-w9Xt#%8|hYT3cRkk4iVQWC=G73V@aqSssjgRwbpVS@?4fAz;=w-QtKFs$bmdD*5>!rce3+b-Njg`>c_f7|aA&1g z<64vcgba-W=r}K!3qOe61t^`xE$>Bk`WUVRI#>Ei**~Mq~1&xwTd8Aokst zxu_v3t)!qP;F>=lc{ys{QXEhVL9MeO#f4Co-QnLla~EkwrDQ_hmjk_kiaVQMZaqh2 z9BaUOdbD#JFxnwkN#tn$!uzz2;e?4Y@!87h;^%Q+-)61~MB>R7p&U>wUfg^A!(GMJ~& zI%0rtXK5+Z0`_VIOyMYzDD~^tm9kj_K<1<0N%C%xV54}*hqeWl12GVIWgZw+{<}2Q$HlP{tRod9vCR19B2E~?4 zo-rfpII0>5gOQBJ!UZuNs*t6E^mNjH-Kb5ia1b^^s{AJ^#TzRl!$Z5^-c1webgvQi z5tDLDMDn7dsw%ww<3jj$6`=Ek-SDw&?o{E%h%+T6xl~?KqR8X6<(938P#k0gl^C~9 zGk9)7YC>kVa8ne_xg>C%VJvvgg3Q%RBr3S{@g2N}BY)4dd=SXDIie>%nt{jdlS=0qow99 z*+fb%?n^4HPPP-ACl4Dt4#w(|XZWM+fSRwzB3mSyt8XvXP$T4SsLYg;|NiT0(05le zR<3U${@-TZoehK~c#RlKMY1iwiY8Y{WX@oA>;-pU81Sy(9fdP>iytEGgBR7#X@lGi z`p&02$)SFK0mzAew(<=&$%Pq9t_~1>Kc4&CoGB2RM*Wlj?(^88_&nu*2J5bp})F(q^+LfX`VBo-2lRz4-XX_Zy zEhPW@uN;OGQb07vNwlukIRaGxo_%ZGAyX~8S5J_#haS5*O>BsMG&?uH+Oc21V z2SV6oh|D!a4T`Ga2_RrQh=i$UIsB1hCd9Kn6%^{&xXE_!K4hfIj!f`3goU2$5nUnn z@Ve`0=P?fCH`Zs1OsHCV%$WF`N=X#Du6 zY>+aU?4K_`Hyo4c1s%*3{Mmnu-ao^bpG>*&95!X;RMLeaC=m^;tnR2zj9GME(C4oFolN&BRb0a`w8`qHMD$KlkYM4WqUCsP*dg2!ARmwhTsxXWx%ky$ z1hehKlXFoNNJkM;kPPl|6#J|f)zv52V|s3zQq-S}Z+;&^y>ZBPmo4>EgGA(k?VP@H zzkWN$jUBs{1}Ysn$T5dY0a?rb{`uVoQMwkK-vUNPR3BAUXOU?Sgr@;85wv=BwzEOs zvGjx1Sh+yvSxRBvLna?%Zyk8;HAbHznk7ZH8yATH1JYgP#n)U<7fuMg(HShVu+n&f zusDIAS41b)#%whly4iE`9hXD7EEBl}*b=>AwtFg4Lyyph4TMXGrzyGm zzhQm8#?)_+%8t(z#4j*AQp6UMHoA@;M#fB`npQx)P}kJi@KC4NlCU6Hkj?NeG;rS| z(XJ~YY8}{qwemNG!f-y7jP1*GW~~L3UZE0ZCa=Rf!-g@;3SX27IM=8a>*9J60ZbRl zMJ|Q3JCp@uG7?NF*DO^CMLXc&S>QdA@}0(WA#@%SI@dJhGIP`I7IG^iW@!SwFGt#L z#p0nXMSD5X&tIUzjAvtP6Qi|_WvHHKPM_|?0f}uEv;jR{!`I?{UhVJis5~{9e}6NO z)HN<|2R;_i^FiXs6j?xp(S++DlKlDVz>520{`?;oU?=>Gbi@;vlX8Jt8iU8KeW zm6WYKpCfH%3DIyRrZ@QQ7m@Na-nUKyKG*3XDceL`jp1FrzVf^|4SJ)E@>FIVAE80G z?wVzH;sHC!h4FA4VWlK-rA&t$lUC`45F)XN5g_mK<-?4zN`7if2O4_EpQbhuH=)S} zAdE_;E%A+r0vRy>&ExjZ_-ZL9d6odgM3KgC;c3H^w3oT|9sO;DO^- zCSqymE`q4JlP1kQbaxu}#o<3R*EQgwIp8HOe7hqbK6+HcxGfP(odIfUI$VKY7Sqj5 z#vldKq*%?`lJ~G<*RK62KV9JIzlV5~$nAV5h&y-+1yYa>8kqsp00X)4=QKj0-SiI@ z9$c%>*hjpDG+nd3tV{i`%RRh3&-*OoXn~WkH zCAO=JTIP*8S~=8|3S!J!m_DoNiAyK_e6Rm+W@hGy!GpKqPq|mD+n~-Rs>{5j?!^e> z9ue6M3pbAmWGVKHB)X4ayDaz)yjm~WFecNU3hxCz%81qFrXE6~)*wU7MGW`UM} z2k9wW;g%co7qxRp-8Iw#=@egc$gCa-5T7Qx{u+BbA}D)8b0FZY-0 z(c{4HFQuc`9U}^L<+5eb0!hui;TkC`FR!Kw(}OJR&M<7$09nEy@b(b&m}3YMgH&6m z^Ywwjk0jugIcv`*I2*0o3nf390bFU68sT5s(Zd?-ZfNCK#2si9R$1QwfHIk6E zg5%$8NGV_+HlT6gOWdWMN;T1T71zCj9heK zpHr?^v~!k&CA9axs;V|9D}@RBXL{uayv5_3mMocb_sE2Pl=7SKzHv|YtL30=fcmkQ zLs$3iU1_w|Ff1U3Nv7Ivw;S#ukxao2NkA;5VCAKy-P}TN5(>6UeR9V+HWY_mD@@q* ziMwI>Fhj86nJX#*qOaq+wqxhc>s&nK0O<(fj*YSh&Z=+^Ob*XRYI;c*E0HEB5H|G2 z>!JDoVZGLY5u;wjIHKat*290Lq`Z6_gH^~XAD>=VetnslMiwo=bK*8Ft9xYWVocY} zfVKmoxJ!Gi^DRwK=IKKEZc?SY>& z-^yh+)okL|KE1N(Vgoa{IU7*|(E912d8Y5}lk@b0HDqq=MCEwU@)8Mc+%J}e+sLe*4|anc#15FM zH6SZ&Jf;hq#gIZeWj`j1(8Gy|f^2pl1C-y`mmr$F&Cb@&GpFVtu46=d|KM;Gf>B-B z@O3!(s-u7w`d(z1(4IJP0Le2l02^FlGid~t>?J;h&zsD{2i*jq8h;@pV;Ak!OExUk z^m#S7qMXOu1t_ru1^QAaTAT?wI`V`%dD8Tk@k?a^0@U8wKO#%qx=2xrqN1Xbm~AfL zJ_dVb$fP=pc9a5ZSXe@@R9TIA>NieS?@EJR^wzUIbyXATN5*2?hIigvzOY-!?p#8P z3PD1HSe~nux7>r7p9w=!pU)9~D)HI*2NsRt(v|Fr%aB>etOBc54N_%Hy>2HJsI_eD@p2~TCR%f1p zD_icUAO=@;;NzRRJ3suLCd>akhjn9DB2BYy*ZW4m4^_}UP&ytlUorz%GIK*QFYJEL zrArq;VpTJam3kGO3V?T#^_CkO&|KqSue%t=^4 zPKmgLC@AnaE%?!G+e_q1fJz;sQk=P&S)nGe1r|%ZVaihv_maCI;2|R8YQS1s_)<^& z0|>oIEN^UWqlp%s8lqkY>U$iLS_Nh4BiC67r9W}szU$l|Gh;c=BSwvitM)duOt#VG zoF!#v&-Gfite%2f{^r#yGhR?IQg09xVK~~-+A5eeLKo=>)&N^;b~T4o7c@cDOo34O zj+hBN%lDI-NYuq@ZvD+LE#k3#Qjjj<2GXFL!?@q27QNj=mYmzvOx8p|m=H9?3N8R) zn%jKPG;sA(kbu(+iqG+(kD+(z*4oQt<0vho?6xse z&Es-@R9rkrcfy2co4MPDUS2%u7^VSybO=Ka)NxQr(Vl^L+>Wed_UM+N!X<~)Wnyc* z*HtY)_mDG?&O=rdMoEJC^d)xA#6I`mu6fUeFQ(LaP;dl8S|%j8w~j+y1aXCe+RV=2 zC{CDgc96Ni7*VD6%EwRv)>Bt(hloy=q#2Rmc$*O((hF~G;0LzjYd-_Xw zGSgviklQnN1HH|Eyh;VUjjt~QUtLFO&&nfJ)aw>r<}nwa?S}g@f+eObPNYHAQjsL9 zFds)-g7*iZ>Vk$m1w-%RjjZ0kwzfuK2sF@B%<#zCGO8)$T+j#xW@Z97WSkij2qbTyDt+D4+rSlUp8`dd=VVS1;`|DL7TxY zvH}5vKSZl_41S>Z#D+v2n>(zn(D7#X0j8EU-iDA`_OLl;d#Fn))WT7>ybh`?_3#*j za?D2gm7w%sTJ`P-yQ{Y+v}Yh#%KAI$;JucS|GryM{IhkLu9UoftLEGuNrQ|GdPysG z>NQw1DPrWhU4z?eQik+hA-`ODoAmf^SxKE|zE|zlZ}@+bkdx|{yFGaG@cWKWlQOS< zc<1!ksr+}Xd~qhn8WMeP!9^JeU#hQ>Z&E={&b({cI%1TEt8s+wfI3l7`P8AOtZD)& zsYqL51vpOT<_!^?Nyl4*#NZ|N^%?ZHz4#P3p^IEp)AIAsk!2XDehBq~k%>u^aDj%` zG8Y^(TX6A8OS?KU5HmYwQTA+Uoq1;_Kp5?cn~R`t=JL1e0l?IgHBrqi=*9r%CwhdB zJbep_Hm=+1M-7(E-NL3pgIfiw?fWc=>>V6Vau+N_cOT}WYK~s;3OXia;@h>gwH1VE zx*O53H(tD0LiRdNf(yXsKZ6e5BoPTH@c4tb@xYs=nwlY_$?Lg{vBk_}n^sss9DF|< zv|LH!(Ty!}7c~~6phWcFKJ0+K{r&X^s;TWnfd7(};Wygzl+&kAf206C05_{*e3>Va z(4Q1LEn2h-L(Xg=TVviHB+%prY#1SsLSIkP$IwXHO#S%W$b6O=_`(!^cyaYbf|k{U za2H{J(fswIvwqaB@1p#pB1lyUEF=fK``4R_i}mAq>xsC+(<$eq%mT zFDO|&);b6P5buvX*uS9R4Sn^q>nDFSZ#4 zl?Gvu;G?%=O&#@vMk`!|Z9JW!=E7{foZYn!ahfAPM3~ZOpja0)|ybtH_3D#okq0tyLw3Aj2&L z1WAY_&BTlVg7yNmdc;jm?I+*)4ZoT$d2S*vM|ell0QP>(`=LDRfhMR??&ZtRzOdje zm_BWq6I7)Fe6X|GHH~!5if8aG)Y@$kvI9OgN zn94`6uA^a17#5}UsXD9pes#4>nstKfuW;p;K=r1QqysLhPr)HJ(vPl2LxR7|)IuKX z1!T)IyHv+?6jV>-QFq{*IRGaAtqRmL!o zL5rG#ghEduQIj}71vnDwZD8gXMZEYk9vCdmsUbvt!g-3EriMn*T-wS_`t`Et2ekXa z3n{vHulp(js;{zgT&S(A$w_gcp|l|lkWZ}$AX5~Fvj&D=rEC^DgNn-X^^QYjDZKHi z>Dtm}Yw(^v2g|b*l(-WdpqsD~;4_u}J2zdyogq}*j&uHhqGth4bnB1-SJTrc@Se=0 zxc4_$``^PeWQ}hlO_Ig@9f)m3*#6;1vj)=NcnXczliX!5Ssmv=Mq!uqIj`Y4H-L4|%TSeaI;tpzU6O2(XSLgn|4nbMYw^2#umo_eI9% z1_DB2K{3A zRAwY0CPCNK4(Foi{4fW|OT!F7cX(qx5b=EFk!M2$C`~d8wHY|@=tQJi@>6Z=nb+fa zY$iC^976V%&zt9p%A6UYmVd(~!+u2z+8_RxKQI1u%W=w3$Jsa{GE$Mh)So3*M~UQ7 zb+xwQjvH$biIER-VdM9e3*cR~GLk&S^-p*^Vmm?csiU#{OurHb@N$WPe0Xe0oxpD) zHH1!9uj7REU|xZjHxx6kdJGT>*w+a1c4vR9KJB)=Lf9kGh@vx|C zsL#4_oO8I-$Rl-OQTuAHt*y(U2^;g`)0VAc10ixlgLvv`e62IiJ`QDNfJxIa!_>+M zcw6u2Ubj-MRokJnhiO^$WkjiAP)*@;CSfi9gL~7H8q%5HkbBvA(hpbuIF5Cu7i#84VJ0V ztB$G(>GLa~leO#Cy=1hn6*DPHR#qWC*oGCVaS6a%7jNiFY+`)b*Ing!{o%Yc<5VQg zEz_AQwVDu2!CgTpT}RdT;N(^E&pv`WwfWC4$0Io5%(t_vV4zS!1FNtR=BFEz+Dfz{ zQ4ANUcEg3sTLu;w;LoIO2+dTVl|U@k#rLuSy>75UvlbZ%mNAVY0tLh4R$hYl zlxKmwkC05pjaUZv5ochKUm#RTKq12MzLGxvE#GoiEN3LKC0d$R!t!emHVm%-;lp5( zu#h@;A}OiR2eQQl$ZbDh6|e0ealnUwCe%Y*0_VRc1KJg;?ASI=Ro)ta95aX#;p+?|=R(QHe%;Ld)s|ialYsxz8B#q+!`*{SeT;gFbWMdZ=*3VD2;#XEb3QBV zeq$K|bc~GRGftm&pSEW}#w2FBfw(HL1~$i!zlN2CCYMSb_gxZar3{548>g~yL&u3U zZXr>$U~i}YqrF}6_;Eo?@gaF&trbQY9Q_)^hud*LQN4P+8mYLeCk0f*x1=-c%ttIh zZLa2h^#*>t&DWXjMd6s|&uXj<5_dfv!Auq$`RhL&5m|9MbY`~& zag7%uhtV;DPeY_;FX0^X6RD=~-iiS9E#m9+VG4ZB@s$fvcf^#Xt7P&OpZ@#7b;N=) ziSOP|+{TN~j$3pks7A~}X^_XQcr8XkV!Kp>7`F4aFq z>euJfWwh`z9$ss*Xb#o;mag?i8c?-9eIf-Kdj$6R8g@M4>Ji)C1b(A&u;gn*ps)*p zP!fcg{<_Y=1l?wGx%Og@`573Xmdfmf*%`)1DrYk?ZqT4)v9+0&Zs%kflS@#|&BZ4k zSQ+nbfvvl|my9&wy$!4YD}mEsxBYwf`SSrlIfLoGGdTxMAQ4}nqV^%p$HI1BpfLUC z&9RBLwBTxS+zYb^qQrLc`$Uir4xcj!ziH;?s|E}D%wft-own;GV<3)E7vO3H{%B3_ z8pGGrSz6kNubb$&T0pKaE<7@Eg4xIJ+z=4Znh4XGh_MC0>%cPL6{ag#eG9zGn6>Y(FPTd`@0<|pi6 z7cw2&5lk3fQm`;}2&u(EOrxEJ0apO;M<<@gg%oYjf zGiT3MVRM7!@nE=W8ZDnczjaX!$6ite21zFvqM7svhnQ}YfJ09SV_Ud-;FZ2nDGNEo zk7{ab<zEq(hc?b0H}geXr5cY zeN!T6=^?1jXCwiZn$eS~Fon=dGbTNk`|<= zmy9tsuE~TRdJMCJwf_EYEW88Ap#m=q(@(I@<3-!bG1a6o-Fw&bL3yI_yy|DqGFJHb zc#M%Rwv2hWsy1JdBHw==9(|JA;Tbs6v9;Jr>`yDZ&7oE?e++&6`7y5W zWdXkT9kd?uE`8pK(;O&u9MvGeCPA1g0?-%z4Q%jQYaAkxtF+gGUhgHK%4w#@CRME< zE+aWJ1%u(kXE-=HnN)D3fi>SUA!Whwxy~=|4a{s9qxBX77@@vTQnl}hY`oEAs8a2k zSb^=pEP(&$T(EGZa8-8+cUeOX6-kYZ*7eX4T%cJYUmT3k?FajovO?XR2;QxmFmZ&y zqs4A&#Tp^mWIfL1*Vg5o17v%R{I0AY~m%Y;P0 z1&n7g9KpOKrevs#a!<28x*k1pWVaw;O^Z2}1fC#54zSGc&!*g;x#`>Y?@KjguHilI zZcAJf$~SG*@IacXX47)GAl-!zh#sbRD*-A*f9}LKL;*+FHhvdrX|rE^fG!EvX=fqH zT-v_9i=u);u=mrvk+_Jy9Z8X!E%NG7Moz@!=?S(lDI}0!#BJrUc=7Bc?SE&^nsoz? z+H;oPxV+$Ft0S4bGvy@NU-7)!gV;c3a>#0g@Q*{^M8#Z4L!Za_Fd)*m-SbsXWR(X9 zn@Hi+P0^0J&)InWIAg&ZJ>R;?PuY}pkTw(C)Aw)R%JMe%#{{z`I9T$7em07>_#N>@ z@l?=E{_|3+m}8Ji1@(>8A_cCGA&@)gU zeYj%Rv2S&R6h}D>;SFnET~O=6-z&N0r)gx=_V??tI(A4Q{9z{~v@$X>>csz4(8`a$ zJLd_%yW!caUo1_~}AF@F!lC zeBk4Mf3@qAh0yN@iV?BF$fS-p(8@mJ_01M<9c{r4`2nV?#JMe{T=I%eBND_(hz_YT z2>Wgz6Jlc>iRqpOoxHFqGXPpbC}wVTd~Sq`D$h`)o_)X~$CHlu9IbvU?!njStkhZF zz=Z0VXc`!^2qge+1cPt_7`&hI2oK4ZCuG1fw3+5io!f8}j6t9GQoNX*9c!{XLWsY)BmDZOM99p4IO8CSgo3?hI3(-%GiTzO-)TSa1j z&odRexL<4Qy?zwLioCy?Vj zydQ~DShdv6E!7#SryVeD3o)SEkv9o=%?~xub7x%zX|qBKKpR!+HxQIJOEPTEwn4;F zjnNuIf-GP6PlP~MUH}<`0bcK`rdF%ZEYwVe(ZZI1K5N_(7!g8HCL>>L%~3BsuVr|i zmG+tBZo6{S2nf?VWZZPI2-BHl(5$z#@&A!~6 zE{E=V_LPyXqJJ@E+5U|vCvv&CJ|Mz?Ty*)5e!!f-iAg+`dLBzM0IM}sAeXtZn#>@+ z>Jd6R$LRKDx_1w6X=xD|cVvNWeL0 zNm-c%ernPHsAc?s8-PNgm|R9$XtIrQRgkVxmkHril3L;NGfuXi%$<(Hrq~Z&_76T- zD1%^EN#VbhUTWOvcNwAdXpmy3rPd>aC}AQFhqZ73#Nq80u7%Cu7j_IBBFG5~ogHG2 zO9#=N@MYy)1ZzMg??E@d6Z63JN;_`E@gOw$Bv%;o9EZe4f@xHuLMdXKh;XWL=FFM< z85hZXP|=5r+l=7OWRz3x?K(mX&?oDz5i}S+ynT#ThHF;!5T(dwR2-;-9;)^4|7Mi3tPf(`zu}U#ZkLrwi`_GOqPe&%t6R;*18I@1?N-ES7nm_@kH*D?!yBCo zVlkB(RYwWe0;fyq@-XG2{Rv-n+m5DchQ$@!IC-VJt~cG-Dg}q37`t7OH#^VXUU2n9_ZLcQKM$&&U!RFV0p+xcNy~)(DM3| z8B3L%t|(uu4tKxm!GoSt6~em7%9?>X*D)CVEK4-bX#t~OLxGf)NCd@{; zt7|rhFx<#7tl;XHOCr#PJgltDT7~KqFTn9I`92wdZ$E`->j3Isi8Mwy26cdT;t8n` z68!K9G^0@|6|_;&03F%11$em%P2D9pzv5<)zvX8GY$sF;VIGcTKt@9j9Q%_7|2rS; zJ6bqBoHj0zE+h6V{3elt7l1hE7ab7v%*1AT)r04Ko_1z2I*M(2(koz&6t=CxGG5${ z4*`HAIMUF69jCd}kGkRN2lV(GZr4uXPQk8k4xVH)2P;_}#fBPZA^xUfL~~zRe1)*2 z%w*_#0Jp9XJnS>0I8=;Z+==4;`YUvMK6BG^{o!cp-0}-yESn;7foTh;!oum!jV~{v6r4Dz?~NF2u!A^k zf=y>LH1p-?N3B3Ipgw-S#N=`)e^NT8$c9KnCnGdV{RcA?`~GiKhB)316^Z&oFN0)oRUZxjy33)e>2`N`h!R}hi*U2L z3&&1h?03oURu>q9;mTSWk@w7!MjJKmxf*X_%Ta^(?r~BRfdU704lN2~GG0X2{vIEf zy`Qf9+>JE62y57UUL6VibP93-yD}wWVA2PV9z92RtYgkSeP`OTChRfts9|yfY3FkP zvY9=?nB5I|JUx*C;law=3(PGDk}SBMH``nO(XFYdsmZsP_F#bW z;};Kyawo_BKy#4QeI;CebmjG7bz^d@d=Ro~7XO4AH-;By!YR;PAO>}b5NN3-+}`L=`r%Cs?bd5m-NR^ym}VIH-@t|0y6a zFqvYCm%vh1RftRdqrfchVa`Y)9@lI-2MEpx-F6U02X|p)pd(8|jKbfZ1?A6+8=6&C z3VWse)YRfN{CfDaZ-lqfg^Br}VaidX&BNul;=&_Mt)ngJ5bm;&o?egP;1GDZ=sv(TbFtszKJCpofX@tR_j}qHQY-lBI@GY!HQs5|)o_uQMZIk3xOMVhjN9g9 zIn=TQ$w1*w;za|nu>Yn_m(3s+iIoY1=|^ybNtu^?2|(V82J`fmY0{`uc&aIs&L(q z3#N%G^bd{?zDkkTI~MCMr0Iv}LYQdBUDJx;Q6^3kx~M6m4$Pz|HwgmgbGEL@^l?#K zafX|Iv|6oUY1wJ2?Qa00FtE#z$qLuGPc({kxx~VJSjSbIU%c>IQIQ#InKnF}#)un8 z#FA?xt&)vOxil?Rj9-(dPv8ClpqIV+1fi^hhtRX?(Ib&08O|E9l4)?jrZ0`M=h!5R zRE!|#tr;|#%Y1x9OqmV}5gUGlt&U1hHsPr3;(oTbe~s0W6?0oF2!spd%r0gMg2(SM zWlBgp&|)j|y$p~3yER+xmIret74g6=w`{rU3O6JU#A*t)swWG;1d??=0yvm*9z}?K z4!Z-Qh5A)LeVWSq8N+?ImOCYjAw1DiORwzExE|eQWyLa_Y=BIpCog#$1gVBRxMlFb zZi&!lIOdvk*a4t%vy*KmbC}|2u)Vm;PSUoM%MlU?k6;TpR}sz0QP``607Uih$m=LS zsoFCj>fSl__+1Ux{e4=iH6Lq@k55Ui0K8wEZg-7l>;^Cn%7cakqSF!oRM&>3hHr@W3>ECIi0K^9mG262W6##zrq7}BveGIEJFE!8?6Nve}ea9 z3Uq=Ggo?0Te)i(UC+NWU`t6#A>0&*5(BXplQ>g20pKgaU5jjah|sFY${OY-)~P>LG~P$YNM^bb+Uvn z^%)BSk+zF2NZ^vcKImxupfen)6p3r;`^^Olce6r=8FjslGJzZF0B6-{+;Bbm={ig9 zKYZv8K~$On>Lqp=&P0wSm3iu~ShZ@8FiX3NS7^qg<&|8W>qM?D+Sv^ts>y6-9TlaE z0Ff&&C{;02V4Y(%5&K=?_2CE{v#<2k5V+YOVLJE9%EGvV#aYLXZ zG~r*^1qm0f*YDq#F}nD1)UI5<+snRAm6^5AE+BrBlXuCP)tpHxT zF#nXj2EsOjTYeqsMQCdNqmxGQrg!?$@^)w?MkKeD!smysVHXz@Z;3|CowGd(uqDM7AFu=7ej!+f zBJJEJJnDL56uf4DnL-vdZamttM*3B6TqQ(qnlKrrLr}N6uY!tD z+R9+vQ#&dfDm@aceQZ_rNxU!NaqrwxI{!AsXx6hd~qDY?IIdhp?o;VC11qX z>kq4_dEBu_GalsT0z;k3$T&x$sXo-qvT4&h)Bzc>8U;0oi&4otS^eqLwLRb+p~JYx=~k;Km3auE{*V&D$?{$;3x6}VEs38s5*|XF?kYL zKe{x3|NchoX_5TQ6E@NWl;HxTBuW~}yI|z9zXjF~d?o667S~dZc2q}Q`JP;-`}mrLN-qvdOb7`e@Uc?Ur77*x?$HX_!UL-m`(Yc`$*+i=`K__gjp zxFs^+It}Jqc3Mh&Bioo;B9ZQWL)yF0-aZbMQz`IHu%(tO(mJEv4bC|=-V^YPybAJ* zHe{rNj5Or-hv)=sqf!|1+OF^fHj-l~)_|X(jGWuEV+Z%_HKX0d?877YvTdlNdC2XH zSTnq5_@4(6QchCA!1);q096UmOpi2G!!x*d6LHdgMeBW1m?uM8s^vGXdb{Q|zhO0d zyx))t#4-o@?o)iy&XK6fmTxMMs|;IuqFk@B(d#3)(=%qw(1PNssT3JJH@y*tg;>qO z6h9b`WmdXST#^pT2eD#%KEq#a=zT8(Lx$Qs#9m5r+eCO!D}1Ddw3B))W9H&D%CM)t z4yzDQn_HiGv$Gito#-6b=K~j#xU|;V($b5JJ3E#Y%h0+0peLKlLsla8=OG-hhzf&| zfa1;o;&X7`VA*vDmR$(wViVXO5>{)UXBYO9hGfLWa_z*iV?xV3oszW;6cO&hxsm+# zc&RE$f&(a>YHXy<6q2CTe{5>56dsz~A%ehL1<#M10k%XKH1_e(ui;tfIFk3V0=)`Ajq+V74a~9*v zd>RDZ6P`Da&AtH;V0>uU@+g?obQWEB7=)y@6jVsU#i${X=t8;)cy?i}DFFfOR!1Bt ztpwg(z}rdo>}u@hH_`lg=hUbkx*JZIB`lfMFD^V5kwe+UmCB(mE~Net>gCHnYdJaT zfEBvjOuA%M)H8lG@qa18G?Ueb9iS;*T~mbh5<~iGUZ?jwqa?cJL;S942=4nDdaDB7 zXnGwC7m48vUiVoMzUIA*;`MzW#JKd4aY>Qp@+Kc?3YVOlRL71Hj6uN+z!M~s^U3|e zm~PQu#5vNaH2){zq%%M#v$xjX7T|v5DG%Zz2-QvW zvDKiUBl%V5gSlicNv*ULE@1(P5+8H4cF`?hN0sA@MyVDZNfI*cj(kp<40Fgk7tBaDq4FGH*%M%o+pNLF$Avkeq>76^Osg+F#zP1U! zBtLd9(e|hIqqQo^#XRN?>Q3GQQ*yq9tzsNSW&tC(0{Tc{mT(v=Ah38(%rm6>?z(AA z=Bgm;x>Ehcj@g7LQy$AZ-@ku0)lefYA~4y@z-Gq1+n<%|Ll(+~;366^T19+&?Yy{C zFr*vV_7bYckS#vRx%6xCjBM@FBqIh{NOi% z&W5t`*+j=jk7F(yq6=9i=8xKZ^Z(-lU=N-qQ=B=37C|C8&A#gV)ala~U}_MN$KV@C`yt>+}z4dKRF@!FM2MZE--B#Iy-hTq$gAU85O`I?PpKRAnphPt~ zWmKWC(^8S!_M59fB*Y?BD<^Q^2nJISDpCogt-62TmHitbuqGdxnzWl+TJ+zoF(F2@ z6Za%8h*Icib>T$K0BQHfT$;M;FL%rDBgO;!n0-`{O$mH6LS(eNs!oIF(E=VHPt&c> z9I_6=o^pAM9OPM%T@k=KcQx;D{^iSuk>XtVz+>FXue||sCbQ2??v&ficN0~^XMqoA zac;%~4EDv)Ly)zE3Z?Gxk}YHD&Jz9Cum7|K!YpXRCWaL@D+Ac4F#dZZfm=r7Bi?t` zyX<;W?1q14H6IB)Gv(34hZR_>+~+_%rDxctb}Iq1sO{N0J3t_ksY*k*qt-Fbi0w}o z@!o7||FW$B2PP*3X!+dy&nR0Fq&2M-4Jo3xJi-(bo)U#j`oe9jZ13sOHk?AS60KE& zUz`n27?0(WXdS`8LUHLFnx zU1T_GMJ^#F-`5~wsMY-lb(!5Rs*CXVphMH(`h3H()&b_6Fi0SEz>tjLemrPIm4n$l zn?t|Ius^kph`DC?nuY_h@Px)!$yho-MI~(@jv7_ZpU-9iV2f8srn#*hp=Vxb_0NN^ z{DhE!wM9!i%!qjma83Q*UIUElzvbKe1aD{t8?Z4t3D0gMEU=W+RN<;|9sI?L0smT> z{VBW)%g6J3^r6oVfR^K^6K9V^3Zs<@*g;_+86aAs)4Iv(^=ZZL5XU9ab062S3W7nZrlQTZ4ed+CK_AKpXmf=Zp*7X``g$V zIYgz@@fQmW^=04ytsz6c@C@8pbe@D?c#i~+{iUz=S!fDJ;2pfWOKCZWGEwSK{lWu} zY)6u^|4rZ-T)7Uj3~C}hijriG$XWIpkI9?3jS_V`F15mF8MTTfa^JB&%r^L#KM3=bDaQOIw5j0f|3Id$hbimWZu%Pf}ob zI3J#7!PkH;ZDb&?%6mu;uLVWj#l?UqpV-XJjCwQqrQx>7NUvVz;!cm z!USQ3{t`|%a@;h2@@bJ^&75DrC<0m*#co<8uIg|RlV6Gn&ep3wh89eBzK~Zxdi(gi zBI+ql1< zd*&3e*poe?li48{BbX zcTIEqDYzAL)0NMpr;jlOtaTs(5a-An0__)gN-dpDXNfP5>^isKNnW!VLt>LLWz9xM zj55azu|{`gW#wx7GYd-)a=1X0#r@?vGrJ-n;uHn=1)7(9w`rFPv#n>w%`k9~RhkD*h{xvkqWUw6vi6XfABpyV$=&ztKn7 zJCSioAIK53-MMq815TlWtRUvfX}0@DYHQcA|1t$bB{k;_(UM~rOJ1`9cnR$B@#^2- zqsP)r=4ED93}cFAzmZzVLXc900r)rrQ&agO-JK-w=m|Q2t@2ddCn{Kk{DHI;hWxXm z1oli)2i|my*}}j&>oN1A{Ds4hDVi0pE37}G4nuXGpsPEXXH)}4B$5f|epQtU4R$Bm z&YT`HGOF+?Z`vyNdQx5O4D$IJ-~wl)Vqq;+27i;duzEgl4!utVF>LS`zjLDdQ9eX5 zHsCGB6)QrhRAD+!glPr4Q^Y`@V6VnT-k*WH8C0c?1$DMK6qLI;1E6dE@SbiVxC}U; zS@Tx$uDe5dnFj1eBJ+$N1p_*r&&zWnD{MRTz1l%6s-ZqpY;#|p4b4#cahWydewBc z$8Nl+348Yz%1L)jrpTjODY@&J<3UU5Mf!ja)-j1fm|2D zg)|^~HPHD+uAFWxrW)v>JAwS^D@ZF&G%#o_dH(!}ltglPb{A~7XiH>J?3eDGmfDU) zJp^%TJUnP7tb2=*`SoQLMq`~OqD%;S^p^?o1>j1e{r30sbKx|)v&Xr_1HkCyHl}$Y z7=j3!O)RH90F~>7KgxK-#*D?bKY89wpq_GUE%)@%zsWqV0UA@mm|X&1od<`8hHhs7 z+F379>NRxi^YIk)rd?>nD#@7Vd66k2_VD46E4d?~EOqI+ZOY=*N?$BT_AsR}3TlU_ z3*OTuA`Tusc`_VVwylt;EZ=_JAObsmA!uKM&HE{^<+Yz_xjs7qzL%~PtNx_bcEn@H zmr9|@A^};Q3^_X;dvx!vNk81cgWD#OIJmHSvgoa(_qopV3#4;}v0@3?k-XkP0j$ws zZ`(WB+M3bb7gss!#&?jE0W*nc)sMWo9hIjJgB69e*5={EhmG?WEqXQ&SQSk=qLN>f znBA<$8jnoaXoLqWE#RJGCAtVuL=GH+f{4-j7oSBeGp`NFzUpVzS1#MV1$~0+sHLxUfj$Y5!AjBcoYdPxQk^Mvp31Te>VozEN&d!0WPsFSf8gzK)gJjj}}J zSb}~!((q~WA&LDvTYvz~m~c!@nQJSFIu@&Is_%XxHf2SO)>8;aF)-z^TD+8rq$EmR z=F#KF(+NSEMZ)H0WVZxuIx+ac7xe)7VLiq zx{1*>q<+D29RW8oh<1VG%+kL(E>-{t!&os6G+85aDm^_}L_G;7u!xL|kq_{$pb;2E zGw;o1+FMPn46n=v=ucZkbp2=3a-mNGyuWnx=utQ#VXuwu;oIjck!Tay&ZI*)u>)5R zOWhTH@n1jjz!5f@-;p=zBvJS6;`s&~M=nK9O)c?#8h>NvIWra~Jq1AOqLAXG{3Qtt|SR*jReu;JF!( zymK_xWkp3>|CDd}6MCAEp7o?63a1Tr%hO>0?ZsbdIb1&>NA->Kuwp-8LH8*}SFl<9 z55Du8jz{%>5Pus9o#gUyrl!`bc=g5*d=H(Mjy)_qEDR&4nvMXx>bmkfI!MF;^gxfN zmjTNPmhOOoN=lmK!Vi{6@Zzuk`{(`IqCQ^Hk1Fof3L%p6%GNn2C_X& zgQxeOKVLZscA!}i7yxV?FG>HL*zJSCM8VH=H4WUI^fp9egdEXPGSaGS6Xn zP>T}y81Iz7!@1>;EV;!$p;)VrkBOP`;^O)_S@g%LT6u3>)sti%RG_Kn zel=%#?8&A)7Rk}(mb>F>|5>sL&-V*o*g!!5bSDR_A#ecU-qT=e2rAq+Zk$Zyl~90B zmfu@GiUge;YFAoXT3_6m%Kg+<81$`VUMa-8GoZPy=s9)UmP8I3@=w2*j0c%1By#ZH zvMnp%hV6-?Dmv_7R=;Z%r8{s`FJpIh0UR@l=_3A3puT*un3k>B&jLC^} zCT}boW8Zr`@TYiZ#ETg3gu9-(+|N?z|vv>aK$MUmY zOo3duZ@KefVcUv@ZbgN{A&|csG+S=dr*E&L6SuUn*}Dt^D7XE1vP{{IjANi!z}Oo` z%C{3L>Sihd4my6Uwa!{WE9E<>YT|-YWn5b`WK3j zYD5rbN6Q$sL?Q;E5EP@}bk<8~#&7H+O|K;*Hwa)gbr5?oM`MoHOX_hDTgp`XQ=K+k z9)RJ`FI>^#Va!7LDXhpgzb^O=5#;ce?;j68qaD*?h%RQ*v&X?4Pe-(R&iDrd+I@y~ zZo|hv$4c_KmqC=*!9Ra>*2idB9Zb^z?i4-{)%h|tg zUp?G%f=6g;2g7ymv1!^oJyvWJ<}O@VhW+BhFTkB5Uiu@*(|i`zQJ_gpKpI%wk2bU2 z7tq$MK?z%o9Gm-gpf8!HCzLt=)Vu%ws903 z(HERuNbU`HmOHqwIYL)VMHJ#Zx>40%BIX(a_&0}VDr>9UiNU6sd(oKBluLo1kNp>7 z6Lp9tCL>041+n0p@nSS!@cRiVW+Fl%><_omRC%MGzl&WM6oUU*wUal|#K+O#W>Z9> z6VH|L7^AKp&%pF{Gni_|dEBK*Y61$|OW#uqaRS$}o=jf>h*ZFMti+VJ9K7Wi69Mpx z_?};LrTgKnI>-@n#3&dk^G`i0W1O}CZn6;3qSV$<2x=3FWWYk}45Pgr6 zVci#!c;1>|5U4oEcbu zZ5s-Erl;{j111CXAPB2PETmLHP|&CfVhgwfZtJJn7(K758nScuZclg!xNJ?h3)AW+ z-ODVfPD-Q#!Fp+gJf1?NRL9=R%0|$x#$cZ4%#0lmq$n#1#u;Z@x7`hc&=8{~4Ci@# z17rxnQtk`Gd^1lxpSae^IFhWTN9`}yt=oQKv}8Db{6z?vInES7giqIarty%7hlx|e zB8=$XWl+&?QrB1gg<|B0>0k1hGu=^`3__K3l0WXt6e1!}g4JcP1)Q(RtN7TA$Pc=7`ZS`e zbGhV1GXP~?y0kDZHdgO6DK0!;> zSpao{0J-$;!5dr`p`2eCI<1oo0^G8DF`etp6ddine0d1MzhN|$IardSNXku3b>r$x zkdv1;kdhPv`~)x>ffBAJp@)6m7?f#X*xFo+Vt4ow|0$?Id+wB$HgxMO)%5XWXPA&d z5Otd9o~jtQ%V?a54+{XMhA{Rov^GS&t`dpB%dWsyhE<>l85snq1fvmSeI24Lh?M7;0z~R|%;*TEv`_}(! z57f=x+&vGUKJ91ypcUOjuKlT3155)Z8=IIAA**u#-PcAT(!@$80C7sLrB;Yw8Es^d zgBV+n{i4tvW??Xyf-#wXIXxAS?+YwVLV-|QlR?9dOr=fSy zY)il?zJbA9qtEka|Ii4dV=~GY#I>GOjUpDbh@u9dk?u>Nw<%RKub{wD7bE*dwx_<3 znQLy|>_T zWm2b`d_bB&hv%5yf{J;UPbfRWN~h` zjnOI)R+c$2-C6Lj28Uy@I)zTrOEvrhy>u7h$_(lL^rDL@u=G?!n+XYhtBrP}z>~l$ z%t0X*GM4q~-FvB1rt?;cS0wLL@v*ctCqT_~Cxm^L04We_L#>aPa;GewsDM@q45u#! zX-V`)TZISbawzV0)2DZtzhFTrfOW;ghu3kmcB3VBC)Tl=wlbLsGA_z9Jo<;_ zn!$#;h4=d;!?us%p=5+Ig+eV$ zefz~xzR!{Wk4Nq+6H-m5T{##lRZ|!VgO%kHm`PW29ky|SY=q!Y4Y}bII>KS3o+re{ z7PDFGDddRvN=wZtk=67Zi?YR~g;+`eHz?dj4GK~J!qIj_^%u$_%aI6<=`ahN!93ig zq@>z$twNqPPb6%{A2={Bg`F3Fs~=0`3z$I4NG6AchtGg~vd7cieJ|~U1Mo@Bix)lE z%GfMke9!yYg`t!q09uKm)a%D?tToZ;@>_Rocu3cD0s*OdCbZeD>_!Vy6ZX10s*kY)eZ{YAt-j zn|rmE-Mf2tEEh$5AfYojUo?Sec&uLi#To75CETJ``uf_O+w%8M5zo2?i~0=Fe=vDY zl?f5@twg3?&{Z|i=a+n0Gtd&y*9K8B3c24dvPu_B?~Vu4-cki-0t#*C3=4CT0iT+h zf&-3>BT@#pzAJ*&RUe^X+lFpNU1DWnW)~rYcaJdeQ1cfGXti5OwK;kG_*1mZ5*il-^k~6{JdJyJUqwC)qG>j4A0spO1@V4~W56+t zOnit>TnN!yiuL#BfBe$c)X>ObTsVt;#GXpOe2R1~z1l>x_D9d1JD16%^PcW+6aFBw zO?b?{vY)YW7(RZ*{MQn0-;EbFh@ZuA)5Lkuu481#VEpf4HKsm-rQbnYJ7oB(CaqMf zfNp)-#|99E!<*1rZ-WC;wGH|MW9qHOzJM)fr=`Lidz|_e$w3_)VcY-7vu7pjza?gr zV3!`y;?Hr%3jd-GyKX2I#W0k`V)Ywq<^}+oi;!602PVIxiiPqOf96G52GzKlOIJJ6 zTfX@mIQk4GDgf&c;M8bfhv~x0e}Ly?9jn=B@TCL*b6xy-D}^FGW7;%L>?RhRlgYGQ zy9gykux*ARa7+62;NLb$+MA{RY5E@=g*zoW?K#FeCWwmlfp)2$CU+$B+zEgPEE7US z_Je4x$2eY6*4m-4sAn;%`62eeX388KaZ-kB4lk^K8%@m!gv%uSJe5MAP@IBN_J*K_ z9gv6jSNfInxJnsRhhP;%_CyBdA%;dY1xCIKAAYxAssjLLH9c#ZDB{_Kr_)VN6v#)d4ZYCp*V5)yFwXfyQ+W04StK>Cl6)1KtO#mh3hRE%bDt4ORLZ-# z#VYPIP(Undpwk@ES+i$nWwW`!W}>I?DIT6)JPOyg5_rW%k=`Tu7Zb6X7k>>fUdJ_S zM!c@AEfAz zSUyy1CG11|z`}%|Jqheg?0)g2Nnm7c3A&UlYRa@f){b!d^UsT9Y%Jyq3}bZN$0~Bd zFDw}Dg4VsKn?}^54Zp>l70RSgLob%M*oC&k8N?IJ(hc8h;JYb}{E+dq1&(k{?O_7D z>Ndw5tv|0`4hk*uYkkn}c*I_`rN?JnRYxiIp7(AY$Nb{o^gHZRJIf+LU_fHN!&M@^ zy9-7>T4&Q`pGY|M0le-Lc>%`bgtE!8yEAU!Z@|(9(iWOnq+M;~Dm8>{acbq~ewgEW zu$v|T>Hcj56#aRF6Zv?LblbKKTTR?fIekS)XVT39lYEh*sX)y%`-#@^C>9f$BbllMD}`}_%PU&*mfCSDG*v=Sq_ z#DpZmz;>q%3_|%uk8g_TtoINItFP%OZc}}V(2C4vvxzf}KuNUBgQiTM-kOE8wip|5 zt%UPd5C&Jr9j{8i5-urXr+|}FU~~?#v{XTF?$6nBadsZ5+M-2AEGo7nC0Vczz1?F> zl4Mk9a?2ZNv})B1R3IER5gI|AgtDi|E3VW6ic4|@dB+oofNINSBRDkYD&af_@E?Fn zSUv@i1!P@2ynp`$-PnsrcAla<0j-a=Fl}_=Bu^dOm*ma6|RId$#b) zpYTFdTE#V^ZsWOMw2T?V89j?Pz4-%xNQ|N_{{@ppp1T`r_HRgKb4dYjcKzzrJ<>wR zaEM=*7R(x^8*Gp}c#xiS6uw7y$kL`d(Rwh?i_6MlvF8q_S_j0{DO{@Lp2Hu!lYzm4 z_G&66To51Vjw)H~pWU!Qvl9Alhj#5Qb0f_(>)UsaiA$I7kXm} z4Q_LcuRmF9bw6E(`FJ;z)z)=Mn{tgnt zJ{ki}dY>!2kEuN2d>%;wD~@(|PcPmJ-|-6qcMZt&7IicNFG(`N|8HEp|DG3g*3K{*Susq{rnoXuuDwv_~EZgKKGEEQzJgb+MCS{qp9 z_nmsGB9ONSkMNBfVq@FFG>f^m^N%A$Om5o)!1##aTw6^gp70`)2R<>`Ps9&0yXs{+ zw@1jNgPr*j$uB>NbA$M9qG$=HojJy1lk4=?s;u#m-ez2oD(s)?X{2jGP3u`7A9C1b zqfiLPSf_pa+0>L{7?P;Kne!eJXZQ2-g?F#dB!FaHg*AoN0k|NTm#>v_vt~xTFf0QGu&rAUV} zOix{PH@KeK>JiAvJ22xJgPuNxN>Tm*$J>~wC~Nk3H78CC$|Kj2-0e|n^NjASQp*Jz|Y=*)6r){6JfHdKIty?g)OTi5`MWhi<1`CS&pYl! zyMN^f52i`i&d+vUyd&s36aSfgs#8##T!u03DAtJ+&&`c8g{T*X?8H`~k0< zz^q6lxY>89c=*g*SworL$-vRsJ!v5K%kdK@bSNs`;KUKIcBq}+nAhCpT@-KVM@%%$ zXtO?nFx+0-)ELr*UU@7rFJM(Iv}W%SHzpiWNTS8ZOQEggWj(=H{DcA z&C%eJjOTgqmD=08bsIc*GTXFz1~R;^mTlxWOoxZ`6G8cDX2)K%s19;v@?O|M^kE<^jvrPLZ@saaEenc1Ux^Xgj7PwCk_!T8$VQR~O_Z1t zv+xKA!`SIwxKC^N4fe_l^eV&XdbdBusIx=YuHyhPmjGqhxoyVJqX_Wsw|`&0%s_!_ z574iwrScZv`z74NEffm1P;0oWg)nz+33H1U5|0TUqT2KeYc%^HE}Qd zj#5j7i|Pb$VijTrg+%%6J9%<)JWTALNJhMHQzydWCUavJYBNj0ym6jB zeMk4ceXl;aaU&F-L=lD_<>)agufOn)F7PsEDYuVh%xEl(1ZimGvXEnbLJZ;-;sbJq z>AO8=39IR7&nZ(1XsaS9AJzO;O~7C7{kB$717M%MgO+-amu~Cb6Mv;G5rICep8wgX zOTzaL`lfU`X%=AP3(Ox32JAxG7^9>hQ0C+=TDyUL`)+v19%Orbyn^5vA9_MeEG*xr zyuBJ385u)QyAmBf)7HrwFmY0u)=%A`jz&^AYC(-l5l<>96iSMG-klztnATkBcI{*B zaUUoXRnh&CqeeA=WdVV4v2X`R>gUySvfTmf@mO0Ur4T%675v{qWW#C2EFbF>OL0O(jx{ zPL!y(WQ^gX-OW_d7oI^;p~LRtJ5JhNpuOm4&ei``yRNGh{c;1(CIbv&9z!A{`#*#% zKEpUFfbai22=;1mV_?n<1*%ICHrKv`2NyGY_Q%)Le`j;0Q)Bn;!nlIA+#Tp+4ILSY zTA?5TEyg-IWipYU;@GtV$L+vc_cKB)ndA-=%c76G%g@ix%ht~BE|Zmq(9oCwwc56| zlZuB?m^R>-+0dc)n0yoHBt2MAxj?t#3s7yT@RAo6H=6{e9Wd<r7<`U! zvMoC9V?M#NbX=X)H60bBhb&|+K*vTDAI#w?6ypVm9&a#0fyQ6URyYbuUoK}N6qvOe z$8wIYdUHfIed$J|ZEDBxA}}d$db=X?dJbNS%rL?+LkTleRT2D<-|7+MHeBAa=_GXW z&CT}AO-21!1fd90dN|{?DRjg`+)amUGlC~$BF`)zM6XZ`io*~Y;|}OHiIh?pWBD{L3BZi~S=QP7HtmcG^M9d?vj&$g1NtqejK1TgyT;G&MyYkc zdOsiLpP7mcy14B*T&1>Ema~p~CR~p{AXU5(@0xPnKWL2~OHeC)fP9J9MMrH|c>qx& zKqP}_J^oFd9Ncti=6K zXBoxpdK*QIJv^db#>PdSlP3?B9Ds*7ST&PnofAg#=Lv&-#@Flx6E|^b!|eq6A9MN~ z>`Ko0*V=CL_4p)N-dq(Dz}_b&vlvhzg9S*2YM7Fg)e05>FhKVjs_Ar7vn60?0Y&Wr zsB9&z`yO%AU;!? z6CU!)Ss)-1b#1OOYwM03Bm68egFGA_f!ksO_Rld7eIff87ejq z(t_ns$QWm56=vam@Ds$uvXQBi_jJ!KSXm=he?8U>bvJC8jjM=Fm2FrHT6=parmrdG z&b}~^5RC@G|GUreP6Wz`0M2V6QM0gP9&;)~D_$;eJtDn*czda)umH@lD2MbAsaXFyink9d-hThz8-L-Qur^IU~<&k z+*}LK?d$0F^N_Oc;qH+#tO`bB`J=!i&@U6J+~VB0f<62O2vd-pmhqGJh?+S z(@K%0%Qp`5(jTm;#m#v~oz+ENfvZA%LI@g951$0nsT<wI2bqO3nKM_0}XP<_h6jWD)* z8g1Hu*Z29C6kk{`EpgZuh(#O_WH4C0yD0eWHj3#V{C@fe*@yo4uGfw7 zFpIW7gAs%^~Rcq??7pdm}QD+jtnn#CN=t%QTAgm{)2TVgt;| z&Md&&2u?#V?Kn7xaKCRb_S`1+V$KE!I~0AluU%W%9X&+Tt;sV&kWkEIb{wFmp%v-P zM5xQ=eHl0NZlDfIu00HwgRZ73%+Y&g&;FfN-6^h1Y;%Umo*Av5kUxp?@vMZWk2B0f z?r?D_%f#OS^)o~mSiAaSO9X5l(e(AJCyPU3JeEp$&>=J_^=!w0>fPxSjPxQS*wFX~ z1cb1iaDz?u2|Rx$xp8sp*H^&ri=mZN#}g}xrL5$W=bH5FS%)gx5(~akI05s<614~) zhzyuqTKx*%l4#ziBIJnMoK5Pu$b>cIM2n_e-20_u!Da;GhSXzOwMYG|>ZNA4U1+|{RwE#%jt{_$wm87x>)m(H zm(-PLUdE=7xBYi@MNNRgWcGC6+x~o=m+-tDNx^)AQm=x!(hnBRe?U}_9fQeD&-nH2 z@h@?z?-J>~*-&kyp;iUJw*%O|AM#szkwP6LG^nDPdzO*xo%;LAL2Ycbu-2XP=;WfE zMeyOavJs>Nbr}i(Acpp{nN{4WaaK6-%Z2P|sFg0OpMEpWuct0s?Lj@nwiE%H>2`M8 zqzd-@3;@eUt2(3Rj~BgNE16UTm8?qR*ho=}-(RV{QmWvb&oj{o|JnzotG z{*MdL`zbA|>3)KX_;d|uT1B-V$l7R_u6;*vjG9-koJhd7t+;b%9S>a9o8T%fwNmNZ zSU=@+dEcZ=`0*#f8OAQK)c?YN#1v71K)#6+R)}h+kauAUgJCVrs{RGY?$G<_v?ffO z_LsVvTKv1F|Hy`9>9xmmi~UKjCCs7DC~Mnw z?!2MBu5Qk^PjA{rFzQC7|a?3oQemYw+sVSr{~ z28H0Nwq>Z6Gn>sdSid#Sx$}>*A|Gk&kwH`%r~pw=1Lvlmcw!YN7&q&OS}w^t9?Lqq z!xEVA_wi^-qqJC&k+O<%|B?fh!gX;5SY|GRA?GoJyBi(pG}rOtl@TF_v-vs-iF!7D zlYRHmoG@oO8O}JG!;m=oYn!v0a+u>74_N!_{H1U#bg8Vd`IMA&3EghvjDSMFYu7gL@fsmr$fUA>yYmBgVKsNJF(Vi(c3(>5QK|9>H|wDfM6l{J zADw7!qa{A-X!Mk}|FL$*PI&pNDD5V+YIF1=G59xliYIdI@-DdFrsL(~khF8>ZyqNW zbi9QY1bk-z{i!dPif-4g*SV$R#s5ltae(gwC0vYt#TyW2B#pd8cD2>hQV{3T!)BuAPRqWaNEFD@O0)vQ$ns#Qc$4OuWw(?%U7=G zgO;%+FfGd)2{_@DJ?TBj{xTZ86-mA+tO7Q7AN`E(dOeN?*Ui_H$W<1I$G#tb;0arU z4eT|2iTTL~9vX^53U97AnU#K}Vgbf42LjSEZ^_FW!o|{qd(=ZiO-(_P4{v(LFScKm zVml3Tuq#8_ZBh1gjo@*LI{tz zj^{8bf&M!Wx3V&2pQ0&ZP+|5lOWSiVyyd% zENI6l_Ps$GMU6arVkQ1$S3D26bSWg(o}zSO6G`Ni56#_3HeJvE`|meuPX@GtzsR4l z+CFwKYPxiC2yVSxa$YqoBR4a%umDR8HcMJe^~XrKX0N>2YRC}p@h^YvdU@_b=GpVV z-YmY;t6XD1k%poD6-B7AVe3Dw&!o?Mm+NKZUQw-WzssajZ;bQnnJe$xTufR&Fv{zq zj<%cjL@jNlmxklB9{pIeCg}UWf7Bj2dV9$H|L&ac7d?9aM14uO<+?~hjZn;-T!zbA z?*RiQ0JM)GykzhSqypqEv3F3 zoTv8YyyIv9!{LYLvyg#o5dsQ0o6#3JG(Y{O0IQcSUi^sXH+B#G8T(#?7v(aNT;ehn%?N74IwWYtU87;`;?vRc%UZzmd&jG@9z!9(v5P9sqdlI-8MqQlwHaIr6)o;Wr zvL`b_+!_1}%;E-LBrRF@%S>|yFbekKYR~w`dQwP z!<{yC={&LgK&WW$#1%(nekk#@6HRraJhN5zj@|wQR)hGowt`;L?>Ag6f1(ts;O|~U zOWvF=LBd7XysN)FGB7Yt?@~YmqhSs+)k1u~<(*`+U4t!aI~K>74@YRWZjFrWclBSg zSY$xNzxg+oVw>^p<81&h@NEu4 zE9k-I0|vD1pdJxM?ehbHwIR8HIy9TL!w@EaGHIh^LB<=}jY(gH-zb!gHDF*e;DO|> zkMFm2F;i$JHR1~05~Ex1L4%U;v}`{FS@%!A(^phqCtf0yDTlf$tpC8p8VBo<6j>U_ zQJ~b)zPtrxzQ(5b`#b*45p-;r%x)GPB`j+{I&`Qx&UB65G=^cI@Oq;YC(`g3v~M4& zr0`?uN!fU7R+cBQfjg;Z2HM(2elVS=1=aMT9d_HwdEstNl$hvoSo;wV#y-2x`4!9E+45U(B2hs4!2qUT7>n!9h$9{c`0 z^Z^R%6IA*eU}!I(mcvOhgiAr_qOs=M?{8cDVv!Wj7H<^Jm!zau;P!`#yFrF^w>p%y7-TX#GEKGkg*Y*lkW&iSe^a!+h+i>Q0>LojE={|zW_uGJgXdXQv{eLG z%sVdexG(4uwLzJn1SLastmpkPkPuD@CtAv^iX+$)y6{}akJbr}Dp>i>^WZzOw6ZE? zGP!Ai24i#9+)Vbu<6T`1-{3SO1RxvE{W>V;qXrHh9LC*OfT(Ww$ZkqZU>`qx*bPk_ zTDu>uFci-f+-$4hLw@4~90iYm3b39{z1zq4ja#_LBXJ;nGgyCv>_6*iH?ZPz|Mlg0 z*Erw1=}aA;{+9xY0vTpSJzrQ~mLRJCxGlY~al4uBu*-ITBLKP%7ZzeQ) z$Pdj9xS9&ThMKY$D+D8HCjO=HPvcu+EX$w|EMYCuNqjz#bF{>dqM3qB*aAxDU0$j) z_}?0kpSy+I5jf&X`}XbMam2?m@8*JmcDYyQnLsq9*RP-Krcsm+)v4S5iSw{`e8!%ah^FP#=^!MM1k!Xh)>U6lEDyW` zFDP8$7iWfmW_^L^f^=H*92PT@;}wO_8sh{T{O|U1buuho0#vP|GtCEK{{o!*9YVt% z+C(p&?op>bp{}}na9{cF*QoEx5@ihDj&9CrbyK4fxQ+#P&s#_n&Dkm$_U?Vf1d&er z*ozea(8<{Nxp668gClyI8Fdg|$-+|S#>!x)u*DC^;r6d@7kj*P4z+zPjU2ZSs;((?2cPQ+QBIRK*@xMU(x|HVP!OZ{GohvrN`S#m6jooTT|s2702b5&jycmw-3RTmKwgf4K)1hYG3Q z{&(9ekjE=rjp8Ujar}5&?!)KY!zv0zth321`oFqr%=D?qOViA~Z5QE9`^WY7yOB1! zcT1=+D+>j3X`+?U9i7;kjZ7BPN&+ADUyxnMWr4UE4%m)557y~tSvsMhNvGRsPS03P zr>e#6*u)$6mc|H%b`sC$&AtN%@*rwGv}U4T23|ITtQI5`xBV=|@fAnmaPA=eeng~D ziigBxRYVDaXIEg-|aT(;GYIvy2L@mxPN<}3 z>BjOv4_m)MytFn{7Xkc=Z{6AgCE^eQer$xVv3q_(3sZzDZwmFkiCw(7@samvSVguG zGTGM*n>RhPPs~6l4@&)j8qx#YeD?OSJ(rMeiIb!TSYRc)jx&3q&k_LBgT~q!Cvgd5 z@uGp41m-0kvGixJ_+&l+d;itfs}MmE1AuA|1iBOrPeg&27ICk4Hm9b}<9BZ%F+nbr zllPdfCK;-+dJABfvGdY<#^6}Pcz*!%W{XB%b#DfzXDq!1+C2*o=l;2sCmR8HJ`lCu zMCT~R6v%?M{~%hznevA<$5cL=eeW%Wz&_!)h8lMZZ1so=2(-99*8@57)16pxMD)VTTI#L;_2>Q4PtafX0qPw zq)>bJDhMzj25Jl<S7|81iJ{hp6OE_LE&yaLEK79hYD!tUS5{RO;UhuY9;$8Z zkc|J*TcD9Z&a`3w{%$xOO<<9UxnNb+;lpE}@l^Z5ce!)>b_`@imkZ=#(Up}^ClZey zKYnoyNrvp5U-KeHaMViZI@p=D0Z2BNMK)U%Z5%wq0+E>D4E2nc6R~{)Ze79L3(Vk$ zhF>>+to2VaO+J&CH8!+(>xBzj!%$6Gf9R5ZX!I_Q)C#X*De8!YSQ?S0`3nXWF$_I} z7y1D(YZY|wdrUkG8z~&`bvQ+lPS6I;01c(&JSA3CR5;L#meR)KQ}K*-iU*?_y}O&w z{Q0rVSFErAh2;P2^Oo}FLo!Uo;N6FN5MDwQzUfGN9Cb!2N^2}8*8`ukF(UuZs6&uXX^d6D1q=aD;M ze38A0iB5KwFqK6wc)1TT@4ZG#A2dgwE^3d*D&sX6`-B|q!DH22dcx86)T2z!F!v_(HQ&vi{5%B z4`8qT4q@Mufmegm=w|Nf?FtN$_C77ilCk zT&m9E%1W0d`jer92jAgHbunizkj3?#%Ut9|&u7cF#*dNjM_=?;QoMm^e3LnNyv>8*fY0RY2Lh6Trn|8C&;Nk z^YV(%|8^d>)QD4o@?2(1CT6!vcKV6F6K7j|^)-e%lT$*5Nt<;v079%lcHV{rAPY16XL zFMb|(>h$U5<_v*-&CJ5JRBGuWQxHR9=o&}OR60r!x`L=+t-jH`U5)Iz)F^j^v zm{ny3w{ix}I)UMaTgwVKYNrLm0OmLY<{M513Cr>er;kJ!}=$q-#qw2ud(Y9u&$Z{c+@r{ zomH_bS3F-;4y|kxZRaT8i7YkCmU+uLKKt545{WJ)$rkCOyvDPI&A@^JOjuuA2(c@duxMH*L!o4n{0D zpHWO^6s8$Sij7w$`7Vb);bf|t$*WS&Zj=`&lL-@Q+&VTvBaR{OxW=yIXVTtuuHS<1 z)K2NbFEg2hFl^^y6>W17bf6B-$>s$&FlvITy!*_V zd*n%77TYa`rUZy~ZJ#tWlyM%fuq7Lf1s*@6eo>IR)XX=Xp>|M5zwan_}}G zLcf1dv`PgmHm!R0+=~rqSX-44?d^_*M66tK_y&_ygpQ z2QA}{%3&XcT4Q)H(!6eb$JTfgQ~YkR&YZvv(Hy3w4_;MoP;Os?G2+jAGnSB3^K{eR z*pud>UwGB2A$gLiZUwt|bx5L_4=#NBKQ4eZiioM$EnqC^$@97ks#446l^m8P1e~Jy zh=BSG5peUw-g|asX2!EH?&3Hhn|@|VG)dz1u`w}InXg~M zNc84#;^_JC6yqesl{Bcy&2WQJQIyj3lP$7Z2kr}B=Kaq|`4|nJu$X&*t6*SFzS3xn zZvJD)2$PcKLt<(J9fvB;ah<25>4rq^z_WOSX$`K{zCw{&`{V1DSsv}J@Q|l1&CART zoq<@-L`&@j>&YIG-ph!Yy+lIS7)Qr=g(8YxG>v-T!A_1JzMobmJku1b%dRoe(c0(~ z+qzUCV4i^{BMN7FO4vInHx^^M>r6j*>mEYi0T>nCI~B0&Dt(7ITXTVp9un7(hN9(e zRaG+x)bD95ys%L^baAE)xd3L#Ov3{S*bqlTi8(YuzDNsE%Zkis+_>x0X&=%S*{am@ zwcCLNyC7Yv;({xo+8x@p%kB;C6 zbqQ3!g2DLf^POv7Fc;wC5O)l6FE`QHyo?O*k#NZFGsq;fB1?d`?*|$12Sx-ge3nB@ zL(HJ;=G710x&kV?sS{@SghpD!b0c8w9;~TbXAsk=Mtax7cr0a@?0ezY&tbz6RFGs? zz2N={%~3 zE3{5e9g8(_NQSApFxJ>J4`F8}>x4tjCPy_iG!)DRtZLHOTgBw;-+wuE>()8bSaJ8= zbeTY;=AckZ#=5xl09`gqBDH)S8?$msY6Y_l46&pQs6>_lE*2QU(g8qCy^Sx~iA zC;(S7@bl`$Et>{&-PF`HpSkXErQcVupBa!tHUxWDGq-rN%P8QBe+_#w0M$t;GiwFg zt7E{p%V5iv;X>;{Z*!mjJqDhEGZS0|x9J49|VK-J7^hh-f$A9tL)PUYrgXc&Zm@Zs)*(3*?&u#L&4w_L+wu~th@Q6`?y#3JiZW1X@4p)pwdfmL=g zxo-3L_NwS5PZ`-o)-Y$+5O&>zNm`^uL_h1JJaE&!NT;x$2lSSaaTDV6U;tKoh>fj& zv6wivZ=d67{C4hf=)XYGr4%pK;EJN*q=Wz5_oa3yMh{SH;u0^0ME>3Kev zYffr*%7nxCvY}xUC}ix8VSo37;xY=>Q%lFha5hEb*cCU!AwXFX%wGxyXKO@@H!QGT zEt&8ZBT|Uk#a`41HOvEPO2t1ulzP0FEBiYTV>&i!Im{~0uv)Pplr;qteA=PNI;%Xu ziI=ZlDZ{XN&K~xFJ*^??_K`F)0nX0lJ#m(Sh;tVuO5@|raTPTGZ+<~Q>T>Xw`evsd z&{m3&y#6Bq5%c6Bv$9NX@Yggtr$DupS;MD6(dgZ;U+bTEfO3<&@m1{xiH~Q-^8_0$ z=h|_{3XshQK{sN1N5Vqd7g)4Lw9?7^V@$Am0KWT}+Mh_hgl{{AhMa3~+=jYSjt6gz z*C?tWB!Ou<7z&R)uetn#YN4vq4;H>ZOa5=0>_dl+NwOUU?8Z*2t2S`xsLP%;dB z^;FN$zVXiW9Ll7z3LE1j5FZBRj;uEN;#+c#H^~NxY9=hnKP)XLz5^*0<{$<6ScrEr zrVf%gcouV(O|GUR!IeFjF8$9B|p9 z(AK7hu`_m9Zw(`SH&-m8^xbUtHJO~qo^|;SDp3kMF%648ISf+| z-nv2NGW38YY~g;f2jVWV=P{`ZE)1&Lr8ALyr%{4^#hJiv$>`hh!w+dmSWj(&39wJo)0o_4HM`cTTw!&^g|8P=uWVk zNdq%Tq{RD(BO=$*(C2k^#%xtFauiF|8a}5#ASV6&4kXX(z@RTL+CETt4^Q}_>nJew ztV+F56L!TT#87;&Nl;ul1=xe zjg;104krCe>2<6DWx&DLXxLjw(_VmXrC#Q)d>Z$K3;X?WDGX&swV~(D0hK!nRS-L? z=ECD#zy^?LfSCjlk})$HUAiU%#*74Xc}A>=3XlR>L-k(^VVJb#mIL*#rV~4)q!5A^ zsfichR@U)a(8^21Mk4#Nv!JQjCYm5sd%hnf>pe`+9s~?anFZSDn@jHEfJGvP(wX5E8d><0aYh@cb75r^|)c zkw#fuFgL-8JRX*5x&j(mFH=)j9#|T@UaFz3USusLKK=0Fh1UJH76CWmQ`(##ID_tR zKAgyl^sc8_J%sQ}t${WK$1^p;)~J(~RxsFB9nHv80vT^rSF4e#uo3^jY^wO8r+krN zs4lUit$?3hL-&-(n7OaOs{(4kq2mk?8#iod2cho@eGvWjdJMyJ%wn}v3S=0^xhtH; zdNgzxS=ttIIR((nFx6D?r5Sxs*z|jOFp@MtX0n^b5yYFN4jG8s0`7x^%gQ zG9#9!)fZL~3BpskYa0O}Z?hN;gvnjb`%}TiQOats{QC9htc#?*+r<7fI7|ExHK!vT zB=Vvd!gV{bYW^Jny$PsVSbSz%%QAqro2dRL7#ubUfhST}2^JRPvnKZLOKr-e%K&RYO$Eiol!H2Kl&Wo|;D>Y=8rq(}!s%*05eiSY~>fzX=k z5i?xJj^Gs8)p$HkT(ROz4aiw=omCDm3dwKwz*ZKh3PpEIYBFI+W)^xHHzBl2G+%wE zwfP_*CLo@%-_X@GBH42T6&nbtUQL8dN6pGuSENIVbLOA!4wrxh6(3`$3IXcL3{b($ zQ*Dq5O?r;5Xg}cM9(S`WoTOFg3+6ESW(0lxu)l_TGK{hK0e!$-n0b{Ketzxq2Upf> zmPa}q`By*+S&V}V_;tp;8{UQo>PT3DGormuG{moIA&P;GLpi^naoj@&(M2sLHdYg> z4Y6WR#}#Z9C>7n#Y&IX|*HZ?xSVX1gYVJpj{Q@>P7OZOuIPb}{gl-hpIE$2W7D_$t zgTs#K)vG15=LjaQKyG_Yu%Lmos=)~AtAP^Z4Au5!WGK-O|2D(Z^S~fI4Rrfec!X$j zZ%k`qqFRorF^L@eeOz2LMV_#Rw?Lf!#bjq874;L}-)&9-6Y$##K(N~K@^a`ot#kP7 zS?o2HAs?Yf9Ztsr6y7eHfmCbL<=p^B{n4orlpM%b2C~$nPuyQg^!!cG>dBe} zqZi*Ydt7O|sfCi!_W<}xGHmYLz1#8)no=myF8v5E6IHMorjKfE+O(i0vIRwfxjeFC zWCqw_4NUeg2uRPLIFWSl(4ozu$dI)l5ks$F>UDieRwn=a`IVm!i_|I!!Eb*aMhiY| zArC`~9w{E9C8Rb*$PV&!qIVxXfBummMel%Ss^NegVgXaq(M0lHGjv!Baio(WXg6BdAbw)C0JK~cS7=WW0qCEd(6H4qtj6>4!%(ME^<|D1 z|Did@JumY_4gmIiEi5YHDLKz3&HFp|pIC)E@mrLDo^11*JGTG|_#x(1e;XSm03P?$ z=ga?vnv`EuWJe%+8TkQFgs&EJ-oLQwB&-5| zudrN!_hJIw6I#aC@Mnt5H9#+y9tRW@iitK;*G%bM-#)Q#1Ua7vN9h5L^i-C4;I#JC zKfux}qwMVqC{l|V)+6aiZP6+6iG_jmmbHI~L!3cNMiYs6z&8R?6Ias!dVVXS)0#`f zI5S?b5Rpmh@E2gdyN)o9P(9=|f@!{{>3vHt4GGJLJLrC%Z){klp8?ShC%TbY29dCS zgM%@^?~s0=V&_K0Iy z2C;=k=wm{2ewSCbmYHWaaA#y{s{T9*#36#`p7*RDALoV^BuUPp8M{U9l*D4w(LEEv>`=J+h*4+xPrcZM3JP*Z5C*{u58^1m-U zfTw6ZW=t7uRR5!lxx>-er#N=cLzCWt8}cMAM<7%CdR%r$0ZB*&!uZ4{VHO?FQ#mDw zshs$?Vky!SIN#5)(^di+GeSF1j~KUt)ofX9(K>ISFw&33c184ElRP{|Zy&nUi5jpU zSX+`(OM$h}^-71(z7|$}6K~3F?sKb40XM;9whLE_tGW)ZWY5(YejwrLcMee=HaZCu zF3U#P3C9e@aqOtw2sL$iYGrB>cmoi{|d1b9}b0?CFGf>-0t5=6i8X}1x0Xn{}*Xy5I$0_`=_V!@Fif-^aLP0mSVJ2S6yBW`0ChLW%et!G9 zb97)}T0oO`aoKQ{fBZC8S_+=subCp}A~Ms);<5^otDm)Xa0OR;OEf8dt-zJBlWYu8NJs5S?2m<0u_7d&xNHWuAG)^eSpBOAk3C4hj?VHQt4vt<8%tkiz+ z0NJf7?S%Kb8d$V~g=a35MM>!+IYBSOn*zKz*{zp@C)oqvUo|`$@to=!2wNNp1`{&G=7mP<1#>TVa zpD#DmiEbkKK!#U;xIf(?Q>SzM+F)7bA*wF%NJ{28b{{zK7V5(`gBCuBV)HaMk5Msp z`jI(E&X~}|^GH?euO%Kl{R9{dX;V4qq4Fw;hFoYDP)aUxZ6@hFKMWT$;o9|^gqw0N}9@-K=pcx zeE5J9AxD6{6)~t)&^D##<=r4j_l|SbF+&tuXDFu&d3g%~J*v6j4HS)_a5M3nA{DL( zQ>7ewi9C0+iCEu8*PV*WnmdL~W;RkccBcE89vuP==AbQW0SY{a&+JQg6DE|r$%otv zu!qWry7C&}xeYc^1Gv>A8M3XYI1E*WOd2;4V1#3|@S3NS!QraGPIoPFC=Lg2eS+_7 zO*8>k=>7kzsR{myl^>RNS$w(g`J4H$CLbc5hKvx4zTmu;z1ut>$JVV{h2gO^?;8W; zE@pF>KdA(NG+%}QupZN&**kw<83`%Vm~c$C$oMX{1qO=OV*y1kw2o;`8{isy)~>tGgWp@wRk zU@XxzxPz}YN4l5JY;B3VS|F!6pZ@qIU27$q|4=r-?L!S27uJ_P4OC;oABA4u-K+$T zgd#L7EZZusjzi;1xCo}yWN2N4l=p_tYoQuEUv~cCkim|NkuN!M6WLO>MWVjCm3_Xr!$!+-U#qdHc53#mx!j}@p^9aT-_IwWbgTCOIv?svO_D^?r$DiyrbXZijhTtY4 z5X{x6C}Z$lIniH#Lp-zJ8#V<0aG%em01vUU@&}-auk@=oQY!?0DxkxPVA7opZ14Z~ z-_QAf^-=_rikZhB`@l)7r*E8%CgOVPz*thjjvz$aDz@02wX_Vk5BkgVZ(8?K7PPpQ z3}}GDfU%VkoSANPqQy8VvVwhA06@EhrdI+nU_V&;fjS-FLBtYnLx`yl{x$?f>Gf+L z>lS;_c7tC3P!9|???He-Wq6y2^o8WfOo=CNfPrwj$BY?^y1+6F;;(tp=~ZJ5_CJS^ zC~f0XZn}3M@?uJij9!I$xp94TwCKcxryuzYyUEVwDcn5x0XrF$#N*t+r8AV<31U*uNG+T(n%M3@*K>D-= z;(twVc>C6^c0`f(BJ|V@&{{H{SABW6=ND!NiOeo;+(MDK3(`;B1LaF4gIQLjytH3Y zGy&r*!d3GN@9Wa#%fH-#MBD-D{d>N^=dWMW3R>eE$!jl8Ys1aWgCoMjKcFh%AA15i z7s(m(q>Y zwJStzdjqVL5Sy{s?7U16NT#U`O+L$MvyhV|vTZId)9Z$Wb3JRCPfWh^M4v(mA3d5E`zIN}i)jmTSc#mX`+o-z37vev{;HP? zm}s!kRl{|XzUYMbQ)eO8I=_E^ORkdF#EYL_{@$dImYO;6^S0;9S1^86fX{s4bA01~ zcn*mh&|kwiz~A2xJ5^R9%pzmGjl6+?K2o}ib4KDVdM|R1JID^3T}Wz(u(ebF=Y;%C;~pPh8!){ z6m-YuA8loCEENDo;tr$a<@4t)grqjY#6%UUcfaGrHimG%adq6zscoVxsvy0f z6dr}INzr38F?hhV`!$E?@jY^Yo z)DU+=4$V5*0*u$a$g*>@uZL`G|Ko$Kwd)W@Wq02*zLo|Q*^Cw5E(AQqFZyG6Jy(q# z-^W}%X6-|F8s?^E-4gbWAoZNU6hy5xKme6^_*$WW;@*h_J1&I>bpV5fA^Rt)i?C|) z!C!x*Ui)@%|LfKFza85)QOCBW){5O733jyK9p$wsD(2j^>XJ{#*ZVG*)v+OJyW8u1 zZc8R^-dL@zHPL5cXmI8+*WEpgo__e;;qe9UBga4AyT17Gou>*>C~$-eP-}4M|5;p8 zqR*(mKGXCw=oF4U6L=Ylv6A|S%gE{Atvni>3UEIaEfo)d{t*5r1K(mSC_4$?rWhV_ zD$c^E*Zx{_Z1?W)^JomeL(=zSM;HYLkGP~Y_&|Z5BFCu`1w*zhS7SUelCWN8lh zDuX!`YqsbP&>1G^+H>zal8;irOq)WfdC8$DC*X$DD5LOlhN>L;DC7%7b*y>;(pCib zt$=ayYIsD1H<-a*{8|R{tG(xxNbthMep}x|>kOq<>ROVm&UNMw=xPZCZ92E!-ynmR zD1OcGWt0}KYozxquJ~UZf$vn=59=Z#+}O9v4mel=~) zUB2?+>|AQ!2I*=P6;rf(FjcJDRawd!ts8%Twvk#UY^tGt6^5rcFC#!7)4)t{-k3Xo zei2Ym5WC#^?c_hfjgFuItFgjt`ucSy8|rdOTn%6Uc|Kw6iG>fHz!>A8b=CFg*>eiM zj~H60kmK^Bns~_$Iy#}?d%7^bW=6K@8VCNTSh#4>e-{YdILAKzJl*3R1O$i00~<+_ z&T-Gb+Nr6WWpyUD5??>P>9h$_6zXzAzKh6A)7?xG`dn=70Oml*A7{au^@KTGOSw5q zAqWD^_rg=}ER!O(K*hdz{g*HWLD?)4T3KgHKP|irud?3vG~IOBfZT20cL<4BldL@$ zRGIrmXfJE%7!P@7XYo$nxpz+;vY09C4*If@0{qY+D_Exlz=yvGQxzGCI<A3UnkgYv*H}txaqF>@~#KGI>QXtQo=M8sMu&T7Lyf8klsp@tLZ+L zy_-~Q3~9K>Gl3lbOxH0IH0(Yg?0v4jb<;`q96(1&lM=?``vO~_@z;;XzYs{}fL-mh zL-cnV?({qq|FcuG`bx|)ZW3XRiQN-7ZK`Be{FgD#1QMGPwBZ48DLUYx=?NxdyNqi^a%U4Nl-!(UNrbiOq0Ij)A{wIkdC5%C46E4AFhNYo( zr&xfgbR4?Gl<@Vt@H9t2s^OSS06U#ZL%Eh7?i~o8_$quO1}K4k+{DO8i8?#{cYIYP zkQdsN9RnX0kk#NEE+R+Ti{%qrKqGG3^++!~_!WjwaFG!karvGQjX5fewkqBS4jq~Z z8P=c8sgx#5;X(ny&Xf&?EnDBjpkz;vFcEgQPKOSc*h{3y5)ot}nw#)uTT;Gw(`{Cb*a`FS z31$Bt+{ZJ4Pn|wcLL3o)t%kAyWn}88^?Dg?G&FRuZhjfZN4wQ?bo%PxxaW(+>2Ko8c0F8qMzAxO@VgM&jR14$^H zB^7QulN9HYE<*xUoW_k?N9C*sq(P3?>C39~;vM=4kiI|e(aO5^bF(Kk0Mf7XcK^%< zTH1FEKL=S@=#a~pG#KqY<^)^zA2=|R?T_rR8~Hr(@Zh}Rbo7K^ZUFe=`|NE{a;4wv z!D&~6cou2fw{O5-({&gmq@Z|)$rwzkEAF6 z$cVO--AEz7jU7)_)I{UpWAQPYC@#zS$Qc-Et(C0+g0Qy8=8RXIdnE1oGFlnajhCA7 zeI(O7S;g7$;*CLRDUq!vjLTns?HrdqvG+@<-(1h>gc|o^@c;v4Gp4(A1nqQml!0>6 zS==DG#AT&GtQx?g)M!7069@?952eu4dQ!jG?<2 zttg=fz<<=TUHkSo=n>na?SN9$dG5l6%T|GnNaz_>VlUa63_+Lp3Fj)1eevpP&8pQr zsL>0u#Witpg>qWJ3(>^zS;KepFUNYBTBso^^{*7_Jf{8G41l6^YKEdIc4W8r;J25+ zX?CHijovwQ>6Lx}uH&I9SPmXMfg8z-skee62R)#g*QH3srh@WO&mFS>5|tzR!v24} zaWBQYwGgvb&d<)%$P)9Ii$xi11U96h2%g4%dVqh8wU|Wr!?f)NguBnInzpSaoZ`T~ zeNmv;!EocQqIZji7BPxZ%mGEJbze@v-z*XTVOuf*^}}emowR-7n1mHk3W-=#;GH@) z*<1-)8dz#Y4*!wn!v%+;|lmFC+@GG+L1B?jBQ^%6!3{xpaE=-Z?vqd(MiWpnv5`? zt>Z3BhT)h=@%hf?!j*6=M&;e)kz1oqAoY6`J!KSq*lBV`Uw9-vW%Kv|{6QVgIx=Ww zjiMmnPd%3^TjYU2x5xwIyW++V1ha7p?H#dMpAmFtp%J`*i91poxUEsaFcE#t^@3Pf zV{6rpocc8d)FeNJVm!Dmt+EXdwbo_F@YDZso0b9{q_DC5$g)0!R&hF)1%R)R#l8Zx zia~+}hjbK(UH(RT9*MOz1C;Yu8?I%y3NxA~%J>1x2mLR;N8b z*nyu_YNl$Z1;(9oN_85M*xxSt1R&Q;U?)Ia~+M`5-9Gk97H%E8kduT+?nRkF4r_P%s(fWHqMYHeOOL~^65{vS=}0oL>0|Nk#S2%$rEB?%!)LTFNiiaV81 zNy=;?gmxrp7$vlb2qk-#7K#=T9apu65e7~R1d%RxH_0l|V zrplN)X+-1?`0d_kSB0*9 zE$(s;IG1`%iJnMTgb}%Z4*~~FS`O>zQ`>1V52GUA1|-=*NWUp)%xb3Sec)Uz{Y9XW zV{=s&b#~;NH!H<7j%LEDpA!9jZ_l6EV&ErYIJZQ|(hndwH@Pk<*Ijkzem5m>mB5er zrNoW5FeV&u;u{F{DWqG8SfC)27!lr;(197hKE0kMvMU}?Gi@Rb6y};yTSYqTce34k zFTdj4Pp*G|{f}?&j~q!X%7lUY(?J~e&p)kCBurFn><4w{;`n>y5-j0xzC%eMLIIvZ z3Z46ZUJ2x=<4deMFpWssz_dLZaNpMNY(}l@56nbTdaAYM6)*| zhaPaGliz%d&_g5i)uQq5ATqo0*s){IczPM45lc!IrMgK!smbocf>+DuQMroK|B9|c zRo|gnGR$z*s3XCu0lmcz|i%P^u9WH9f=GZL9b@qT!a)~alY*wv02kIsbcr zr)p-lRS=idjvE0Z578o}nVGs#h;<067y!>bT-{FM&ZfDIH~?&~Nm05+gcna#Reji> zX>$+`B18?NsMIf+&;+hiLRZ`@#vR@?Hz_^o%9UqhCr?g4nqS*O%M#k;IpYhGu43Bd z9cN5qO8ZKxC|{QV2-1p$5TH-HbNK7GwzYZduC{XKg;ILaiEW!!tch>Cr}6B#&7kMA z{u|)ithS2JuviJu$R)1JNg2#9WbWpve{gxjACbZf@~BHUECl%Ol=))V9N1tFuI9gb_w9R~ zF>5(Lk@0w8X%}vLFOHEU&WKGIi}Hvyu>0L3#qHqUy^Rz2o8ok~KbXkXEVu`H{UBPy zmt=Qj140<_jdsvoV!GWILxA+W$W!ZRmFEyG)pOVPb`v0hWgkQsE4L5o8Ao2;ff2Kg zeMgFrg8Y0OdM~r{&yNtKoem8mC-H$ncuKZtW47Dza~Cf<@f^$vEYdZX6RP(EBqXCR zbnGN)q`bMo19}gB^%%rU0F}Q7MBo|M^(iokS#XU( zU~EtAkDP5bt zc_TMk22WD@27=m9DWaKsJLvhdXW@uOD#gjpiCN5uk$zHHzq!o~nOwLHducU-UoWVF zbeUn=;hHg~X$+kfh!;Mdorto#O6duv7*yMWvb~y{b0-uv@sHpi^Dgpz zhmx2k(7A`WRY$eAw(1Md@lYKvb+Q`nzR~mVrig&iX33Il#xVq<{HfBZT<0kABUwq8in&@C?8*MmWXENb zdtNv|#ncB?;yiH_ws0`J@s4-FkNxF)$Pv zl+Fk3lB<@5)pbXdY%bB3$Cok=n_H(V2@qaeZ+5d;X*1#c$AJN~T#@~3K0 zQo^isk@x@~JQkSYzK{?<+KZPtxzs@0JGXfZ+oEw>PA-|s8rj%~Af$aiJ`bPnUsD?#TGDWA#Yp0%7%q-`Yp6YhQ>Hku{oKZUl1*bHF`iTbjM^A~7FQ(J~}4jkfD3$FUOCLyzReaeWwg z(7{Dik7eWvw*m8&y8}lxAdUP~Q=_kDb46NYDi9Hy&3W_+?_E1z@c~DqCk+NOUjxMq zVyc*<(fPFzl%s0}M#ieiH;Md7TRn&AI`yEje?xZ4>L7b0hjIgAvL+hD4F(2X2sPM} zQ&e;irB)ZABHYtl<(pepHUJ|)1J@qt$4+1L!9@)9#$jTSInPx1%s+;YyGcXzrLJ< zT}(^HrvDOP^}jjDx3^CC$de-mqrZ{)y}3!9z*H?SSS|p=SjaUw0ra_;_9_Mj*q<7E z3CGx`Nt=iM*8D#$071;LXsoS}MljiZnIpJH4zNlIpT>bp@&+-fVu%?qwKekbV${)M z_J}D=`$?>n{iO7Sc*0axYCAIM+bCa$kfU<+_;I$l6dQ z59D)Vvim8^^Lmtef1%OBpfV8rVFTdMsxxJ7h}xC0j{pGhm!5c1q=8Pfj=$tP8Jt2R zaaKRUu7{s4jr?NzP+zVR>b5LkN{q{Hu-@GzW`H`os8*dorFRmqvW&FNkMG_Ef~492 zwj!bL#6CT#5&MK0OeuG>?9`jRbgp2v5xfQesY2@LrB)Ehco%XQWntRq*{oc-sg2@e zHD7~s#9Dn*rFIhnj{_7cS2HsF+4K7BKY!}L_b3v+0HQ{c*7FNE!R^SJeY6uN(Vo64 zE|#>mw(dnMXw5yM2NBW68ewvx)nu8~g4z_zmYmX8TgY`cva_@M3S5~>pQ%LMzJU7` z-ua?sSgj-N29!Vrg=hC5+kGd}3ejJ?3nsi_s=n@f1WpIE^6oU*%ScQ8%uicBquze94~_8A;@N zE-Jh7nZGu1jl@nB?9eDxCUD*~?Ek&)ITKwupC2C4*8il}?^ z@Pg2M)X-+m^=? zQ|VD66GssOE99y!vqWb^Utx+Rzw|Nxj}`MOAU{bgS6avO{Rr^U!S3G?sXQGJh}<2K z2TKC-GqI0J2g^e2qDW0>L<)whEojWjafdC0evFC2>bJLkQo7E@ zMEoCsE&)pu2o|}?X)4SC{!I7FL(Q26s*}c~N9IMZh>nP`?<7e>3R+3)5Y8%BL0b%f zSH0qvXAdgNaLjZ**q93ok4^3M``yu1@sys@3ssho0X9Cnp=~>bytkZ@H3C|KJ^sdP zam|W+Cl5*~XQU!OiLWr@#~laD!9Mmwv=Q`4Xeu5gJRfMQUdMv9py-d-~z9S=oE z8^g=%&p6SCW(DD{#wDApj}fQMtfCt=M#VG?7>3%y5KTU3xD_|mH*y*>0Xes%o;&yc zHJLNFw|uY^z|J=?y6s{-6`GqwKEMht-Zz*90lC7(IqpGVsfblKro0iWJA}eC%64LR zWUWdRuXWc*MoGeX`8Sm#GaNQDnS!4bCNw5oDfVKn1oKMa1x9mibii#W#}&^ja;REyd+TSAp;qJsnLy-L2j(U{B(xOPV<%@& ziAXMz51Ndkj>XSe0O)e%{fZbzlFHonuDo=HcUkiGZ4u(Ee8l6;$im$JzO_Y%%bDDu z!nbeZR**LInk)=-rb~c{G`Y#2I%`_cr7eL>edEQtX#gmWW}%$(`t$p)u}V~ye9&ON zWls^Vg$K;VZQHg%7}gTZRIiY&6@mld@ZY@djeO!Q47nLk{mJcG`-^W=Oe0hxu2?qU z0M54WoJWTYl{z7#G6cgh2}$$<9C^wt6!6Ok%92vbEz3-GOw`V4@3fz)U^HY^g|nl- zQGckT#6tGk73TFTru154`q~BNa!rh)CW-kpPc#$YP6QFwP&O9BzbGDAqj-1arGFua zuxa>W2{#2^-?2Z}4*;YG&|M&N?MDGVDG`Qqda`}=WMk2U#F00Mgtqrl5}=oP0!s zXiG?f3_rM@HQ5h$7K4A`^+3y=5b)&!o=sch=$IL$vQWRzFzrl1-SW%Vx%2%gr-KsD zcF@#X&W`I#7q2h;mfDjh6)}@J;{$(`Imja=5+6{%i(p%^SjpSCAMRoaYmF>>u?}Ru(N;O2V z+T}b4Jyr}*+q2-Hpa;q(iL6|Y)RdGg>XX--HHRz|^erT%c!=1sv#DSxDYR9YVtO_u zr4!{fQKK5IxE?Nx5VR`<#{1Q3qi!9r1%n(>7jq+(tK)twBU>}%7bu4v*EwkY&^l7e zF^;eX+#!^_7(z3M$Qg-5RU&cSx;2Ppy9f&67zEAY4R7zwvQ03%!f{i+8hrXLSp~wO z-9r6Sj@cM&qXc>-5U0{(IG9d^k$`XHbI`s7S&v32{)*1i75d=^$aVp5`#ZiE2?vXf ztW*LZtJ77RCiKoxsO(xmKtw}<0Nd!;6DNnNOkW{rUa@jzpT3B~fDlr+8F$;GEZ7du zJ&@5fh>wZT>>+|HR?=L*%2<0)4ZI2$D$BW=lRtYW^K*sBdKFxJRzq8FMv@C7=IREp zlW&MLUeYpbip%mo%h41QBy-=hTe))`!qE=J#r>N`C{6*` zpbtnrK9ClAgK^~e_wLa{Pdls(zp)wfgx2rjycG#qJrLtrvYcSTjLF|)XDLzTv3BbD z6kp(aCgZzVPKEG;?37ZrWN#T6Ww&A1N!;liWG{u-u@;>_A7<~qebrzqH#wx`vej+4 zCIGFD1A2nE3p0@~z1(@YCfcW7+3J?eV?&Ym6|6jo&g>y^jAtQjCQE!k^Nq+EU&%;E zG{7(~1q_{K4Elwepy0zP6f=;Hfz(aoTMQM*_%PHwtk@^G7H#1lI4WL=!S3#qF20mu z44_&Qgf_7at^W-aGH-aY2S_Xxg1k%sMrC9GAm#d03f5O`7DAaUNBEzJV_`G{rq#-L z@GaZ|HdyyI6A7x-4~++>ZvsoT{miff0RNspxHu+VMv;#uTR1eExKRxNK}2cMPs$g6 zPgn9=W)OmZ`u6SR>HO${BS*#|p4@!p#tnIjfHRB={fLuoAt|=UqoS7l5$ah0Xh7+tjcnQHK$nL@VE0LkYX2^ z?fWnQLf~YX^?2O*Jo2=Q0YOZ!gRnTu0kV7>)+zG+f;e2%Vu|5r19!#Qn@){rMvX`u zUydy!EJWg2G!Krc>n*bRZk9ZOL{b;X|4wk=G%e@r#de5OlLGqz1wc7cK$(2EZhEqc zVYMIGKZw)qK)4oD`V$p+^-qI=KQUvMkwp*!S&U>=jbLlm-8*7js+8n8rcX zmz7GfNa;Y~v3V`%q%s+9R)l>1s2CwZBJvc}E)Oqx=ZuO%QP@X-kT-qaQJI~c^oxe+ zij~@zI_--ScrIdYGM3b237sI+JqO73(_q!70v)%e1Bdyf47fe0Z89WOkyq zKQAQ5TU}ix5N0X}2@8dJN#sH9g*U;C_Xyn~8mGTV$O&7?z)^U%A|xjbqCi-?U1tQmjtaczy)B?A&V(i0 zUY*~EP4E(!N*-XPiT8P#!lDr8Mm#%TfggVywJpub0-&QW$mPP2&50m|3c8~fgi)na zk@A_IDcgtYtci`!kl_n{@KNWR+uDAVl$PpaX+_!%rhRh^x}TjW7*K&40feS6G&XJ& zK7`I)y4W+Igh#y#AZI2i+{mmWKLbZEqOsjYQ{lLI^Cc0AB5cWNXdh;vXCZRjj2^T* z;a998cN}4fY^V{?=O`s=K?5ET7d(y&cPG$QJV#6xq=h*S?=8%v!GA#QuixgSbC2ag zwCw(;vC|F;J^=ctimuTfkNO&sw{JLit~;W;MiXOWJ1m_Ma3YITqS66r7a?jApx`-< z6WDg0%2%%*Gy3(ghl4}nBPP?^(kwo!sMyy@5)BB+Y>^OKa0W4x`vcqhG#fRPT2GD1 zyLfkUHfUwAtZ!hYoIYd5R+eVL7O=!4XcZn&9z7Bv2psu8`S(%O9@MwNxP)8zzn^v` zZ3_%R=E4D*3rA^(t)`uT2AdTV z7k7|eVhZZWQ$U>C$S46NnDhhEy%j%51BR{7SelOPY^~e3kfN@PTz1AdOy0n9oWPAV z7s6-THy|-FuUMLrmE&x4Jx#qj2(AuktLSvcx}pxhfLf>%XETe%Kom!~+*G;RORy3n zDa*+Z9h?m%->Tfd2dB$x4G_h4F7|7jzu&n*%e2F~P@H_E{0V`O@&+24%X=^3e@CFh ziiRzlKMLu1##a(YkR)W_5Km1`&ePJ?j=tqq8}y75Fm8?Y4BDAhxWVZY-j_LR)=<;q zp^L?k7YRh}2?2q{yeGbhDjKG0w2YCD97VRc7!`Ac_(-18WN5U0OjtgZ7dE_ChT3|J z6}1f4fU-J>q67S%giqlSGA?Y__(?D;%d(vn;*oDl8JrbSYFi zPR`oN`WUAU7Q6fI%70&0R+_3=rRgpRkln zp@|VpuRa+meh5pKl>r%;(4U_KY9oZv8stb@J}82HIg? zI(oQ4%YgVY)MLfM(YVKw44B|PlR?KZ=+RThP;BPX$cWZK*z$go{px`k4l<|rdVF4N z?V{dP*B+jpQn1`rEnYfU+(o`u7YWAOHKrJS)@a4{LkWqL!p4ixvvuUR@BtN3pUFer zp^0b!H0tS&9Ghx)WCpIPTxzrf?3ZE+^Z$gUXvu98Em9hJ64`jk@XQ(ldhf*2Xo#Hg zBnsP+M&^i@B+D!;{22DVm!ffo1|n}p{~5?)b0Bc5yQG=waSPR>j6^a5(WMTIN!369 z^zqUeF-b)wIbS5WcC?HdBP%Ts*GzSp4GbV;@QNYqtQ%Wxu9Tj7)FUZ~FvRq$6oi4~QI2E^mkXJN>Gkj4`LeVed1jf;nysk%UD+~6 z)>wO!2h5OJHHj{3ON4rqa+(GM`Ey=SD>scYr4*Fp~LK)SLnjs6Y=0 z1nX}nqA`x{;euJDAgcgP$K1k&sPSd+2lI`wO&|INCO8;XBvCE)qUqbkvS=Wfn0_W5 zr%B+a4<7~)?egWwp+m*6R2^av5JDR*kvM{bXBhXI!rzsZV(Q(He7EM>{J!oSeVm5g znEmvHCz+qzNn}P=adK^7a5f41F5K4KLpw9`^74k!9=3k-{=mY{BCyq2N-r8aRR!|r zYUTy!u!|}5xHZo=H3w3A{zP^bW;>P zRZnYlujzE%vRDAasEs*K@%K;lg=kwXn0Jof2?W$id``l@RCJbvgUnhxI5`Av6zR-=Y0sQF z3gFjx?b0q}gTe9KgGzl!dP47Gy&ZGs7)e6Mt2^T_( zy3A@)vmO;Zd2%(?_GW)x==rw*Q``%0pF#>ECK$pnoq|^Oax9xTfQ77#yuBF_aiK%H zEMKw`7^GUbnBGA zz!kK-BBSg9e8Mk;4}+;SSPOq~nk8dPZNFrl5wx5_v7xDH9|6_JfE(Q~+cbcUh%B=U zxMx7F<`z7DTtynYo0Og~8$CoDEis8SCFuSohrcE;g*(#cmm*{}xYp}7k@PSzbGecM zQv#`LSHHk{SL&=8#;#dOA-hl|st2(+7)pPNfMd`dT#~!iiS~gBZK9+1;A@->4(@&h ziZRf3;=c$qJ_)Do{4&j-DA%3A$W^5MyhL^a_rhVk*z4dfFRWl;u@$KlE-0b4b4N?9 z058FFI?Zsmvpn|B5}!sgnaa7P-k|63dA=@?m$eX4Z!2N2Uzn+daYTs=v*vY0g)i#I zu7r)Wx70Fs`_7+ZGv0RldECSO@*_!P+On5=mJ&VP;yIZH5`q7IcFaWuY))b@Lm9V#&(d+Z6eV_2 zdO6%RLDkjvju0eWcz9>%h=$^y=?2i=%lX(f$P#~=r^wGK+f2!eEa4BgQxWe3tr zW?hZ`($MN8zmA`N-= z8Eh<9!~t8{+NzOCH;R)4JbVE+OaWvuQ8|0@CT&7W#^AaIj7@<-QqRxNucd#;Xlrh! z2lvBhN9`&tiSne{a(uTUz1avN8VXkY5gv=_jab>9 zjlG9GYCV2@Q<^DwPR9@E{2o?m_hERVKZ-UBn3F=`HDfD%i96AS4Ou535`G(^yYg~n=3w!K7r<&5%Ng~6vb{&wl+SvcGHu!*I*ojMU@+6c zf_R`*k^cZ)kpNuxksG0!F-KOg$S%MQ1%MRkAix$JiDIsHCZF}cq_pQ16@zW=@4hQ8 z==;FLR_KXPn~a5+pnB{FM^Q(8{Sg$;isv$&@4<|~2w?M(=@}UY$b>H7YDT?S3rNCk z#1y#z48Wh%PtM(o)d`4yFT zIr-{lB1q&(+yP6SsGy*`ST=y|`xh~8X<)FK*j;ad1~ee!iZPd)N~r5$4JMmWmAq#w zEJd_K+teh$hE$Q`I-C4gMrcN(bqULL$fC+H zLX-ukc4OBMtu~V$GJ&x9lx=6&XiePSPic+IprQuMYu_>J=eLHq4Bp=va0sG8qOK6S zW_fAl#@g!YgrOrwI0>;vh`szyx*bPu^fKPjEPC=@BHDN>a*IdmGN+!eJ0Oub&_~?y z_3b1QBahKXG3LrxV0b|a(xeGL<^`TDYYHXkDwhv;7CJi}85r^6ek~mOLN1WOvKt~% zFMj#*tQGW-6|j^QmZs8@68jNDhh`(H5e>##hFh)X7IjC59#Zgb3e2$`wN)O!G=P%2 z9!~vQiL;bMLJpvHPPMBsK;<9)R%hIPi)RCoG&yN?MvaQ&R$&ek6quG6y)In(yQbLTbRla+NQP zmolXN>sJk=Z%nv5lT%Qzo{B-joEx-+FRA72!7f_)(O)Pk$%1#7;a>MpDrM=6Mq0IL z>gp1Dp&FzD4#I1N!9z3A@_ZAoJ+k0ktMIhmMfPKCYMO(tcQ|9w>7xdd`fePOs12g3 zOzSW&0H{Z2#9{zrSK2X-smlbevtLUnf(?Wnl7}UzJS9{;Czg&|gIQ7+!QUSUib59s zKBxcz6N^1xa`M)T#gGv5Qz{Xpvyl`9GkY^Uxr@0cRU5FK>sarI3ucEHQKjnP+i=L7~82 z_ArXNoZGvaZ>0fwTnvsca*2jR+ApMj)$qo*a}tRiUUF#3I2Ue#RPMedHWvm0-z#0t z$S@-O@$8Ea>GvTYL_)>yXNti*&1imN5^;ie<3d-O5}|sL%;g1TS7H)XV*t&*J(>14 zjcR4FNX&o$_hXxyif1RMCtKdq;^svGUr1Pv6+-!Utb$FT9rC167~sm~P@Lh}7`}lf z-4d)-pCOi@YC#M&c|bbD7q({$i%n5jhf(7ucF->r3e5xo%k&8P$W4xwHhBu)@I&r~ zXAbG{c%sedEUpt}f~TpFvn2w!xJ)(V6>g_m?vzKYTyqbP3`O)%PYMeS#mT|)M_}E8 z3D-GL-+muvQ*Ilh?p?cPp-j2XHoZ$?<7m{$Xj^89_If8L6zA0x0f{3Lct`k-6^@7} z=~imeVYP*u@Bf;Mj!lEM#tZ!${~FBnfhc5jd(osMmN?6!Bt$9sopV_v8{DB;@P)v7 z#TPGSC}$XJ7A?BDOg4vt-ZS*rd#Mr7u;d~_-HWnA2#8${&kqEP?8oY4Bu6%bn5tdcz43*%OwHx+;Esx&y1raKtkzaO=!y62k!UzN1HA)- zU*`9Oa0#EKDw{=OJ31!_-q+Og{)Zkp2H>uuCQ-yU&VHK=7;um<$ZE)ME0w4ca5ytA zk^eD^3OT?R3e!fw=5p};W+WdHDLp4(M_Fpz88c^|01P^d(s&Vt2v@;fvS|zUu2r%8N_m+4y|9$hV9LaQGH~3J_YX{^SOc`MBO5c4H*Iy_P^!J z_v1K}6SF|YIS21BDmc5EK*;&j!fAku){IZRxO7|z!es4PvrZtDZO_b52_n!a&Rmn=YaecpQf#B7AXDG7Hj&R#%tCtOKo`h-A+Z zBLIavO{PbW1Y$cG&^MuvM6!dKH#^{>8UfL7aoKgKj2Ux~e>C{=hj?HMRHCdA*sCF{ z>I#}9G*X7_#j6myiZtB-8*1vx%BOe@Xn7CB7Jo*eEa~Aj;*V_TLS;#$W|M1FgT-P!`4-+ zMjpXF0DQ-7s0ciMlRK!Kb>@#dN*$cF4#DUb?&Caazi^PXL~?_75G&e>Yt%|ibKu}R zi_n|B8DOf?1tGj2a`{k+%V|npSbsW6a)=kQ!7PD4+HzNc#~4gw^sKliaSRrkYaD+B0-^6qm}z?%lgT zq5N=%H5st5Y@+SN7tPO?t%~W^z54|ghxf#z`~SCW+0N6+$v`k4on^A4A?zF|7V80t z8B|)$F(A2W)B87eQg`ZdSB^^Vc|&{o`ij@DA!?Q*@F)KFGWdxXnq^OB$lKsRe(NigD5Y}&dv_NY>oVD3UAtxUbd7fL?XdG8p`k`ZJFM^V<72$EK8@3Y}h*`+v{(&CpBX8&-5X}>c_-6R)^ndUD2-{MV(_>%+^n75=!ctW_To`6raxb|=GnT>(ad0$NLq;=pJfv12S3pVUtbzOW9@a)pKG{A zlX0b?V?Mj`QUeo%y7F))1Hx!Bm+|!mw$X-7n~nfEY0P0D=1j&jYa_Oq#}d{Vagdqh z#Ns$WtNC+g^a2>F;4bI_!MMT2#m#xsCN1Kpp4%>2l0>0_H1^(RFRu<=N>Grg;LZ|x zt=QfY$w!X(XE>2K9{=|G*1Cr4*PItv$YOt|9p+Dm$w*_tBB$g!42}bq{kd;U0>`Wi zs!}GHgVt;FD09T%I zYQa2umYNp1z#8JV?q5BBzF-%xZ8b7mfN?@65|NlcnZIa}6OUFN6v1Tbv}sW&I;;NB zJ(SZ_KXlfN6l2~$aKn9}rUEgk@8J1HfLBN0D3Azj72rFEc=Q2-2CaS9;&q2&Q60|s zh^fjbWOR0->-2zg`wZ%y!Ck!fukn_d{3vyK8CNV5TL{P~L3`QE7VZVi55eQIc5S|3 zB1eNGEi*T_-_=~z=?+w14ONaQP9TucMf8qYoMMH1Xhm+5x^@EdYH6+aLPgDo_zR`B zn|TL4IIhyugmK8S>3bxp_JrcSKv-lQH=8v#TL@83w9M&f9QLxgPKNB=tAHx!6G!wy z%9zd+((CcXFfqmxj~RN-zLCbt1DW^@X>CuXwH^=0pnSLWOP3yUVIswMq+{AMW(?t^ zP5}K<<>dHHXE~bTlTkP0<0S-n^)i#Y%D}2=gh74diF{kN>+@eR$BsQldtuK_*`E_7 zk53pAr)5+x1>L}0tRFkcOa5tfv>D{acRNL_wQ}@cRll+HT9uK>pFh?IEXT> z0(R6ITp$bilO12+6sM**;giams<@slhV==C>igEntXa`SL6A#fF=4v+bCBO?WfKLt zo}xfTjJa?RHJ~*C`DdxRMEp-HqLKH6K|F0}O90tRL^pm4d70g91Wxws4#l{lX((Sj zITTVM*cBn}+o?tGf_+pFxhVx*jf*K>QQ852WZ#W&tgp$L6SGY8DO*Gc*57a6{;Mk+ zfZSA0+@2gOlOZrfPM7w>nRtrh2qK+&@>>3ad5KSDj9hqI#kT$Q7M0z_r z@0~hjiXXV~2qZ&BG~sJVQ7;Y%TbV7;*7aPfO{R!Ku1b14dv-~r1pa-NOhP~=+1mKAl?*8?)UWJ z#Z;}Is13GL?v>M5u0!+xP%Ktxi?v_Be5v8e<;i}sLW{~(FY*@-aZ~k4XY@@v75H{S z0q;%oy73oel*LnTzR!57CWw8 zYk>SMh&HVUhF3lEOIcN7)=eEJH5Las@u6L{hn4ecbLp2n*~ZLB-)KVtHXaR#P{B5W ztKYy^BZCK3pC?9s6cQJyfznc9B=X_x>P(4bqDkZp!ue+*_2=VPbC2)iG4w>;c8Nq) zGxSjDWOyJ)JBYA2jIK1d+SQ5{_l$gy9&ES9>}~(p;)vLX{g(Oi)*`wQ8ypBz_%}jd z*xnh$!^*{YzDST%hK zJhPxhrsfy3G)iz%Ki;4f1y8$!KW!i@CCB8ap_B!n?Rk{&<$U%4IyI&dCKL2JtcnSU z1Eloo>+3@>c!mn=DiahcFT=OJvQy8b<09!b7?^4l_ijC6qh^Bm)lf#Z(gT?j9;ZBM z(&j=!=OYT<<#2#qv`7*9PNJye;q$jsKr+YvESPJS?Zl&$LJkB7_5-gRv+|CY9&_ij z(Pri0a}x;>zAG<{!KDE~E}vj#3_fFHCb#BTY-|jl`2k3NXT)L8P~h1LV?!+3^oY2) zNq1eV{l4*1gq&}~l$cb2l2hpEsJd0PVh;de#r8!7kJ#K1IREb%vu8hDG=IJlFQ)bn zt#TvW^8Y_l_wUa}&d`MLa&87D3<&&5qOgUF#e%Dcfjj37V+Drx)!fr6g31@p9RP?6Go-w zAWRsS=5>;U)2ftF*Ds}8G+(+@iU8zrV1sf(ghCahIqaVBYSI|`sX~P{De95>vE*ve zWJ7XWjytTIvXm62Oqpym5d?@WAHzgn1~DnLwY6ywA)jWTuOGlMn8OkGh@K?XOtqQg z`zBh-zQ||~0txk+^W&q&eyVV6S(+=&%{w7V@B=5COorDy{#gtUnchiqgdL+T<_qU- zzV|KY8~%`%#d#VlQJF;?KOP#PypPf3zDVH1`M5qXUWM35lDOtg&Lk(-U>Ql;aQ}ABdjtfl z$-WViYaZllZh}(C=u9MtQHVR>80_(4%A%hdB^EI?Ndo)E4mW`n;!GV>9V3G5X>l*} zhnK+EihvZ6I>Y6m2dvgk5}g_Q0B4m&CPxYf#SvrWa=6X)C;_?~nG3}QD&Ir&&or*9 zLra{6yutu8ze63}b~NvDI_Ytx_{o{=_|s_rzyk)dhC+Zt7n{f2pic10|0u~ycW`=V zV`lrLwfRjdgjygWdsd{dGB17tS!^Z~kB+Z$)fVHW)OB=Zu3x(rw!0;FP%7tYEc6Zy zMmiXOBS(D#t5w)AkO5OZ5w8LoOlBdVbWx=VZj6q!@&K+S^ZK>Gai_ingB z|4y^-qF@q9B4IVDX$F+OM}(S@x~LMJEeW^bpfM@n3G6uiOD_z_9=L<@OKs}Zzr<`1 z)B-oCdxigV0EG~Ncbx1`NsL0MKw3%zBv_OqxNE^jrN}5~ox9e zMRHpUNOI3q5AD;puLg^rx^pRRS4~>4iLp;cxNO{L1OPN<&YZYK#>PI#)e;D58!3d) zNY*X?X>2^(0Ov`ox<(0?rNRsL3Y&A(-sF--t&(6S(np*&9xq_<7M{FV9YZ1 zq9_1AlI3+#lLxYAy3%p!5hxP3>&xI4tWrl1f^*G<)9^e1&b_|7=V@J59+O;DI!UBL z5})zuVxWVTa}El`yOhiQ7buOJOt!+rqrLtmb&m7x0%0^FKaSx``3;3JtFL=pjls}TSsgHWtPv~>q3Suf#T{f4IfES0c6UBXAYNi#7ZZJVcO znoPGY07^7ALZBlG22Rjt$;62fq)_OT@d$EhIw>s=DUYK{s3C9BoLx*!Rp#V3_3{(oOZ1A~NChe{twpge013E;N9_z!Zve z<#8fMjSSuL5n><31-b7AM+U^sOffD|q;hoDla*yCBz=Y!bN}|5E?g)eA!l>*h+kMq zO_?7hsG!Tl$*-WU8v|jRihB@@ADXKx-fiIB*Kc-dLDHPhrvtW?z}p zt2c5y``xhJ^k;3{E+?>DA7v9uSJyN}bl-E$L-B}1%-yiXm2%r+73@hm_HpB2Ha|5lU zq0}q8G8LDvth~H)3gtO3|Fqh`1=#O%S}-Uua6k@Ci67`6Q@J{G!(uk|5o6|vXkt;TO980%p3u^ zSoq@k^T{Bajr@RcYFi>wqFdYBl_?@zcC!1|b2;op^QPvfkjeLwqDpY2LU3AffA=g8 z2)tJ4@&D#IYA#$tDFDDIgxq8Ba39L;Hjz0?pyr@Iz(m0*wEgM-svc_Mka>Tc5w*;olj3=a*7zi6~8woCnwNi^0!;P zbdU>cdeW3T0|X!*?MzsbFHqceF0ZAy-o_5x|4kx!ke?sy@w3BiL`Wi0F4E5t2?}i< zRbK{1Q1L6cTe4?!a0uXq*rj>&nqqd{I^HK&qz@@F5dcOufGvSYa&t&1x+!N=J*-c6 z+WFNCXH*<=pj#XB)hYa4*hp8>(!#~$wN2{sRqP|^E8pLEbatUE;1F{vMCAYL=T9&B zAUD@4CBa*ObIU+`KNfW$w0aNFYGvk4Avx0;E+-d2#zRl!KpTjWV}S%?G9<|_fN5^) z90&w0r0d;?Bo4?ZO{CHgUSI+9RxYLk2vG8DYN{jOHw79}&_TrI92IdW$NrGMM5{5_ zA)`Wgx z)8RR(8@u1(%<0nt8*div>)JJICJ)@-6$wDv60l-7j8XPWmPCESjbBC6gYEs@!_lz*5|S+3VvVRqe+lSaj|Bsp=>_*G(vV|xL_Z$?423Qphb1&%r%m1dHw=$GU7%Nd}>TdmCZuQCW_M- zGQx~#^A)7|66)P{?_2%Q^&nQ7%mr!Yz;U1y?+Kb--S({tSA)3qU7?$;7`}~XA(OyP zc4$0uPC~Fw!oU)=<_oABn`C*OfHwoK40~tiM)lxqdtT=X0NhL<1G4zdP;wFPD49vR z{AW;MdrggGIUBzDDe?>KtSO0QX;K`xQk_bpyJNnzyG#|SVOc^ zL%~0XpC^KTT>pL10(OP84iq6tv{(`BErK5GX@fF&c$=|KhAX?cZQZIOku-9j%5Z0X zEGaSf%dgd=eJvDYN*TD7V38ucUP?4AObM?cq{0DUOU#uM6|9gVX0t|)aH(d4-_2z$ zzf>DofPC-~Q_(w}oU*287m32XAOFl$nlCO>SHv9 ztuu{O)>xe82kNECnu~PB6fIy9v|`=JJwFx2wUb3uTNx=Eo1C3}pd$=!-@Iu~`FI@aH$uF6 zyjA49Z=m{0n7#YwS*`+Qb2%dl10!gEKpysci+CF(95gT{n_;#IE^3q zVnL)b>%%1^v5}tIFWCj(UNgzHFX_1l^0dFB%SfddsqAnmo%)}96}}i$Zvb^0U48&W zj~M0}1>3i^`a#zv?C@U@oGLhTzJLk~C(+h<^MV&)%Z9b(ERbGUR?Jem08V-b)mas9 zN(JM5BOz!95>y}G+5KzCq;cbF#05!vHimPy&srxZciaxNlP!iI0!5)25;J5 zu+HelDOZh3ZXFiWF9`C2IA>hwKg?II4BPYl?eEFs$ME^+2xDv#> z{P6tKH2HqWAD@UTnv*=fXJBUmb0NQyfwP}GVb#6kjUPVLmOp>q5B(DWoP^e^9WMTy zNO$;fCT@2kiUVy{Y#k`77hGI9FD>nflST&t6O9NN#6V>%*lEnr(S3^!gL4f?;$lb5 z{z-*WdJ1SLfnO@A>`3iT!nzpECv2n55hfEghAN<0-;ZcMoOljFjn=T}`X8P@bvH4n zHmY(D5VHvszQ`e6V4Do58vUuR&Jeee?d`3-e}TtuU}I(RtRHcclmaxpBBgf$Xcv}= zqMt|~o{CG0y3Wo9vLErg`q%dMMp93vlQdxlBQT28zsn53oECcEz+^E&bu+y~#~4c= z!eP(Mj`%uECfm+WK|&kOD^SPhimq_~8p^CK$jp^BGzM^Y3Ra$Zd(zqJi2#8>b+cxJ zW<)2dMp1<$_z=)4R;X~`hM(MF3n0}PJ>f6f@sy?KssVShC`y}nB2c!mf0vZ#iOO*B z(4l3E>B@oYjkj#MN`Y{q1(MnbaI-htG8;%Fgp!XGI6V%e9E7qHkdf4}cv3*=c_(JK zFgn~yPM%wW>#?7-)Gex>2N-{S5mdcE9MumA=@@B0{+>}BCEB9(r3E$vNwb6Lm6fvZ zjkCJGp`jP{@2t3Vd6 zz-xiR=CK^f_8bTq2qIM}{};f&zb1c_aeUbjOil;G!jz~*3K6*s+Wleqk#ubkeBT|+mUb{$maujO zz&O=Nj`y-!KGP+L1l$i*Rl4j@Jz&LAuxuirE`)Vq1Dh}g7my~9wFMk0xb-Hl)sfSWf`TwcJpN1d*G!E*fG{rfYBMNhoT@dx1e6rzhtR%mE6sVy<4n1f~% zbMHN(Yj?%hSPUXB%b&Nw%E-K>g=$olViNlgiu`c?;!Nfc;XR2UEquR4N*zS*T{hMq z1gim7YI8wR_Oj?+bC9$Mii3{H7pSY~C-OrxHXZ3N1rYitfaIJF4E#^%C#X{-$Ab9qrP$$Vj=2g|&ssZlFoz zvp-*7Ah_@$F;UFYI}XYvBB6Y-n8(=5t1?|zmQX|@i$9WvxDOfM#;u;7r-1FlsfEdI zN=L#ZP5;}Ih~KSOcpk#Db;e}~8_rX@SFa34BQyUu9B6I{Zf|R$?I4OKgWe#Ow&V_O zh)Uwd4@XAo6Gni<(T5tDDZ@m%Em}zn&Pz0)!_KA0uwqhcH{pZ1P9kCf4!As8yC>Yi zuE^_03>e`58(AemRqF2;tiyVvEPb)6j4Jf{CkU&#H*7C<0Fl20Rk0TI`4`SNF^^y+ zv}s^b!vLf=8sx!8lB7$Gd7QN=50vgg86)ES?-A~H_RgJ31Suaxnj;m-F`AEgm^{C7 zyeJ`(Y=b<}Mi$V{3rCpfyNdyer^$vt5f$Z+CV7AVeTHG61KJXw1l15R-|a!Q>sclT zl+yU(6ixzgZshh!03>F3*rO$?a(QBm1T|1MI_vKo!_~}vuHvZ?5p4*=b&U+Eb?B^j z1lgxj!r_^nBqi~gER%$?LmTk`Nd<%VK~agZMyW`2)_HpmJFst`FL;s`J3ypNWI1J| zpo8IZ^V zk=({{6DM|q5Ude*LJdpSLMh^5w;+3u9Xn>c$j?9>62XRLHy8q|b z!8t5x#n>nH#5b6O*vAt#%xnuIfQfJ_F%QDjwop(3y8V5H$*;iAfuJy%)EH-JKPGc@ z_#HYVH!$`|GCMzkYp{e$?Ev(?HP=y3iEi?u6OS*UjG*eU=4ifi>E8EsKiNTsu#swv z-xj(J97r`${+YjF4X?34dmyK3qz+)hX$|`=*hq(v`$6rwLXITGAu27lQx6wzdn>$Dtwh!=!dxn5w$^oYXwTw%JJB*1 z!wXg8*P4vLD}!Yd3|G{Z!RMP19T>4?63}n9S7!(Nkpk;ZY#*U{YfvLM3V9|4ykN(| z43$a_A3Zu0tieRu?*Xygpfy_hz-&<9IP>1o~n^<^ltusf$$Y)}QswxuWgjI?PKs<0T-AWrDx;#$Xc z=?CUijzefa?)Tnfkh>K?-4X5mVYTfg;Rz~dJvjTG?AiPcMQ-^7&~T6 zcib#`*aTi|&{|HF7|emLK*T`7JNJ4PtOY;iJb9(;sD9u}Y&2ONCTAd9-^Vu}ODBW= zKY@n!BTufHJRL={Uyzq~0#x5eB6}gJ7eY{KV{WbolI>4@tdGzS^+h1vvl?fjh-%VL zS=t%s{_?qV>rN33K-|nnV3<6|DOue2`M(XySx5ko!LTfqc-_8YEE`;xmLbu% zEQ@*$?__WcVPR|IHf?;o?JS6R$cV#^=;n>gcA`3uJlt_KlgJz zzQjP<%|8p7g(+Cj&&Bno;3+RoA|%=hVm=%X`RoHoR1afQD-cxzLwASs&o3`fGHSXa z5f(t0lRj1$tMkttcs6R%erA1wyD(&QISQRJfh?;iD`)Ob0r4zkQJv#1HEY} zePkP-zW_vf@+Yog!9NEtAdRofWZC5h2>xWXii3y zT|it@KU(z#6uF@kDxu)s*U;{5;_O}v3X8bPf#SMng4x+0?OrXM6^G@FK8x0E+@wj< zD0nVYo$2r*#0Wa_o$TW{n!e6qW~gwHcGr_V2UWJBdpA8Lq;sB9kcP!+kRkJwzuacd3TObSXRE6*DRB^Hh&?|(iOFnTWU{rn znk*P}LCVfSbWiaDGsDL_kto$`XP1t9%;kCAN=-iYNszf*Ap4>b&ZL-Nt+b|3pQf70 zjppuU1lF6g7AdaeC14-xE#!e~D5yOVuC!9(T;}-Pz*%B?_wLD6Tp%aBb`J@n}?7pV)ziE&R;nWEq0iPMn^qL#e9@XS<38U2l0|7X=}g42$h4} zz>Zc?TobQ3gy6O9)#c^kDuvu}DKz@W>S{$Wr!fMw`t&kA+X@c>3%~EC}RF_z~gWt)k(*>Nd98b5KO6RdqR`;2M1 zfx}0R{BL+2$6q?*Oe~nyCupDh9W}jtDd9yw&EuY|9kzr~MMmawuWoO<1c}?&t7lI$ zk{E@)r5%UjYs8Fhd=O!t<{>OjDv73hddL&&U@t8-Gz|NeeF680YOpGrb7%sun*<%Tc9QTh+y>CD7`7Moc7pBBI(C6gMPUMvs# z|2x%=hbJa)Q5ZzV#`eNoYzOFJfG?qmlU|QIZV}K{8ifDoKz+qUk;?5AYvV+Kg6I7C zkN=c-zS+F+#GgO+i>6N>)1|Xi{|7SRY5LKLrV-VdUC(yCs&wzX<%f&qB{SA-yt?kg z#V`Kl)+zCZA94bBDySND-4`!kQ6Q<9{=<7u%a6X5FSfj$bNA%e&pUKRZW6iWB1z>p z%FS@P-ULC_;a_h6j3d)~H&|&sDN48JCS_6hEaMED*8)+5%W((!7Wy?S=0YRFaKsib<_B(P zZyI9aUv>U@4YT8!7bFC*dsyZo$D1*IXGD+!$M$EwZ7U$bb~H;D={3TDuOBjM2CukL zmU<}criP%*mu%d4;S|IuR)vgO?Wf!mOB2k5ngvixB)q-EO6`HB0*~Qd_^&6gUk^=E zeLR|gq{n21lXY!4oEoT;#0wg{maKWW0=0ZMS1K&!f&2C5&h7RY*2#={f+T_JE?F{w ztbs}q3bs)z_8S%FTFwgS<{^ZG92hb+ged6<5!Aye|B+*~5)ux#hcCLL4$d(~U-q%E zwtfV2*qQS`m-2{Ou^0KVV8VN(uIqnpj*o6ZHS0bc0BKV?vNUujgsxu_MvPs5Nr zjpABf%`K6CJ~g$DqDPk;A>qSnz*MkKb4VjFgAnlQ8yJ{NB*K3Ba?|xP5%CXL;)j1r zkx&IeStB4CM>cdi#Y{cl<`7X~EUeA^t<4P<3e~KTaI$i|3G3>@gK1_1jn~D)(8TMW zd+CJ-z63k$K7#}jD?^@(Gu^)qT_#Ls528=ExoZy#0uv1If@E;(3&c5CKqc@See*+OegDNyxfAv*3hw@Mw z%NsODlo>ri8;#SgDn||VS$IqeZM{1J zrO~d#1`)p%(bm$U*Jt<)CM4zPQt`(gvX>XsFb84Fv=~!iigJMzqJR#|8BY^JsD#WKl_6zFDO9F9 zrUsI^L6f4Ap(Glp5K1A0@AsVVdf)5%uJ1kXq4xd{&sytV_pn|YlUOf%m3{;@4_4kg z04ATIVajk8rRSck>PxxT<$yjF8=J!h$c4BKdo^T`i^g+X*3?L9wZk+R>+{n@)Q>B z@G9ITJ~YQx3ms+0MT9POcp{W(t;&&9H8(=d|Ng_0nXS0oM5GUv>k16Eq@f9 zdQ;| z76NHyfLGTlSptSelHLrdb$k3RD)n&17qq9Esc1c_4#NxMS$?{Kt_z-RQ(q4yV~r4TZ{J1lQGLFum7TCyNj&$h>^jU0knP@c!^ zm2?i}sxt^O!I+SA*=J8+U>n}z2G(jHxFLg=>4n!}GUUO>W$fz)jrIoOAhH7QfH-_* zTjT`$A(DDsDIhL#B+P^!>k^#~M9mHGz6bW`*)xs{cly$$hwUq@2Do9(6! zCnXnxW)9xZZ6)zN0_C<~*ZYArUJ!$EJiNET--wBs&#R}$MGo^?S8WveG{ zDLb(Pu?tc+n#4Lt>1$(S8$saBb&9n)ZzixNyk8!%H{v(05c_YOKxhP}5Vm2=&#Ls> zy}RAtCkw8XeQ2N+onV8hu!t@d835F~?KmnwhHFR9b7uGKxcWaIZ+>#&@@zVN_N)UB znI$D9kIUKc=hvs5*obzxj0@V4Wed_6VP~BDGy|Bz`OWAI6_-?iW zDHy5ZkCELr-o35{YWwr~{cq^G44ASbngB9|(=@|c4Gt&fXJ(7!Vgr&2FvBQwKBi3T zkCdw3w`4m1of4m(`Op@-XQHP?nI|(N~tj4bM7_8#d zuVLfx8#{;!I-7&b7F@zjMo{gz|g*z$8=*adyUgcL-_ACaT z#Ny3n?%YCfqtpD*?*OnZoUSFT>4Y(^u$o*$(eMSF`(T6&#e}A3wZ7UY$*yn!4t)$h z1oU#@>eVB^TGtzgS!xNU=GGt0Lykid2WD;JJ(wC)-5}_P@$vB|KwY;&21o?|Mbefo zHcS_aoWtncOfNs{%%1iI?jB~BpKV3QlEE^le-a9snxxq_Fd$P9d39Hm3kQHKrI#>a z$w{)=@IERk@`&5L5eU_R7<|>%)|MhVy0G+y$I3ErM^8+3KnCwo891>H(5T%!L2CNn zC?>QxH$fyBH{OWYFewer`;Q;90Js_e({2J85iM|r_V}F5`Ajw!%@m*Tv@{bfn5wZw z&KB@7cq5*|94pyZouZy0n~wK}O zr*mn>333elFelbG);za@a^t`xVTDZ77NO5XkV;5Nwe=gvy(fuJ!~wy8(Ka$tg=oAC z+0YO&2sn+6X|r=+GFYQ+e95}#1u0%$QQE86m$*Ua4B`POWkndp(>|G-KSW!*0{lRm z4BB`ov(056Hp0D&r?T{gMQ!lx`SW*b{3^bLKjzEr?zVpJOgvzg^VR1nD*mtLD7~Q* zTI}iSTG&WhwLR^}pn(J1y(5`iu+XeDdxTKdr(iDPLgh@SD;X$fQi7h~JFTQFL#9da z2N47x%5Tp9fyr7LmwGr2lyI4YqGd8|+8$%ALn9d?PJ;*Fd;DMe#Bfp{Xr@lH*n7)x zY78^&K|w)pmlr7^zw4LP!v4I5DUL2C2@hbEc9)B0AV}kNDrLp8@cff4j7Vj8ZFtk^ znDYos7tit^_C0IKI2|dIrDB`Z2oJc01?t0`91&1vipA#cRQoG zr6*2L1LTa{@!~DFmx_DN@VrOGKbk#+Cwqb_{>595dPev-Opg^g;P$bZEMUic5sP6E z*X!a6hM#@W5XIDQ%!qCpa&YuyQQ`w(puD z`!A$B#%ymaueTcT+jMAmIUFAMkbde5ayj$u1UYHg2#hnG5kn#=SEidgK^O`s*lw$ATUm z=qdC0TesD52M(yYwTpffPca_ns#fwK3Y7@QP?Qcf+O# zTv;39md1Cue`7qUqxgfm=%vc!|Gp@7#Q38PbRN9ib$q!NW;s(9}@ zNF|z}Ni6t1(7$iFu=MtGee5#JfO}3;9I!eCvi`S zG0R>PW~a~0BV8DEtP5YfP~c;9A%u7rbdFKf<{BS=z-!k+JYE zXqdw(b*IGv4_#r5+J2yf*KCl&py?b!8Q6Gw<#P|@|5?uZ&l#IdX;mJBOS0ATV+j^U z2b#)foQ3FzYiSRtSIa2fi|AKmC`oJ`J)jvJ0A9aCuuxr1O;+;Nt8Q>Xbn#cERObUs zbQaAV#8NmEq($V~g4Jj$%HIH37=AR*2v7pAut{{dw}gES4NWA}Wr5gUfJxdb&=j6c zhGEcN7DFGgHSW~0qZh`1Z+UK)En@g8e)%$p=~<8Zu&v*LDeKjHU*GcO-A5+yM$FNI zz|}=GnDxAQM{23Q`}i1_7`+eYy~%_s!pf*npg2<0+C(>zW+;2@|+~< zJwn09bJkv)%rSKqEZ|aN;z0^T@F)czUYcFf02~>^kl}SO3aZvu>%#NxOFawRfjNje z^^P0Rh~%{m(z`@-LH$#3x!B6bS%>IwjlSA-equ5$8>d*a>8!)pj5=JfIUhyj3`4^* zw)u~74FCq&BQ_YAQwT@6xf?ecTr6@HM#;aqO?7;!Jpk|H1=|0fRwI`Hf3hehKQGH&7iARtw!g{D}2zT1IeEQfaI@4R&Eggc`z zeaeIX1Ln_2#_f;XA1??E^LB#t@(I0;joYN#OdWRzr$2m$#b%zAMqx}tVcFeA z62O{9e_M;|QRq?A;^sa&`6~E9US3b|w$jOyCbj?ly|(|^`1rzkT8F%NxP@(!E%#|Y zyH)t<_fafixAiXM@Zs0^%O6kq_upwMefxG1POj?NMm}k&BNYbj2@0xF@%N$)&k<>t zJm_-V`3-ulWs~+CJ@6I)E zij>)V|K%->B})M9=40q!l?88=@TG`h-U?^#y=Nzr2M@6gG}W-fg~Q1G^semc?(Bdi z5(!<#8+`Ejekh*5l(UOuWQS2X?L=`SFyFM&a2zhC|Nbm<8B1k8S zdz0oCIh6JQm2ewL36D1HKCaNgoABut&7Yr*3ABf~x&IteIB(PC(`Cn<;@K4iZ@HbF zWXAOAJz=L7IK2m#C-}kgB;gl!2bU8Wz^M;?z(&9$?VxIBmQ;|phvBE3 z4$`y;5F7&{vzI)ezz(cL^7xU9+DLMkzM>c<3OOkrqn5tTX3`mNdY#XA|NQDEk=UXj zAv9fr^EFGk#&a_wmNO1YfE@=8SMK}{XEI>Nu(KMzI8k zcIvo!e7{4%!BXvE!!p`PPN0$N%0^yQq5WAxHU!_$9o{JNvy5cX=Zf`LD-3c>RVE?W zLNmF^MqBAC3|1kfk3tGF;QHdVyaAOo06~C& z$Qv=v-#&&Ld59$l#{A>QaXf71cv(g^#V!8#fiURPW&8M7x8^%I#28@=0dTqm!ZKt5 z7ntrs@qV1-_+hAFs@}XYQJSYWr1 zfgN^!f79C)LjU!pm#^K0PuDZhR0{6`JdVRuwwAXMPwt)`WE>nO$BnJj5v=b^s1Yf` z3jkGc@_O|SA_e>tFINO26kMsg(0^v*((KGyg?QXGr_m;jVix8L`uzspv_|{EA-v?W z1%}jK5`sy3aEA4Swx9?sH4#f(1=M-Q z(|;o-#+zv=pH=&JB;7n(@jBB%GY+qDc2*$}>(NifwuLlRRV3+p?xiP(yk3nO=Q?u1 zW+W9(aOUb5nf>_*>)9BKZ}%DaTN@e@_Qlof02GJ7DkABLgvjq;lUER1bQa=e0lR<2 z2ilB*#2d!?eo9OY?lOBB7EaTHgo0L%1bp^@eWw?0na+({9jddy0fDy%s~q7KyCT81 zj`hM%Krv%jQ3v5v^8Eq|Y8|;hnrjUD2mp}bd!a~;3JDK4s>APqDr7kpK9>il;Bucd zyUw&hE05J6<wyFla*>;bPc31FX;$B00IY1-R6XJ56d$>b%pNBinOk`b2 zEC#5~?OzH)j-JB6Y`4XSgM%e-h<<{Q*U>_yQn(?X_zQmVcs#E0xBotYrC1~%Q3czq zU8qsmvL``S-OXz6wfl9psNN(7hQgQZ$UOa>#e3JF>IxpOQ_#9z-rYHqSu7DWqPdmi zi8v6oQbA!dhn>Yxq>rLE*XDw4Q9*TMJg~Jp%AwL;X*&Po0yK!{@%^Vy{ps3u(Sv^l z>BXNdp_cwPA5_|kBb%^t5mAEOuo-Ig!9bTjY^;>9A~*xU;R#6$YUqfWLysIpP0Ul+ zVePat?uur!JfSyRVpTBH#tu%wvL{dc zFs60|aoM-<;&R*!#QJ$L$L@Zqe4NdB6Lc%QQ-%0a_QRACem1i9Vs45lEnp+B(m7BS z8RGu6K!I08#GICv|1GrnJNe3Vp4D=^2JM-CHzHIXJx;x=(8xmL3s}rpa>PQjm0?(K z=#fss>WV2b5b`{iE+5Py5=*OXs5_U_fmQI>S#h*-%3AWF=i||WDo}>#=j<4wg~VpB zBCXvIkN~TbV`d(=Q%MGcTX87Y>Dm8#Ba!gAFb7tVs=B%_8y@qsr%wm-Oh__hznMDw z0ua90K@|(LR~eNd%OOh8lrnV{tmfu%&e*YNyP5<#MoU}DR~F8CJ94U<$9b9s!8TM4;thwgGFM|BpYzk*_R zoojuQV?Z0j7G2&h6%>JE!13nQgq;7tY|JZhZ_cyn;1L>_nJvc6B%| zGq;fQUH~>36G; z#~ZP}y~l395`kmG;$)X_4%ShuRMQKdgls4KgJ#;BjlGPa)r!O7HMQRi*6l6M!yKZQ z8bL8`3Q`DtS_ZJWPCr5<(dWx>zd({tf*R5*@Bas#9Jv4YxB{6dlEg*nXYW}`K8=nbdpDEDN*SRn+V zCwKlH0_AxbOHV84W4k@+5sMjjH-C6OdN9eKC*WOGLYNlDlDzJLkOFT2-ybLAs19-0 zqX5&GkMMEAu^lPF#B&#;EuQiF<1Sxbfy2pW75@Z)AknXa3CUqWHuV|X9Kr|JQV@gF z>|`(4{rO1)_67zDW3EFC?kj+CQ4w~+;_(4rdN~4_VK=a?{E3dZi4Y~N9)YpYh1%eJ ziSQQTKk1`1oX98EGu(jvCr|$Kicb|spFNk^0U)zBK_?IIzCvg& zi3e0hwxtr?WtCRm7kJ|Z%akaF3Rh;G_(6jQXMnc&i6xna{3Jo!gemgxb~@{a+s98WOZ&bsJ5`h8%1+D$N+RuhxtM{0?r0{v;ueUCizd81yJ(+M@vf> z(Z@70s(zEAi8OwCj;Gx7HfoVEkX-dJ6{shOaVz!UDgAQSY`56kx64t94!o6|9Ljyz zxeXNI$C=qfuqKc)kD=uB1*>XdmE=d~u~$X3o-_%bP}MpzSZwEQ&A!w6dHQOWAAZ$_ z^mOKA0R>(oWfo$o5sk?xg^Vb+r0%pNs@!@UTEh=O1cgDm|KBI?qCrM_&cK2V3B_?B z52->%1%>K9CAJdp#T?c%!Y`tXuQZiGeklTlmDkFQRQx-t_^Zghe9L*MCjpW6ElADKoT! zWEcI(b(VH}0AQjD_FJ*TNMMKI`&U~pXnF~#!RMfc&ZK8=EAQ=!F~#u-fI?t~ZmOeK z9ZooZemX%Zxh%r&l&{SebadSAY3vQy^`LVbjVv=?;XRAF{C@s%gY}?i7+7c zILqzDRE%LLO#duiY?-K&HxW}uF%>!3*%?27^vE0QLp_!w*J;w`V}{|pYLy0WzZa!Z zic0h6-9JAkUgOv)A^T7K-F@To5|c;VY4Ii;v^OOWvt8Q14&l`DVMp_>3;qJ(G0Ro_ zg)gA8n%aGl*sO#fGL$ewUEQuU%5yP@_>N>j2#FUX!<%YpsfiHc7T0MFn*&5={_G@U zg>f8S)g`M|uYSwAVl07cuprT6H_)J+sYCTgx;yOu$rR%QnT4!!^jNr|KcbM6$J zl+uJFKlY_l&p%o9;Ne4m=#6b~0$h(I)P;lpBHSvbNEtZFPLRldQ8aDEI2xRI7mxwj zjMW^p_fZ4)M-Ouj6IJV*gB5bmBO78;8lcgoZ!?t zOL;G%l)FMQ6vfna>CzL^H*EOw-*y=aKrXPYfrX;nc${g;5q((>^RDLIyP@Kt{|U!Y z^jU=>)J1nua{lzo&lcg>5yu!iiU*-NE;d%U!akrLUF9|GjOwu?FU;(@bDcoioR%-w zB8s#<=iV+TSHkaB4`J8Me0CuhC`7_!ESCZ?;T7eG?Z#Am3lksg_m%KHjvjuYn;zFhKmzTQ>`$g zm=MP^pQx74VSg483sD5S0tjj{E{BV0A@nH_Qx3orOp-_*;i-7b{mq_nlGGtgx5$su zsRQ5C$9l*|;ws|4X;W{@Fj#ALeH^BDv!zR z4-k?G*iuAXVkfZ?hHef0))lbWz`c8`O*KL)`1iGxVRKJUPX+4d;o}GmZgAi&0L#d& zZ4#a*Nj!Icm=JfQR9j1S&A4cD2v$T4glWK)mFaLgI8R_Y!no}piwBW_Q0D+bl-tPR9X~KsdJwX$O!9rMT3@#9#W@m@p zl#t)k0$K65$8hE#sqaE^v<8LVeBzGQ01R8I_}8}pOHp=ix1g|fS69~|tHI1&NYa_9n{Jh_<6PaWwkoy#;Zf)1OUCD;`6gR0db1K#43%+)>Wh~LRK z_VJJ?qi)~sJ8byyt3nJ8o7aQ|+gNxc{rTE!w1_1}_?t;^y(JvU3eNaq-e_N5F%hf0 zoY$iYgs$g|;(9g#$61&4Q&dbP@vbWaWTEEN+03Y#Vuqs``oT2~r+-^^b)gCOiPJQa zgCLhx@le<(o8V5?3VI8eK@KmY0R6iMnr?#^!LA&q1rVO;Q%E|*zGoix47Nb_rgGTm z1~n}R-2m7(_U6r-hqz6?L_3K+gfeHoTh}gKGHI&{#7=qAajPkCX9Q7X1cPvgY@}2| zb&89eStuzcf`N$39^=D(WaEu+zF>BxbDW=4S5Z;qqy_xVo4aI57vK;X0bm4Q>}I1( z)GLpu2Ltv-h|l#%Un7y{F29Mmn3>i>uzjId%S;rTnVJ;Qy`VQRXHsp5<^ry!p-5pL zLD}0wpZFP=zavl;jfQ$5BT15xTm`;Cy>k)YQ^RCGW;jwmat3 zsq5nO=ee$>WSOv{?aXZMjCmX1?JqKSGKgk!=aabW&1T0RwAi}bxnj= z8wpTWV71|S7ImCF!N^*kBbJ^l5>HE942rmxhV(t#0BJkM!}HRuE_T+bNWiHssDey5H2Ntk`-@nT>6VtKx%3?*6$hA* zY*D0bWfS=lWGRh~yA~IXXpW4vYu1d$Nv`lei>~kazkxq@F#|<#kvGHX->`mt1`rWm zZ&7raUsSUlNC|GB7d61~OHNY3&e{v^OBxJRDIM(wbH#h;-r96iP>{!)x1O%t6+4~? z$Sj1qWewEU&iRPag+ru$B?3nNmLl?`3Mkhl6R#~B310Q_-Mh~_c{SI8<&a5rU?(f3 z^;Cx?korjqO2><(1y;=)&8b~!#Vn+X#)$V2EM#)z7V6|p10L4|=eq`qZ}Mte(|&}! zUCK$jpI)p#jtwUC(4@t>Q|R|f#9Rpv^upYs8(61?Y3k_I1AJ781W=mJ$+Ymjui#;^ zaM7X(yhbjdVtEfAo`tG;LFl{Ik!tu*3?r=9T&B!AjEcnvQc$VbS97H8V)e~Bc@~vi zu>QSKENo#p6MXL6ikZxka7rv$n6%;PVV_TOwM`!Ky0^S+?i_O+XtGD)^)anOhYn3( z5j=0*(L#j+`M3*y?F$}7=z*q?vi5U8I?|d_Z1>0Wo|4Cr4c0&CZ;v{~UMyKBZvATM z8>0=5vaKdxwT1J?j;88w4tiHAVgS1mpJEo;9Z<)KQIng|AaY57+)S$s*CM)GA;3ce z1A}S2NC%+76x5>+G})&zGJU|&yake75YBaR)iadIjL9|?4x;re1sO#SgiqL-JO|Zq z3qDK%m}4vjH@!{aD}3uAfV#$p%E?5^?jx>6mv!PNUeX6USTR_Et0wo9OQW^9Tgo_) z!RdJK_U+q641-^3Za&hNJcXtcCGV|B1^iNmZdF*$d(x3?lSq&urO2t`7U#_B7pb~`cpYh(U`Dsvo%VmNbFiAXeeiy-(A>C`scO&@>@DNNUZ7;R#vZPm-h{Q>X(oQs6U=po@=&gjj?zPJGYj zia2wvb^y6*srbhMI3N!mF7}Gw%Qw9;y0UQ1zPUrM;SkOpNOP- z!=_gZ1cxyf1uDyhwz|pRKBpjK@=WygNPFG`$T@tfs}r5zV~)&He&tl+Hx)$wnE&qG zJwf!9Y#_X_ca15S)HwwM2R)?o@A#R+s-R9G?j!?p zMLoz`nwdsO&kR{BrKJt`O9yN4u<@G{L+n+83Tm5(nU>Js&d?6AXaF~H9ko1v7i+gq zfHNsrt%@#-r{g$|Rg6q^oPDnW@de)be0fr!Fwx1*%5vv(+c`U1EM(5IUl7V^GR~d% za$($DapM$`u!v9)Mhh!ycDz{A=H=#^P$fm`?2pT~3F$0_=Y7VzI^J6QH84mU!gZA) zWMGOV*>64ysl)+L-~j+z`Lx(2+`|>?)_uBz205CfS7^~b(txPDcTZlm)tRJZ_7*_I z9SYokN+8aBaT;!<+e*fMrM=lK!No6UF9vZI)7Ud+8r=sG#vr`hf9YJV3?1>$+6f!p z9Uhz^o6HI`hJ(GD7z2>-jrI$aJ4gz-(+_Fb7cW{=RzXuP(6COSxlTdC+>lf69=TB- zWBYQKqBv;hFeaK239^9-jG1o`yfANUgQ`Ft?g_o}DS!g7CL=UG=$`@e%87NwnwT*< zjgxNsogeScr!n0Q6cwJeQ5DtlT=SOO)8Uz|r#YGkE{0lch=|le6L$3tN@`QeSrNi3e$`631CnvJVaw>l6sa?CTIxA?q&O%%gx3Mv>jk&{I!A9f zasmK_w*WqwLyp`(K|NV7Vt05-9^QZBE)3V)AVzY=Xc54E$q{JN7GA$ z;qmrPF8k2JFBs2*!PcaPORNDvzZlgEl-3gn%J7KTd*S@~rvB>cS3u{!Q;~F7GD^bIUxs~8qk{NUo{Kgw7ss+2O6t-NB5bBuj?lbwW zE9ZTzt*rDCR?f3XJfm_s0OzJ(j*AOnVlIL96NjZ=8a6z%?V3|N(Tul804A16FL)Hj z<2gj}?!AN-z?S_20-H|Q(Y+B4bf(OQIMQ@^_V{rsim;l-WBRNa&kqWg9UEluOC>fJD5oMtn%_-hgb++b1BuB zKohnm!PZV9!CfVlSHVcmL!N6)i1sSzbkfiKXVDTshE(YZxp8du599Z+~@R2(MTfo6mX% zdPit?(2D19s=pB_2_nmj6I~dHeh@2+^G|Nj#d&}J_HFR*Pk(+FY^5v8g1DImihhf| zN}q4_r9L>+l(1XzY5n!9F92O4rLBqCq=a5fpDEz`1WboZo%?^X;D% z%Mbbn``#X7?>BsrdWEq`=EC{!`(67{^s?`!Gf&3;YkT$5*dakxsk5)8xM~btXu81U ztaPiE@4of!Prvnk^{VNgTMwJumh?UbnS~H3LbNgjr)&1orEBqNO(NO`C3d{A_Kv^TeW8G1N8JP>ZbBS1*RfR*ESMlrqeTbmE9S-CP~fEeM|l*D^sTHcG;GQ#GP7$my%QB{2_>~X81@rZy- zdSV-9US~YRG#()->VrNRfA&; zA3f3-%R#!635l`gep^Xl!6e0>_*FRal1x{WBgVq?24)%A`}<5$Q)o|Gh$>#)j@V^<)Jk^nPN}Cg-7W|J3 z5X0_x5PwFCfT77yJoktgh-RAhI_6$?q?)eNEG@4RQ7F)9jk5e2w9Ir;LpdNK=j6PgKe_~XGC#phg%*9q+4F4WE% zW(gHZs$hJK2QW*)#1A&)YI2QLB*xpbGNb4Q5>dHUocMeDyM5K3uV@|*K=~hq#3lpI z&f@Dm%9B>iEM$Z76V=QM^hGnVTP3X3a@DG#uQD=xxXI>7hhM*Yx91L`(d~bJPav#i zse|yylqvL4QqqKZbb8^%_H?wgZ)S|JX?S;p(Wc~bJoTCs9bZ;d)cqCgy&trkMU$ef zAC;6?uqU%#b@lnd;NU2R;u@A|ZWP#94)1MXJf7_7@8O>(9Mu+m`%qS!vg*mu$;^Dc zi4Wu$Vd`$fhX1MoBbyCdqJRSj#xTV_#mcH+w=@94S(hcGi_l1`jF3uqT|)lMqWTR) zwLa*N2QOK?I9^E3IsfYf`yif0d?Vw4H*M||+(h4EtPsWAFV^8GnR`I$t$`>aGAh9U z)9^Fn49;e^&qBs0kT)roB?Z%}!7@ijAJDT!C?e|E8_uGQP?~x$omQ=YL-{zTIsmLI zU?r*Q-puOhG+C%QkGV28B=V=k<^>C)G_2{K09H>!aNz)SdjuA99h@pNrf%sQccaLo zM>{OJy`>lSae9V9qEkD_hiFAkqJjPQFFth&KMYw^Z-pnV5G;>*2IdhCp<{#gP z-4ZV`IGkWDVGMr14$^HVL(L6FL~VY^1KwSK^u9R)D+mAF(b6#BF#I&m^DwY2^44b? zjvc!Pr|@`r??T3l2Dq|u^wJ4rNB1D(tq(EInL?5J23+DHHSh?L_urvPOyMwIF>m|! zUnNMb$cnHa+o}#3ktjV$0z{gkDCZzk;iNNMN_{1E`2gPHJ&C`8m1yX1AIiU|hh=60 zo%9B4{4hiwQxUe}$!W|RVPR{l@`o<{Dg3h&Sfu@v;+l4a<)pbrNZ760x6^8`PKbp% z3G#Lw%RPEN6UwjIx#E!kVdDV>^LUYGF;9(UqOET9v}PyfZ?A$o_#PI5qXeZ!J$ocV z3^8n1Kp$c9qG!UV740>2I+QtOpz;-iG z2%ldF^d@LKyDoni*%6=dJ*;PZ(egTjV78Npgi)Jl>v+J6uW0MV4%l6sNH*t>h%~kr zn7_(*>eQBqsu=jY<{XkH0CU&Mz+@IcgZuNnV5cHOPaSPh1Cvw{lTr*QX%(2W8wUFI z6e2-{{}*{Q8$&Nz#`o0be1_t-@FvJd|+e%XStb%B=hdse0k&Xgtgg@1BPJSmn zRffQ}Z1#iT8`KKb2@lN$QdNll8$e{c*T51cbEc#)s9<%Ve=Sq1pT&*X+RyBe*W_1Z_{Va{3L|Rxa$t+=xvRy z&4O2Cjt^wgai!+rWI9RYI5dwKGUP2w$Zmbq)K&^G{|1qR2n!<9<6nAxGGuFe8?vgGsU_ufYAfz_lUt%e0_IPBr~ ze{X5AW!dmXA!8q!mG(@-E)}%zPC$DIe%yt#gcHsZL}g_{UTh~c7I-K76SP=Dz*Bk) zi()5FPZco=w0?dwy#|)F=8R&D1m5zEs|cB;HP!~|$q}O+Z{6Ko=;`%zWl~HWuEUQb zGJFGq_8b^>5~yEik+TFIIIsdFD3?ibn2Dm$9uHF9`<7t}|JP&xUc9)Bm3ElG;rL*1 z3QG|v6@&DKu&r%CEEq>*CR^JcK)0{Sq+&}wrkqy(0>_g?BBAeH0+NzVjE)I^A{8!! z*gL&vq!rKC;TUZJ_2GdWWO3$E5vS!tpx^s$k*P&RZbV8)gA=DA^DG2$D#Q`x;DpVe zO)2FcdAACY2r3xo4n++SW`dP|mZN8!vcnZo>*(Ntu}JO|MO zLa+~AHj?(N3H6O|^B&9gA5xDR;`GXq3K@U3Z!g4NPFxSA9)V6CQ1i~hj>!W{GvaT- z^RD3oryyiLhVi)=Vu@p=T#aJm=*WAhA(X_{bLaXJt7ZZWd=1iN@JK~SzCT%ncy@%eeuGQ;yF-rsv3Ae5Qcw#?1BLVZS6l+49Rc4u>t)7RG9{ku~3Uodc{J)s^l)a@@Gy?XavBy{G2xiEq6oC4^vT%|pi zON@^hz$?yzUrz%t2$1m`VAvv{>OA7YXCqC|r+js#wN-oe{!2g639zO+3!s+D^6>#h zD~6@cfP&my)e^p})hs2{Msh@eawBJit@g+dRapAk(v1y zU1m?n@R!e@pCjBf1d)5Rfk6!T!dXn8gj|AORwK-8^&%eJ;>dNz=z&z@1chw3Y8W-b zULt~F(;Xl{-QWHzHM$oK;%yMsOV8eu%YL#;r%qMmOAa(qtl`gjGZPD7)J?rNR;F*+ zUS@*EUZU~+jq~&PUnKFS@?zWVA@AYp!r>^}FQVTTLC93C9#{bqbJknrDy^p*V#`oT z)eq*>JqPf1Pow}(Q}nIpx_qV{Up>Rq$->)B7tawN`6yZR`!-wQVrWmh6cs}J&Q=oD!*XvC1$ zT|qOx9Yn{-z(A|tn6>Joh(QN9{0u!XmQ%)!=#2Nq0b(t0uD!i&lEzk!hPr#H!(_YNa#}Vqf|=tI{nc_##+9MVjJ&0I z^vX+8Q8^a_pb4{JEObh6=}?du!)d}ZJwRlq3)CZm&UY3HEVx5b0AO;){y1{7V8i}W&Op3){38qKo3}TVw2}5mF9=oMERtLKwG!*S{ zaXyX0eqaeQ>4A#!cBtbGX~Z^>irQVS4V#83uXrTC?3}&^R%V%SsRs9QTn!*th^ah= z_yhzGQuR**Ru*(x5n=W((;-f2MHI9mz7 za=lf9>)wb zh11%xA0I3~FK-mNED4}7KH^XJkgIHkyXVCtd>v2Yg?Q2S?$yf=@r^6A$W?3CZmena zEQi(zd-GisYx?+;9X1!T@!d>ER*;u>if{pb6GD;bA$#1}(_O(kNRk`)`NwA@Q&|8e>Gvy5Qja^GGXG3t&;e~s) zx{BS@dN(S-=$4iiDXP1xdomrcQ#CDwJV;Bs9+prmM89-+VxJg5tUy%y-hc3**UT_k z8f*zOITJf*{pc-6!s@yO?-ZZf7^a~bKA|;Ek?~1MKEMI@nW3IhL?Wyr4ZZF_7{xT# zj_*^)eH=ym=>s=Ltl;CRO)O})|aK}SZOb@23eRyGs!fU)NkBI1z`27WSdk0@kp_4foq)rnuvjZ zxk!awjA_p{Ua(TAR)P!S4@0m5YT&(Q0(MNOBaa$|wrOFl%k4I?S~E=# zm^0JFoXLZ>o?-MB8@vdXJ3@ISl}b}l6D;U=ARE`+0l0JHe)>Zg&IR+0#=^i;BFOIj z+qVgTfUhhea5>Fq6$Yj{$3G@^2yHp?FoAzo@XYx!^5+tZ@tJuhAGai9_@y(?6;CD4 z$(eH2o(lRzxDpAFGb=Z@qd-{Ua!u)P&A(m)_busth3pEAHIo1Rx~h z(07iVs4$f+5!HjY?mo&jZy%o!x~n- zst_p*a+t$Z0EKPlBuIky-Hz9*V&|Wq{cG@W9X4dh0A|Y+*by#J2%Kmi5YNgn-VLG- zo@Hs4SJJQeRe7M5#$+v=;@`=z|wYhGG`^ z8?nkpN=6mH)Io{l3E{lYF3%kr$Kx`RC2uOmrll`m{#^tA2z&Y(@aV39YWt5KeHK7x z%fnJbZp?U6WJX0ia0N4e3w`kg9j@!9P3MF)4e|t>J`scz(|=?c1~~%aIQ@hpH-yQE z1$VE#nTsLI&(l3Rkj~Iz)vA9b5<7a6wxYL1u0B_jzL z2@;=2-O0#bzW%O|KPq(s=O<)VZhXIWho$nDaFxW-_koB7X9C0j%ZuN(9t=wXQXpJR zgm|j`4y+iis~ZY`RL9UzpDm38Ra8}@0q>%jMJ&!gIS43J&C|Jn zHtai9s6j~Rn4I+S-L(g|^5D34#n+<=yjztL!Z3Ol`#2FRHwqc@Yr&0%m-3a;AHY3b zO+l~353ikiwkrQ`HM+kr*rp_+jSRP3E_T!`k=MW4{;lt@9)bB#cOEL4sY33$K(GQD zZe@@qAy!euqG8hcC;tk2PRs_g*i>Z*|59>+C*&pWgXBmWO+Ii*BM1BqMrT5vqz%o@ z|AiA$K9>oUUj>AxmLxD;303lRP7VL(v^_|^fW<7qm zyj}wGN?MfQ;3qtPU{hV?M2-U5bojIGX0*-uAvjzf2@UPkMv^Stt(q7{S5b4qY<0Bo z0jXfjUP;!fwuz!~+}zoKoM(l656az)nKOHbXd3Ze8!gAcxQ-EKxX=r225S*{$?X$t zUBph9Iv?24FP+(H#Vl^llw$ooG1{MK2^=w7`ZK$*mFX5>_Z?ev)JPAIGlT4PM$SB- zqUpOUupOsh=Qa}CL%F8-FonU2eptjIKTKlW3_j;T3B+GT4t*F$VvKM*L(rjzR`MP> zavRsLuV#h2ndu~-i6v98D+M)xR!f~AqSN#$GKqvsr~4Xp!XCCnJ9J=A!s@r6pZm#9 zXSb=D*#dh0(=^~Uth0(ZFsnFkD(NWbh)x?{v^l_;1+en;5;mZNGK7XC9Q*MBTiN)C%%RiM&NyHp6pl733X1oGV*|=X7pzJB zno?6!0{}Y`cmCN?3z$3`-abuaAFO@OI8iQ7u}f2&Q7l5xD}elEB0HzHwzitq*DrZ( zIl%PSMAiZ9=T3{={yGA9$t~*&whYkF1vS(bp?A)cDFoHOy4yc!&z^TsvELw`SPARF z_0R9^@4!zR@GKSK*SY9xquEOoQ$PLq5sH2KjKkhP7y3^JZ0TOZy^5l-*q2@1D12+m zAgnpUnuQbd{&jV=52Xe$^F8RvAR0SPq^~S>>$}OzOEjl?!#AADnYnM(RW%s4oiqmz zUJrjT*jO3`8WI9Mo@w_H8z3+I%*D#U+xb|{-pr~HHmC29LGO z9L?Rc-ALd3`A@X@DOI?|73C>z8O^1J2EbywNv-0dbwZx2gTh8GL*|ZG^!3Y^KiT49 z8c*R(zIJUGsHSW-p7yk~@nm)uQ64zhk*eMPD?=9V+2~M(Qq2ZRw?E+}B>1TxlgdjE zMW2DceqtsUVe*jTC(G!kPpVnhKxs17L2yhp2b*~!zVIrU%~NZAB_xPD$@ zjL=HqQlZU#%j4GWUO)=hVq05zsJdYQtHmfz+A<(`@EaXjjFCDvFF-0kjx4zM1f2ik zrT)+Eo+eVnlVN=xiH`(O4zid&eN+!QFT^zaI6l+|P3-|Yv;%*r{y~1e@84xz2aufx z@-UmzL5lt37K~|3_%Tef2K1>%?_xQ|={&q!*RK8g&`*PlRHDcn!*_>Tl>G?e^F9a* z2dN0wj9bl2p$%;HcDG>9_ZBsJA^7dW+gnECwY+j~rp7jQN1{W5i1;4Qx5UjHni04J z&KY_{QzF42?jxsKF!SDJ4C`4V9c9Dey^SDfHT-$`nCYaCqUM;)5#*nYldq%(XtK+=@>+8pj$-?Rs_k z3rFQ@Fy~6J)&x4>K}<-VtRTM9+L=ovo^%TZD3J`Y8^7l9cDC-AF})G;Uj-Hxh9$KW^hQ1rz&#trym_|jV?diwhJ7!b$`{o{K`*e+0l1=hw$auyFeZ3SiR_3T zvk~~f>p2AGGgL5p27N^GPX!_QJ%2HQ`OTVTEFi>?a(Y?M_3N7~sH(al7CCH-cCk}J z9W#ffpgmAUe>Js4JYn28qbk0jr{`wtZVsW2f^SdyZ{E9G!50uVXE(D~|~ zOd&Qd&Wn6dVSNFjl?E?yGoVWiKl~%D)m)OAGDV)oYkWkXvSJ$g_uorN8JXNku5G}w z1@Cwdb-9B{^h%E)Eo=k^E=FUldMznQIHtJpaj)TPu@MW3ECF7?t7v9Pewejb?;bs3 z1Vw_qIZM0=Xd5ID0TQWd2VmSr(2KVb+K&yGs(G$i^PvQGlN*f^n}W=DuU~5#Zu%Jc zfnQ$+FB~{lqBZs4r}yvoBHRyWgvz9tP3BS@&UEm?8oAw$Uti|)^zKDAtZi+ZQP{LSoiOtN-9eAdac@p}L z+wktOGd&QJ&L!CE74axdgP|RQdH~`|4ZxF8=(5{Tl{*Nj9zFXkTKev&MXYab{7{7$ zegP65#b|47RC-ro*(LyA^hOYN3~^Bw|C|QjHI$E@O_-Ucmez85g zrk2EBx?~_)y(jO#JcXIozzejAGfQ0$CJ^*18D_N}6z?+r_#6ax>*%lVfo!=vS#>p< z9+W2JWOTv5=Il=+W2we^=r3P-wd>#nYCP)X#GNOm!b4-cnZ5!^7-JqizY-_&67YM_ zji;=B3UQJjsobkq5pp#g*Fe5}_$>A87jk&>-B~q0g+p6NK+ICi$$24>O#OpJs?(R| zW?$UVp`;Lpmd&D#263R7C>PwjIyN3|_HqMxOVa*I@z9<{jDGtYx6$;I^0!lfaoKIUd*#S6vt}!zLH& zM``aNT3#7^e-TGo9B{uqEy2s;;!(*oes?k*?60jUYcB$Mz#c=A6B43f46H~_N;*a4 zMWz+UK6#@xeAvJzt46y)yFJHqc?!eA)sBvn(F>A@)<2e2|d1WCpPHsl57_D!gl<5w1IZKT!h1l{{3&r=dpsk}+>JgK5QkH?Fh z(8il6NlePZNde3Vx8!cXvK>+VR8j)oit=S>7?$Z4iTSXo90{2YZO|Sanrv$uwi2c7 z6|P__c4V7bpq#VS`N&4VSCCT)W?*=5qe~5)cBU1>nmqPP*LgR57-=3+G;kzuN5A){JfwQ@QDmSXE?XxJ8oPeAZ>n8bO$wH)Y2o6l}mO4TW0>c?|jq^XO zTzuXAp0j>BKcf)uci4=s9IxtGRc=F_@(32-8ilmZwC_ngmJc2%!&8ZV0OxpRThwvTvMQbC+FgX&H|M7h1U^5@fHR)kkBOxCuW)T{s?DGO zELvn2Ac~%hX&znaK_s2W5YlYGG@v0|&X|VTkMncD)JGyvlE*rVMkE$mOi%QMeC>u?OM38&szTm`oCpbcWOQ zFc-F^m{i@K4DZ4Epk^*zQB6{h?!=})sS`vT$Q!yCnDe~b=hEiFiACm2n7si_V93FNxc%h+!J zfdf-m^-ws&Im*gt`7`vGCo~C{0*_3u3Hro_zmXh2xU6Za|`$U3(3&mMpLE&&Dy58o@-v7ws#w%Ed z5v;XVAWL?DC6kS>9b4$$aB;Poc={hYBv`Wr=}&Z|7y1rb3Eg?t)`hkV`lgCFxjV{rgCoewaJ;D;Bm9R%Z}CN+IqFDwz2A z{zA;o%*}rycK-Rb=?%xo+# z#x|awaj6RjIw!~q7D+Gik)s@$7DpMFT*?g;K3AC-EZWk#7qf!#E@ZQR$bjUqeB=(kPSr85#xx8-=j9HWA|EVIdaQ1js{xA;a@Nkp;mJ zka8%&-Jy=yvoP9>@4<35VDSi0zV2kPBpiVu!#lEh57M=bm)zO*U2ovgym0RFX%gRO zX@tZhl6lJAC_`!L12N+sLEanP-D6M;o)_NA;n~&E;5?@w#dM?H^MMs&lsAc?}d@$_4#B&ybfr!YcSgjXcLDS$4qoo;a%?Q_5 z{`F^(l}PmAYZd=A7yto0(qsSryWe%hA-rv(sYunI(G#EtitZq$F;g7Y7>umI;cYnX z&8JLJ#nMc4EP|himGR6SPuHEIO?k@lRZ&$nmt^_L*b%bwSPG<^U)xm4DmjP#)d0PM zJiF8vPoMh3Dox`keoQsE&HqJCidN}K^eFHjV8uSM17SP62#KUElZV(_gCD2MnS?PQ zn;|F>X^{mk2BW~IDkyCmS#Ldwu+&;Te|~RPoYTsgu|#UmQ-=i1^c3e}u?hS&9w7~L zViWJ5ArSe08S1%Ck9d=y;QoaMs1|sV5wSfm9Ks1x)B_{NjG5oac55k`up&E|ov{7E z9(Nk)J9EbO>qbCiWIIU|(!`@@ zP8FEba%(?t0ZlbjPZtg-KgqsFZj$H(4A3E z=RlC%#iC*(ji0l-yWRxh1C2A+q@d~m4zE_Kn;!lR)qFf9(#;(#4*Ir{D4Z;)MUWN6 zKH)kMW$##bO2CQ-(b&Nh8Z#5`EvO@J=zJ7IEPSyN8%ZOy0+{WvXc;*fDq+WF0ZSQi zGT7)r*s5g8DivgrdRDpI-eNG_7eZ{rK~NoRspW{5HBI_)gLN-0U)R>op&NL_Brym1 zMHuJl>-u^fkM-;O;Dv0^)+;sETDuTQ%XwHp5H9~H0>!B-WaQI>wy5}rkc+_w&lH?6 zP6{_-v2Gp1BOM z6V!odnUT@v=B9P}7FvfEK)An0r|JQP{|*q^GH&}=0&wx4{smWV6)JRcB1uHELu;i> z0U)M4jh@QZ-#(VN`4Kb>NH3|_tbAcdbqg8b0^0NI)W`@#<29%xZqh5B0?ib>YrNLz zHhf<44IO!Yi63Z#!*0yDv13iyJ$N#iFF^vl!QH*B&G~u0_F>ByuQ{j3dx`w@e`9g^lB+>toojqAFK!Tph60W zg#FZXuy$emle6N`At%wvLZnW_nsMNtZ+HJR0$*BSvXcmN!+^74HdTBz2leh4Z4Y3* zmuwD&-4}U`k947U(WhtP74Q_Kj-%9dWL%G8I;$x4%d!7IzkKxw}$F7#*cpe?st(yrTSjiiE>(-0! zz$(H}s9F$6ct6X?m!RDp&U^1CbJ|~?Z+PPil4!3_8E(bzW52Dsg9TS0p7&i=|C57?rK8rL-uDEM=RLC4|V3{dt|a zuIG>Yx}V!a{eIu&bIy66_d;toWoN1nTgMa{_Q1Nwk27FiLIQxI^7m6DkkI1P@srJ; zc32w$a@_U(qtswcHZ? z)LS=h^dS50(n#oR7P{(pfC%OyYu7+Jil1&=U;Y?MBrnx`2v4hw*WC`vPCi2@ zRN6b>68E)K%x)6siG*7dl{zO{ofyK4p$+~Jc}x1X8oue9C3}p>C9bX+EJzzg0?U~A zj-F|y2V^Q`R5-9^&tg$7u?e4f-WqrW{8*P^#uhDPkoMcIoJV(Ix-e50^PS%8@-kpK{iN@E z#W|G;bb#)aK%%zeaWTKkt!S;Hk_$N_;>yDI; zjmdhL8}#6_-NCY+8l$1erR}@f^z-|rpF`o+v_DZ@2J3wjfmKgw(CvYC*H}jG;E+%! zsEz|JF)gjt9F*B3%*_J<0D}NyvVb6kc)b)pfh;$}?b9s=@Fk>gI9mC_onTMAvn^UE zV)+jDxYGBKQMJm?xR{gEdBeqaUDl4=&n)P}-Ojd236LIyZIB@f7;!R(M)iXJ!kEjZ ztjJ|hv2ewnon7O6oO4XD-)gw5QS7&%o~&WACLuOum5ohd6_WHYrbkU!k{qlS z0ItavJK?+(=0RHnEPm#Qqthgc0o z!0K#7l-QDgE;>a^m`HFNfA#V8eS&pwJ?HXBh$nD*o^oE@0K?u=jT_xmf;G4>i$GSZ z#haC_zbP!mrJj73V)nd`xv5(0Di9v+_QM6|3%i87Ug80mj4l-h_zCUaeW6mM= z2%$`JVO-=+6xSx8?60?fPOMyeX2R<43)UYPa=~m@XXiuX+qa4~m@r(cWk>CfCS#0j zrYsz^^p^X@UWM)vHrtJQg;_0XdBuFl(e_7;ohQTxP3o}gQTqN(`!{cRRB-X{@3WUZ z{FFRF-@qV{!m35#h^Fy*YH4ryXX#;fc#JsO05~q}lAl7`gjl8xsJk2G^7g0f4fakx z_;MJ%1CgYE6r$ImJ~=@I7-nf%yg@lbvD45Ib%Y6QHX1u?Y=VOSo_}E}60;?A%wu6^ zlIm-X)h|l&Yl8+2@@ISB6335^n6~f!gRr;RbLalH1AiXEHvh>%t0!dT2EqA$e*NcX z1&6nc;?6XZELnGrAV&h@x6Io-a_2UEKl_=|F|=*SxEWZVa~%2sFp~PN!kCnpoE$hr zSN$(WoL9m@=nqOnw*>+HXw~)WE41@XnW@_HWL9zdHKF%9O37AakSULr)Mh+TpHO3j1pE(|Wj`l`Scr1AW@p8?gTMMC(lyEUI z0sOP!&6{ToFwsmQ1pwc%)KG~$9FO5AJ+&33CkIx_-`Uwtu=yuu0^v&U%Q0f z+61rXo4jn25ZwUQZv_5%i(lS{Ew5naPq@KpkcU>#sLaD0;&nqqHsp{hVlq+dOaxe% z$h(1K!coLWPZ<6G!QuBf&nyr;EC*YKA`97x3(+2J@=Zw(54!4VZVL$td{vFE-Hxi@ zgRuAv`tt`%ha^m4`gDS7mOP_xf8GP=FymN_dO=JUs}4CxhNDYrYU6!`obZESF%l=J zM&Psc#*fuYlobl73c zn*GQ<lANS;vgUaO>hA9T?LB}^EP$+ov8&l zHIV32fQTU1?e zv{G*KxV&R<1fE)Xa#;WVQ=-5O(XEZc=yUQ?gp@a4_zH=uk5;&9z*v7GFoI#Nh{WMx zRaG&mWbYYa0x4^W3<|qqVmdrQ3<2f6{XWEYI_=vB&~t||b`ble!lQ@*jwkOVkzksP z{%c^YLN^;+$K(W?1ZDa5@3f7CJB_wChzm!Nl?AGP05(}m@0>&n`UGxgEwh3-Up-h7@n8zayl-sO zNAv*^d=**^mR^;kM~%AhM|!0aQ~Zo0x6z{hQNe4d_7Las?iOwl&5${E5xGoH`l@$d zLvu^ZuxP_J%#XFRj&x+sZ_SwCItqR4`&`++xdp0Dp95cL{1vfG8*zZ4RSjC_NJ z4vhhIsbatT&fniZfMueP=8`<(oA^OP61G3K)DSS_GXwG&_9kth_P1mMG2Jp!Jb!cn z=9EFw@*m);4{a_`I4O+uy@13^AT>B`LW{eXv=mr0$8r%0>+0%8VftmtY%lbO#~fk^ zCML2`O!`?~`-W188HO29rxVW}Rx1t$(VFb97gA`|E^PR$#lQ7pF=EHDrV7YOjSOK{ zeau+90qUO*>JEu-j7Qx^v9^%-Z83-3oYg&vyF7$c6k6itEfmhomHMg+pO9mc%+cGg zmBJLob~db}FX;z1i8=A#Z|duHp)>UoUHRTIo9EN%`UZ?kO2kw7HJD*`YO0|th)Ak^ zdOk@g4E~|N;tqlvEZUS@+Ejueo^Xt1;KDB1cr;ljaa7P{+(3V{=o=UHAX8xrc#z@* zSp#x&)pH2U*v==g7+Z?z2UsO`7b(mSaTpc!fW_)Ca@~hwd`SB4M^n5J12a&EczVu8 zpn(z=F;}tCIJV$Yck~QV#jaU5#A6j6E~vUU!wBlQT#ks=ROe)m_|LwDfwW;}0M2;K zKw%D~z*ccYz>bb#@{vIHS5%Cp1dag}Ej`2{Vg>fp)T<=S#Y>a$kSi`N{SDIK^3Oay z+c8JVWhi@&5hj7%`m*H1vZ9s_q&-Uc(d~c4B3_qffD7wCbossDPyha##QicOfeW&} zOoie&FV2=wjq4F6r15Ds;e&}oZc8`;-ac#AbZ;)u?ro~xA(Q}0@i-z%@m-t1T<&o> zQ49Fu2IzI`c7-W5?$VDh+puO{a;>B!3Am-4jhQX@&nc9sY%5W}jpDuTy?gR<0D>&S zMBa;X4ZD?>L?VpmDSp8RSPvWMm*JL{jc(WCTk>4aj!(;^qFFF{rE;nr<_nRx+-W)M zW(==upk0OmR7`}Pc9gQl{Sy|*aiFLo@4Y-Wbqq&0dGHPMP3?SqvfL@D&VZM!$|vC1 zzlQty8E+I%0#eWp^he88O4#oiX6x&-2+>5UE3jRf5M3&lo|<)G{&Ds=C`Bj2!mVJJ z;z!XSbNlM^ralPe!NaQPz5p-}Sdq)ku+A)20cChF1$dC;(Z{FF)+@@S8qlHln(O7& zSPZICih^c-*3#R5S*hn2^N7#4vx`s!>f0ZjmIN`D=BSoahY%;50e`9y>4!J-SQJ!& zb(SlEL7FM2;dHgVRRha7XpDeU#OrLSc=^Dl>KQ$07s;@9Zrv&{S#WVL*=j-1Yp{to z=bYg&aG>OH*iG5BYu6zF;*}S0?VhZA(14G=Gzv4;bElv3M1smRl5bkx% zZq_WF0Wl>K6!n4OVup)LkJX%eg(xkOD_?~6o)WLZhx_9&iA~fyU$)R3Wj=>MO#c8c z!f^J?6etc9^;ru4M&6}0RIn`OBn@QLD*CF*lXhyO|HtX!f#`VZq@VyM-r#Bn(PAhu zF0r%n;mk>!eZuV>prFY}pC@5`=xC8lMj!_GbKkz@L=V+6%+H@Xb;lGKU7&ldKwjE& zFhm*l!RKcSNSz)?N(A~_FfCVY^pnh#>fU7Uvf~;F(tD`{;Z=Lx|Rq$QpKWiR7M|@O0U`$rmYvRc@g`XD)>hB zD|wPJFG_?i)bl>EQ`O*)&;{BOH3B3tpyc}X(@+E2Qa2hQ@R$(3!0wd1s*u^oW;J8l za-K(EI`!`xcywR`kuGbxQuH#^9ktSFy_t1b(ZRE|q_$ z&%zs|aQ6sUj6a~v7XI_kf?JpjLD`Sxer6jQ8O=r(|614)H^g5~p;!aOs)Blx%Ze`( zCeAvXhE}n&_N5s`$znxg)E9Qc$~90;fTw#Rq!V(&CZNiWI6P1wE$M^J7!~&r+11y` zC`6}i$Ol32;jXM_3CB*^fJ!0Vx~PS6*{<%xo9+dsw@e%^5XFq+iAsl*;LT=f{ZaOt zji`!wrh8pn{c6u&7cb6-S!{sNu^T|B!8Ny4!P~Z#P6W={#EHzJ)T0xbP+y@de1+w6 zgEIwYX2)JeM(;|mUe!kJ(gT#vAA5ZamyS$3tS5zo9ZLB2?M4WKg2j%M^hVio#9gK?+q;|Ww>Uku|s&gLmwmaJEdRDs~3_$&-%0gfRd)|~FUK)E|nlz92uDZJZ44joL{?U6V?=#eA{#ONf zRzi)qCod;yDj~3$#4dkJFl#~f^cTpqX<}(N7J>@oI1jxdzW-E>K>Z&T6`F5pFwEhY z6UkcuQ8kiSG{^~6NF0X1)>wrHiuvXrC{0UZgzFdnlSGc5*!;!uZ@pV`?S1Y*?y{Ai2o`EO(|6oXZ1 zaiaraI$%ONzJS30(+`ISgDemN}%*)Q+<>UjikGe{0`f#bw*}?XSzq%eSx&4=8aB zWU47amw|KBT7Y?srmqNN6dgC7m|^1_wIv)0VfwF>-t z&Q3Cj7Roa*zt@s3T{zr6!^^!kQ3K3gd_6_cGO;s zA%wu$koJouD;T1A#`;eyaMx}P9ghq@`ECCN)X~g|BoPk&i!cHMJ(etK{T)W<-v?ql zI3UcdSbC~~Wp6A?d=h6brc!FK*Jl$jC<0NqDuJO9hPL7x5&<#!6<$oJ5axq2=W{;T zu$vOZ_dWvc0w|v!AqBc)v%yYt@!g6F=kM_Kji@gTOpX#mYIAr-GJBPu3)=P+@d!;~ z4$B0IPmrmLSIjX!O-%n~MvTaFR-K^vWFwr3*BBGQ{K%tSm?;4r+4LVYURTK}APv@@ zx>LufdX&=95hD=;4EcV?nO3~q%osLET^Bg5^**!c-#lQ9*kas6@ zKH?6BX#G*6{`g2p$#7gFCpl_&1_~kGK$CR{G0nRUBqaQXdvhMcgYOr7948{raHa~W zv~1ZKO}{0F;UYMmO^j&o7-#iEF;W)X?46Fr|1=@kVgKi}MWtdl>6wGqzj-fMf^b;^O?+ zMa@EU#EEaho7YiG>#`U<&LXy(waM>04HrW%NaLR4Q$GopC~|HRmPDW%7LpJwG!OnP zrBnYo*565=UBeGH!;yEaM!wfnrrTn=_R)x%t!UreIq=47s+dGiDx<&Afe!9y#!0;m z6d$!@J;aVEKGHW%v|QPkLh?A_S#3Um%`l05>J7z>o2l%#;=W7?0y-c@>U&2G2R3#G)X{27s-A zk~@#oPxAo-G%Q&_Kr5?ai`5zQO^sd3)z36Dy>P1q@|%ruP9m_pCYCg$;ed>KLPzlB zDEx$wN(;1N1Ask)RU;HKx{CYeT&M^Juj6;OQTja)p0))haVv{8!~Odp#MpXPzYTCF z9D+ATn;87Ha ziF~3yTX(_)C>gWQu@6E8Ng-jMHIDyi>V`_BmTX{g7N4~2)SR`Dxi`MPH<&lrNSQ}= z986PKFGyrSR19A6M$xcz7=zQGHJPeZo6D5Qdte6(Pw0&bWRp`N6@a&s%4c?&5En`kLRw!o9pMRZiA$jzlg_r_vsr#8F$UM%C zDZKa@0Kl^5Td3RW0?q*auu3Z*{WU_=m}_y*dw@`=f=1;fkk3L+X=h$R@e#W9=YmZo z#I`t4aa(aprFrW;r+$GN0nFp7dLI7odKD`hR-&t@+#Z~-+Srg|x8)0Y^A<|IL?%HH z_Ud|pHZ{7Bv?ML;@;NNsvKeXraB||&4BUee9|qGo%san9b^=P}_=k&U#4tPx;teBN zIa~BFBt$bX7!+y_R!G;_n>yntkCnMK58FH1B-xtpCAn3Mp}Ldzuq`q&6#ilypZ-0# z+FJS=Bdl6-cs>)^M)c;2SZ};ssElm_2%RpKX4uSGv%1h|euve!37Y77#)oHg0lPP)Ia!Fj}2YPD2bP0)c@^m4QKY`AbV6@=hK)ELl0=5k zBc{YgoX}*A+aFWKH^guN{zS?{MH7Y}M5iB=NeTNj1MyR$f=JwS66do4yno#y?|SC^ zC3KzJ7tgc9=H;2^&u!w;!C-oVlWH4 zFY6LZp)2@IunYCsXJ156&1KcVu|iJg67cH%uEFQ|m5W_MFHi-F6wQDoic%5#vR~k- z3oyf`Lo%;xrk>kiy7s-Rq%LtdS%bre1qJ0#LR%fydzOT>09~~Pw!Q`38=x>1rXkfLLj0!qC>e8(^&%wEnB{cV58BdQC~l+5pU3DG z$^PIFT)2y<0k^y8daYWO0@kbpQI4xKCz~IBo8^5OWUcZ0N3K=I*ZL-h*?7E|;FAtv@z*byOz@+Ls#- z9vqDb56_%}5$`;9+p`xec$=Gbw3Gs~9^T44PDva3U|A&`B5nrgun&>8YZ{#3ZYqdj zsfMX$$x^f*S>M`&DQefihe8PKe%mZlI-Qyo!j3lT>cP&sy&SY#@}bfgj;^eQ8u}0m zW7hQNL5b;peV;#h;?LGdAHu;cQd>ly>-+<*kQs2$E+k~$Fz29F-N9XKfic=zJgEdZ}sHW$#dbO_{giF5h{hU?lmrk5$(kHNvl@n!j zJgz>Gpk0uQ3b@ana8`z!n&z@%8pl%;gh5niYI`%?cR}wby`(XNf%q4U*eqZSojr^9 zQ5(KGLL0BMypElzB~(=B8q!)=$K5j&6Nc+Vs$tER%E%6iJpyC!rS=28xLh!H|0hcRnYALVuV(hx?jeS+=2r)(@cFl*{+>$yAN`QkD!fT z-eyX@E12k0DYSS68Hq4=BlH2`GZw z@xSM#_k6C2&RpCd+bQ%PoL=5_4#`KHh+DqS_@PbZsp0J2!<-n8Gtvz03A3RG?B?g5 z@c<-0OGWy~UC&@|(E<#!06SEKiua8`+H*m#$XyD>vff9uPzWdQk)DtM0DhdHtStB% zKPbIq)Vs3NuBEF>1v-(n2ZKrh2C^G%jJ1>Dsw}2RsPH!3CwC-@tN#+i8nZda5PV+x zm6uMok4@-|#Zct8f%CF!d>OLaV@^W}BVBtFkTAf5Dkb6nrfWzkK?pF-d{7f^{;(Pe*jE z!GE5yjS!W_ftZ-_Pz7q}uNg0T?}7zD9LqSic^Ey`p$1liUo?TWa3V`BR{FyoXU;sh z=|g?=LezF{h_K8FHW(ad*TB`?0{zhqzty8;d@xsgvr-kx*k%CU2Db8`;Pqf$f)VF~ z<@F_vWfx{grnL940hGns+#21>N^T30#KB*H=bq69^bX2%E-5Y^3lLz99ZftaT`lAj zQOrN$$O@yAawL*Ir2KhL=s4P2lONClwV~rW38`{=-`#i_k;Xk=DBAmRfZxbxUxqZy zXynMf@S>uaY|`|d6T4u=xlJq{~Re0_fTi1`bnjfaX=Ge0E}I?F{GC#Y40&?37-^fF>D3Zm0_ z#60T3R{v3YLtHkS{_;dv`J<8ektX zKf_rGJsfU3lFY}$sfB~Km)L%wuny>tHvlt32)pnfbZM>tktccXW^*dt#K&)~9JC!c$U)@3bjSWQ(+&M~N{JoGux9^3 zr|ZHDO^kXlP$35Xy(6}7KPYH_56b^8@UJ#R^ZN79MBaS$uc%Ojh`h_m#EqR^9$j1K zpgaXg?n@ee8}tL5MADQCaM48dZ~AxLC`=}u)$?;f54WEtr)=ZZ>4KRYfu45>a18R9 zFEbLDaHJq~r}5B!!?-eps6cEy%-};FguJQA!;}mY8+hd)^bgkszyIEvm{l}}UAQ+! z&brTt$$1jBd-r1A*+gU-e$X$k;0)6~(`n>rV`KLW#HO35k?u5ROU>g*yZg#Z9SE5_ zk0LjNhpLpa-cu`lEbDoKU>%2}7@{MwW84i#5*$n0NG97L7*RxrjWyQR-*O@U|G5Ck z+=JCfA3|T_@8Qb^s1u4L=h=IRYtv029zs~q$Rgvw79|@&n2?0suE*y%5#V4Gq*i>i zE3_wMa*amurXLwKMHA6J+%P+^Xz5kD z5fUQwkB8u0F_RCT9IHQ;JqpSv;;pCC5DwXzoC_Ro1+4Rr=a3!n3RlsfT;|u`zd*}$ zsPaVw@*2c*1Lxt8##>(KdH?h^1a#%FPXmU6RP>^NW4Nvl%$IP@?V`0o4=@P5CBTwm zNdEbspFfJit&=ZuDO;wcrbZa3E?mB1g$?ag6+P(*Sgr*))6^iGe8Xt-?j8jv#6_3g z-DGK?*avu0z?R~O76gRw4IWzp@5?aS1D^7(LJ)=#FS}J@kjq9gV~L|2c0*-|TZFaP zrE}+i@_i#FN_?slpz8t?oqWs44hqGG6Xm1p5aW0eGC0iVi7JPeqwnp%X5v4&v>sH} zl8yn9{T9qT(Rn9fMpo{vw-#CLR~AoEyd-Q&!=NJ$?9n3+|2bbAl-rQKASg0peuk6D zvnIchOK+yyenS;!MN13X}4eWC#+ck2YF%NC*visW=}6_hui zXHVg%>bPT&W`~#TV)TugR+@t%>zus2yx{A83rI^(4}t?y%-RWzBjy@6hr$_6ldN_Y z2yH5XU;ix3G{v*$CAQ7oM{jWAHXUVun8h&80$5k!uhP2pcuaIuz_WBB>l%RQjuENm zA^<}CHMh}V&j*SN2z6FylNDycv1W~?1dhGh?69o(5+gqOYT?EOSng@ zengSb5iEt|IWG_Z@^69N@{;Xiu^GH0UJvt1lw1P_&3D;m2#k&}7 zYL;`Jat#QBXvr`Zfh1=~zuwGJ;LNFI852W*>6qr5HAX`mt`~d8@T>XY!_W3YT8S$c*VecW%OKc%Y&&@>{UrS4)<&4|)7wz?#z{ z8<|Q}kG6Fzb!-V%S2Pso#it$7T;lP0IR+5-d(qT16L1IfxjS|-1z<-}3@meTQw+O( z>sE%OZ{bVv6bqIM^d{qYXJcg=<~9s~S$7%(b04@HG{kX$-be1fdd*5c6t>_4q?(l$ z7y}{=+J|Ok3*p_1gsK46I~KtGI=}Q+UELatMq?RxYAXj_1KsT$ms_v*Ysj9z@`_n?!3ib-j-@!3AGvv z2#GgylWZtyq2Mb`j6>*;{gkQDCKdj8pPn@yJ$k=bEYF`leLSfk{-PvB&tuZRe@AT9 zlu?7Y(TR@rg!uVCyzfv4=RdBkRe}T>9*1$eCGzU$KfnL;vpu@Oc=F|hu9Zrd9htIC z#8iG|QTLJ$xs{G+R?3Y=r#+`awrn}U(>8>NM-qLm{`m2AWe6^uhX4ck<~>!c_OcAv z?CZOWy)29A@m~(BhTOV+dl#2q6R(3q65@f+#VJiZw+KC z90BHM&s^*ZCV^l-Au1{t_~wTf&INbr64!88lA4HR8kMITO3>14v1UPt_)c*vdr;7g zcNEoOT|A9FZzL!;Oo@N*=Au(E)m6XFYxOJqQ9_cnu*e!)IY<}V?tl5JqOlx7Rf*1X zHpM)_3KVBOQ{tZ#bTfuPc6FT)9ZrCU0w1d*TA0)DmvE>I!Ca}>b4Ziuju{xo!KhdO z47L2x`MB>i1Wu4X#VC3(`k3|KnlnWf;f8RCq>&Ul@B$Wg1<#vigKF9zzgsEXa|Cv1 z&$}^cd5E=BCYt2{emNALYcGAzcIOSxfbZ}GnxikgIs_X*jB^Pvhugs+c>>L5+~(oO zy=j@mtFN53B{2{xGk^a*h>Xt55P<4H38oe<-(g7S(1Keic!Qdd< zMd5F0V4%DS8;qgism9eJkaKbP-dtR^{F97$pB56=wXO@V$pi(oKc;Tqp zYEQ_a*w!On6k_0FuKkcxbEYsAXb`Eg#7un=FKbDb3GeH$?mN=)Gn$5Zt!H&sOFgEt z-|$lA5>zqIy_cZ^C>I;FQr0;(5HMrGxqFzZixaV=fX@*}7hyCHxHnUMp<8zyGh_qvKC}kjR;_G98zbA8Seuy*$HAh{qsZR)EYXPGe~h%QPEu6 z%%MCC42A6T{Wd8*Ad8Tbz4I78g3oabl`RzZF8Xr*^0>r<79;_ESz=R`xspgYW!}<} zYu^V0xS9c3c*6_Q;FxzI@Wtcpvu9~x+qPM77^_a{)nQwm<)@do`-KBXe&Q%`WTOfr zqYYg6ojhI^qAOnYv0-*SSL6gCw~Md=J|=1|p5!h1s$ysiX?Gk=Bl4t~bLP*t2lW{) zzq=FSpKO-bz^r~C8D5OgYk4u7LHe^<3e(hdTjB1mkHmR0O3po(pe*zVb;$s6S;Vk) z60eOR_~_2XrADiB=WzDdICN|e(4W)@f_TJfPq*#lEI=7*MbwujFw#e!gA{glBcTsu zReM&zSc*M=exwKGCxMU)OkrLzcZzVnj=(AbGUA7>-0xj`IYiv6_i#>&&ox4(5zGI% zcwu6agT7u-J6V5uLjD3Zv;_$%EH9sm1s>0-DozpG>GOumTI~+1gEuh`)y04VE|a*s z<$z2O1URjZj$kj|lwo)q?ZIoZ&7k4KuR=?)0(HMS@bwoMt$IR zffGK5>gtc6>F}&09<-#D{Ib<7K*Zo4Hs^k4be^uXdiEf?k*ZCU_pGlFRHA(VPTl!10$2+Y>Vxg~tKTxOXhra*ZViALh#6y#2U%g}m=@aW_)6 z9^a4@+(5Sah1|*<=yOSQ>J7NTv@G&Y#~}vu~v)P(AT6rkLDIg z%JXPp(VaYtW(TbVUsWBS8z^s;v@&E{-@J3j=Fi=`mB6MTP8$M`S)kKDo0T=qVa}Yt z#yQ^7rhejj(&Bn~dHR?Q&10E!20)_hYiumJ5JafWKY#eJtt5;~02JrE|BE+o+~8mU zeYSphabs6psv1N($m=eL=m1!T8@ZuCzHSJR7D())ESzS*b3P3KxQrbI;*FB2o-NQ5 zZbLzPf@(8_97m|m&mc3hnd%%psX!2QJZCy!*HlFh8H8$J`ZMUk9ksPd{(hsuTlDHz znmh914H*p#I76FR9W6dJ=NR0Sn_A)4?+@1)!M$0sq%777dAbAoopP>0G9n}@b|lO8 zGD>`B7BMCD^@sjtu!~-IVN)U*5$bAcb*SV@@rw3`JJm>46$@#JM-uxC7oLp}xGUC- z96LEToM5Mc$r)_BGLCmmjmG3KP-HR1K7$ZjTEJOg2ta|RfD);*jy%N}i*XGbX*dWI z9xy57!hI;4Lc#}c{xm|8$I=y4p_bCuDj_9^DlXf9Y{)bK8|VQ3a@-iLLgvPdL+2}q!gm?;LAsS0lF zc)XSpl=dv1f{nb9s`4^V&q0y`2UqYG1D+noc58l^H)btBsi#1YO&Omp;_Ms{?pO1I zZlryuj1A1g*Ss~hcX~)i*CV=5;*3n=W^`0l0s1`6L{TN~8Et?0WyxamI3gwIk2W_S zw-FDk2o8abNa?VKsF-~4CQ5)}@vURYzG(T7ghpPygHQ(Yz_;2k+>a-M`}(zO?I2Q` z;Yk!NM=@>Tme9~SK&fl#0ebuZmVz?1=vMlva0JTxb#bUjw~I4}As7@A985B7%;OHRit=ZYy2WN&q)Fs=_zPo_c5U0}O#K`|yiB5R$aOaLr=F29+ZBV+ zOg?yfFhk7iy;)Z*VS`=GbJoZ#D0%ekFT0gynfSn-1y!**a$Bt1pyp@5>#4b%pO4v6 zE=08qq;B=>el-Anp+6~TwBuu9dLmNj|JUuMGJ9ah*N=N}4a)y;9HL34QX_3jgnW41 z&oXBrOVYbWsLMid{xTLk6A*M=N4Nrfyih2z=g<=$yK7*|q z&5qEN{yZ5hDD|3K3t8&%qSbQx*H1TdN32(=8raC|?X~U7<>t?yt)yKZPMlOT=tvnW zsT;Vb+^6?@{*Yuhuc6-6c&z1P3?-?wVv6 zDElig1F`R2g|E&jc%H*>Jotl-V$iZXCSO>xC872S0b{typ|%DcwSwMJUp0{BXbhcc zOX}J;(1tJ6s3^!CaO=VdAm7S!;HA0k${n0yBv#RwgytWDTE73hp#92qI)~xxMaeL_mMie_*hJ5z5tw@Z; z7vGv-uoduTOG-zlPHn>{O`tBM{5ga5~QCCgaZ( zcZG-LiuXL!P5ndli?wl#LC^?(V`IVz`<%`n6$$9%$_AF!PlP~Cud<_!hDI-Rl78%( zdU8r!fLLXNs?|g*+z-+j0znn{QfUmtMwHw%M#wJU^Wy5$ku4Qg9~RT`v1_5opzc|* zN?uMvTO+&JMj+q)*W4Dt4r`>fRMrh$3MHcn!P=m{k&O&% z&JUD?Dk1`lX&e`U>L+qW+~5XG2X~l6BMPhAkkaV{+XBUs1|EBRJ8ZjxVa>pAwqXX} zo+Yd-!!fLZjieJ$2tV)*gvwjA8PQPS@e7{E*p!lPXl(Z-2e$)_rsGtCXt0y3WWfX`Nn+Jvxe4_N8}@3b`LDn;bHO*q(|x**-uUIo z1rZAA6(x!6MBMPXsrPX2^fsky@?%pOm2CW;>sG;O5I`I74vN3EZfH9e%T4TsRN15e1R0;a0P(Zhrml-LTnDk4v_IZ8(_Mc zUHAvK={`K4l2_X(akx~k1}0rHTCe<|o37zfE70-MiFWz;?po^5tawTprc(O}>LB44U8wy;9$A zWcU9)z1qJtR$|=-k`!Gs6ir-I9)?N7kcvo?n;E(e-+|IS{X;Xt_^wFb7 zp7hTdP(fUQci~E94r4Do^nY*W?KQM@Vc*0o=@*ga@r}2kfbUq#)L+5!)0v0&D=B5- zV|$VFVuEF4SHv1McW>P?25Q-Y3c?>0cN*m>O&mCQTkKeZ26FX%sEf{^B~z0eoN&H} z{r8w}ZpX$SPgNWIDgaLtIT}4tcj_`lp|o>l>*>s7(rHTNe>zs284b{wPp4-5g7JPL zaLfaKMiOVV5+G(EOO{ZKeU7sb9%xd!_3guRy}j9qGLOAs8u`E*{Ten>YZ{d-?9%Fn z{!SK=pr@Fj}DfZ!z9vZ|?O<0W3^Xtb^ai@O2Pq8C;$ z_hs0mB=HI9q(+A@^K0_K*{px$)Zf7VJ`k6^2nmt-Q81r5ZA5zNJg6_&?($>_}XK(Q& ztbI?+wKq2+JNOHCFY+_eXAa-Dq7f#+9@sxEk>f<-v$h+bUFWms&L#0Aq@|?f8x0@+ zMNP%Z0TOR#U0q`_Eil3v-<)gFbl1W}VWo+&%8ExY1Q0Z=+Ks{84=4r&l&p1>eb&|1 zFevo72)IO!jK#Kd@BaONpP6%PT&PPU7;+PqI>@P8;Ne}pfgNb8BJWq6gAg*N-h?>2 zO~vgscB;%Sa+k~il=9zI_;cyc>1;EDnbo6QkC5NDRrlRglQql{G4 z!O3KLXJHT03bu4@*E(Oql&=&R`# zN5Hr)n}_o-6W&!|4k5R>0^w|=tM0`xCYcv2sF|U3{9(eaN=;4mXDF(HDmImPEUmXM zUur!%yZSdRRtfOQ?%lq<8cvF?WX6ym8;8y;YS5Gx##=A*)}E+7=`no)02cvzm)R5r zkf##BFrb9MM7AdAmX7)Yc%XW0YHZZJIcWKEclR3l@LG-mbNI5>qR(`4a>wQ)5XL(F z+~p$>OE|aQaTlPbsa4TN6Dr(^WN8VRr??r&Ay8w%hr;UhL9@ z%!aGg6M1JqXGFKcG3b4dKuF|6SHMQ6;9lX$tnW+{i^c{a^eOhTvI8{5_8TCY#k3@S(wL!p}Kd(|1^ee`j!nNCT8uAZ^Jr(F`|Uu zoiK8(Irq()HXA#PKzz5ISx0A4)uE=3gYK1ydtvJys+VW>?U+NyMDmR&#p*#w+*58$ z+RVgO1*Aeqr|%oahk6d27hFSPp*m=`4JoD+$1w!4X9(N`Vf`e>9T9{3dZ;RMT8pl0 zAl}g^{+F=6TTH`jRK9mu3+#t{v?sJ_% z`j@k4z&rvR8wSLJ{7(7~ zaY}jYtg|^xPCsY*K53Jp#8N_qSB4#K*WS^S!dO;w%y{#nYpUFUP^&DRE6726)<>TS zZZh?GG70=!2Unb60N+xIr8xzk`MnphqCZheR0AMaV_-iAQ1B3xDm)vikS$3ocZ&-a z$C5?zdy%erat3w-NCwEh@h_}ya|2Za;otFV=iK3uK*MN(wtD$GP?QavE}>~2jlv~` zshh(gqXsR$1{C-@L=y?L6~hHl#tJpV1-nE^TEcWS2#V(i7Ng0=Tl)bGNp9FJJgS95 z57oCFPxoo~f4FAkW5YBEKy3j82W%CuQVVy1T)7J{%IYvCF0O>GDd#5-?=wonbEr|0 z3+&Gk*tqPWI>rb~kg<9(wXOwKhQPYU4|Z7t>WerwTnjZ-;>5$4e#aJ$xiRy@$qf^( zC-lWO~v?Qf9C{16i~fR3z%80N8b z=|XWn=_|QEe3cS8|o6v;xsWa z(H}%M6!CPs#jdXYV5JiGOmbZwolFp|MGM5gOPzIX{Mc|F2IR9c)_&h-s{yl|mbSJv z{E7r@{zKsQuIL0U6+Ecb7M_=uG;0Wm|Mwff*`B?7?Z`ner+d$UsV6kxOPhc0pN;iX zM4crIiB`M>F$6Z8!JFOP%PWcpq800Ie-55Im6g-ipoKwfIPE-EH{?z1XPdehs)-9z z-VL7MaEi$Zg`!62lMmQ7di(f{yXdn;vaP1t+A2{v1_Sw0#EU{%Wslxbl)?g;%)wlw zyRm~jwEhPIyau`S9eDeQwUl{%0>teASV>(@|KBkrnzGljJrPgePwY?r&|3;V?~} z`|Vu@x5dl%DZT-fyka4!19Fm+FYjV<#>PsCs^UIpPW%t3{3fJ(Ar5GNVgAa`lu0CQ zOB_;eD3~sJ+(vL@nqd!cVSYI(mQ|LKEy(9-(o_>>&$*LG>Z<_u?<% zKBu1Vt%ph}3fbjBz4*_Msb1#r701d@Td!kfDPI&t`vX#WNKENu!=A zQI)6r5G=Qb7cbf~g>Y5pwh2sRC^|~p;Ypb}PP)giweM3oGF4H&riP45@@7^S4^88k z5)}+qOM0l9gPXPG1hr>-{lex6#-u*9o&9uRW1>$rP%+TV21l3gjG$I_Gr#jY$9}(SQeaMX^{x=;xHM2Yk&T@ zaE$Vj1boEG^xKvdQT|8wmE>Q<%#% z!}U}tZ~wJ2I~q1svL|S$`fN?MroAC6KHV8cXEWlSb)$W=P^*0zzXEnaMpDVG1qJQM6XwAZ64HSC=mcZY6b-wY! znVOsL9F6qq3Df8k9%GhePr}*%DhGaXi6xq94jUCzQ;QBvUA2D zN}n#P;L4SjwN0NtkkM4bjozPYYIh0&<2o8G%uM2q0kQ*KV)X_u)t)|MMu8ALtTe6R zvZ$cjQoRSO60&wM&(fRExZk_8b5M$`y9|e-1JAAka%-x-st*7j(zj-Dr?6_hE$VVq7V@FfRohT`0BnyrYs`m!3dkTZC4k`_E* z^Vs+h5Nsl0X)oYRM*rK`mpvTK0XJ5vz?7Gjscf||e?p?O0^`llZpeaIKiYdOGDSj+3 z{1^kzBNvULCZ^Nb&lFofRRXoBPGecheBiu2#EPoKW=Hqs;(7N!q96u<@-)1BX}~(v zMK#cr(kD8GcJSoyGbD?Fkx=?*lFu`aJ!aPb2ull;dP^}A_YcP0dUj6tu*tXx@FH%& zBm5c@n|NMYvl>#{z%E6s=rU}PKimuC>v^f^N`aI*`B*$(qzESw6WfK5bHDTh!u26sz z;j*m2UCokw=WM>uZ}hnjQSHI;{q^YK!xE{`3|o0TJH`$`2No=#O%#gDbQdLT=Dw^R z-t7eP8W%V(1V&e5fm8t#O4~}ak?XWeT)+N>3c}+sE((}AgPQc;f)vqVG|eUsp{BMw zGsI&5(IbrwUp|OYb_kcupMQOoHf4*JiqdrLmYF6;Rx*oLS5;~9Sw5l$F!YEIwckIo z4KYN0kIgzVn-DAtn}ikO7_`7fQziB8D^WlWm(a^oq_B+UbcdT8MG+E!rJ0#F8(g#t z+nPl**R?2F!J>+>5GmwtE3rJcwYPu9W&G6qZjqa;53#KKLU)!&uU{z=LppW+!!x!5 z612f>assDc{&EnVGWrP#J76m`t@I<$VO$xtpOst<810BT_>54xw5dv zuIGxkUXV*He>e=%LBEesgv9C+dvW&LWOpyYL1sG`&}{l3O``7iKh3G}K3N@4{v7G73wpIJ?k!*{Y^aY zIg`Afp!4|vJ39%I2K&OCOo zIxKii?^2^ zym-BZwn9PdN7T9t-5L4v(J6#nT>nYq$gyJ`F)vZO#8*B9^(ZL?H02kCqW;yZWrTp& zuy;aSs&$fRrOur?-Ccu)7Pjj%0GmBbb^XKu8R>*1t!x4E+Sbi%VK8p)yG}t)CHCZ* zjKB}E&;7;)bis`Y@`gU%)_C*rX~x11z(dEIn>T9Jv;Wk4AHXcKB|C_NyPjulThr$a zU;9i?bAVH#i$&@dYP(s^JbN?sAb}H-Z@5>m7UgSs(b2Tx42STN!(5%u@2dqAmJI1+ zk1kM);ET4BZ5Ca|eO{4@L93qGWpe~w^r{V1XWiZrXn9Vr7%Ps{lue(Xc53E}ldh{U zBoGtM=?|Jb%l>mtPI~*~3B<`(KD6V|aB5v-FDE zfFC`VpqRh8M=*9LJ*cQigo?3;|CuV#0T`GUuJ3R$9=>Nor4s5Qt0xZeg%O4&wNlL? zK^0SpiPpY~lI|hOS-}#$zd#S zK_MZ6O5Az&n)3APs91%$==Ng4WZq?aAPD6kItaLI)~bXKg_?=TmacOxO+mu`{r0>8 zs^|q@4PtpB%ey%5KmKNjORji8H23)X7sb$(wV^Nz2Px2iP#r~0NFYkzqdBvWUCA>xpcf1It zU{d~+)P;ZuGuQ{!KYKPr3>%VIs?J21!zq->IK69Jk^(jr4)Ip!@y0Y9Jot77A%F{E z2p(efBA#BR!-sc~G0zKe=u^f($-i0T3R#aPz<8OdZU#`(`Lk!^S>?IVc}Hl42g5yn z&KBQ+4V;LmI+-{*v#@eQk+eV`&e-DFd%jHG(*nSy6T6D7cfibg@IJJVctPx!`75D> z?42-Xad(lVn8AE(7mU(3fz%9)egQO_s+7od_^_!6p8hwJ2I)8^zqHH= zV>W&%V4Gq6g{9xU|EQh-7({US&qjH+)Kpg-ziaQhsp2%$gZoEs%hA`BR##i>s=s0m z62>O=1}*m%;)}R4yR9{?oe5561--rds5$V{woSCQcBXTZ{h=K!DN5LgQ)uq5-npam z`;prmbu-{oU4kn_cI_h4Xx7u_Pd13orMgBjoSCYp02}w#+2+E02gT|#rSImsoSaO~ zYB%1Bglm{)7|8w)`GGrg3nsoxc=;eXCHnU3GOS_5pgbEY4 zJeZWWep=y2L3aaObj8K5w|3ib?2)cwQWnqN&Jmz3jf9HGqPirIkj4DdC*8HQZt}z( zb1+sA5RE&jt8S>{;Tf*L0Kz$eiCVmzI$cC2f;W zeMOH1cvPi$$IJxW6zZp7*DL_69kqQy2d}yhdXL2BGh)~bLWtRl8Ov+$!D*@}&H6y5 zN-hhm6ekm%hqRQek%rzZEj@DS1rB zJ_+0>EfBVOxLQd^4p6IyqsSVX$u%&@C}z%Fy1zHl62kfpAE@27mJ{$d2*FQ4M6IV! z@9-TboQ0TRePD!s2tW`D`lPH-uaF)L_ob1Qu>*thPo7_M`u(}M5P(y};!BY-?Xb0aej|Wx zJ#>T;tar=A%xVCt=rJ7Jm5l$HbR3t3H^y^fVpVyPL43W~Ki=X*4s%Sgu~k!33y12n zhP4J#&Yr{IYUM+PFy*#RPPviGLd{oXZHRyJlY#P9;WvYa<3=HO2f8pt9@IKf?a64r zh%yw-cN(o4SV*qVBXA5S_53Hw0l7E-OAd_W7sOx;eFmQ5f+>hE66X!*4Vx|+VgSkq zEKCnx+}a&EyunLg1TQvj)8@=+JC8-#Xym+W;L3@Q(6gq`gw-l1&`w3xS=yWE)X|o) zz1a%`4XA7ks1}#U03D$*_EWZYb_K&C$D66QAg!yIwK@dJseJpMVvsYscklECM{eIk z7W{%Qdj-z#cMe}YXWb!SLrK(cXt+ikXJWuQlUFNLH0+nYf1F#x$nQxeL3Kq%?xcg$ z1oEmtgwv9BjZ}wsG;tGzxIcWjL{zonxnkJ63RhDY%h`ZBuNVf}z?z=|jlZ<4%uvU~ zX>3d2IG(UxQjw88^%QCaJMCWLLV+{rLVi&@alfY0e?u{qjh@j7$o3Exr0RkCk161b zSol}LAe%g8%0Y&ZPF=f>xb@&cgD|Wj4Q;0xYDw&8C|&%tzPsOZlJ?;fK!9;zJwOQY zB%(RA3cdG^;%e{Ozh8ss)DA347s6qHvp&V%zL1%D*n8&RQ9#aONxF6A{nN+J96T5@ zb*JHR$OLm79E59AjqE*ELfRNsY8hVrnf$oMAv)^$1#IT9nF)reZ9(H&1Xl~8xLpyF zM>?PuAo`wAEsCEvS+O5h*U)GyJ29Y|Gz#|dX@}XhmokpEQEt)VE6VF|5Q9V}z9EF& znM2lh7K0wpDhsC16V?W`Y=JU4CIBqcm>KhsfgFR|)(w0SS!X>xOfD7ER!L!s$>5%3 zW3f*OrT=T7PrGq^M&BHs_7ChgdQ*6!IjREFJk~L&TdL>J;N|?loB5LY?;{sIJ3YNM zE4!eVFJD?l)%bb*B(iQC z*U(>o5-gKiezzTJF$Hw%T0pc+0Krx`kTyYd$;Wu36kMd1Z*9f<*T|=@=5>SM`g$B> z=D*IJOQ8JUl_od%u|s!3!{S`vMrSAx;w8TROCV#voJfggtrA zlRcOV=qqfkt!8MmmsAgnh%f|GX!8TN^#9CdfBki!jN3C=qx0=A@7bf%Kr?1Q*LQ?D zT{vzh($mw63kySADsr`SbPAbGN512av3cG10F6G#kOlj@SDc*noN$LgO?Y26fq(xO zc>yHY8jd%?uZ0vl&4e+VH~kOkA;P%vK(#qMkgO=`k55`4tW(%nDF7#Zwp}q_$gds+ zOUMP~me1v}1oho279DL&`M9uUnkwt_IP?hC16E5luFJ`t<8K z$6ch~95m?DCtNe!dLF-X#JUgeCs}EoB>`iWiB2W=#fBzRf$buW;z&pjvdImAOp9y+ zjC@kXg9i~)6Zao*iPPZjo@!nW|4g z?g8Cma2{{A1MtoQ=vorLAl{omTVvO+KW9#h%`vAvrLvLTii1Mo&*4`?KUK(cnZQ7h z3#l!Psc5pOsERp?XJgBS2J1cH9(FhiU*`B}fEQoH&b^U_WU#+}*!`+1RY+HMjOu&X zD4slZiYKG*c2KwTu;|`FK`9+*Vv>q)$5FI2X_Uxs91tIA3OfTOUj^Q^u+oIKnXr5D z?8jgLF}R#O661ATeHX&v$F73xt6%zSw!EoDJjQ*SOA~zuytRJKO;Fj@4K2V>97GSgBYwnyGj2UA?;9BHdoN23RR6 z^qqCnctEev&wM~4L6BuFGvNl#3_`QogFRa4bnSX%GrlNI08}O*)8bt{^~mkV;O~t9 z(+V6va9=ZpSctT%wa&o#5vl3vDg1v`rc{OIMt&>XF(E# zHSga9$8;4Z? z@Z8~}B+}N(!~tvOrTlFnHORRK;j))F@4GHu?9FF0b9lYF2fb!2cdA%)^R6VkXO2TL%$I`&5DvySX$MSo;M-`(-NCYf|R z!n8UH&bE;T-4Lb(4;Lm%^_2c%Kp1%MB!GUWwt!1R3V`0*{yZ~VF@4WVHi zK@pcSGaWave=emn6AM-!@SVPpxc&>rVRQZzVqQ8{X`|`RJ|Gq;qciYB`>cdtTNsa~ zKGaPf(+NDSxtK<}0Mcvg;V6O9cCo4YZ>X+^1?#J_oQ>PNw=B;zf>4V6u$N+xUiiB& zU;dQ+r9a?I-8O3j^Bz3{I9E5X1ryIE>X03PE*S|sX3d)QnC&|xD?8cXa>D!u)Of3; zBpe&J7Hda#Z)CWs!8Ph4uSs`$w%C_E*#7}cfa0wvQ5SH}`iD(DBaH#uv~6pdTAbk8 zX|sg3PZ-i}B%oAF$g!mP-Qh6!#=SW>Z$LWRN0Ej2a-8hNu8#$p9gg=??Cb_b8-~O4 z)MssF=Okg0d?c_bHWgcF`UzFgJYjp6`S;1qk}UlUBp- z=nR6^`}sLjD4(4@cP^9np_X-tKYK?Lyp*}fQX%?q<7JlliW@G9rT|LqZeocd zNmM98aNHG7!U;AuJEenI2kYlKKP`;Ic@eiTkQK6c4SZtzJ&T46GP&1wn--`~Ny_po zASS^4Bz8*TIE?kezQsJBHFw8M;qtq(r+%_y%nxG@PD60_DT2xk#pg*fEg+C6(U26q zC6o)jyj&On&LG@}c46v%W2?UEKai+6^_8$H2*yY+U=_Itcq8iQ=mbK~L1P|+<6QM1 zAh!qZgR-bvIxzL>Ap*{0%KiqIG*6H^_$^ko^nT-kY{Te$+?FjX-MIKe7Ge=QM{V(9 zZKQK`>1h+u{NF1`1T>g!H1EDYV9q0I!zLo}n8R7I&*2iW|=i!Ou z<`e=uRFWu4qtV(9{;HJxsJ9otMv93hK~vwD_=B*?7UYxBfJMU{b5ql?MC10M|IP&( zR>l^913Z^=y%xDIMno3ie>2btRq~7+1+?T9|Hg|aA%?yL3pCQAkzg3tna_6@^IzHf zb%dBexyuFn{R?QDc2xZLt{m%U!-*+6} z@xJe~_d{LRd98D;|N0MU?4%(v1o1`CPom}SCBVxjX3@r~Gye_;n#`ID`34lN1v*d+ z8_X8r!^=}F)!2WhGx`{PcC}QWZXo{K!F!?qE;v)!tST@RutGu{saV(M>-Jg2D6$nk zTL$2*0mL<9iR3;_!)PGO1CF{zBJm3Afgnuk9p%iuF1bX27#8s}+)*Vl%lWc@T7j40 zI0BwiI2?(nYkDflMRCCwir0hpSu*!}Jl0ceM`E$yF*b8h?%lWVRNAWQTemtnW_fhE z#sJ{}%HZVSP=~kJJ$AX7>_df7>@_s(HVYOUxx`gC3jZjAZFh^Lqf0Cqyb(#107@i^ zMf5MQ&T5w1t?YUwbhRf4s1)!zRxmYyyJ{mx_P{wnQ+V<~&5~tHRlqrIVriR2UXKjU zRvG-v8yPZO7&bkP5`t(RJZU~)j;=-Yo&-cqeat3rsGUK)#}%y0b?^A$9^f9(Qk}P< zy;U9WQEN&}D+-KsmC&B3leW!0?dZmAf@xcZTNAkRX>8R6#`_7yi7Pa<`+(l_;29Ra z`nV2|$QTYCTg z{FC^J9q^HkkBl@kUL~h2oMD54c2YQ>N_B!u-KU<>94*dr~#K#>dq{VFRe0{L=- zSiw*elPJ_L82MREqU25G4{W21pMZn|(HLZ65o(Mx zWTU|qr`i}0sO-n84hL^vO$Xwk8ML+`n7>Cck?+!F6u^Su{<<6XZ>A7^qO6usc%2xw zGhj3Q!pS!SzOImq9%XyFFw-%IcJd@HS_m4h0&z`Wx9(CUyE!{dFIJGccGz^?3&@^! z9Lul#?vHVq{el%&b?1&9J2d>Xg@Cvf-3?sUL5WKqJ)t@4A}51^SwhW6u0=;djhpu z@U}H0>IJ{BiRv1}GfPC7vUKIj_kcQ+nKhf)VoKPH?Bpv9RP)06t>?pM&t~wO1~L%s zV&jyRmDP#auEf*B!&Ufq;z|JGGfM3JP$rBb(vCH6x1Z2Wv1B#yLKcx&;2s4Oys?PT ztr5Dq4J=`uV9`)D7eW$)C*%WQ(XV%JSupiD<#0>#oVidnA*46)OU?p8wjgK=CzPTk zhW#hA5+-Uw9;f50z?ZaMRXi_2-@VQ5f{9YxJAEQ{?0A8Z+Urv%%%Ou_Wj2L`jk1Kqve62%tdq*Ia%H3)EBFJ!aPIv5JwSr_)3=j)CZS^z zDsz|Fvt`+9K4Y79M=Y%Q~Qk4)y4v|LqaE`pd9Jn(I$X>R8Sv1D&!6|LpN?#Y*D z=|k~DeqPl~?g*6N)^CzKK&JpC!;Kn;$8s=Zio!H1m08b zL&K*xdi3lvK7I%{hWI5q&z`M`&6$CZi0gtr$6zJ1U|V+=Aa7>UBb4e|dKK?yS8HB^ z;%=gk>xJpLI%^#fd^LsHH1?cx`u6!}wXe_mqZI09A2V$cXT_W~L=)55sMJ|YzI^;B z9i<@cp=zASqJEG_M10*5?17dJR0WJ^N(S@Xu2Q@Xt>_<`rRgvW?$A0pd zv130jiTC6S)fKFE9`-bmMGB)7+ELFsl#c+>kyNHn4=Os6t;iDg8BaBi@p>p0=ER4U zV5=YM&0eNc{INE`gj}jqE9jMdL_NKLJO>T(Lo621!)T4xUL}(h8~GEr@7^t7^bTLU zVZ$nflpN`NS~4ZhQzAwD9lJo$wj6*ynnNXr(z#JSI1~c3_RjEd(Mi|vH?=ZGgtLEK z2H4dHp9RDV0|xf*pUkxvat2#YBAfrhRkZ-tQ66bCW=ahlxpYd0j0o2Crk?G?WF5mj zO&mJiC|U9$YS#R!B2p0hFmHz~{eT$8uB@)dLJzx-c*{ZraBJ-?R)tt(4eNA1 zHX`Jz8AFMDhMBGKUPJc2^O7hi9NlBko%)4aT0s5%2Zo>!Ewb1&OP_lVB~l-D9MM{T z-}F^gnXull;r;tb8T4=`u+2Dj^5ir~FSfw;`U*II9R|(>_q~eabWvAn#%F%y6k#U_h)`8p8ZlT^)%zPiMKk+= zrDQN(#k0bZ3f2|t)IbKVeUu!aZO0B0S4s`kLrXAaC%Oy^GF{$H6HYFHX!|{$}ZeDy#E3*GH5lYv}p( zJ(04*c|^qJTz#t=OIh_F4JBXqU*B?T)ugYR{w1ap4p%Yi27xohfn6*RYE3KMJ}F#B zc!K_>h-~5;Nyfk{6@iK-HY7(0q^4fQ+QRMRwZ-2imn#Rh4u4VTp&DOR7TC){_(YKNW4fzi#6v?Mf-5CLc#n8K2 ze=WkiMus+TihVBgk3M-;<%V{q!2Hm*=xf}d>wN%+8!eGg`La>|A#vYIHc)p@m0K^N zogahYAH<5=;sw`&CMEW+_)LAyuw0hZ|eb}2B z`X+a08hj8gyF~Z_1}s7+VYNSJ=w1^`jVK-9@o^j5Gq~%{&Vz-?lv4g220cYUPe&(_ z-8rL-9HfL*G-fqCeh5F?xNa`Q#KvBxl@xK>ICqFx6NVRQ4*$plx#lbS1t0wQXg+;G zg-dW~;N*s~Y&KT5ssUh~p((Gid;4}3stp5{#vBw5a1La{MOoqK9B zhDc?EJ=?$>_ztBv1bd3FydDqcBG^6mgbA_8q&u)<=gtt0RRLXxKTyCiApNcYZllPY z#05l8FJTMfatTu@^ut`;6gi3qsjad`4K~FM0@=Vhs%ZxwI&U7=zSM zO8uVNwE>cKIlH}F)qee!^o1cw2Yv+@Ocr9H{O)Y3=H`ES+sjDPc8+T>4n5_Dp3RIx26C}=IeHvE<>9R0k&p+A8iMpB>l!n4@JnqvYxyIb6$Q_`_+TF+H0u+|?i z#nzGA41Z=c?ZNnn`X_uFA>g8ZaD)b#_#NG*Wit+p5^=#RA7h=6JTqSUiS)T|?AbZ5 zqF1=Xu+YF+Y9Mj@jsUf-_y?GyuICvTuuU>y9MX^k{;j81ism5{e`d_C3c(?d>EX|F z;NGhqzL#7!FI-qJ36lo#xUD!YHzBH+>Gqin$4YZCr}qo9&^a#S9=H}=qgTWHubV%W zxdrNiJx^`f`t_IFc<~k7!3Wjkv3jtCTW!{i9oPZn1?vFXxEp*dCQo*YKW2gxlcC)? zU->B#ZD8Y~nO>kT{Lh z)-LoS*ptoy=Y>c}!yP0_GLqASY|B*(zyu&`FFVG3!P!Zf+<|(G+fcxu7u)# zAq(L09dibuNmvXbaR*kh9nQ{s={9hlRJjJVQ;HbM&sbHsnqe7xitlVFEYJVgP(A&&R@{^L6$Ef*35Ya5BOo?X}d&&o=0DZK9 z?MXQwg$a+Qh1hF@)^8gzbDf922U6)8YyRJRmRZKknk8^iKUU71lnLf6mQV9&uQPRb zMrwsK{*<7!kYz=*a}`?A zLfAH1B0dA~TVdD0@qKV!i)ArnLf_Q%fn7%e=!+e-razX7adS@w3Mj;B*LrgDspCB=WO}(w6r7`0E52@!!FK3 zIG?(({$U?LxJCh(@ONumzU6#i+9!nf!Kabo19I~bVrF9$Y24pfC%6cGzhM& zh0ZDEjySmm6Qh*?79$jNtRL@2$Hq>9Lou2`SA^ zlzeatO>PTAT@s&VAE`bub8NNx$(gGthRN0$uYj2$LlQ0%^Xpo_OOru5MV`{y-#;&{ zVLaz7J?I02HwP^a9oEtTJCaMF&^RbaRRAx*1u751P9HdFvkT?#l?WIaLuH}<_s||O z;_m!evy!p28Y3h*xJ@nwe@o?)%dRwhx&~t23V1pJDAl&aF518*viotWP)|8^5XIFT za5tzV|AS@1P)@6RK^50btCgjH2bOpv3FkX{l2IX0sXK#deIN-4X4&9-OFp_R=;89=phyw}8Ee3MtA& z#K!RW%-MgG0pScHHm~OL>fzb=0SL?T``m&}xG@OpfGyxM(TkjZVCOeN;Oe*%ediYN zXYK5s zHP!U1SMh1tv1?aD6&yXHbm=MEDdbzRCV9d?&BqUvID>yl$uU6T_o%qI%WvGdrLR8P zfIJ&Q6QV;DhP#uf^x*9ZQvC*cZHw2;-jL1?pIZHg^RZEVvY9+UMZe)MZ^zCQp&x~*LaKgjfLl(qeO}&Hp5(} z{E9&V33ofOdy!&QNtSRhL(X;7VZf&6V0VpUc|-FRLF%?d63I&12i|8h_lmBvRZpm< z-MVy1LL-ukazhctxoMzV93Pd5%Yp@G_51HORkr#80;mMY`52MWI%r-zeQg(;XgS0K z5x~*NqpJ|V1t;r7%o%2dQ{HxN8Y#zq5L?Q`S!rg71iaY@vBzMornV#57{2EKj%1i{ z%Vvm0Y3hqigf)K~dt29;5KQSJwnJ4L4R7QprhxBQtvA95v1hqNLsHFOFxf@#;Rjaq z_;;!7;uy2#`;8-lBjp#Jg$kJ=Jl+8$rwV!bRxTaktqv`NYog;92<1uqe=AUi?`g=J zhEZqu1w|Kmm!uY{@_lofer%ti)?Sn%JiCcrY$Svp%gRHe=Xdbj7JhgHQ zik$XPwe8noMWXcNuKe|M&GS4CwM5uwilsT+M*}njI8RQ4U4G&OL#}~fp1jr9lD8kBC+c?`E>|UN$Ro%j(-kojU zEk^$uw2<}+QZq=gV^kEr2t5z|O5NMHc4A{mxP-oms-n32lW46RV8(TjnBnuA$;Pk3 z+h|pnU?0A-4}7^(`Asc_AK^!mMdDbkX5sr13;?Nv!}k-uhTfnT7Q3gU(D#IjaSHpV zqA!>(`x@bZ2((-aoDs@=oyLCr9=Fw3il>NjEi=4Z?QP_VQzX3ckx%d6*UHu3B?9Q5 z@S8vr&zoRkQiJ(O$*Yez_O`)ob{rNH4El18OG5~(Si0@qYpIb$q7zcFTl@Cy)1d`G z)oU!coyc;=#2-4eTm|_6`NDaJaZM_LXpNBC^#U1bog`+Q$%C8>^|U$DQ0wvaD18CI z&C-3t^>cDLO7{{TW{yf|CLd@mSDHON8-YwB5gZNFF#0@~>l+WCN5I3t%Vyd%@e>__ zJ=?e)GprTRIOBGk9%|VGo`~=(_vHh5&G_u&=2jPoPKYSf%*Sur*?s+(J!_U6mrf#|pNL!#&&qn* zv|f+Bjo9i{I6F9~Gw5Z}I-UgXMQi1OJhF|SG@o|$J~i8h){`B>o2c_ugytvGO&Z#PNB)lG!5m2QI6R29hAmMZ$TIe2oD&gZ%M@Y}QA>ZTcGZAqJlW@rdO zZ@1a_2a)i7oJKyAoh^>v+r=qG>Rl)j>~GSHV?P@eOQ#4k#T+KaLbQ{nB$A>K<@&=D zKkzIj*7&bx!nd%ePQQBeHefwJ+#DQtCTMEP3gEl>kFhLzK)1f~axbvL%|gXVmr34D zVL6lrcN&{8T zs;VbI?h83NIb-2Mca|he@koleJfn!)Sjva03QH6NIRzJDw+|9gfS~E`RjXF*W}Vf5 zuaL#;W46uDvzPR~!U_DzHp zlRzGd;(A-fZ`TvgxnnWMmi*wsUSmihp=KR!#EB5J6_8>~zj)z71+(7^V^zgROV0<3 zm;+aR`AD3<23a{={r5^h4V#S}hGWJg0UR1#d0KjXZJP{q(@Y)QXWI~vP>O4?7!etd zb6C+aKZkK6mGhia9d!qp5}&pA;6a6Lp`kJfcW<>kyO!WgyiL2{Y7(hAh~@wL>P+di zqepKlVRLaREiIGKn!RGx8&+6f&UBtiAKV2_Ll5|IQ^+3j!<#ZxBrk&XJ^Dm~NEt7o z?9Jzc;yjuweW5=wwuy$W;?5el;_1`3wEK3#ScbFc54kXf^;}?Ky*W6XZm=4kdmKs~ zW-w38i;WLOMdfu@Q1D=}aNAzF0J48D?Um?ruY!^E_B?kW####($@yX#DMnhNLiq@q zp<>|U(wkGLj<&IgU%rjQwp-aMi2rIgR(iEJet+MI^UwZ_oSbSAxCcpo6_zW}fOLEe zf2!G0_(}o|uvgrORa7H=zU!agzZ`{$8b0vv~<{qRn569N}qxd z_qQ+wDFMY*xM#L1$>m{0Ay9t)$OOU`9hnyrqNClJ{up%_2?IWHtjZ|XiTrM*+_-;< zxa4bQYPC$K34B(w8Smk7Fa$u8Y#WiXi+J-m$(U$yvQV50hc$gPBUGhs!Mez^mY#Wbp4?#;ol%dCEWo9fLLs(tt zk=?{lF$mpN{Ro$wGGeW#lTgwJIAaQ7Yq9b1OBpT>-e*_w0UGU4dVUgtEya$MRZ0e` z>@SS$1h#g?%-MQNdutvneci3eI%NDR6#5Fjv(N(=Y;5cTBTBT4axY6x z*L!6Z6|b4r*0sR)CL#pyGQ<)e>LjG=F)G1o5u7gFp+l#z#OzpS9I=V)5}c@YZ*2Z` zc{!}c96~Qq@t6S0wlH_^%+Ai>0Jz5^Xd5TwzA`6jf{=;WK)TZ ztJ8t!v9Ua1=5f4|`1L@!!uPAxF}*5@S+`t(R-`ac`BxxB@aH>f zWW_+ojN|%Cv=8&(eFd>v`C9zaPg!DiXwsJX;1ab=W8)x&KyOK+*)6zpXL>u&E{H#I zJ}BEW?x5+>9)BPyijkS|JV;5-jpnhRYGIA=*JB*t1^hzgL&%oFcG(#IOaR#O4(2Hg znkFH9CZNn5vQ8V5>|emVjr1!OAeD^B#xb~4QFemub(HyD@6(jaQRnwA_VB2sj46X# zKN{tJQ$8F92(0={boW?!y3yIFqEV7U5^zWShSp3xS8G~>FJQxjU%qq+$qU5jBtt=3 z8*pWyGj`Qng^}zb=1rS6t(ueV#$s_HEAAx0%*O^rTHj_ZAKai4dO$e6vXmPrqCr4Q zEdccLv4}W%=FE(qILxvj%9zIlT!(!io@D6-m+Vfz z6u~fXj(KAzA15nIGqF&YHCLKPkFc7$Fo#00m+npk?Lq6PG;&^?KzTWpd zN3>QCs5ZM!;|K-0E|BUYOay>J9K@AJu)x*h$HSh2_Dm&w9Uj#j#2~*=jSgZJCWY|0 z0Y>~<))!DzpHR_i8L;SL3Vn=K4d@k6V}4@Q69?(eWF$cH4Om>z2tZ+t8-P=t*b->k zL=$Ob33{7jHQa zM-?2x79Qmxl3qEoV)kgq@vVaA?<8E?pq4DeWe-+Z36Ja&C^ay9twfZ=LSBD4Cv>R3 zzET7b3}5_VLQ>vAcbv=swTsIUKyCu4Ybf022`p~y_(x65YVttSGYEa}6K?6xrErm# zj4-+$T2myYJhp^*fXFGyI z*U2FGI;XGjQ6$_ffGkS(-3CD8CG;`2 zbXb!`rYjec#yE&4Byf*#(c8a;0awt|aQfLMWWpM(+#3o zQ2ageS)s^6NJ1;+2K4C2WO9yp3(|mY<=3~#af4U?`Tcd0AC3GR`0n|yJ_f->p`+Wa zW%Gf1XB`vU>YIgytB%b)Qc9%?*pXaI-*bj(rvSOdg1K{tt|V_kba}KplgBKnI2y!@ z6lS1pC{!)@cI-jU^kSx^!Cg4VY%35g(b#)G%I-oG!y;&yG7vc6Ra>%(FGJ8(BTPDx zpNcu>0?rtL$g><^iIu_oFX9gshVcl~{*Uzc%c zvnN!R9j8>Gdv{-W30>jWPhq)e zMhQF)5Yve%m%kay)gE5)$1scrk(Iit4DHdSOF#J@JuV7k0(OA%wAB`5Z${JoeSkk- z3-*2SWn*J}^xnO7Y-vOifobfbflw~Lz%_} z;|#groIaUh3@Z^C46V6sZ!SGQ?*7s-|NifPQ^7yXAv(TiCH;Y^dIN>9lA(l!+QISk zE|d8-L_R_=rELeGl$kFq;4T1D(7BH+q`AjxHsy-?SMsj)^zm>FJrwiOG@H z$ul#)U1h8)i2NiZ)d>ki&v47D^t~_mgN8cBuAZuN^LBk(8Pt1`Q-dVUhQ7W;-la3A#rXRI%t(aTn>@Z^#%=yE?gYzbC8>1f=?tdQ4&)YS^$Is0 zHL5(KJ|`FLSX8k`2&%|~V9Zf4W}Z-f{V=M`6A-&_!B2y_5Xk2>14EV3AK0nPfL(bN zVX+@%MHXp&Uva@hrt}tuMU7x`XQrjT5Ddr_6qSM-Ki-X>O8sAYu7*g18A<~OcAs%* z+{?od2|>eaS-ZTzB6LGhW#wL8SA&!8e~Tk%97=>d#*Ey5rynN+UTw>bYw8x!T2=TN z5D5_U?%B0vP}rLo(w#!xa`+I&5*2tEJX^T<8bVU#263l-aV7ud8Ff|xx1PVK;USyQ zunkv|R|O@zNDBf#G?X=qHk;S!wD1k|o%fN>wKL-96WH@An=}6dfGL+wvRI6M5xaN4 z1o!)g%-|K^?mbv_mfwYT%HBWHMKA2)^2f)1zCLxf8eqB>-u8X|p6)bhhHwh>wG0eC z>;*vDsV0vT^7J}x?~drohC;O%$d})=B~(Xu!h}QH)V5y@c-wyiPD_~GM1Ny5sTOW2 z*w#s;uh3K3;&*GtR6^^S)k~TYgz*`^C3dP<{D8{@(VckH@RZW2TwEOxFbfi#51;P@ z96_|XN8qnNrf`p94^NO)JYWnwlYTX5TR(v`exlsPv1)FHH1SL{#AldLw1f;F18@s~ z(LDO#`(P(y8AazQ4jk9;4kNT1*gZ|>{w}U>6G8Ke-Mb6F;8NNVRV|yYeA1*}b`<=B z)A$k-I-JjRoc3VpCbA5!fXuAN`)?i68z-F2fG(@>s0i@#vYyCKoP!;a0G(53r5$op zF*P=hVLBm>j``2wsw z3zaFblB^NHc_9iA+p>4`gMx$L&fMZFG5bT@OCn!aH&Uoyw9gE=O-w-~oL3i!69Wbe zAe7}^A-t5z2M@LZ7>o6kC;gHu`@;J;F1Ev3OXdmYPf2mOOk>M#ykZH0#JNI#hs_f zOASZh0i<$>OQ;dQ^#WRkh@XHSs2#cj?L4HthkE>J_7o(%RE$M4aRu;U8ei3K!19m;o{x}M$)#EGfp zUe&aP+?v_Ng#WT%UP8oX37hdey5KPrA|UUqT^p6IjnpwJ=i*z_gxYfOC2R2nIv0-oxD~Iwfxp zkE0?WsSl7UN&|=s{Yj^-MTW!Uo1@$3F{^r?@?Z{+-IGql&v7r@LtL1x zvv8sj(52W?E(Z1yCfNi))PR#zvbDICb-EU&Vk|$OlwUg0ihf&hv9dE@*0z`UlKx?8{bVW!&pBIn(HVc`Fx|E(C_SKDO*9Y zbn^B-ZFTnH7FKin9CdHuWIGlduLiO*U{tSV6&DC(tT>g-?{=b~Fs``BLdOTEh%H`aa?pWUI47CT7}8qDr!8Q{QYk zxopPj)#uesOq{s+oa&Gg?7}R<1B=PU2-(%(Uh=g%Rj_U6Oxp4ZkmM4d29Nv~?8h&E zHum^QM@tg@pys!43&2A{05=}vsgt(U%S&IhiQ`li_jA?sf@^Ueo_NoVBz$Dt^_ys-81X+B(TNpr{E#R1&h-<>vhhO z3^RN=KiGAy@}C&yEI)xJc)F6@>4$ILa?4+(!oB0%htpEaUYq~k2*z&8oM9cgKhOR` ze$dW8vs$pRt)p;9Msz%jVWFp3v7D~)zAzBM8J}Bs44^;)>+pnn3cz5=W~i4W7ixY~ zZ`we3jWh5}=yrt_maI**BYV>}no1#Bt0^zof8mn z;RNAEeMkr95rC~6tP^n`_k_c70wGL zQRVaJ_u$Ji(FMAxyat_%2LE}({FOvGEnzmq$gzySS$KeIs)T-qR+$M`tc3b@fLnAG zX}_r)-htb9>`1nW_AfKE(;hOU3umajtgIHz+-*+fPQ%raojn#n-nYd%B!mFw7X6s)Z(no^e}`&wZo@utV5pRN_n{-@a| ziJK=IL6n`2ax|?^6|-9fuWf5We7rH-f}1eM)zNoiiPB*-s6Q>~+d0hBgJGj~mYDHX zlz>l6@N1Cgz2`$?!55KbV`5fTulVt6^i)(-%wVDI=7%|Ac)-@583k<-DVFmb9kFeysLg$1dzD~c;ek=uEU~^dr3&jkfDog$(F*4yoW*x0{(q8AZvK1I^2W-^S0g>oR zM)2HW*?Nlbq3=ll=qLOcr4j1p?iw^a+smBPjJrnc^K)NQ;ADV0QJZ5WgqkQSARUb#)%>xSlRn?+Je zcD6FG{5F^xiMF=3N1K)QyE#4KjtcCvyR+t4SBq(^DJ$^K z2s9$0T;HPuLCm*j&;_&Cd-QZ2d18J|u`VnCNjmVIP=i7^QisUoQj*b>^sb^o9Sac)hc@$HL;(}rJQ9t;WITw%WF>)6D^TaVEeJ9z z8_%*a$T{7JxUb=1Qgz;E6PH&8ZY?+n`U5u~B{2=CUk=a22Bz2)s3+ICOO19Ut70iP zOeNGDzX+kbo}2Dmz;HJu{#XI`yD#K&39;JDFI5O!=&$AZsZYQx0h~#5P##0eIEsJx zSO5O6k(Acs7vay?d6T=hnWuXj20T`&v**u$7te~3$@k*=_SU)x>XSWX&I1p{Oj5<9~87Uvbgv3{AF$xu%u#K8tG&So11dEI%A|6vCuEIsG%}haCR*zd{OKECr#7 zr7$4YUJ_&)I05tcvvvY8=;}+i$}}itSWno?1qaYrjM?#bEH{Owc-vUWiCqhQt33?LVdosgIBpaARNG3!6ue{p zB%6EzSTIYSRgUSyi1CPC$S+*&3j8O$WUZ%9mvS2wZf3D)$(y=^fMz)~>O~MX5iACi zyln2`a_|>0_X?IhlO+-##3jGEr9`@7m4`tlM|iSWe)7mnSm5oY%0v?WlFMo!n`P?; zyjVZ66fwgzIS5H+*KsTWm=RJb>0bb1q5-bB97>oswYZEL=u-Fb?l(y!!hdTZ8D7Ro7HUd?)EJ14ePL7tiFGZ+{LdmC$sdcdg~u(`RZv&^|uH$s8th;-P0$Er)PEB^M7tA>Ij3vbb%wCmY0pxOBf zNCWV6(p7TW9zMh$>}5lP3R{&Y!XtMc4~QXg_+_vfGX0AAcGhx92qO6`5D2K*ubTZY zXP_6rjs8H0oOv!z2j0+seo_pM(x{3}6Z#UNCEaPSPO5xYMuh2{=G(~+-_+21!?`_UC6U-tI{xe&d0oGhnH+>`kz?$@+r`Cs zrlzLWq~2%hPG?rB1gM`~?S0`MZx-A2P72aQwlFy^dWB>}GT_-jFl0jzk0nLx{b9Ln-DBkkk^4+ znAD9f))UdPG9R@Vu%yaXQ5XWNBL!n!k3-ak-#^#+@#F>Przd0cWEgK=c+UQW!@Ha} zol)cMiN#ZTHPJkf1pV*3>KlVHHd9_%TOZNKsH2=upMj|B`j3s>$%G)0Z`ca@$@|Zq z&A4>s%GVh{?~Ojjw8h&HD+I!({)thM14M`4jMVSIFxG*M1R}9b<0A^;W7wl-F)x$`6p{BZ?Cw?yf8uwP{##;c0OgUj zo&i|&*NN*s$O(y8IDFLJxM5BdmQB|L(uSTNBJxs!F5YEjCPxhurtiXMO9OArj<74% ztXc2lWB&={*4kHfEcam|vOd~fM~eDgsZ{pq#g&7vL52h-lm~f|0rR;DiIAv}35^U} zP4I!DsM512ixEukACQH7Vf+?E7)&_IwkR8{;7e@!<2CO6MV+&++>RVyKC&f0Yu~@? z7o42ld%I!BzoJg4xlccxxop?5?Kbz7uVik{NStDx+-vuU3yw1Vt>(yW{YS}MDN^zA zk>6J*UEO@|hW(n@xhQa@V(fj|z5``CuipLE2q- z(iksl=^wCl?S%Gdlft@tc%4}Tj$RHVB zK6BD9IbL}ZbKXV__I^TbGUd@VqAl_vD(g^u{P)8+RnsuoLOaO>(0eINAZ3CAUjf(? zPhg4#KqskwxS3j_|G0tc!GYJ3>xdBNB|D_a&n~T+Y7EvEM0=75kbN94)s<5Z%~-s~ z$0re_`<77HK^}5rvgnhtq(4*FZ8}6EgG)ETe0%~#(!@-Xd1TX<10tH%6^ST&!V|T0 z%w>c{;8qIL#5S3s4>q=@AOMPSkP(a^l>TJA5qX+VwT|WYROZ%_fs0_zL{P%v91b4p z3Lffv<>`ukoHA4~`Hc+?u0nam0Z(EEf@--5e5eZNGa5>l$F_0REf3pbYLFJZZiV*8V(Vwamvy=@DL))6PERqDmNo)+?A(d57bXv}aTv zn@dkzg*>_h2STj9fc&BbVORq@q-sX%Jeu+tNFaFn&oX3@+`nhfyjtT3B7$AnnY6c@ zySG**NOF%H=8tPl=V3Zb1~GlXjdlyy%t?4&SQy5}q7~fa9hm1XOAQAL0Z&+v()^duNb2Fc4|D^U z*Yba}XIJv1Z8*SglpDQ~BadUhF`6o`1#pi@wvWDo6nNq%K;0AQ(w3EmRO-7(!l(2k za8*`P#HPt2eePb~{GNZaJZz3USXuydWOlsLT+8MQH$A9-3)fT}op6P#e)W{y{fC@P zPw#kS%Wsp9)UWG#t{Hz?r9F1B=0}=L729p5WC_WcTN1bifLXBZ!^v0q_xeUBMM*}T zaVh)BQvR}0DhdHCgGxLMf*3!SG1P8?ZT=4^-Dk?dc1BptnTDK3h(P9((ujXAD-%_5 zt}_mqd@Qn9DUV`Ce9R6dhePOt0}8-%$zdWUKtjFE_ zFF*4KHoJ;oO69|1mS$iBO~$DPaFJa((e3mH=SsZ)~y z#+M6u3kEG}EQ6L(1d};Kr+Au7f^Tv8#t~Prb^8|Gz59|wvtQRq1{xSB%_vxaT}kIe zETu+D^Blg#YOsWvtaKev!YPAS@N%PI_0rqR@{oTJ?Pn>H_*8>~gF`XLx znZQm2qiBGp%o@7afyGq_mr&!uBS%&W35sxXB4YF3`SZ`*UwS?ky!RP1$#zPC_;{g)p}npk8^`)RfBw76KH58Fr{5W*T{v&_!?zWU(3G`zgf>^&@jK z^SUn{x)RAI?m;uX{UQ8oX<}(dms2j7`MWU|`wC^oc+>B1uY_Y4g`13*i~j!pu2^@b zrP*Y8*JNGiH(tfBsbp6f4COW+R+1pZU~lMbRsA-ARN7_YHq2wBjicleDiQ@4M2@c( z&NH^q^Sg4RF6Z(|Ve58*Fa&sfaVQ>obdXeo_(mk_c%br#14+={e+RZzAX}VotVut@ zvIh|wbsRtMe!RGO_hFrrSCb8S;BCP8vBt*RvGIdl9EE-_uN^ahUEDh=7QctuygO;FMffp`rAE(|Mk(x^rMq@}X`*uj0f9 z%>KuLdpfSzqZhBc0%of>{OVe62HzaNm$!ZBZ%D0~a}Y5wE>^xEZ|024!?DIZj-^5* zL0}np2!s;h%$DFFZgDN*2E$Yz4!x|Rs&Ou4ns}c45XLtj;Jz4$&ASaPE57w}n9@g!W%NZ=jkky1N zGB|LJtrV7c4xlml)Q!9wT9qZ2r^~<)o%$j0;o`v2($_HkWTmmiBBb1O|Lhq1Nc+ zkTxc-prFkDHzpDdmSkV(bqm zdk@hyOUUs&GHm3?R})_+t%DR4j6ltpYL~3DgN7=SrGYCn2nY5f{ysit5Z2m8UT+T; z3ao*+rwONXNHaOv+4p0yrN+L#8#jEe?-QK%y+ttXC!8;H=&JJ|4Vpu2Zv|6VrdDL3 zHRdE{aC42ZJULLoLvBKZdQ{vF+F5smT_8fRe)sAvusNDw!` zZFUYDGQ^72`V@Za$?&*l@S&6I+)qU%QbcpHR=d9X&0JS~6<3NT6LCC5B(>;isl@pW z|5-kKIzV9e_^~SwTWa-kqr3aW0@}>da_ishsiCaCfFDGY;7=xCSE9EPjr`5t;hAXMtM1H&95b%ccK-vQUuv+PelDH>9X^tS5@1bm!2RiFWRB^jNz(m+0 z1worJFORXd8s3e8u=awF)CeK?JVtUBbder(BNn{y9lYHl3WqU{OpiaY*$LraoX=yx zaObUPZ}zB~yhyBUmMBOw3fw!;TD3AYs1WCop*sMA>ghp5POBC+Go9nC<)CckL@$(I zULNH|QU_cCE_nE|laX8%vF__iy&pq(<2$yX{!^!RW-}AUJ}z1!an{>^j!{>+$io1` ziV}4!9kO|{iI}m$LeQA_ZnFWIPI#{Yy(xf`+~<%)(wuyvR=8e#Tn@Eo1%n;ZuTh!A zm9k4x?cd+PS+`GlZEa%fxqA~APF!DfY0IBq|NDTF#gCzC5O|`e!0?lGx&qXRW1B}~ zXg_V*j;9_5QJ5_ZZl}`c0DcdR)C;<&6Hd-FwHNriHt;J2 zz@#}qX{S92=epdlgPGAJ8ZlOp!hG%N{A=qMH5!*P&2YhtMNQ*~g^&Zp6tNm!jELtb2Z~GL zo#vo~JPjy9j!rO_xeZ{TFGvSg2Yy-ei@YGG!9n-zsIa-EjxzC$pYBu*1h{8DELz=4VM}m*bY7A z8T8HN5QaD7#Z2naal_Hfjw&0 zup;rDcx2>Ox9VP$Dy%P3U_M>tm7TJQ-p8DgKp_mPH(mq%4sr9`R;tM`b@dJm;zTO z%*`!n2^+GwDJlKge1_1mbkaDjiS>C#;{`+Z`IMxuKQy&_l5eqa5av+vEgee2VodHoE+RF0K*d! z3>I~Lz!bD|&z_5{y6!MhO!*JIf+C5PiF5RLRLL%fGueB1;w+7b#t+5Xf-Vkv zVG`j9)F^DRvu#)f(=jET|QY9I&5& z5P#qJ);M5;g+*}F!ZWh0KSVgfWlVz)CM4XEk>oProL~)e8dW1xWHKHiJrZn(3LqSs z#s`?phTNY*xA1`z+WvUuja2;Gf`t*5yqt_NA1ya^JzRG+B-y}88DjW@40n{OHkzfy zPJ#i}(%Vi2B@!`*P)-&_ap8|VVEg4aKV8fO^eiVTo8!QOzkt79h3rHeZ-&s#-a{ZP zk{2Jq{Vitmh8@}7@;9H`nF_8#D7XZwG>-{5oa#Zf?@516>d%0Gu%sC{Vg0_oDk}1H zq+SvATjk8lh+*fV=ztC{-gI47T(VB|u_b!2ksI399%RLXq+kd)n6TzOUhWkqB$xyr zAoC}+dn-%qSC8)B_n>r76x`od{1V(v2klYq*DsHTt*b=opy@ilCXP%7_l#hi7Exz9 zm@6Uly|9F62A!P+8X3;%!pkKHgc#AZ2^-cqI?5FQe`_fu253A_oIn4-7w?WnB$u0! zFuX9Zz`c~P*QyY0;W*%w+WaO={p@*E{>sO?iCdkxx%L+%_oS~ zvR|Pu7gr?I2W3ehkk}4>i_UyU3AmzI6NkD3N4>>r@-Oh%DWDp7(PhoVTM}8LmTI9J zSvnD@9i*;~P6j7&?(e8cCG_-dj672~ zv=#{0dv)sEIYm&d#4fT5#P0zf9wmZjPuq4GLHd-GatS|ECd^e?*8pvsXTEpb%1F zc$a0${wMfZ0e{S7@vER}>=Ou7RL>b(XK1$yW0}+Q&mQNNwZ7z{$eJ;gg|*=6YT>TW zLOB@0%QPKjcXTPgAaTe@%)nd7NBMvmOZu^6olFoVpd-r_&i3P2Y24?l;P=um^!>yx zrH(bT`n1%FcNDBXfBj{SMxp~<8r_x_{dyq%YS_Ex=AImaIM!}|f8?VU-?|1S#QP2$ zD8`XHVvvf8gziuGPL@cP&Sdi15gu+15Dmks7Un=1*Gm7=;$k^xG-x7mkxRwEY7AOD5BOt&RVIv*9`d=;eu=d zh(wK3iIaxh;BHFZP?|qc-1oJqPV6Z2G*u7{loCCWQh9PnL_x~@U*dw2Pg$LQ-vBe%P7}q;s3*Wh8 z$0S}s28IEG4^6jjL833{m=;+ZS|$;)%tT;|VE`P<26Om<@vSNi7o^YaEu51JcMs6! zps17LDBRzL7`TZ(;0d=?IR15ROg$n1?k$D-HE>Zc{wJ|rFDYCHXscbpxNEPxzrIk^ zV`TO7`D;A{jY7nHj>5*6CaOg+g&^iQ^P}g`rBpBu_vW#KVw$tkD1dkzs0K3H2yw8D zved$5VTJeMZBmO=A+<+iDDj}#-w|-I67Cz=`Q>BBLQNoIi1hH^LUbHap9mK83@Owi z$cT#OkvT*FZ*z;IclX3(sYvK^(Sy_gj@I$G-)1a*Fl-eg#yDf+6YaS0dh;J%`v^%~ z7vf^_+3VkT+;8@GMY?N`l6D;%NK7lIwgdX*G8xO9dT@K3iAf&&gstrTD30FQ*RKz% zeDm3bX|MI;$H_EqC;0=6BoZS&a0JgvMhV?nFa`tfla^v4XD*s4yp3Iv5S>8P<_nl} zlc7S+T;LtAj(i_hym1J6HwcBa0fiz(w^jG)RvCAbWmI)QaW1vmycYVP14=UwRJuw=*3 zsVkel571bK;VVU#qr&4!!MyIaz5?yCEj17HZy5OFddAT;41kpYR)TTqsMX7tH#3KM zun+61FP}dPOO_|-Ru0nBheJx0CCRxn{|eC~D}PWNT9~v&g6#`@z|~oDav{{h&OP<; z%_YFS6pjQ-?oJZ>!98!#XL+K;?+~nZlXiI>MR_b6Ty)Goo^}b&fO{Ry8OvwgTf>_) zMqr7mU%h(p zEVn3FUTkkUw<<3~FP^xwqhk{x3k!#n7s?FH1DT6{#p7I!nUm82DI>~m3i4uLbsTQi zh&99m2u!qrnXKlVzr26`{xv!-vHeF#Tu(F8fnY)IG@*M1PW!>uoOosX7V2>oO}T(k z{wxmBWO}mz2d&5?=9<9r-uN)D;_6nB1m^m>2+iqtmfNOC&@h=Y=2sPAqrw1jBb@>E zH6#)xLa&{Nb;ZYG?IVf6{V%pKMLogLrkb}tiq>wUvegof&%_KvKXy)JC;XEZ*@+lM$xF6IL}Uz}P6`x!hp8 zT*ReV%yOg=y36)F*IfF4F$wrFSC_#e$N>bJP61WgJtYLPozSB}Y6}Dl?qNE9d_t;D z*Gex#Tgr=NO1Z+ZW5*PsK*~VHQs^YD#i!*aS57-ulUNt8J6q%32iIwwcY87N$*#Zl zY!Hz!8!)umyKkQ*E^wm& zXW0*o8;eZC4KgW)a=VGak{l5}4#Sp{p{sLubXGz$BPlJq8LFd3?sG0}Z zOj39kp|>Zc#MK)0{0AV(a~w$b8{c0K$GPe-=;ugb{(2)NWwJdKhn82bzSdERe*$`u z#bYH5e-4tK12QdpZqc1S*Fg{n&Rnt@R|!EwpezUyjo|9B1*)Ys9h`AomJ91BTjs}< zT9VCN$x_9Ncsh?^TPE7>MJk~ivB(<{YCV|9bpLZcnup0^o;~59%Uh0Iw?=YISC-H!v5Ok<=me*6H~ZsNnVWw4ZCDD}insV~$l6333?^^>RT zm?7CfAQkRcYp-6sxSwUrKO!Fc0sminV*aQM{yWJa(h>A*PuM?~pjo=l>kdJKy`B|H z4#h0WIsF#1b?V^Hn-nRM7r}7*;@4qJT9PJryey?uA8<_%tI4j4iWhK6J!HBrn&iYt zmVbrUvE`s+@S)MmVnf_TioHB@P61t;6n@KTN|>6K*488O@zb88EC~vhP=$Zgt2?jh|W6oAOrt#hB$U(mUKGDK*9|`A5L;$ z4y^L?I}QBgNaDk*wzZ%5YRb_m1o1ai04Cr!fz8xmNK%~HC#(m7?~1tZ4ghCNhi}=R zmp~Y4SAZ3R{~#NdsOx#7nlw|UT;M24^6W$MTJr#mS-&MByjvprE1|d1(vo8WcZcGg zC^GpnFyFe35f7H0$>*KlZnxJtN@yEA{1867`FZtiB^<%dya`vrRMXC!sUk4rvzS@w zH1ZjxJ3?wp1=h|4Me1a(v({Di_}?S>24COWkv8nKg?Oh4wn7Er-+?9N23kB zoS5iJ&U_d(@)j(I3+@G7wO|GbjF2Pr|7v;@u$=R@|Nlx!r3g)~5)(oQm81x*6rn;& zp;9RtQ`urH?J8S~r4m|fjVy)iv04!|(OE|NrMWp8J@2?wh)w6yzHBIuLkBn6#mcHUo>w1A!l59o)xzj%3@qbE#AoGU zE<>#McXlwX0_vuOweh#o5k@~MER@0Kr4F{PSkIrNKaU0KG!%kE znUD%Q3kboJm-af;v$tWgUF7J)T4?x7c$bv$-*meak~?AS*xm!|dU7t#Liq7#SHkY77zm$pV$GN+>*ori$ircq$;q(5ije=~zLher!3I@mT3P?73 z@P$sf=!L+;{E8)vvPAnCTa3#X4*drSC4kW<3arwIPAPvqn_%ouZMgqO`L%48dhH+AAQ%^`eM@;9zAh?s!Lexao??6>_P&H7?f zs7v1ZQZ2gkF5LbUn2?TyhW|~|;YZ(U=eR>hU%zsmySwr{H#bjcgQIkHJ@{ZMVvt;o zLgXE~=L!G@VRXF)-p_;k_Y>HJw-8L=2AX=FoR5_>Yp-b3NWI&UgY{P)knfS~Y(?y& zaDG0Ll{N4L?}`m)8Oq6XR6$Q*GhBxM0^0V(E95c+fJTmrbU|qY#7f|)kG}KzBby9N zGz)|RqloobCGX5!SqYlx!`_A(zDAl=;JvS~gE>t_fLw~bh|Pk({yKISm2^>_-e~G4 ziP6I!3J-k46uk;Wi3C8~i&Q18aP9o#4Xl;d6g!1jCusvos(gxwQlJP zv-|(Y1&Bwpe*E8mmB28Tg@j1=AoDvx1o9)!CK37lsP~9@a)?-_!C;xqJc8L-33+Aj7U;IBjKm^;8HC1yi`dUMGd?a~69Z zRni9WWC`I!zQ?-% zjRiankk3slxP;L=(S0AcDX%;GXz6B2s7qVrjXU5Nv07&@WR)&zf`k0gu3 zlk%292V}sy+6&~c-}X&dk~#1WpwaTc?4gwjF_ELei6_vSO}z80S;eG1UPn1A2*QRG zyWft>JZ^wSvuenqXFfx`M+_F7;|0Pf9LVerSNAYX$q`|kq_BnKr96$CSzPE2a(WG- z2#}Gh!!mIYt^MkKoVzcYo9*_35wT6|=^^!nbfRGdf?-S&Z4ad=2!x~LBpx)kWBs{6UEEf?d)DBSk7A+6x=d757K7c7?%VhK5TdTI$54^yu@G{(`N z+S@IG{Xzlt(WQaK3`Rw6BLZJxYL>3&ITT3Iu#qFL@p;Vc?Umiha2l=L_`Ie@*Zq|5 z*6ge-S@8tECNnRDc2KOHn%}-%iUpl5)r;FJW8q~{>D;X&{6m5?`W+OjA3kd&})KfCN{C%T*`6qH&Dp#MZD zvP5)s-2l8i*U`~)4{gDGLZDJ7{FC3;LRGLnZX=~CrvHl;USK@?R=B9)R@rQ!eh5VCy{` zCkzCROfNb>0Ws*Dz`&;AM()M3G=SqxPEt2qO(_sd!6Im*w{G4HXVhq8qVLryEPXKr zZ{3-B6LMw`19S=(l8Xut{YvQI1KEll0pv^Iu+QY>YOO;9$aKNrtV9^;C+3$L-u(~) zO*LWFy#y?|#DBMk`!I@kieQN-fsTQay+u*GOLRTL&d`Mp3v1+$$H(nGK8p9@tnV%0LK8nV+MI`gTe4a1GUZ)6K4p7ES%4Oz{s$Gq9=&NPYEX@Qn-~9 ze4ZT%q&)skI)}A>Q0fqG8!IR2C7On1x;ngx|ew9Czs#6c?99IDhnZdtdb_td_f8@ zf=OkAcC6}}lr;;}$)toB70$%yO$n5JI8j1wr97{o7svC&&Fm{N$T~Sd&sqMl! z`szDEPAQM*c{W@b_(}kd#}bAY#R@c>PhB~gM8oB;yY1bvqXbI==FsCjug+|r{{w2s zr1uvKJv_z^P3eo-8w$(6I??vTB`}Gkn zYAnapM7zXrw)8!?LU34;v;5~xP1M=IW(QSDBga=Jxb;%RAR}0RU@Nh_Fz6Dl+!11( zj-kICA2gTG9{_rVMVXGeQj|AP_;QFOOJ3gGNhd1pNDjyVQ6hwuz20WX@RERjH*4VgcGuNCjp8oVQ2 zz`}p<_HFZVeG94UJOK3Mr&rcLCK}J9Aiy{R#flypH5|7pL`IvLXOWyP{Wrjn4?8P4 zdi)NM&%x@OWysWE)&BkJ?_Z0E1L(Zi*Vj^PW2-S~T{IEX!e@^j-Hfpw@Pe(NVS??8 zK!lFu;sAPwr?nNYm4LY{4_DVq;AH6woB1^7qn&93 zNwokvv}Wm63>Gux9D0bKEVvZ>Qv1PuV^QVEy?Fip{mAk7%Bt8nk~$pL1Mj0Ou|f*Ro86r%fj5CPApY1n!>0%JN=msgDO_`8s%5uOMEb2$q$T zU`3wfGB~LX6PFU8B4bJ?r(`%9TWDnl+|$8>2L}VTuXw{rKp4=eAX?%a^r9kb;bHTN zNK9MAHX*IlD~y6zf$6qT##L~H<)ZF^PAl)ma-oD)7;_G9#L)xrrb&YpQA^zwJ9!(& z*@AdEbwGkXvw17qa*t!q<@iX)LmDxltxIFkz7NCXvzF;x*khHQt1M|;-MxG8~RcP-X$07qKG zS$LS2lL%zGhwe6xSAgax3?DuXQLUhTbu{+NZLcrhv;e8SqKZmidec%*=_=@*1{&xc zA}upYAQp*X=RL3OAZW9NDYty*Y}jyR0*KEg;)Zd+=z5unqPlrS3zXFiS5mj30l~E! z&f+ty%C7`}B9la&W|$vUa2D=@17UBThfQ23-XM9w1A`5wU}l?=J$p$)`O5&-{m!&^j3;wY0PPX! zqnx`(Sz}!(Ou?f_ai`L1^n}Z8F)4Hc(IXiuEfHf&8ZM z9%m!nlh);NKV1DsD@QD5Tg=jU=KhI;?xdN@vZs-HO0Ufz`_*`l1ui(B*jDFJDFXlD z0e3xIZ2&02uG)8bU|<2!A!&Ql(rS2&*TIwP#x^2^!_6hnJGP0Qrxc*C3ljEp=nV6) zx1CHiW+~DsxpP-_5BjqLZ$mpK)<6s=D9CID=Gdng`V>&c-El1Jr=iiGkKlXHnfHwOeTj{xb{qk)6E>6j?T*PFc%}H9VbAf8o5Rk769=V zF~?T$f@OpMhXtXBOe_wUd)U@RfRFis>mG{0yz{{l|}w(s%6I zzFv(3K}c}Mi#U-66g>(mDmH?oNAKL}RP7clovuT(cnm2^h#q*`JfJm*gs^gS@ova* zXEIKv`={F3+pE3D78E>w3#*Y6tcc|$+U%DBECRoN{MZMtoSG?;t?PlQaZf5?0Wb*b zk`=6i+uOX77>Q;z=CRN%{;+m_=veL}~7ot33E9jp-o(B&ke z(&ZPkSYNHTuNhBVyA4RJNj&J0*18$m;~O~!wanrtnkgOvTZk)76JUHVge;>V*BG9 z!Gz+#Cpe3AAQyzZ2%xtYwmQ8Lnesw~gBK}F3Iu1iCY)*9%-DRV^*G z6|D{#*jx0lc2u=488ECNf7ww#o92sW@|W~I_K#ZUKf3BJIpY|(S$kPv=n<)mjfG6@ z=#RH2T%EM;TiV9@3%A#p-ClWl!JXZQ!PDjL356KOFrRQ$_(jic&xW`N1x5#my*2m> z4EIfZmg7#@H7v7wjoW)WmjHsM=H{W4)?LErk?SQA@UBAI6KATXq=1kQ9|$~^l)&+& zB_*GL!4-ihieJ2ZDFZ!zJQ5-r>KSMp9fepK*~U%5dBFzEgw8lN8oI%3EZEmg+`kb> zv5DW?0-GqE(QywL`C34EVLJ7qp`jC3etV|wOO7V|@?kM$s?O^nA03gI5$af?x( zW(eC=7BR#mb<~?YqgA#+?4d7A2`TqWL@g&~OGT3a9hyeZD9b`)BO?)aQXuc zz>#xf1XaWFz40uQ#w)6D7G4z|EH|K8i;LBT0@s5bSs4C zFPW*jpx&G^eVrCtW=s;UKGps9V4RO2W4li7z@z3Al8i@R0V)WpCV2|_(Of+}?kZ<9&1W=(vu z68>|H;V&9I@6^WM8;(xSsu$@?!GVF2brb)j2hU?G{6Orjm_n|j)m{!hb`n6+MbL?H z0PlK%5vr`JDoESVFwt67j?T5u%h#{Vmcxr6w)z!Ip+gLzKwy#R!yd7lqp`|^MhYLY zRHPXL4)lQ)h}i6mi1&((iK*i(pc&tqLU6J=ihJf{Lsmetz|l?Yr{_SYl4X{|RLT}~ z`joI^27mORzeJ+@f|61|Hg*Tt<8_{y8g5Aw&B)HdLxz0y_Vq2IbrD$cH6W^1hJ?dl zkp}23nvUk=tl-g)V9TYBFowvns!wb-x_;{@2fxoa}5*V=g90c6SkO zo{NzU4?~_}*RBz;y{-c;m%|6nVYm*5^0}6rsq2g|HLRnJP{v*0eFVy!v5>r?gR_U- zvf4E^1Z$$?{reYQ#h8x|@e0^1o%Yu`%2Ecy#4Jv4?x{91ip_sWqdYuuXxlc@`C+8l$FZeTB6viA;2gVZAFo2!S`UA}Gj0 zAeV|tN=48D>!Am=F-fWj*6Yrx2P=S)YM|b{01Xt8B;5s%F+g~RQpuMi6ju#1H>dw_ zNo6sKS&(Fx&rnI|R&(tFM!^h*dfjp3G>LK8{2XZK4QH-09fmy=P~6cOqr&=XX^Hv) z-{Z0&eEx@iMlR0IyG|jw5N1Wn$W8c%20bCNa0RBj9F8e(Aop_|pvkoX(L(bCA%FRN zclRTVdWX)R*H}Qh~=#MN${-OtIWAqeD5)UxD4uSVEU>FEVunyH( zWL2;E{p-Ja)@0f62U6Dvti4ly{uZ7$7WE%c`qSJufZod;vuQe3F8| z^X`w>&cKSdLvFU6mt`GzOINP^Y`b-GH5sdQ=zX-|c8V~S z-=OWUDA83S{K89L8jpznC=cHRyyE!qH+Td@!cPFbq|j&yyIO3+Tlp-Q-xcaHq}iy- z@c|pa)}L?&6FsXTpkhXr!yE|RkGr0_wg%a(s3{=Ix0xpMcslcAH*YTHmiqBT$!sb% zz(jHVtfTjZ-b_#!r9rO@vE(yK2`?)}i z`xp)nSKoY|E@c;JSY%nF1=X%3JR+hNG1yS$;_vWv7U8xw9u}Zrp_5$fMeF6CdI+~z z3z&V78Y1$~o?Q$KmB8CL724W&RK{_h;uKQMo*h@HUqRN44nMMe^eYV zNvMn@lqhy5BngS-FLm&%V+ROwJoXFI1e51kqp@QLGF(Pd_s2k`xOMyXsujV(Z~h#= zO7FjK9~ZNTm?wN^ZK3W$Wj+Ha{#dN28CYTX%0+G3ltgJj&oLNSyEA3=lsia_re%bZ zfSBG=F_YnSeS@TA%C6a261r2zPE7wj733<=c7$N|Uc&#Fz=ov**yEiTN5G?2AY1U| z1{4s+LgBJz^XBp5?fE=x8lJyUo-tD;iF`{yQ~p8T5gQFb||A- z6|eGArjo=RJ49An^!pDVRx2whW!~}~J!!!ZmAo;52pf}?xJ>)d$v_U-6y$I>Oc z%cEo@r!QZgz#=GuPA7scZ36mUHm9|MyNbX>o@NrnP@l&wm<4JR|CJwbV`JmVd>=TmF2CuwJP_#)K?9dVe$(Ykm+ZOZ*O(G2sbgrJ^vBS=tY`DYj5Y?c zff~jER_xfr4mu4@#WCS51cC{6iDsZ1oKro7z?bd22{giUT;zH4=f4valwvNec9^*b zIg@9_10-by-T3j_115|6Y@9zc=~=_g7@A<)MAjAU*tP2FUU26pX+t0a1lr|ql=_Qn z)j~Od9B~?a}@SQijUxA{= z!A0)^gJGdBKX?KM7f8eKvO9O~$m72a5_g&-`{4a078pzuz2%1=RS{e9v2of8B3dde zqr*0U=o=Jno|88IQicPm!2&3W^?zG?d!8wD%YkZ2{=0lK@bRw&ip>zuGy3p7pkm*C z{OHOV|Bdjp{SnLK(YR-q3FQXU%M}xUt(^o#JHyku+ivjf->uG0{k3z-?UG86+ zCZt6rpdZ;BqE*zCO&t0uB_-B{xP#Z=thg2N*fHjs_3bZi^o2E#p3LDxK|v^&Y4}YD zm>>BtIviJHpdj_+(Nf2Urs5Mj_Ye*!TMj6CJ3kCD(IS}c<+yqI@!LHCFdcMKWHhej zZjy5~RSH=Ts#@xLm{aG8RLFtSW}m3D65oHCxw{y&a^VyZ)O6_BIZ-@cF>D4PaEzw& zj^Ol)Bf@pNtiZRTWE1GbJr1%Gjs|#6 zm$zGncPNybl?ebAX;sdT^B|@KAU>l=?-^SAM4{kAVI3`%=jS7+YQ(a|cHrXyL+AuU&ygbGriH~lsi zT0_{Jl)=-IMfOqqlA`gH&drm_H~~ehqpX%W5Naj2i)=5cp_OaX zr9FZKNvB~m6Vl6b3L+46%Lh|0deRssiS6;*; z!H@=ENkD*+&Y(dCP!9|+7+S?a{FBNM%)tOdI={P|#dU=B@(AnxfTShee`jo--ZWOJ z?VID`LV1Q(^9{@vE_^-_pCX!g!OoBWV_AL@#Z$flMF3!kPoMAKYy&)8Q@;|U%Sm4{Z9dj;& zi>tZm`#w62!;k9}6LSW`cDP=s$2tBe>vd?0MdSyO@1N5a8Bo{x+^XW=h_$#O=2b=-VF#+5rQXZvRS~@iT{ejs}96zoOzzf z_Qq1>1L#e~ODP&U7FCBt49Qw<<~XocVL@S1o)=FR4dM@NlnVpP^E{}xF*6QltugB- z{Lt^e{agvTOqe83#<4(<7`TnXG%gvs6bkBgEWo9#gz%z^!$QmvBA^yE0Z`OjM$(GX z*&G}3hWGDh;3d${iV9}~tkTNG%Hd`Yge%?xill`3zX$M5H2F#0MB42*_}6=hal;u= z7tPpG-o^~L@rYUFB>EW12GTIa(f<{*9Qz68ZLFuKOrbuRViJ^D`uzDueNH~a69?$t zO0>Eio;z%(B|bA-+(*8C{qm)Tj6@g|*MK}8wLI+Wi$nqdfo=m_@Rc9Yld&Nta34Nyp~+? zTinrI_`qqtefu_>;w&OB`K*;dmX*v6Nt|&as4xG@m1n~eY_((vJ;!`d_=YYeQvwLd ziJrTi{fCnnVUR??-qra2M=w6dYtG4t7``lFu7BfFX1W%lS_EsA=jcB8npx=jMt~pH zdf`$<$4u!TC}4 z=f;}D2M(-&qdQQqJe4!5lqS0cNa7v5525|d%+FW9z}{QV*j*kR6`Qr?{QQm@>dKCTPku)hRe`Im1m6(M;72O%f8LmFL3 zqxl^&F>!#si-OX?PQ2iyr!$poVph~(tJ+&e&H}n`uUD^McNd$CM^umQeT=ts zeE&Rr-S1!P1Ri9Ag%)#d32oeTg2KfHSNU^V?mFB``IV8X1a2My72 z3tut;L{5*6jI8AT>8L4j4*GKr?!#I=WlG|K`@k=xCbw+hc!Z+XU)Ttt>~G?En>*D} zEf4pokrD|=N8FCxyBBfo2s}3DXwm@|+k#V7f=;ZA#9kt4;W1(W(>;9te2Oq8R+WHMa33H6ii)Ch+cvC4S6-)tGmVDf1d!r4+a6fK_ec^nKBvv zBMM!tJ?@z@=X6~C{oi&L`A?X-o}W^cbL3b5WRYRWHoN#`@CcTKP~S!{VTx+d87yTL zt7Q-Jcs*Q5wl74i{tzkZ%ss{`3^t17XTi|h84OvTt#bg~Nf}csS2P#wPLI9+3D9PY z&u;S6)hKuC)=n=T%KHu*D3pjDZ;lpK*?vox$3%SHFJC zc;7k^&K$^}m;!k;5IJuZ!@V1O;yR=TjzIYy93KMKH|5wmfTlYewD{kH2W^P#x;cP% zGXRwqjLAq$9Xqu=IiIuzTLmJiZU8{7z%$1W0BAm`5f(@9=TSU5^ZuD~&^CPdFmug6 z|73xbIVauF3Hkl&^7c)erd%bc$5aSwmM{MUM4^S_ocHMnN1`5*0|YXIMA~Q*lW+3u zVT1TiSO%sjckkXsAv|8kE|0Y@R4FN?y2LMpX z`#BKw1(fX?OUSTH-99~f$S{V~BJU}wt<_*=L%cr*x1l}svx3rRiBq7KY4Cwt)Kq1w zrE*&`bq#q|GyNZd)&&J}w1@LJM1YE_B8*FbebxiQ*{P(97k_9=B>c{E1jZ!OddPX@ z*1X|BorO$*w@*Ed*eId1i(vtSP+5m8+z{ zIA&EPGnQ|S*Qv!+Hu5*jxNy(n=oFSP@!;osMD7w*SBQK=`zXsF!9{Zqh{HNkZxp(B zKa9@s@7FJ0SktfLbHB)Bo0UWYDC$I1H+Q|iR+%gR2Rrc{$BNXzOU}{BDf2r5wI3k3 z9ME0pbuH((y2b)rQ@MNcn7a(>8T*NicV}FS1U&AD>JL`rtxxYBkF9(4Dw;ZJsi(di zvsM2v^H9PzMS>PPSU~EyGsZ0m~ zQ-|#g=Mm2B8S8z3ZO-Uzjde)j?}Fm?;9YsgG1-$|vlyOc4e!tmFqJ+MiEzty&#F2p zXA#K%Xu$-ANxa2&#DOAw6Zb;8k*!=_NwNM#M(Y7%#;govg2PmM3vuKMLc2u^<~nkB zIcroGmiGw%y9^mLNCo4*Mk?o4IPvxHP94NQF_=q-N^%e{2vY{Chwc|w9ph*3JeHj; z(j6paL7KRLxw)CLSQ7Svdb`VW`GQr##BK_`TGvY&Edvl{IBr}jLDO>7>Lx6- z%=zqBn0vm{Z_?EYgO41JZRPD{vgAWwfro#V+(A0^{r%xsv0KEC zxQtZh6_dSH4>e6(lJZa?jp~HMZOBBsJBr0#-BqZrH`0+a`x6DHC;B z6DEH6>M0BFa7{+DIS~8dJ)D9js>N{%MWuu`mmCgR7fZ`dZEcNHjG#SYJmke5v_H(r zP3(tfkn`tfX{LVpsop;@n8Tbp0d@GGIW}|X2Rq~(bF51LnbhHwd zhY5IogfzOpM3RRAB%r})B0+aDo!G)xQ&9nnhY=JaXK|7%rYC0+kAfSW<=-A2b*H%B z^PHS4MZ7D>oHaGH3|?=ttxg_#%KSJwgd zNDB8!omweOjdmm?^f%Vr>WeM~F@7Ovmx zR1~Yt^DJgRp!6VGYZj5>t^Dp0fP-e?+6DNndy|uMjRF?W0^|w1C1F!)3Y!bXlC2O_ z^ShzM1gEg{Gzb4!3@M)P-7v=3*pg?j_rXJl#7f~b6Q~1_JgT(@j_@xSARGV^O{1e2 z0pp4B`y~qfsC=7rn?OOs4VCURlDJ zB5d*Am3jqTM05*RN|7(#6Kkgc3kw+#(HaUEMxw%r0(O8V0nO-LM!=sUWV;WQWDT^D z9gwXdhIIgU2C@(%+ma=R%|1fWIiNII*~+2BC>R>o6I5arH*PjV)LX_xim5MXB@>ae zoCU(eW>$EHVwuPgQq8J7Fap6ZeuQ+e|E8y#8^B>i2X&4Ib^h69eT5~5t*%;6edQz` zo8SyuQ7@fx}>PNUeb}na-x+;xCGc97c&*H?Qtf1h*#b{#ggvXjx`Hqn z=sp$uC;i!X!1V;m#+Do2iBO#TfSn8=Um956?9oLG9X`C6@(_m18(U|YT|OzH zp`jD;l4KinPq4!5D3)^5JX}|zgOwSe~Icj=>BxHbi75+?ts=>S-T_ zQWoqvubT3F%A2T&2HfOuv2)qIdkBjulkH_$f*X2;`0B8W7so%ndpDd@>9Va(LxH}1 z_chNgprQ<=MRwryy8be_kRKzw^Mp$x^mZ!_Bw3DyMJRlfBodlaYec3}f(KM2q1>|M zpByf0L9rNlu+x{vj`0qU?&`6|#*bKQ8}f`GCt3s~)eR*~79}_kf~`Pks9i;Zdm|%> zgdLX=5D*|Q)-1e)@`$TWH=5jrDAUXR!irde<-FM%I-;!Oj!P1C>fyPCLXWAeuC_vz zk4_;0L8swpcJcV;n2zHvxb|Pqknb|qa=YZ7)d`0M#NC(~|BWdDy3r?*- zkPb&j<`HvvO}|2v4L{m99MmcRtIK#f$0X^*Zj%}m&2w-l=yq)K5h$f~Hk;L5Y5%I& zY%It1aRmkB0bNWIr_2E$zKX`S5WcvNfimFA029b*dA$|n&fzNoo|lZGV5VK-fil)o zS7WU+pYbp+ShkE@?TOFA8%=1Z>v_*bR@x3=-dS_z3?jSC=7q29iEQ^(J{9$-A0&jf(wrN_H*4TJdUlp7PQ3z~VncCjKZV_{QE z9}?6GsZ5)QXv?dBz8wnET6F*mL*e5y6mFph#PiLrHgD zu;3YMyF&JCdeCijHmUTvrbC7%OX~-m;>$6di|+pCZ7-=W`A#F(bGY;&7Y;9nMDA=9 z^Gyz`Z1ruV4npJ5nTI-k9;`r$P$HA2(&ELhf6)Vf;YIwHhI4~h{Fv%1XYuCs+RiO~ zQ&$%X&}k8T{)sSo$iIBKFQCa|C=?M$Z|XjNYXCP$<$apE;Nq&S?7B}0`g$P>!)QUC zP=mNcv^=DRGJv_QBA0?`q5GiS4kp$%b{k$9WvX5c1UBooA1cwKK5|ivR zC5>m^n-%;SSVIG#ra&Mo`}Fl|7QJ78NR3KFFKP>NBVby%=GMtjEwG+4v%&*>*1UPc z>VnTd0JPOuKyBoJWB7vhnh zFfmSX%WA;xSzfH-a1Ld<+H5)k${ z{UxDURemkv@Mvca!{SIn$}X{3>+t$zk&viYE^LW4;JBeM6Fz}92c7y0Cls;L61B|M z);1K*`fViZ*6e-mk;F##aOVYkYQup87xD8vq{jQO841FAdlxGdFatMO-|_v2?U@c? z>~fyHE`sySqE~>^T}36h!YYP4`-~Xbp{) znvEm&_=dQ!Df*@QV)E&GtD+)O#4fSwO~Q~VY9l^0(6Zyei{)h`fj`k{j^oeCpK!?y zM-6=POm1!%2C_m}Rl{JAW2N98pgTA1`_FHqO|X9x(sW}Ub}FI`hOBouw5-r`P3Mar z;=1VK6HG}4_mF4j5D3BQ@$={1ZLF;;4O6zvUAVA2k~LKt`3kxi(1Hj?p@nmuot5ko ztv5}6E>syp5njL|Yo4QH7;4T|NQte4eBTG*v)d2+NZ{Ad=kPyO`}El?QjmG;BvAZ5 zu~SW_RVspR;6YFG2~0GMW84HWZazbHD7H!W8Bsp11V9ak1@Fq690G@-o1{{N|IXG{ z4Mf(=cpb%1xQNpujftSDnfm;^s>(vyXWQ@R^Fsykj8+kH%}!Z~Hbq77@(+1-&nY0o zQCnhXZ85tzI*hf{ZDC??ja#+{L!VmIZTVfJEXNVCpb5mTsuU&A>$tMU_1Lmm{NAqJ z0eWqLH;W%arp^)tO=7ajXy^p+{XtND9=5Bm#EXFmHS=f5j~M_@>3k2l-w%hgWk~}Jsiz?pRuf;?fV^R$Bg0i%&-4%i5%UPji=iV%4C2w2 zXWep=LrC8)aTx{RO(%(jEejBHZxO$lkea$=I4Bc_xl>s_ATc?KJNz?b+-)p%otO_f zgosZb;gTPqF&2d;Cbqs{wqd}dr9J%!SzYiPjxqrw=dMJb|2NOi$d{kgD-)T;E zVDG|nGZ`pU8k zE`@}ZmX*nZRKO*E3_018j`;v;Mf4nl^$>oEh43tvq_fm~KJs8N@1xWJgNBSgM7M=)tH8FnLW(MHn9YN zrVF&5B?useW0-ARW<1-9={{^{D#zwE*e)FyFio&58np?Ch5x`4yPLAG_J9oBU zfE>qI&e5Eblq8E6VEf_y8#19@L{aus6crTAbDWQsE;(~oBI&xV(f!Q4zs|#x+OuVJrgZS6#UuK3r=(qFcD^@-TtFtJk2W&-NFUiJpP6QUe3rYGr&NE>s&Xwo})L= zI&n0Z^u%1Lv9TE8k@xIje{6)ob_itvtIAZ`-_8K8xqoixklC?wr=tj>owVVnEzPHJ zYA(kh(I2+`-!34vZMYnW!;WmDg8TRXz*uq(OX#OqTqukDBn9?je16cdAD`dOt>N?l zaSjgo_4E8?`@z*`^Yb5J0n-lp{~5^ljZgWml}N$5*rDO({-%KOdGSR5Ir(t)(3Ru* z?!$}z4c1Cb8tY5&b-coTR^pVrPW9~u_IrTdnuwmkmlw3JHX+CgpvY?x{XP9HY^HRH5&skBRuv;^^{6&ucBX> z1iU){cXD*FI?p)UG3_-!eCSXfm*1M*lRUd7Wq%`0T<>cc7z}alz%Dlm1=nb^apNwC zGBblH4?6vuA^-fdoR>C%6&;3HesF_0|Eu3qnIEvs(WUBPLk@WIaH1gW`9G3^UXmk} zN$gl8h#bytOX2Y}kl%WY-UatipRCNxbY?Qa!iPqrbYNP7{8=*XT zqZv{_yUdp6nV7&yu2nrV@7A;#GkjS{&*bc~)ahwKT1_~hb*)$wh?5%y?GvJFngJ3j zAgP#>iz<}Mt61v)oq;Bdwu^P%gq2XXPC_CJ;g(=PY|dw`W2j3-FE~^1mBc6Bz;x=z zei8dMp~LcoxBw}tkW*SrAIL5H`<_Zs-&|@YXMvs};vMTnNThra+>{AY0v7!a1#S3FNj46gCeyRDe#7Ap&o1)5sUQSLf8Es^L#47-s zhpEpP|I}aw@W0+f3`D2To*fNtY@Su6DmE${t7e>L`Q$r8^Jo(jN-c}mVoY_guh@6l zXnGQ5r-hlLp2rUKQ`-c~1=J^Nfqt-gGoppBwk6_k?~KEz0A_=cFzYOY3Yu}^#2sUt z0j5r$evtRigz~$BF4R|8I~LMgRPfXF;xcO({IcPB=F$o|!1i5%z3pv=(roUEh0*kC zmVfsED5_|3cU}uhL*Fp={=|J;~51i0XEKq6JKLZ762>m^wfi$Aap*%`Yswiu5EEq zkxPpGgN@K>L*P7zgFYW5E*Z#RD<9kik|7y4DZpGoEE_BN0%3p?kM7)=8G=d0G#DS= zOP7{gIPQSxw=zFBSHs+KHgmIItaZ^hhX2oOAgKLONDuns&443(@diG`wf>KL&cgZ% zn6K&()8?wcKo6=$4E^L{P6ze}3K$J=mHPLL&0$lT#vs3$7I3_dmMmj~11@7dG&OI+ zl_!+AezdYIo4H(k=Xeb90`lkp5W6^JdaB$9urwc6b*k_?^X1D=8L?|NB<+ENz5r~v z3qPtE%L5oE|6PZi0F@)S1#m`s&(BD>Bvb+mYhY>c*YhiT1asLOlO+--z9lN`s1drl@hoa8^m@jg#F1WD6#Bw_^5f9vR^JxrXR-bk z5mz8MoI?_9m0^q#+ZedZRwolUwHXM|loEFrCP+2Us6f8awPh~wdtHDk? zz>rKN5^{PBAUJT%16YRj7XSsEp)=K#d&fD!<0q1f>VB5I3gWW58Xl?Cceia zLal%i%YZBUv8tA1naoY;0E^3@nH#{Ry_+ze{+cfrAs+90m{M?;1{htJ#XeYsY)}M~_Z} zwLj!Nw__f|gsg5{IO>G7ojW_T&)>)H)*G1W5ypfY$vm71#d{w;(jks^fciv~F3Yhy zz{c5KJHa-GD-aHv@`jmOc%fX8kz5Cs67zw$qe)W_zJOSHox(Odd$u9^P{=Spii(Ot zIGfd}%~<{D>c&|D4Ei?0yyT8N`_2Ycv`Wk90S<{B2y((yP7ggRvtYiq)NJji>%+Mu zljQyhgY90&z$nIzGzbK$V6a}|&_R+6JvI*Aa1Fj_$zZ}*E#U}c4Pb3ZT@qbqmu`Pf z%*pV-a3VK1K?+G11L_$9VXA)c;LGnN<9?qw^rme4Rbgpz?OMl`{sHYi6{dq1%AZ>k zXjJHsBp2aemuMB`b7V@%s3FfYynDp&y<2JTQ0d_4aNTIype%<>qd$_XWp9QJZJ(c? z`)|K0G-?9Qm}!3;U{s3Ug6*J%+34NJBj zkO4g9gPv*>2?ngc1df~x+$@sDFY(UAk|Qnp76_weWCU$AG34e zJ`{68y1BdSrS9E(kUxZ!sPnGqsW6FZD0__laj~(%O*!*dUXr5aw&VAeJLp!m_ijjdbjEu`1^4EJ%(ESu0WK>Brr~)hr20@wuEgW$ z(##Jq1EejXR(dAtoPW6B9~FFIud#}*q)qC@)hCE#3VpMlwd0QE=iPO-HHOm*n*id1 zjg;X(f9Vn+-N>XHTK`|mYXQY}jjCJ^K!{TyS1TbOp!QHC8K}v53y(%#+FL;_&hVPtug&i~e za89djI3n(Td}aN3uT!;6INIr}$_3G+Ss+>!`76U213rhC;htGni{F_oN!}9VFfQ0{3eix?jZ&!7s=#1$?wMW zYB&B-C(9b^|3c-LOIK)wC+pl}&KEgcg@e;2><*q)R4DHv>IydX??K_(!X!kjXJ4)u zS9;>v)fftPJwve<9K&&#=w#oxVVCZF^fJ;+%*s{f&YrDLVEG=lth?_od3lWXWUQf~ zCF%=P^y8f{aY&ds@tfRCdwXlxn$9>Dv71Z-KB(j{sA0x!#lc&k814taq~mMDG4-ms~*)|KqRwuPqdI=B(Q^U*x&l-)s$!zI=dCn-*X z@SDIQ${OlV?~(2vj%s~(Pd)gPh>Iv3rJhh+aKP|Cf*Uav?(wP)YHebKs3guA*f8#b$Acsl%m8$ZwqMqnIpm`V5pgOt|v>e(3d z$w|z83^fQ)YYsjiLv`JN6HJMIh1T8~udzLqu0(6)!-9CczP`EGrN=a$3*0+nY%mcF z3oI@7uO-??ur*Nwb-C`SM-?+s>Ll{}!z5Se}tJ z%Egkik5z=0g*LsUm%6(A(<|%yh>Q=6g4!u?zN{hF0FAH1B8+Okmaaq|7%U!jO(jrS z2it-$QbG%W^87g;z!5(_1KWK@qJl_gE1t1`;t1WLLp^~9JU5s2>@G>>p;<+IGd9;6 z%EcXco4By_V|%S{G=21d1O=kpjBFf5zTJPM1O;Dw`W_|COW^6Y)z8*!63-Ai=#xy# zx0jr`$*#5)T)+SHY7rGMpP8Wx%Zj_G*Y#LHfOu)|fPw)PfJw`2#O`u~h(c}AD+qVL zVJvC`0ReaC<=qk+6ps9?v~|_Vg-?aawP(wh5Jy71iq4f zp}ODpE;vjMfTh@(4Qb=!fc$hJ5@z)%Xw@44Ayzm!-Hk+m0(h4Ty{{ca(8Dq#o(&lp zvOOXSB_!doJCese={#bgK7^GZvKkJF%~R9HahcE{fdTL3#k|DULyfbxh}W;Dr0y4~ zd&tS^1WZsGoymd&(G=SS6N;AjZ&-ig z=Kj3N?eEKj+`-%lV7NmEoOH4_phOS>R-bp@0PJ#LLci^!l#9K+yfQ^X$`^Ji`q03C z(MB~<3BT-J{fwLdA25kK-#R+3!&_Fw$VqI3=AZIaAyNb|#gi4wt>zCOUU!j`3t{1= zcOEg>7czMZiEq0N;_+L+>Kw*d$bqkbfR=&rPQZGMJxru4Ow*&)%~Ca+dKy9`?`tQ~ z^$=GCju?|9|1MRav{*E;5u9dg2b46+iokJiMaWQjyS_9voDb&{m_)^jOf z8760v zv;+{xSAes|W5#^LJLMd3_HM$o`;K2_PoQ+e`*-jDfCf{|@l?YSz31d9Qw}pH1OP>} zVmRU_R73zA`f4^AH2jSV*MI(FE{TTt_W;m-r-uV5HfLhkf)I(hd;FGy>=h2^jI`0;M>2?=56+()ikwd%n%sOGd!E>v_UT8Zwk>ic0v z31r=RYwYvD6PLeeexn~P$71~mI^*#+j+o#s$C&5Ib3dbT*akwQ^#I@yjzyIqTdo+- zY4*dSOA@kfU0Ni*0rQTH;skC2uiJp~Qn*-mP@G~w!@~K9-MIu&X#4CO)ub94^$Ttp z?e)r)f@25LMZ`gxX`_X@=9OE(Frd;w8zoDzbEg4o;UO{=Ln){PiN)FsL6&eM%&6el z7hsEe5-&sJzaGh9aEH72=KyR249AQqJ#qACF@LUxLtsCAx()b!I6(1nKn2%9lq1d~ zw;@fcVI^h@;_`(gY?BES_OhE0_M%MN$(%7T>AVCD_{xC;-mI(We@5ajF&Im%bKNI) zgl74tj%FXobH%bjS!g4|`LMHCm@Qz4(1r))4)>-NBK1%v4_7_)^|NMG-#eN&OeAYA zJbM2w%naYMYuC;J5D8X+h=#fDdulo^LuB?vGkz}6JVTLd<))I~c@i8eyczNZ)8>G!P$ zw3Bu^S>W$KKWzNnh*Lul=<*7BO$$0p)1~^-OM>k*v7;^`1$F6=>DAZ423#lu%x04Z zCk1hY9dREiRLYRk&vQy@TBvql-xLY<)e2Bd`sYnX&mIsYyD*TT(3VvQSKVGV5PP-A zt3TVP!RZ!ptlsq08N9m+*sY=KS_){NbIZ4wwN!~j(l0JEvdYhzZ5}c2Gk7B%L7BX1 z$gzh!4^k-{y!Qbzu_aVG#|qDYS`!E!?g6AYaM-Y^7x0}8#OEF+YWdZioPaN|r9VNn zp2p-H4=-LseIWx1kD1nS&v1Z2_~tcQ23(a#r-%8sBDib z5$4Elt7;`sMLp1RhEui)<{ST>IPn@t<<09rlP4^CKY<>fhuszF<2oa&>KxF`(I&E9 z)_re23^hzOE)&U~A_PIK2pf`8Qta+jR20x6Bq7LN$%Cq+TwKV@JD7`V3gfc@X!{aB z?U$ejGRP{ioNGZlUT=5MDelheQ@oE^^g6j5XKAhY-lO(Eiw})X&)CBhc?>9Sf2M!v zMX}5=*G_j(^U+tf%4b(0qSl#-9^Sp1xivOc1L-1}CFwNTuFA!A^x2Up-Y@>#dddd*SJ_xyqj99s$x @@ -41,16 +41,16 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1280" - inkscape:window-height="776" + inkscape:window-height="800" id="namedview24" showgrid="true" showguides="true" inkscape:guide-bbox="true" inkscape:zoom="22.627418" inkscape:cx="14.025105" - inkscape:cy="9.2202448" + inkscape:cy="3.9169442" inkscape:window-x="0" - inkscape:window-y="24" + inkscape:window-y="-31" inkscape:window-maximized="1" inkscape:current-layer="g4146"> - - - - - + + diff --git a/core/img/places/music.svg b/core/img/places/music.svg index 1f39766097..0a810c8887 100644 --- a/core/img/places/music.svg +++ b/core/img/places/music.svg @@ -14,8 +14,8 @@ width="16" height="16" id="svg11300" - inkscape:version="0.48.1 r9760" - sodipodi:docname="search.svg" + inkscape:version="0.48.3.1 r9886" + sodipodi:docname="music.svg" inkscape:export-filename="/home/jancborchardt/jancborchardt/ownCloud/icons/search.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> @@ -41,7 +41,7 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1280" - inkscape:window-height="776" + inkscape:window-height="800" id="namedview24" showgrid="true" showguides="true" @@ -50,7 +50,7 @@ inkscape:cx="-2.2078397" inkscape:cy="6.5568429" inkscape:window-x="0" - inkscape:window-y="24" + inkscape:window-y="-31" inkscape:window-maximized="1" inkscape:current-layer="svg11300"> - + - - - - + diff --git a/core/img/places/picture.svg b/core/img/places/picture.svg index 26c3d6312c..8d7848bb97 100644 --- a/core/img/places/picture.svg +++ b/core/img/places/picture.svg @@ -14,8 +14,8 @@ width="16" height="16" id="svg11300" - inkscape:version="0.48.1 r9760" - sodipodi:docname="audio.svg" + inkscape:version="0.48.3.1 r9886" + sodipodi:docname="picture.svg" inkscape:export-filename="/home/jancborchardt/jancborchardt/ownCloud/icons/audio.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> @@ -27,7 +27,7 @@ image/svg+xml - + @@ -41,7 +41,7 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1280" - inkscape:window-height="776" + inkscape:window-height="800" id="namedview24" showgrid="true" showguides="true" @@ -50,7 +50,7 @@ inkscape:cx="14.025105" inkscape:cy="9.2202448" inkscape:window-x="0" - inkscape:window-y="24" + inkscape:window-y="-31" inkscape:window-maximized="1" inkscape:current-layer="g4146"> - - - - + + diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 1f16fdf7c6..2509026dfa 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -44,17 +44,25 @@
From a6f3a570c68440eeb51d8b25cf7a1cef0f723a15 Mon Sep 17 00:00:00 2001 From: Bernhard Posselt Date: Sat, 27 Oct 2012 19:42:25 +0200 Subject: [PATCH 073/418] dont resize content div with javascript but use css box-sizing to do it --- core/css/styles.css | 9 ++++++++- core/js/js.js | 36 ------------------------------------ 2 files changed, 8 insertions(+), 37 deletions(-) diff --git a/core/css/styles.css b/core/css/styles.css index 677d44658e..1fc958940c 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -56,7 +56,14 @@ input[type="submit"].highlight{ background:#ffc100; border:1px solid #db0; text- /* CONTENT ------------------------------------------------------------------ */ #controls { padding: 0 0.5em; width:100%; top:3.5em; height:2.8em; margin:0; background:#f7f7f7; border-bottom:1px solid #eee; position:fixed; z-index:50; -moz-box-shadow:0 -3px 7px #000; -webkit-box-shadow:0 -3px 7px #000; box-shadow:0 -3px 7px #000; } #controls .button { display:inline-block; } -#content { top:3.5em; left:64px; position: absolute; } +#content { + height: 100%; + width: 100%; + padding-top: 3.5em; + padding-left: 64px; + box-sizing: border-box; + -moz-box-sizing: border-box; +} #leftcontent, .leftcontent { position:fixed; overflow: auto; top:6.4em; width:20em; background:#f8f8f8; border-right:1px solid #ddd; } #leftcontent li, .leftcontent li { background:#f8f8f8; padding:.5em .8em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; -webkit-transition:background-color 200ms; -moz-transition:background-color 200ms; -o-transition:background-color 200ms; transition:background-color 200ms; } #leftcontent li:hover, #leftcontent li:active, #leftcontent li.active, .leftcontent li:hover, .leftcontent li:active, .leftcontent li.active { background:#eee; } diff --git a/core/js/js.js b/core/js/js.js index c5e32f3c27..3bc988a2b6 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -464,44 +464,8 @@ function object(o) { } -/** - * Fills height of window. (more precise than height: 100%;) - */ -function fillHeight(selector) { - if (selector.length === 0) { - return; - } - var height = parseFloat($(window).height())-selector.offset().top; - selector.css('height', height + 'px'); - if(selector.outerHeight() > selector.height()){ - selector.css('height', height-(selector.outerHeight()-selector.height()) + 'px'); - } -} - -/** - * Fills height and width of window. (more precise than height: 100%; or width: 100%;) - */ -function fillWindow(selector) { - if (selector.length === 0) { - return; - } - fillHeight(selector); - var width = parseFloat($(window).width())-selector.offset().left; - selector.css('width', width + 'px'); - if(selector.outerWidth() > selector.width()){ - selector.css('width', width-(selector.outerWidth()-selector.width()) + 'px'); - } -} - $(document).ready(function(){ - $(window).resize(function () { - fillHeight($('#leftcontent')); - fillWindow($('#content')); - fillWindow($('#rightcontent')); - }); - $(window).trigger('resize'); - if(!SVGSupport()){ //replace all svg images with png images for browser that dont support svg replaceSVG(); }else{ From fba7be1194fd31da2cb71c56d0cd8e6ab1c1ff49 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 28 Oct 2012 11:26:31 +0100 Subject: [PATCH 074/418] add filesystem watcher to detect updates --- lib/files/cache/cache.php | 8 ++- lib/files/cache/watcher.php | 70 +++++++++++++++++++++++++ tests/lib/files/cache/watcher.php | 86 +++++++++++++++++++++++++++++++ tests/lib/files/view.php | 2 + 4 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 lib/files/cache/watcher.php create mode 100644 tests/lib/files/cache/watcher.php diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index cba48e4dbe..138a5e6e63 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -234,7 +234,7 @@ class Cache { $entry = $this->get($file); if ($entry['mimetype'] === 'httpd/unix-directory') { $children = $this->getFolderContents($file); - foreach($children as $child){ + foreach ($children as $child) { $this->remove($child['path']); } } @@ -325,7 +325,9 @@ class Cache { $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `parent` = ? AND `storage` = ?'); $result = $query->execute(array($id, $this->storageId)); $totalSize = 0; + $hasChilds = 0; while ($row = $result->fetchRow()) { + $hasChilds = true; $size = (int)$row['size']; if ($size === -1) { $totalSize = -1; @@ -335,7 +337,9 @@ class Cache { } } - $this->update($id, array('size' => $totalSize)); + if ($hasChilds) { + $this->update($id, array('size' => $totalSize)); + } return $totalSize; } diff --git a/lib/files/cache/watcher.php b/lib/files/cache/watcher.php new file mode 100644 index 0000000000..f04ca9b465 --- /dev/null +++ b/lib/files/cache/watcher.php @@ -0,0 +1,70 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * check the storage backends for updates and change the cache accordingly + */ +class Watcher { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var Cache $cache + */ + private $cache; + + /** + * @var Scanner $scanner; + */ + private $scanner; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->cache = $storage->getCache(); + $this->scanner = $storage->getScanner(); + } + + /** + * check $path for updates + * + * @param string $path + */ + public function checkUpdate($path) { + $cachedEntry = $this->cache->get($path); + if ($this->storage->hasUpdated($path, $cachedEntry['mtime'])) { + if ($cachedEntry['mimetype'] === 'httpd/unix-directory') { + $this->scanner->scan($path, Scanner::SCAN_SHALLOW); + $this->cleanFolder($path); + } else { + $this->scanner->scanFile($path); + } + $this->scanner->correctFolderSize($path); + } + } + + /** + * remove deleted files in $path from the cache + * + * @param string $path + */ + public function cleanFolder($path) { + $cachedContent = $this->cache->getFolderContents($path); + foreach ($cachedContent as $entry) { + if (!$this->storage->file_exists($entry['path'])) { + $this->cache->remove($entry['path']); + } + } + } +} diff --git a/tests/lib/files/cache/watcher.php b/tests/lib/files/cache/watcher.php new file mode 100644 index 0000000000..a7076d9b0b --- /dev/null +++ b/tests/lib/files/cache/watcher.php @@ -0,0 +1,86 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +class Watcher extends \PHPUnit_Framework_TestCase { + + /** + * @var \OC\Files\Storage\Storage[] $storages; + */ + private $storages = array(); + + public function setUp() { + \OC\Files\Filesystem::clearMounts(); + } + + public function tearDown() { + foreach ($this->storages as $storage) { + $cache = $storage->getCache(); + $ids = $cache->getAll(); + \OC\Files\Cache\Permissions::removeMultiple($ids, \OC_User::getUser()); + $cache->clear(); + } + } + + function testWatcher() { + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = new \OC\Files\Cache\Watcher($storage); + + //set the mtime to the past so it can detect an mtime change + $cache->put('', array('mtime' => 10)); + + $this->assertTrue($cache->inCache('folder/bar.txt')); + $this->assertTrue($cache->inCache('folder/bar2.txt')); + + $this->assertFalse($cache->inCache('bar.test')); + $storage->file_put_contents('bar.test', 'foo'); + $updater->checkUpdate(''); + $this->assertTrue($cache->inCache('bar.test')); + $cachedData = $cache->get('bar.test'); + $this->assertEquals(3, $cachedData['size']); + + $cache->put('bar.test', array('mtime' => 10)); + $storage->file_put_contents('bar.test', 'test data'); + + $updater->checkUpdate('bar.test'); + $cachedData = $cache->get('bar.test'); + $this->assertEquals(9, $cachedData['size']); + + $cache->put('folder', array('mtime' => 10)); + + $storage->unlink('folder/bar2.txt'); + $updater->checkUpdate('folder'); + + $this->assertTrue($cache->inCache('folder/bar.txt')); + $this->assertFalse($cache->inCache('folder/bar2.txt')); + } + + /** + * @param bool $scan + * @return \OC\Files\Storage\Storage + */ + private function getTestStorage($scan = true) { + $storage = new \OC\Files\Storage\Temporary(array()); + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $storage->mkdir('folder'); + $storage->file_put_contents('foo.txt', $textData); + $storage->file_put_contents('foo.png', $imgData); + $storage->file_put_contents('folder/bar.txt', $textData); + $storage->file_put_contents('folder/bar2.txt', $textData); + + if ($scan) { + $scanner = $storage->getScanner(); + $scanner->scan(''); + } + $this->storages[] = $storage; + return $storage; + } +} diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 5144fb1caa..8ff03963e4 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -20,6 +20,8 @@ class View extends \PHPUnit_Framework_TestCase { public function tearDown() { foreach ($this->storages as $storage) { $cache = $storage->getCache(); + $ids = $cache->getAll(); + \OC\Files\Cache\Permissions::removeMultiple($ids, \OC_User::getUser()); $cache->clear(); } } From b07672821bcdc1ce31e169d691cf9a94ad5fad21 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 28 Oct 2012 11:43:45 +0100 Subject: [PATCH 075/418] check for changes when using the cache api --- lib/files/view.php | 6 ++++++ tests/lib/files/view.php | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/files/view.php b/lib/files/view.php index 0f9a5feb5d..c5e470b438 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -672,6 +672,9 @@ class View { if (!$cache->inCache($internalPath)) { $scanner = $storage->getScanner(); $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } else { + $watcher = new \OC\Files\Cache\Watcher($storage); + $watcher->checkUpdate($internalPath); } $data = $cache->get($internalPath); @@ -711,6 +714,9 @@ class View { if (!$cache->inCache($internalPath)) { $scanner = $storage->getScanner(); $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } else { + $watcher = new \OC\Files\Cache\Watcher($storage); + $watcher->checkUpdate($internalPath); } $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 8ff03963e4..fa562cb15c 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -146,6 +146,23 @@ class View extends \PHPUnit_Framework_TestCase { $this->assertEquals(3, count($folderView->searchByMime('text'))); } + function testWatcher() { + $storage1 = $this->getTestStorage(); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + + $rootView = new \OC\Files\View(''); + + $cachedData = $rootView->getFileInfo('foo.txt'); + $this->assertEquals(16, $cachedData['size']); + + $rootView->putFileInfo('foo.txt', array('mtime' => 10)); + $storage1->file_put_contents('foo.txt', 'foo'); + clearstatcache(); + + $cachedData = $rootView->getFileInfo('foo.txt'); + $this->assertEquals(3, $cachedData['size']); + } + /** * @param bool $scan * @return \OC\Files\Storage\Storage From 7af8c6c3cbf00444b81a6d3d0c4cede983ff8edc Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sun, 28 Oct 2012 16:05:31 +0100 Subject: [PATCH 076/418] first version of breadcrumb 'root', still have a weird feeling about this --- apps/files/appinfo/app.php | 2 +- apps/files/css/files.css | 4 - apps/files/templates/part.breadcrumb.php | 11 +- core/css/styles.css | 5 +- core/img/places/files.png | Bin 0 -> 372 bytes core/img/places/files.svg | 1779 ++++++++++++++++++++++ core/img/places/home.png | Bin 372 -> 364 bytes core/img/places/home.svg | 83 +- 8 files changed, 1853 insertions(+), 31 deletions(-) create mode 100644 core/img/places/files.png create mode 100644 core/img/places/files.svg diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php index b431ddfec0..3f437679e0 100644 --- a/apps/files/appinfo/app.php +++ b/apps/files/appinfo/app.php @@ -3,6 +3,6 @@ $l=OC_L10N::get('files'); OCP\App::registerAdmin('files', 'admin'); -OCP\App::addNavigationEntry( array( "id" => "files_index", "order" => 0, "href" => OCP\Util::linkTo( "files", "index.php" ), "icon" => OCP\Util::imagePath( "core", "places/home.svg" ), "name" => $l->t("Files") )); +OCP\App::addNavigationEntry( array( "id" => "files_index", "order" => 0, "href" => OCP\Util::linkTo( "files", "index.php" ), "icon" => OCP\Util::imagePath( "core", "places/files.svg" ), "name" => $l->t("Files") )); OC_Search::registerProvider('OC_Search_Provider_File'); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 0b886fc3fa..64e49d687d 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -81,10 +81,6 @@ a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; } .selectedActions a { display:inline; margin:-.5em 0; padding:.5em !important; } .selectedActions a img { position:relative; top:.3em; } -/* add breadcrumb divider to the File item in navigation panel */ -#navigation>ul>li:first-child { background:url('%webroot%/core/img/breadcrumb-start.svg') no-repeat 12.5em 0px; width:12.5em; padding-right:1em; position:fixed; } -#navigation>ul>li:first-child+li { padding-top:2.9em; } #scanning-message{ top:40%; left:40%; position:absolute; display:none; } -div.crumb a{ padding: 0.9em 0 0.7em 0; } diff --git a/apps/files/templates/part.breadcrumb.php b/apps/files/templates/part.breadcrumb.php index 71b695f65f..37b6b63eab 100644 --- a/apps/files/templates/part.breadcrumb.php +++ b/apps/files/templates/part.breadcrumb.php @@ -1,6 +1,13 @@ + +
+ + + +
+ -
svg" data-dir='' style='background-image:url("")'> - "> +
svg" data-dir=''> +
diff --git a/core/css/styles.css b/core/css/styles.css index 95dceb50de..0dbba19742 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -177,6 +177,7 @@ a.bookmarklet { background-color: #ddd; border:1px solid #ccc; padding: 5px;padd .arrow.down { -webkit-transform: rotate(180deg); -moz-transform: rotate(180deg); -o-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } /* ---- BREADCRUMB ---- */ -div.crumb { float:left; display:block; background:no-repeat right 0; padding:.75em 1.5em 0 1em; height:2.9em; } +div.crumb { float:left; display:block; background:url('../img/breadcrumb.svg') no-repeat right 0; padding:.75em 1.5em 0 1em; height:2.9em; } div.crumb:first-child { padding-left:1em; } -div.crumb.last { font-weight:bold; } +div.crumb.last { font-weight:bold; background:none; padding-right:0; } +div.crumb a{ padding: 0.9em 0 0.7em 0; } diff --git a/core/img/places/files.png b/core/img/places/files.png new file mode 100644 index 0000000000000000000000000000000000000000..c3dbd3e35386f938b7e8928a0f90ec4660eb88a5 GIT binary patch literal 372 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4XK2Qi{56`sSx;f&^RV;~8eY%-?4$ ztK7jEG0k<7%EH{+O&un_`Z`AcSY(b@p5OH5PN}So()rKbxwqvn*_`)3Q`jiLB>1|P z?Jm#8_tgRi|F$MdIIx&E7_tf8VNhgoDPVqV(ZrFLV0<_Bw)uUdU!0PLtIsL|JgTe~DWM4fyC09M literal 0 HcmV?d00001 diff --git a/core/img/places/files.svg b/core/img/places/files.svg new file mode 100644 index 0000000000..4b45ef12bc --- /dev/null +++ b/core/img/places/files.svg @@ -0,0 +1,1779 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/img/places/home.png b/core/img/places/home.png index c3dbd3e35386f938b7e8928a0f90ec4660eb88a5..5cbf1114cb7c3300ebcc14c9ba75243e31a361ef 100644 GIT binary patch delta 262 zcmV+h0r~#)0_*~iZhz@XL_t(Ijm48aYQ!)Qh2Q+hm5;E2T}TtuPUR!y3Sn_>lWU~O z4ML9KN`0EZ7ReD@O4e3dvXN~94n!ar!F%7lkr0Itux0TkHvla~*L8P9^q6zLT{95T zN6z_q7>4(&1gd&u=7Wf&wav^2RXtV-n0XH%j^p?cLdbL7&3}yAwtWGxzxYbwoQux6 zxMT)^5JE1xDuJq|wb_?+-vP0f&jPM=e+OXZUjcsVHs8VJ6+|S8NHjBAGMgC{U6sH& zcLH$Zy?^Taeiq=p2Y?9RvjQ|t^O{n+XXf472-7ruiO5?y)4%`5GbiXwY2s`-1^@s6 M07*qoM6N<$f_b!jP5=M^ delta 271 zcmV+q0r39p0`vlqZh!GfL_t(I%caplYJ@NpfMG!p1Xm)uQB)9I2!fz)L>D41q(O>p zno^m2%{h99GMVuZ^G;%8L2#gPGVS7nge3psMVh8b`j6d1S(aaz=RXD5!&lGqPOj^o z*cvH{^0~SjP*oKNK|qV5fQ9e-U+a3Y8_+Zjhha#os)AKnmVejno*U40{Vj?jTGut~ z`88k|28rXCHcbQfn5KES30Rgzk|d#R+k%KRO)o4m{sbJydC#)!+;ts1BG2=SZQCEx zJkL+eae)v55$k;h-3+nsdl13>g<%*#1UG;&1`*rD%PDHLkV1kzyc|HID diff --git a/core/img/places/home.svg b/core/img/places/home.svg index 4b45ef12bc..c56f966128 100644 --- a/core/img/places/home.svg +++ b/core/img/places/home.svg @@ -14,9 +14,9 @@ width="16" height="16" id="svg11300" - inkscape:version="0.48.1 r9760" - sodipodi:docname="help.svg" - inkscape:export-filename="/home/jancborchardt/jancborchardt/ownCloud/icons/help.png" + inkscape:version="0.48.3.1 r9886" + sodipodi:docname="home.svg" + inkscape:export-filename="home.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> + + + + + + + - - - - + + From 71435ac1ccdceb75342202c86a763b4f3f7c8b0e Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sun, 28 Oct 2012 16:47:25 +0100 Subject: [PATCH 077/418] improve user menu --- core/css/styles.css | 7 ++++--- core/js/js.js | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/css/styles.css b/core/css/styles.css index 677d44658e..bacb6367e8 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -106,9 +106,10 @@ label.infield { cursor: text !important; } #expand:hover, #expand:focus, #expand:active { color:#fff; } #expand img { opacity:.5; margin-bottom:-2px; } #expand:hover img, #expand:focus img, #expand:active img { opacity:1; } -#expanddiv { position:absolute; right:0; background-color:rgb(29, 45, 68); } -#expanddiv a { color:#ccc; padding:5px 8px; } -#expanddiv a:hover, #expanddiv a:focus, #expanddiv a:active { color:#fff; } +#expanddiv { position:absolute; right:0; top:45px; background-color:#eee; border-bottom-left-radius:7px; box-shadow: 0 0 20px #888888; z-index:76; } +#expanddiv a { display:block; color:#222; text-shadow:0 1px 0 #fff; padding:0 8px; } +#expanddiv a img { margin-bottom:-3px; } +#expanddiv a:hover, #expanddiv a:focus, #expanddiv a:active { color:#555; background-color:#ddd; } /* VARIOUS REUSABLE SELECTORS */ .hidden { display:none; } diff --git a/core/js/js.js b/core/js/js.js index 6ed50b42bd..d0399063cf 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -590,9 +590,7 @@ $(document).ready(function(){ event.stopPropagation(); }); $(window).click(function(){//hide the settings menu when clicking outside it - if($('body').attr("id")==="body-user"){ - $('#settings #expanddiv').slideUp(); - } + $('#settings #expanddiv').slideUp(); }); // all the tipsy stuff needs to be here (in reverse order) to work From db5e8f3eeac46708d4a437fded5d83fb6eaad558 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sun, 28 Oct 2012 17:22:18 +0100 Subject: [PATCH 078/418] enable hover state, now that needs to be added to the other icons --- core/css/styles.css | 4 ++-- core/img/places/files.svg | 35 +++++++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/core/css/styles.css b/core/css/styles.css index f2d986118e..34c08fb7f1 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -103,8 +103,8 @@ label.infield { cursor: text !important; } #navigation { position:fixed; top:3.5em; float:left; width:64px; padding:0; z-index:75; height:100%; background:#30343a url('../img/noise.png') repeat; border-right:1px #333 solid; -moz-box-shadow:0 0 7px #000; -webkit-box-shadow:0 0 7px #000; box-shadow:0 0 7px #000; overflow:hidden;} #navigation a { display:block; padding:8px 0 4px; text-decoration:none; font-size:10px; text-align:center; color:#000; text-shadow:#444 0 1px 0; } #navigation .icon { width:32px; height:32px; background-position:0 0; background-repeat:no-repeat; margin:0 16px 0; } -/*#navigation li:hover div { background-position:-32px 0; } hover & active effect, activate when icons are sprited*/ -#navigation li:first-child { padding-top:8px; } +#navigation li:hover div, #navigation li a.active div { background-position:-32px 0; } +#navigation li:first-child a { padding-top:16px; } #navigation a img { display:block; width:32px; height:32px; margin:0 auto; } #navigation a.active, #navigation a:hover, #navigation a:focus { color:#888; text-shadow:#000 0 -1px 0; } #navigation a.active { } diff --git a/core/img/places/files.svg b/core/img/places/files.svg index e2db10ed36..617b0422de 100644 --- a/core/img/places/files.svg +++ b/core/img/places/files.svg @@ -7,6 +7,7 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="64" @@ -14,12 +15,34 @@ id="svg3349" version="1.1" inkscape:version="0.48.3.1 r9886" - sodipodi:docname="home.svg" + sodipodi:docname="files.svg" inkscape:export-filename="home.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> + id="defs3351"> + + + + + + image/svg+xml - + @@ -73,7 +96,7 @@ inkscape:connector-curvature="0" d="m -525.14286,-108.23534 -16,15.938823 6,0 0,12.0002 20,0 0,-12.0002 6,0 -6,-6.061183 0,-7.93905 -6,0 0,2.16234 -4,-4.10093 z" id="path3328" - style="opacity:0.3;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="opacity:0.50000000000000000;fill:url(#linearGradient3760);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> Date: Mon, 29 Oct 2012 01:14:07 +0100 Subject: [PATCH 079/418] added old functions again to not break apps, but show a deprecation warning --- core/js/js.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/core/js/js.js b/core/js/js.js index ee8bdea725..a30d66c00b 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -468,6 +468,36 @@ function object(o) { return new F(); } +/** + * Fills height of window. (more precise than height: 100%;) + */ +function fillHeight(selector) { + if (selector.length === 0) { + return; + } + var height = parseFloat($(window).height())-selector.offset().top; + selector.css('height', height + 'px'); + if(selector.outerHeight() > selector.height()){ + selector.css('height', height-(selector.outerHeight()-selector.height()) + 'px'); + } + console.warn("This function is deprecated! Use CSS instead"); +} + +/** + * Fills height and width of window. (more precise than height: 100%; or width: 100%;) + */ +function fillWindow(selector) { + if (selector.length === 0) { + return; + } + fillHeight(selector); + var width = parseFloat($(window).width())-selector.offset().left; + selector.css('width', width + 'px'); + if(selector.outerWidth() > selector.width()){ + selector.css('width', width-(selector.outerWidth()-selector.width()) + 'px'); + } + console.warn("This function is deprecated! Use CSS instead"); +} $(document).ready(function(){ From 8a6bb7965d96b1c4297da8f5dbc9644fec7aeb0f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 2 Nov 2012 22:25:33 +0100 Subject: [PATCH 080/418] add Cache::move --- lib/files/cache/cache.php | 29 +++++++++++++++++++++++++++-- tests/lib/files/cache/cache.php | 26 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 138a5e6e63..9d13b56104 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -242,6 +242,32 @@ class Cache { $query->execute(array($entry['fileid'])); } + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $sourceId = $this->getId($source); + $newParentId = $this->getParentId($target); + + //find all child entries + $query = \OC_DB::prepare('SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `path` LIKE ?'); + $result = $query->execute(array($source . '/%')); + $childEntries = $result->fetchAll(); + $sourceLength = strlen($source); + $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ? WHERE `fileid` = ?'); + + foreach ($childEntries as $child) { + $targetPath = $target . substr($child['path'], $sourceLength); + $query->execute(array($targetPath, md5($targetPath), $child['fileid'])); + } + + $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ?, `parent` =? WHERE `fileid` = ?'); + $query->execute(array($target, md5($target), $newParentId, $sourceId)); + } + /** * remove all entries for files that are stored on the storage from the cache */ @@ -296,8 +322,7 @@ class Cache { /** * search for files by mimetype * - * @param string $part1 - * @param string $part2 + * @param string $mimetype * @return array */ public function searchByMime($mimetype) { diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index 4f22e9bd1d..9c469aa937 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -149,6 +149,32 @@ class Cache extends \UnitTestCase { $this->assertEquals(2, count($this->cache->searchByMime('foo/file'))); } + function testMove() { + $file1 = 'folder'; + $file2 = 'folder/bar'; + $file3 = 'folder/foo'; + $file4 = 'folder/foo/1'; + $file5 = 'folder/foo/2'; + $data = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/bar'); + + $this->cache->put($file1, $data); + $this->cache->put($file2, $data); + $this->cache->put($file3, $data); + $this->cache->put($file4, $data); + $this->cache->put($file5, $data); + + $this->cache->move('folder/foo', 'folder/foobar'); + + $this->assertFalse($this->cache->inCache('folder/foo')); + $this->assertFalse($this->cache->inCache('folder/foo/1')); + $this->assertFalse($this->cache->inCache('folder/foo/2')); + + $this->assertTrue($this->cache->inCache('folder/bar')); + $this->assertTrue($this->cache->inCache('folder/foobar')); + $this->assertTrue($this->cache->inCache('folder/foobar/1')); + $this->assertTrue($this->cache->inCache('folder/foobar/2')); + } + public function tearDown() { $this->cache->clear(); } From 72c38686449a8887d8441377a11f0bfc910bc31f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 17:42:26 +0100 Subject: [PATCH 081/418] add resolvePath to filesystem view api --- lib/files/view.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/files/view.php b/lib/files/view.php index 709e374d86..e3f9b762f7 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -101,6 +101,16 @@ class View { return Filesystem::getMountPoint($this->getAbsolutePath($path)); } + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array consisting of the storage and the internal path + */ + public function resolvePath($path) { + return Filesystem::resolvePath($this->getAbsolutePath($path)); + } + /** * return the path to a local version of the file * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed From 706bb3ccd654f76c0a6c5b7874fcc1e288b13f54 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 17:47:00 +0100 Subject: [PATCH 082/418] move ETag generation to storage backends --- lib/connector/sabre/node.php | 16 +--------------- lib/files/filesystem.php | 10 ++++++++++ lib/files/storage/common.php | 16 ++++++++++++++++ lib/files/storage/storage.php | 8 ++++++++ lib/files/view.php | 15 +++++++++++++++ 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php index ebc9e53962..d0ab0c2498 100644 --- a/lib/connector/sabre/node.php +++ b/lib/connector/sabre/node.php @@ -210,27 +210,13 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr return $props; } - /** - * Creates a ETag for this path. - * @param string $path Path of the file - * @return string|null Returns null if the ETag can not effectively be determined - */ - static protected function createETag($path) { - if(self::$ETagFunction) { - $hash = call_user_func(self::$ETagFunction, $path); - return $hash; - }else{ - return uniqid('', true); - } - } - /** * Returns the ETag surrounded by double-quotes for this path. * @param string $path Path of the file * @return string|null Returns null if the ETag can not effectively be determined */ static public function getETagPropertyForPath($path) { - $tag = self::createETag($path); + $tag = \OC\Files\Filesystem::getETag($path); if (empty($tag)) { return null; } diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 76a188e412..ed2814211f 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -654,6 +654,16 @@ class Filesystem { public static function getDirectoryContent($directory, $mimetype_filter = '') { return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter); } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + static public function getETag($path){ + return self::$defaultInstance->getETag($path); + } } \OC_Hook::connect('OC_Filesystem', 'post_write', 'OC_Filesystem', 'removeETagHook'); diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index e22264d0da..f471752d1f 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -267,4 +267,20 @@ abstract class Common implements \OC\Files\Storage\Storage { public function getOwner($path) { return \OC_User::getUser(); } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path){ + $ETagFunction = \OC_Connector_Sabre_Node::$ETagFunction; + if($ETagFunction) { + $hash = call_user_func($ETagFunction, $path); + return $hash; + }else{ + return uniqid('', true); + } + } } diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index 1f5c935629..bb1ba16984 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -63,4 +63,12 @@ interface Storage{ public function getScanner(); public function getOwner($path); + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path); } diff --git a/lib/files/view.php b/lib/files/view.php index e3f9b762f7..a54c3ee356 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -866,4 +866,19 @@ class View { return $files; } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path){ + /** + * @var Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = $this->resolvePath($path); + return $storage->getETag($internalPath); + } } From e7bed5ddabea5a3841d4a08082badaec025ed242 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 17:59:08 +0100 Subject: [PATCH 083/418] allow creating Cache objects with only the storage id instead of the whole storage object --- lib/files/cache/cache.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 9d13b56104..dba026a611 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -19,24 +19,25 @@ class Cache { const SHALLOW = 2; //folder in cache, but not all child files are completely scanned const COMPLETE = 3; - /** - * @var \OC\Files\Storage\Storage - */ - private $storage; - /** * @var array partial data for the cache */ private $partial = array(); + /** + * @var string + */ private $storageId; /** - * @param \OC\Files\Storage\Storage $storage + * @param \OC\Files\Storage\Storage|string $storage */ - public function __construct(\OC\Files\Storage\Storage $storage) { - $this->storage = $storage; - $this->storageId = $storage->getId(); + public function __construct($storage) { + if($storage instanceof \OC\Files\Storage\Storage){ + $this->storageId = $storage->getId(); + }else{ + $this->storageId = $storage; + } } /** From 3f644fe70c8451ab1ab877c4dd97f34366548b70 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 18:07:30 +0100 Subject: [PATCH 084/418] fix calculateFolderSize for non existing files --- lib/files/cache/cache.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index dba026a611..83d3585bb5 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -348,6 +348,9 @@ class Cache { */ public function calculateFolderSize($path) { $id = $this->getId($path); + if($id === -1){ + return 0; + } $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `parent` = ? AND `storage` = ?'); $result = $query->execute(array($id, $this->storageId)); $totalSize = 0; From 15b8a3f98757f220db190c1e9489d5888b2b1015 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 18:10:54 +0100 Subject: [PATCH 085/418] move correctFolderSize from Scanner to Cache --- lib/files/cache/cache.php | 16 ++++++++++++++++ lib/files/cache/scanner.php | 16 ---------------- lib/files/cache/watcher.php | 2 +- tests/lib/files/cache/scanner.php | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 83d3585bb5..7c6bba4fad 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -340,6 +340,22 @@ class Cache { return $result->fetchAll(); } + /** + * update the folder size and the size of all parent folders + * + * @param $path + */ + public function correctFolderSize($path) { + $this->calculateFolderSize($path); + if ($path !== '') { + $parent = dirname($path); + if ($parent === '.') { + $parent = ''; + } + $this->correctFolderSize($parent); + } + } + /** * get the size of a folder and set it in the cache * diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 7062888d74..0adde1d354 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -107,20 +107,4 @@ class Scanner { } return $size; } - - /** - * update the folder size and the size of all parent folders - * - * @param $path - */ - public function correctFolderSize($path) { - $this->cache->calculateFolderSize($path); - if ($path !== '') { - $parent = dirname($path); - if ($parent === '.') { - $parent = ''; - } - $this->correctFolderSize($parent); - } - } } diff --git a/lib/files/cache/watcher.php b/lib/files/cache/watcher.php index f04ca9b465..d6039d9945 100644 --- a/lib/files/cache/watcher.php +++ b/lib/files/cache/watcher.php @@ -50,7 +50,7 @@ class Watcher { } else { $this->scanner->scanFile($path); } - $this->scanner->correctFolderSize($path); + $this->cache->correctFolderSize($path); } } diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index 34d38c4273..f784a82dad 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -98,7 +98,7 @@ class Scanner extends \UnitTestCase { $this->assertNotEqual($cachedDataFolder2['size'], -1); - $this->scanner->correctFolderSize('folder'); + $this->cache->correctFolderSize('folder'); $cachedDataFolder = $this->cache->get(''); $this->assertNotEqual($cachedDataFolder['size'], -1); From 5a173b901f90dba3465847cde4477be9994a1341 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 20:47:40 +0100 Subject: [PATCH 086/418] fix Scanner->scan setting the filesize to 0 for files --- lib/files/cache/scanner.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 0adde1d354..eb947a2d53 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -101,9 +101,9 @@ class Scanner { } } } - } - if ($size !== -1) { - $this->cache->put($path, array('size' => $size)); + if ($size !== -1) { + $this->cache->put($path, array('size' => $size)); + } } return $size; } From fedff3eafe73a3eafbc1bc7af364297e8135d086 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 21:12:40 +0100 Subject: [PATCH 087/418] add cache updater --- lib/files/cache/updater.php | 71 ++++++++++++++++++ tests/lib/files/cache/updater.php | 116 ++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 lib/files/cache/updater.php create mode 100644 tests/lib/files/cache/updater.php diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php new file mode 100644 index 0000000000..fb9783023e --- /dev/null +++ b/lib/files/cache/updater.php @@ -0,0 +1,71 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * listen to filesystem hooks and change the cache accordingly + */ +class Updater { + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + $view = \OC\Files\Filesystem::getView(); + return $view->resolvePath($path); + } + + static public function writeUpdate($path) { + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($path); + $cache = new Cache($storage); + $scanner = new Scanner($storage); + $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); + $cache->correctFolderSize($internalPath); + } + + static public function deleteUpdate($path) { + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($path); + $cache = new Cache($storage); + $cache->remove($internalPath); + $cache->correctFolderSize($internalPath); + } + + /** + * @param array $params + */ + static public function writeHook($params) { + self::writeUpdate($params['path']); + } + + /** + * @param array $params + */ + static public function renameHook($params) { + self::deleteUpdate($params['oldpath']); + self::writeUpdate($params['newpath']); + } + + /** + * @param array $params + */ + static public function deleteHook($params) { + self::deleteUpdate($params['path']); + } +} diff --git a/tests/lib/files/cache/updater.php b/tests/lib/files/cache/updater.php new file mode 100644 index 0000000000..8059418dc1 --- /dev/null +++ b/tests/lib/files/cache/updater.php @@ -0,0 +1,116 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +use \OC\Files\Filesystem as Filesystem; + +class Updater extends \PHPUnit_Framework_TestCase { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var \OC\Files\Cache\Scanner $scanner + */ + private $scanner; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + private $cache; + + private static $user; + + public function setUp() { + $this->storage = new \OC\Files\Storage\Temporary(array()); + $textData = "dummy file data\n"; + $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png'); + $this->storage->mkdir('folder'); + $this->storage->file_put_contents('foo.txt', $textData); + $this->storage->file_put_contents('foo.png', $imgData); + $this->storage->file_put_contents('folder/bar.txt', $textData); + $this->storage->file_put_contents('folder/bar2.txt', $textData); + + $this->scanner = $this->storage->getScanner(); + $this->scanner->scan(''); + $this->cache = $this->storage->getCache(); + + if (!self::$user) { + if (!\OC\Files\Filesystem::getView()) { + self::$user = uniqid(); + \OC\Files\Filesystem::init('/' . self::$user . '/files'); + } else { + self::$user = \OC_User::getUser(); + } + } + + Filesystem::clearMounts(); + Filesystem::mount($this->storage, array(), '/' . self::$user . '/files'); + + \OC_Hook::connect('OC_Filesystem', 'post_write', '\OC\Files\Cache\Updater', 'writeHook'); + \OC_Hook::connect('OC_Filesystem', 'post_delete', '\OC\Files\Cache\Updater', 'deleteHook'); + \OC_Hook::connect('OC_Filesystem', 'post_rename', '\OC\Files\Cache\Updater', 'renameHook'); + + } + + public function tearDown() { + $this->cache->clear(); + Filesystem::tearDown(); + } + + public function testWrite() { + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $cachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + + Filesystem::file_put_contents('foo.txt', 'asd'); + $cachedData = $this->cache->get('foo.txt'); + $this->assertEquals(3, $cachedData['size']); + $cachedData = $this->cache->get(''); + $this->assertEquals(2 * $textSize + $imageSize + 3, $cachedData['size']); + + $this->assertFalse($this->cache->inCache('bar.txt')); + Filesystem::file_put_contents('bar.txt', 'asd'); + $this->assertTrue($this->cache->inCache('bar.txt')); + $cachedData = $this->cache->get('bar.txt'); + $this->assertEquals(3, $cachedData['size']); + $cachedData = $this->cache->get(''); + $this->assertEquals(2 * $textSize + $imageSize + 2 * 3, $cachedData['size']); + } + + public function testDelete() { + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $cachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + + $this->assertTrue($this->cache->inCache('foo.txt')); + Filesystem::unlink('foo.txt', 'asd'); + $this->assertFalse($this->cache->inCache('foo.txt')); + $cachedData = $this->cache->get(''); + $this->assertEquals(2 * $textSize + $imageSize, $cachedData['size']); + } + + public function testRename() { + $textSize = strlen("dummy file data\n"); + $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); + $cachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + + $this->assertTrue($this->cache->inCache('foo.txt')); + $this->assertFalse($this->cache->inCache('bar.txt')); + Filesystem::rename('foo.txt', 'bar.txt'); + $this->assertFalse($this->cache->inCache('foo.txt')); + $this->assertTrue($this->cache->inCache('bar.txt')); + $cachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + } +} From d4858527c2fae4b58c5a9bc1c3aae9ada0ee7b24 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 21:35:30 +0100 Subject: [PATCH 088/418] fix rename parameter order --- apps/files/ajax/rename.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/files/ajax/rename.php b/apps/files/ajax/rename.php index a2b9b8de25..470ee635a2 100644 --- a/apps/files/ajax/rename.php +++ b/apps/files/ajax/rename.php @@ -11,9 +11,9 @@ $dir = stripslashes($_GET["dir"]); $file = stripslashes($_GET["file"]); $newname = stripslashes($_GET["newname"]); -if (OC_User::isLoggedIn() && ($dir != '' || $file != 'Shared')) { - $targetFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file); - $sourceFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $newname); +if (($dir != '' || $file != 'Shared')) { + $targetFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $newname); + $sourceFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file); if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) { OCP\JSON::success(array("data" => array( "dir" => $dir, "file" => $file, "newname" => $newname ))); } else { From 46071fed80c7375edf4788578776a6b313b6dca3 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 21:35:40 +0100 Subject: [PATCH 089/418] fix quota proxy --- lib/fileproxy/quota.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/fileproxy/quota.php b/lib/fileproxy/quota.php index b359b9ae02..a8219191bf 100644 --- a/lib/fileproxy/quota.php +++ b/lib/fileproxy/quota.php @@ -22,7 +22,7 @@ */ /** - * user quota managment + * user quota management */ class OC_FileProxy_Quota extends OC_FileProxy{ @@ -39,10 +39,8 @@ class OC_FileProxy_Quota extends OC_FileProxy{ return $this->userQuota[$user]; } $userQuota=OC_Preferences::getValue($user, 'files', 'quota', 'default'); - $userQuota=OC_Preferences::getValue($user,'files','quota','default'); if($userQuota=='default') { $userQuota=OC_AppConfig::getValue('files', 'default_quota', 'none'); - $userQuota=OC_AppConfig::getValue('files','default_quota','none'); } if($userQuota=='none') { $this->userQuota[$user]=0; @@ -59,8 +57,12 @@ class OC_FileProxy_Quota extends OC_FileProxy{ * @return int */ private function getFreeSpace($path) { - $storage=OC_Filesystem::getStorage($path); - $owner=$storage->getOwner($path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); + $owner=$storage->getOwner($internalPath); $totalSpace=$this->getQuota($owner); if($totalSpace==0) { From 41a61bc637c3d60efc5abe81c39fc726fd78cd1d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 8 Nov 2012 21:36:13 +0100 Subject: [PATCH 090/418] fix undefined index when loading files app --- apps/files/index.php | 4 +--- apps/files/templates/part.list.php | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/files/index.php b/apps/files/index.php index cdb88ab83f..4e5f4cd93a 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -66,9 +66,7 @@ foreach( \OC\Files\Filesystem::getDirectoryContent( $dir ) as $i ) { $i['extension']=''; } } - if($i['directory']=='/') { - $i['directory']=''; - } + $i['directory'] = $dir; $files[] = $i; } diff --git a/apps/files/templates/part.list.php b/apps/files/templates/part.list.php index 4b5ac32567..9c9d9926e5 100644 --- a/apps/files/templates/part.list.php +++ b/apps/files/templates/part.list.php @@ -18,7 +18,7 @@ $name = str_replace('%2F', '/', $name); $directory = str_replace('+', '%20', urlencode($file['directory'])); $directory = str_replace('%2F', '/', $directory); ?> -
' data-permissions=''> + ' data-permissions=''>
From cbcd9ba84ada830d91a6f1d7bee0ac59762835ed Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 15 Nov 2012 00:57:30 +0100 Subject: [PATCH 091/418] allow storage backends to implement custom permission management --- lib/files/cache/permissions.php | 36 ++++++++++++++------- lib/files/storage/common.php | 14 ++++++--- lib/files/storage/storage.php | 5 +++ lib/files/view.php | 6 ++-- tests/lib/files/cache/permissions.php | 45 ++++++++++++++++----------- tests/lib/files/cache/scanner.php | 3 +- tests/lib/files/cache/watcher.php | 3 +- tests/lib/files/view.php | 3 +- 8 files changed, 76 insertions(+), 39 deletions(-) diff --git a/lib/files/cache/permissions.php b/lib/files/cache/permissions.php index dd7233abc7..d0968337f0 100644 --- a/lib/files/cache/permissions.php +++ b/lib/files/cache/permissions.php @@ -9,6 +9,22 @@ namespace OC\Files\Cache; class Permissions { + /** + * @var string $storageId + */ + private $storageId; + + /** + * @param \OC\Files\Storage\Storage|string $storage + */ + public function __construct($storage){ + if($storage instanceof \OC\Files\Storage\Storage){ + $this->storageId = $storage->getId(); + }else{ + $this->storageId = $storage; + } + } + /** * get the permissions for a single file * @@ -16,7 +32,7 @@ class Permissions { * @param string $user * @return int (-1 if file no permissions set) */ - static public function get($fileId, $user) { + public function get($fileId, $user) { $query = \OC_DB::prepare('SELECT `permissions` FROM `*PREFIX*permissions` WHERE `user` = ? AND `fileid` = ?'); $result = $query->execute(array($user, $fileId)); if ($row = $result->fetchRow()) { @@ -33,7 +49,7 @@ class Permissions { * @param string $user * @param int $permissions */ - static public function set($fileId, $user, $permissions) { + public function set($fileId, $user, $permissions) { if (self::get($fileId, $user) !== -1) { $query = \OC_DB::prepare('UPDATE `*PREFIX*permissions` SET `permissions` = ? WHERE `user` = ? AND `fileid` = ?'); } else { @@ -49,7 +65,7 @@ class Permissions { * @param string $user * @return int[] */ - static public function getMultiple($fileIds, $user) { + public function getMultiple($fileIds, $user) { if (count($fileIds) === 0) { return array(); } @@ -72,17 +88,15 @@ class Permissions { * @param int $fileId * @param string $user */ - static public function remove($fileId, $user) { + public function remove($fileId, $user) { $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?'); $query->execute(array($fileId, $user)); } - static public function removeMultiple($fileIds, $user) { - $params = $fileIds; - $params[] = $user; - $inPart = implode(', ', array_fill(0, count($fileIds), '?')); - - $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` IN (' . $inPart . ') AND `user` = ?'); - $query->execute($params); + public function removeMultiple($fileIds, $user) { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?'); + foreach($fileIds as $fileId){ + $query->execute(array($fileId, $user)); + } } } diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index f471752d1f..cf6fe64a4f 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -57,19 +57,19 @@ abstract class Common implements \OC\Files\Storage\Storage { public function getPermissions($path){ $permissions = 0; if($this->isCreatable($path)){ - $permissions |= \OCP\Share::PERMISSION_CREATE; + $permissions |= \OCP\PERMISSION_CREATE; } if($this->isReadable($path)){ - $permissions |= \OCP\Share::PERMISSION_READ; + $permissions |= \OCP\PERMISSION_READ; } if($this->isUpdatable($path)){ - $permissions |= \OCP\Share::PERMISSION_UPDATE; + $permissions |= \OCP\PERMISSION_UPDATE; } if($this->isDeletable($path)){ - $permissions |= \OCP\Share::PERMISSION_DELETE; + $permissions |= \OCP\PERMISSION_DELETE; } if($this->isSharable($path)){ - $permissions |= \OCP\Share::PERMISSION_SHARE; + $permissions |= \OCP\PERMISSION_SHARE; } return $permissions; } @@ -259,6 +259,10 @@ abstract class Common implements \OC\Files\Storage\Storage { return new \OC\Files\Cache\Scanner($this); } + public function getPermissionsCache(){ + return new \OC\Files\Cache\Permissions($this); + } + /** * get the owner of a path * @param string $path The path to get the owner diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index bb1ba16984..73dcb8fe36 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -64,6 +64,11 @@ interface Storage{ public function getOwner($path); + /** + * @return \OC\Files\Cache\Permissions + */ + public function getPermissionsCache(); + /** * get the ETag for a file or folder * diff --git a/lib/files/view.php b/lib/files/view.php index a54c3ee356..e516a4fed6 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -701,7 +701,8 @@ class View { } } - $data['permissions'] = Cache\Permissions::get($data['fileid'], \OC_User::getUser()); + $permissionsCache = $storage->getPermissionsCache(); + $data['permissions'] = $permissionsCache->get($data['fileid'], \OC_User::getUser()); return $data; } @@ -759,8 +760,9 @@ class View { $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; $ids[] = $file['fileid']; } + $permissionsCache = $storage->getPermissionsCache(); - $permissions = Cache\Permissions::getMultiple($ids, \OC_User::getUser()); + $permissions = $permissionsCache->getMultiple($ids, \OC_User::getUser()); foreach ($files as $i => $file) { $files[$i]['permissions'] = $permissions[$file['fileid']]; } diff --git a/tests/lib/files/cache/permissions.php b/tests/lib/files/cache/permissions.php index 4d47929a3e..56dbbc4518 100644 --- a/tests/lib/files/cache/permissions.php +++ b/tests/lib/files/cache/permissions.php @@ -9,39 +9,48 @@ namespace Test\Files\Cache; class Permissions extends \PHPUnit_Framework_TestCase { + /*** + * @var \OC\Files\Cache\Permissions $permissionsCache + */ + private $permissionsCache; + + function setUp(){ + $this->permissionsCache=new \OC\Files\Cache\Permissions('dummy'); + } + function testSimple() { $ids = range(1, 10); $user = uniqid(); - $this->assertEquals(-1, \OC\Files\Cache\Permissions::get(1, $user)); - \OC\Files\Cache\Permissions::set(1, $user, 1); - $this->assertEquals(1, \OC\Files\Cache\Permissions::get(1, $user)); - $this->assertEquals(-1, \OC\Files\Cache\Permissions::get(2, $user)); - $this->assertEquals(-1, \OC\Files\Cache\Permissions::get(1, $user . '2')); + $this->assertEquals(-1, $this->permissionsCache->get(1, $user)); + $this->permissionsCache->set(1, $user, 1); + $this->assertEquals(1, $this->permissionsCache->get(1, $user)); + $this->assertEquals(-1, $this->permissionsCache->get(2, $user)); + $this->assertEquals(-1, $this->permissionsCache->get(1, $user . '2')); - \OC\Files\Cache\Permissions::set(1, $user, 2); - $this->assertEquals(2, \OC\Files\Cache\Permissions::get(1, $user)); + $this->permissionsCache->set(1, $user, 2); + $this->assertEquals(2, $this->permissionsCache->get(1, $user)); - \OC\Files\Cache\Permissions::set(2, $user, 1); - $this->assertEquals(1, \OC\Files\Cache\Permissions::get(2, $user)); + $this->permissionsCache->set(2, $user, 1); + $this->assertEquals(1, $this->permissionsCache->get(2, $user)); - \OC\Files\Cache\Permissions::remove(1, $user); - $this->assertEquals(-1, \OC\Files\Cache\Permissions::get(1, $user)); - \OC\Files\Cache\Permissions::remove(1, $user . '2'); - $this->assertEquals(1, \OC\Files\Cache\Permissions::get(2, $user)); + $this->permissionsCache->remove(1, $user); + $this->assertEquals(-1, $this->permissionsCache->get(1, $user)); + $this->permissionsCache->remove(1, $user . '2'); + $this->assertEquals(1, $this->permissionsCache->get(2, $user)); $expected = array(); foreach ($ids as $id) { - \OC\Files\Cache\Permissions::set($id, $user, 10 + $id); + $this->permissionsCache->set($id, $user, 10 + $id); $expected[$id] = 10 + $id; } - $this->assertEquals($expected, \OC\Files\Cache\Permissions::getMultiple($ids, $user)); + $this->assertEquals($expected, $this->permissionsCache->getMultiple($ids, $user)); - \OC\Files\Cache\Permissions::removeMultiple(array(10, 9), $user); + $this->permissionsCache->removeMultiple(array(10, 9), $user); unset($expected[9]); unset($expected[10]); - $this->assertEquals($expected, \OC\Files\Cache\Permissions::getMultiple($ids, $user)); + $this->assertEquals($expected, $this->permissionsCache->getMultiple($ids, $user)); - \OC\Files\Cache\Permissions::removeMultiple($ids, $user); + $this->permissionsCache->removeMultiple($ids, $user); } } diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index f784a82dad..c53da92727 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -112,7 +112,8 @@ class Scanner extends \UnitTestCase { function tearDown() { $ids = $this->cache->getAll(); - \OC\Files\Cache\Permissions::removeMultiple($ids, \OC_User::getUser()); + $permissionsCache = $this->storage->getPermissionsCache(); + $permissionsCache->removeMultiple($ids, \OC_User::getUser()); $this->cache->clear(); } } diff --git a/tests/lib/files/cache/watcher.php b/tests/lib/files/cache/watcher.php index a7076d9b0b..0125dd843b 100644 --- a/tests/lib/files/cache/watcher.php +++ b/tests/lib/files/cache/watcher.php @@ -23,7 +23,8 @@ class Watcher extends \PHPUnit_Framework_TestCase { foreach ($this->storages as $storage) { $cache = $storage->getCache(); $ids = $cache->getAll(); - \OC\Files\Cache\Permissions::removeMultiple($ids, \OC_User::getUser()); + $permissionsCache = $storage->getPermissionsCache(); + $permissionsCache->removeMultiple($ids, \OC_User::getUser()); $cache->clear(); } } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index fa562cb15c..a173094b1c 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -21,7 +21,8 @@ class View extends \PHPUnit_Framework_TestCase { foreach ($this->storages as $storage) { $cache = $storage->getCache(); $ids = $cache->getAll(); - \OC\Files\Cache\Permissions::removeMultiple($ids, \OC_User::getUser()); + $permissionsCache = $storage->getPermissionsCache(); + $permissionsCache->removeMultiple($ids, \OC_User::getUser()); $cache->clear(); } } From 5569f07e8d68716f01267c9f41e5f0be299cc67a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 15 Nov 2012 22:04:48 +0100 Subject: [PATCH 092/418] add OC_FilesystemView alias for compatibility --- lib/filesystemview.php | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/filesystemview.php diff --git a/lib/filesystemview.php b/lib/filesystemview.php new file mode 100644 index 0000000000..37e2a737e0 --- /dev/null +++ b/lib/filesystemview.php @@ -0,0 +1,8 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ + +class OC_FilesystemView extends \OC\Files\View {} From 288ecf318cbb742b2c6a40d6c6afedfafb3cafd4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 15 Nov 2012 22:08:08 +0100 Subject: [PATCH 093/418] add getFileInfo and getDirectoryContent to OC_Files for compatibility --- lib/files.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/files.php b/lib/files.php index e6b324f062..3abc8c4aaf 100644 --- a/lib/files.php +++ b/lib/files.php @@ -28,6 +28,14 @@ class OC_Files { static $tmpFiles = array(); + public function getFileInfo($path){ + return \OC\Files\Filesystem::getFileInfo($path); + } + + public function getDirectoryContent($path){ + return \OC\Files\Filesystem::getDirectoryContent($path); + } + /** * return the content of a file or return a zip file containning multiply files * From 54240140b10f2d718753445b656c9607967d7c64 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 16 Nov 2012 12:14:29 +0100 Subject: [PATCH 094/418] fix incorectly merged smb.php --- apps/files_external/lib/smb.php | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index bf0d780d27..a4b2338e3b 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -35,6 +35,7 @@ class SMB extends \OC\Files\Storage\StreamWrapper{ if(substr($this->share, -1, 1)=='/') { $this->share = substr($this->share,0,-1); } + } public function getId(){ return 'smb::' . $this->user . '@' . $this->host . '/' . $this->share . '/' . $this->root; From 3358bface5c20aeb4ad41dc1edffb895bc24af71 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 16 Nov 2012 12:24:28 +0100 Subject: [PATCH 095/418] fix incorectly merged versions.php --- apps/files_versions/lib/versions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_versions/lib/versions.php b/apps/files_versions/lib/versions.php index b365f45679..7fe3d5549e 100644 --- a/apps/files_versions/lib/versions.php +++ b/apps/files_versions/lib/versions.php @@ -189,7 +189,7 @@ class Storage { $i = 0; - $files_view = new \OC\Files\View('/' \OCP\User::getUser() . '/files'); + $files_view = new \OC\Files\View('/' . \OCP\User::getUser() . '/files'); $local_file = $files_view->getLocalFile($filename); foreach( $matches as $ma ) { From 40fae0acbf2e84782c273a82eb10a2ed9fdf8162 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 18 Nov 2012 14:10:28 +0100 Subject: [PATCH 096/418] fix outdated permissions cache use in scanner --- lib/files/cache/scanner.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index eb947a2d53..404029ee2e 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -19,12 +19,18 @@ class Scanner { */ private $cache; + /** + * @var \OC\Files\Cache\Permissions $permissionsCache + */ + private $permissionsCache; + const SCAN_RECURSIVE = true; const SCAN_SHALLOW = false; public function __construct(\OC\Files\Storage\Storage $storage) { $this->storage = $storage; - $this->cache = new Cache($storage); + $this->cache = $storage->getCache(); + $this->permissionsCache = $storage->getPermissionsCache(); } /** @@ -67,7 +73,7 @@ class Scanner { } } $id = $this->cache->put($file, $data); - Permissions::set($id, \OC_User::getUser(), $data['permissions']); + $this->permissionsCache->set($id, \OC_User::getUser(), $data['permissions']); return $data; } From 77fdb16b7cb97f4e68949baf6796ef0e290b03f5 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 22 Nov 2012 00:22:27 -0500 Subject: [PATCH 097/418] Remove incorrect extra parameter for init() call --- lib/util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.php b/lib/util.php index 89fbcd05cd..c79afe1957 100755 --- a/lib/util.php +++ b/lib/util.php @@ -52,7 +52,7 @@ class OC_Util { } //jail the user into his "home" directory \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $user_root), $user); - \OC\Files\Filesystem::init($user_dir, $user); + \OC\Files\Filesystem::init($user_dir); $quotaProxy=new OC_FileProxy_Quota(); $fileOperationProxy = new OC_FileProxy_FileOperations(); From b76d1afe193fae3bf94aed9a95e270b452e62eb8 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 22 Nov 2012 00:44:48 -0500 Subject: [PATCH 098/418] Create public function initMountPoints() for initializing a specified user's mount points --- lib/files/filesystem.php | 53 ++++++++++++++++++++++++++++------------ lib/util.php | 16 ------------ 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index ed2814211f..cee3cd883b 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -229,35 +229,50 @@ class Filesystem { self::$defaultInstance = new View($root); //load custom mount config - if (is_file(\OC::$SERVERROOT . '/config/mount.php')) { + self::initMountPoints(); + + self::$loaded = true; + + return true; + } + + /** + * Initialize system and personal mount points for a user + * + * @param string $user + */ + public static function initMountPoints($user = '') { + if ($user == '') { + $user = \OC_User::getUser(); + } + // Load system mount points + if (is_file(\OC::$SERVERROOT.'/config/mount.php')) { $mountConfig = include 'config/mount.php'; if (isset($mountConfig['global'])) { foreach ($mountConfig['global'] as $mountPoint => $options) { self::mount($options['class'], $options['options'], $mountPoint); } } - if (isset($mountConfig['group'])) { foreach ($mountConfig['group'] as $group => $mounts) { - if (\OC_Group::inGroup(\OC_User::getUser(), $group)) { + if (\OC_Group::inGroup($user, $group)) { foreach ($mounts as $mountPoint => $options) { - $mountPoint = self::setUserVars($mountPoint); + $mountPoint = self::setUserVars($user, $mountPoint); foreach ($options as &$option) { - $option = self::setUserVars($option); + $option = self::setUserVars($user, $option); } self::mount($options['class'], $options['options'], $mountPoint); } } } } - if (isset($mountConfig['user'])) { - foreach ($mountConfig['user'] as $user => $mounts) { - if ($user === 'all' or strtolower($user) === strtolower(\OC_User::getUser())) { + foreach ($mountConfig['user'] as $mountUser => $mounts) { + if ($user === 'all' or strtolower($mountUser) === strtolower($user)) { foreach ($mounts as $mountPoint => $options) { - $mountPoint = self::setUserVars($mountPoint); + $mountPoint = self::setUserVars($user, $mountPoint); foreach ($options as &$option) { - $option = self::setUserVars($option); + $option = self::setUserVars($user, $option); } self::mount($options['class'], $options['options'], $mountPoint); } @@ -265,10 +280,16 @@ class Filesystem { } } } - - self::$loaded = true; - - return true; + // Load personal mount points + $root = OC_User::getHome($user); + if (is_file($root.'/mount.php')) { + $mountConfig = include $root.'/mount.php'; + if (isset($mountConfig['user'][$user])) { + foreach ($mountConfig['user'][$user] as $mountPoint => $options) { + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } } /** @@ -277,8 +298,8 @@ class Filesystem { * @param string $input * @return string */ - private static function setUserVars($input) { - return str_replace('$user', \OC_User::getUser(), $input); + private static function setUserVars($user, $input) { + return str_replace('$user', $user, $input); } /** diff --git a/lib/util.php b/lib/util.php index c79afe1957..25fdcef1ae 100755 --- a/lib/util.php +++ b/lib/util.php @@ -58,8 +58,6 @@ class OC_Util { $fileOperationProxy = new OC_FileProxy_FileOperations(); OC_FileProxy::register($quotaProxy); OC_FileProxy::register($fileOperationProxy); - // Load personal mount config - self::loadUserMountPoints($user); OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $user_dir)); } @@ -71,20 +69,6 @@ class OC_Util { self::$fsSetup=false; } - public static function loadUserMountPoints($user) { - $user_dir = '/'.$user.'/files'; - $user_root = OC_User::getHome($user); - $userdirectory = $user_root . '/files'; - if (is_file($user_root.'/mount.php')) { - $mountConfig = include $user_root.'/mount.php'; - if (isset($mountConfig['user'][$user])) { - foreach ($mountConfig['user'][$user] as $mountPoint => $options) { - \OC\Files\Filesystem::mount($options['class'], $options['options'], $mountPoint); - } - } - } - } - /** * get the current installed version of ownCloud * @return array From 208c6fd966dd5f9f387cdd1b4f030a2723144347 Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Thu, 22 Nov 2012 10:21:48 +0100 Subject: [PATCH 099/418] fixing namespace --- lib/files/filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index cee3cd883b..9e8ce3ec18 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -281,7 +281,7 @@ class Filesystem { } } // Load personal mount points - $root = OC_User::getHome($user); + $root = \OC_User::getHome($user); if (is_file($root.'/mount.php')) { $mountConfig = include $root.'/mount.php'; if (isset($mountConfig['user'][$user])) { From e6cf082fe07e4e33c883bd5f9aaa0cc72b082741 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 21 Nov 2012 22:44:43 +0100 Subject: [PATCH 100/418] emit a hooks during the filesystem scan --- lib/files/cache/scanner.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 404029ee2e..e650a220f5 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -14,6 +14,11 @@ class Scanner { */ private $storage; + /** + * @var string $storageId + */ + private $storageId; + /** * @var \OC\Files\Cache\Cache $cache */ @@ -29,6 +34,7 @@ class Scanner { public function __construct(\OC\Files\Storage\Storage $storage) { $this->storage = $storage; + $this->storageId = $this->storage->getId(); $this->cache = $storage->getCache(); $this->permissionsCache = $storage->getPermissionsCache(); } @@ -62,6 +68,7 @@ class Scanner { * @return array with metadata of the scanned file */ public function scanFile($file) { + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); $data = $this->getData($file); if ($file !== '') { $parent = dirname($file); @@ -85,6 +92,7 @@ class Scanner { * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ public function scan($path, $recursive = self::SCAN_RECURSIVE) { + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_folder', array('path' => $path, 'storage' => $this->storageId)); $this->scanFile($path); $size = 0; From 186c9e77e89dcd057a311ed08df9e1bb9e13ea8f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 21 Nov 2012 23:02:43 +0100 Subject: [PATCH 101/418] add Cache->getIncomplete for use in background scanning --- lib/files/cache/cache.php | 19 +++++++++++++++++++ tests/lib/files/cache/cache.php | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 7c6bba4fad..b52c0f4067 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -402,4 +402,23 @@ class Cache { } return $ids; } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiply incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete(){ + $query = \OC_DB::prepare('SELECT `path` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC LIMIT 1'); + $query->execute(array($this->storageId)); + if($row = $query->fetchRow()){ + return $row['path']; + }else{ + return false; + } + } } diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index 9c469aa937..e9105cd5ab 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -175,6 +175,23 @@ class Cache extends \UnitTestCase { $this->assertTrue($this->cache->inCache('folder/foobar/2')); } + function testGetIncomplete() { + $file1 = 'folder1'; + $file2 = 'folder2'; + $file3 = 'folder3'; + $file4 = 'folder4'; + $data = array('size' => 10, 'mtime' => 50, 'mimetype' => 'foo/bar'); + + $this->cache->put($file1, $data); + $data['size'] = -1; + $this->cache->put($file2, $data); + $this->cache->put($file3, $data); + $data['size'] = 12; + $this->cache->put($file4, $data); + + $this->assertEquals($file3, $this->cache->getIncomplete()); + } + public function tearDown() { $this->cache->clear(); } From 8687e0d346e7dad6890982c1a7b08befd8998aeb Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 21 Nov 2012 23:18:58 +0100 Subject: [PATCH 102/418] add Scanner->backgroundScan --- lib/files/cache/scanner.php | 10 ++++++++++ tests/lib/files/cache/scanner.php | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index e650a220f5..3c83b6528a 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -121,4 +121,14 @@ class Scanner { } return $size; } + + /** + * walk over any folders that are not fully scanned yet and scan them + */ + public function backgroundScan() { + while ($path = $this->cache->getIncomplete()) { + $this->scan($path); + $this->cache->correctFolderSize($path); + } + } } diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index c53da92727..6d26150d82 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -104,6 +104,28 @@ class Scanner extends \UnitTestCase { $this->assertNotEqual($cachedDataFolder['size'], -1); } + function testBackgroundScan(){ + $this->fillTestFolders(); + $this->storage->mkdir('folder2'); + $this->storage->file_put_contents('folder2/bar.txt', 'foobar'); + + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW); + $this->assertFalse($this->cache->inCache('folder/bar.txt')); + $this->assertFalse($this->cache->inCache('folder/2bar.txt')); + $cachedData = $this->cache->get(''); + $this->assertEquals(-1, $cachedData['size']); + + $this->scanner->backgroundScan(); + + $this->assertTrue($this->cache->inCache('folder/bar.txt')); + $this->assertTrue($this->cache->inCache('folder/bar.txt')); + + $cachedData = $this->cache->get(''); + $this->assertnotEquals(-1, $cachedData['size']); + + $this->assertFalse($this->cache->getIncomplete()); + } + function setUp() { $this->storage = new \OC\Files\Storage\Temporary(array()); $this->scanner = new \OC\Files\Cache\Scanner($this->storage); From 810563ae8aa5884a101cc64226c4f8c3c897132e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 22 Nov 2012 12:44:31 +0100 Subject: [PATCH 103/418] don't redefine inherited functions as abstract --- lib/files/storage/common.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index cf6fe64a4f..9978cea571 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -23,18 +23,12 @@ namespace OC\Files\Storage; abstract class Common implements \OC\Files\Storage\Storage { public function __construct($parameters) {} - abstract public function getId(); - abstract public function mkdir($path); - abstract public function rmdir($path); - abstract public function opendir($path); public function is_dir($path) { return $this->filetype($path)=='dir'; } public function is_file($path) { return $this->filetype($path)=='file'; } - abstract public function stat($path); - abstract public function filetype($path); public function filesize($path) { if($this->is_dir($path)) { return 0;//by definition @@ -46,8 +40,6 @@ abstract class Common implements \OC\Files\Storage\Storage { public function isCreatable($path) { return $this->isUpdatable($path); } - abstract public function isReadable($path); - abstract public function isUpdatable($path); public function isDeletable($path) { return $this->isUpdatable($path); } @@ -73,7 +65,6 @@ abstract class Common implements \OC\Files\Storage\Storage { } return $permissions; } - abstract public function file_exists($path); public function filemtime($path) { $stat = $this->stat($path); return $stat['mtime']; @@ -97,7 +88,6 @@ abstract class Common implements \OC\Files\Storage\Storage { $handle = $this->fopen($path, "w"); return fwrite($handle, $data); } - abstract public function unlink($path); public function rename($path1,$path2) { if($this->copy($path1,$path2)) { return $this->unlink($path1); @@ -111,7 +101,6 @@ abstract class Common implements \OC\Files\Storage\Storage { $count=\OC_Helper::streamCopy($source,$target); return $count>0; } - abstract public function fopen($path,$mode); /** * @brief Deletes all files and folders recursively within a directory @@ -180,7 +169,6 @@ abstract class Common implements \OC\Files\Storage\Storage { unlink($tmpFile); return $hash; } - abstract public function free_space($path); public function search($query) { return $this->searchInDir($query); } @@ -222,7 +210,6 @@ abstract class Common implements \OC\Files\Storage\Storage { } } } - abstract public function touch($path, $mtime=null); protected function searchInDir($query,$dir='') { $files=array(); From 8ce5e0d30d874bf59a81aa01202a497ea4cb8492 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 22 Nov 2012 13:14:39 +0100 Subject: [PATCH 104/418] don't throw fatal error in updater cache if setup failed for some reason --- tests/lib/files/cache/updater.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/lib/files/cache/updater.php b/tests/lib/files/cache/updater.php index 8059418dc1..b2eccf9130 100644 --- a/tests/lib/files/cache/updater.php +++ b/tests/lib/files/cache/updater.php @@ -61,7 +61,9 @@ class Updater extends \PHPUnit_Framework_TestCase { } public function tearDown() { - $this->cache->clear(); + if($this->cache){ + $this->cache->clear(); + } Filesystem::tearDown(); } From ad706229f5dbc6382ade61493e9f100a2dc07293 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 23 Nov 2012 00:17:18 +0100 Subject: [PATCH 105/418] explicitly sort files when using getFolderContents --- lib/files/cache/cache.php | 14 +++++++------- tests/lib/files/view.php | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index b52c0f4067..6b93673097 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -33,9 +33,9 @@ class Cache { * @param \OC\Files\Storage\Storage|string $storage */ public function __construct($storage) { - if($storage instanceof \OC\Files\Storage\Storage){ + if ($storage instanceof \OC\Files\Storage\Storage) { $this->storageId = $storage->getId(); - }else{ + } else { $this->storageId = $storage; } } @@ -87,7 +87,7 @@ class Cache { if ($fileId > -1) { $query = \OC_DB::prepare( 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` - FROM `*PREFIX*filecache` WHERE parent = ?'); + FROM `*PREFIX*filecache` WHERE parent = ? ORDER BY `fileid` ASC'); $result = $query->execute(array($fileId)); return $result->fetchAll(); } else { @@ -364,7 +364,7 @@ class Cache { */ public function calculateFolderSize($path) { $id = $this->getId($path); - if($id === -1){ + if ($id === -1) { return 0; } $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `parent` = ? AND `storage` = ?'); @@ -412,12 +412,12 @@ class Cache { * * @return string|bool the path of the folder or false when no folder matched */ - public function getIncomplete(){ + public function getIncomplete() { $query = \OC_DB::prepare('SELECT `path` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC LIMIT 1'); $query->execute(array($this->storageId)); - if($row = $query->fetchRow()){ + if ($row = $query->fetchRow()) { return $row['path']; - }else{ + } else { return false; } } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index a173094b1c..ecfc803dc2 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -55,20 +55,20 @@ class View extends \PHPUnit_Framework_TestCase { $folderData = $rootView->getDirectoryContent('/'); /** * expected entries: - * folder * foo.png * foo.txt + * folder * substorage */ $this->assertEquals(4, count($folderData)); - $this->assertEquals('folder', $folderData[0]['name']); - $this->assertEquals('foo.png', $folderData[1]['name']); - $this->assertEquals('foo.txt', $folderData[2]['name']); + $this->assertEquals('foo.png', $folderData[0]['name']); + $this->assertEquals('foo.txt', $folderData[1]['name']); + $this->assertEquals('folder', $folderData[2]['name']); $this->assertEquals('substorage', $folderData[3]['name']); - $this->assertEquals($storageSize + $textSize, $folderData[0]['size']); - $this->assertEquals($imageSize, $folderData[1]['size']); - $this->assertEquals($textSize, $folderData[2]['size']); + $this->assertEquals($imageSize, $folderData[0]['size']); + $this->assertEquals($textSize, $folderData[1]['size']); + $this->assertEquals($storageSize + $textSize, $folderData[2]['size']); $this->assertEquals($storageSize, $folderData[3]['size']); $folderView = new \OC\Files\View('/folder'); From 8fb4dfd2eabcc267725d3c151d93644d6f28f010 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 23 Nov 2012 00:20:46 +0100 Subject: [PATCH 106/418] use background scanner --- apps/files/ajax/scan.php | 97 +++++++++++++++++++++++++--------------- apps/files/js/files.js | 38 +++++++--------- 2 files changed, 76 insertions(+), 59 deletions(-) diff --git a/apps/files/ajax/scan.php b/apps/files/ajax/scan.php index 91b20fa836..53ae4f5ff0 100644 --- a/apps/files/ajax/scan.php +++ b/apps/files/ajax/scan.php @@ -1,45 +1,70 @@ send('success', true); +OC_Hook::connect('\OC\Files\Cache\Scanner', 'scan_folder', 'ScanListener', 'folder'); +OC_Hook::connect('\OC\Files\Cache\Scanner', 'scan_file', 'ScanListener', 'file'); + +$absolutePath = \OC\Files\Filesystem::getView()->getAbsolutePath($dir); + +$mountPoints = \OC\Files\Filesystem::getMountPoints($absolutePath); +$mountPoints[] = \OC\Files\Filesystem::getMountPoint($absolutePath); +$mountPoints = array_reverse($mountPoints); //start with the mount point of $dir + +foreach ($mountPoints as $mountPoint) { + $storage = \OC\Files\Filesystem::getStorage($mountPoint); + error_log('scanning mp '.$mountPoint); + ScanListener::$mountPoints[$storage->getId()] = $mountPoint; + $scanner = $storage->getScanner(); + if ($force) { + $scanner->scan(''); } else { - OCP\JSON::success(array('data'=>array('done'=>true))); - exit; - } -} else { - if($checkOnly) { - OCP\JSON::success(array('data'=>array('done'=>false))); - exit; - } - if(isset($eventSource)) { - $eventSource->send('success', false); - } else { - exit; + $scanner->backgroundScan(); } } + +$eventSource->send('done', ScanListener::$fileCount); $eventSource->close(); + +class ScanListener { + + static public $fileCount = 0; + static public $lastCount = 0; + + /** + * @var \OC\Files\View $view + */ + static public $view; + + /** + * @var array $mountPoints map storage ids to mountpoints + */ + static public $mountPoints = array(); + + /** + * @var \OC_EventSource event source to pass events to + */ + static public $eventSource; + + static function folder($params) { + $internalPath = $params['path']; + $mountPoint = self::$mountPoints[$params['storage']]; + $path = self::$view->getRelativePath($mountPoint . $internalPath); + self::$eventSource->send('folder', $path); + } + + static function file() { + self::$fileCount++; + if (self::$fileCount > self::$lastCount + 20) { //send a count update every 20 files + self::$lastCount = self::$fileCount; + self::$eventSource->send('count', self::$fileCount); + } + } +} diff --git a/apps/files/js/files.js b/apps/files/js/files.js index bb80841055..f95bfa2bf8 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -602,12 +602,8 @@ $(document).ready(function() { }); }); - //check if we need to scan the filesystem - $.get(OC.filePath('files','ajax','scan.php'),{checkonly:'true'}, function(response) { - if(response.data.done){ - scanFiles(); - } - }, "json"); + //do a background scan if needed + scanFiles(); var lastWidth = 0; var breadcrumbs = []; @@ -676,27 +672,23 @@ $(document).ready(function() { resizeBreadcrumbs(true); }); -function scanFiles(force,dir){ +function scanFiles(force, dir){ if(!dir){ - dir=''; + dir = ''; } - force=!!force; //cast to bool - scanFiles.scanning=true; - $('#scanning-message').show(); - $('#fileList').remove(); - var scannerEventSource=new OC.EventSource(OC.filePath('files','ajax','scan.php'),{force:force,dir:dir}); - scanFiles.cancel=scannerEventSource.close.bind(scannerEventSource); - scannerEventSource.listen('scanning',function(data){ - $('#scan-count').text(t('files', '{count} files scanned', {count: data.count})); - $('#scan-current').text(data.file+'/'); + force = !!force; //cast to bool + scanFiles.scanning = true; + var scannerEventSource = new OC.EventSource(OC.filePath('files','ajax','scan.php'),{force:force,dir:dir}); + scanFiles.cancel = scannerEventSource.close.bind(scannerEventSource); + scannerEventSource.listen('count',function(count){ + console.log(count + 'files scanned') }); - scannerEventSource.listen('success',function(success){ + scannerEventSource.listen('folder',function(path){ + console.log('now scanning ' + path) + }); + scannerEventSource.listen('done',function(count){ scanFiles.scanning=false; - if(success){ - window.location.reload(); - }else{ - alert(t('files', 'error while scanning')); - } + console.log('done after ' + count + 'files'); }); } scanFiles.scanning=false; From c47bf9bbcefb0640c24ab0aa6ee79f4c52222b45 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 24 Nov 2012 16:42:54 -0500 Subject: [PATCH 107/418] Add checks for storage object --- apps/files/ajax/scan.php | 15 +-- lib/files/view.php | 221 +++++++++++++++++++++------------------ 2 files changed, 127 insertions(+), 109 deletions(-) diff --git a/apps/files/ajax/scan.php b/apps/files/ajax/scan.php index 53ae4f5ff0..391b98608b 100644 --- a/apps/files/ajax/scan.php +++ b/apps/files/ajax/scan.php @@ -20,13 +20,14 @@ $mountPoints = array_reverse($mountPoints); //start with the mount point of $dir foreach ($mountPoints as $mountPoint) { $storage = \OC\Files\Filesystem::getStorage($mountPoint); - error_log('scanning mp '.$mountPoint); - ScanListener::$mountPoints[$storage->getId()] = $mountPoint; - $scanner = $storage->getScanner(); - if ($force) { - $scanner->scan(''); - } else { - $scanner->backgroundScan(); + if ($storage) { + ScanListener::$mountPoints[$storage->getId()] = $mountPoint; + $scanner = $storage->getScanner(); + if ($force) { + $scanner->scan(''); + } else { + $scanner->backgroundScan(); + } } } diff --git a/lib/files/view.php b/lib/files/view.php index e516a4fed6..468808566a 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -671,39 +671,41 @@ class View { * - versioned */ public function getFileInfo($path) { + $data = array(); $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); /** * @var \OC\Files\Storage\Storage $storage * @var string $internalPath */ list($storage, $internalPath) = Filesystem::resolvePath($path); - $cache = $storage->getCache(); + if ($storage) { + $cache = $storage->getCache(); - if (!$cache->inCache($internalPath)) { - $scanner = $storage->getScanner(); - $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); - } else { - $watcher = new \OC\Files\Cache\Watcher($storage); - $watcher->checkUpdate($internalPath); - } - - $data = $cache->get($internalPath); - - if ($data['mimetype'] === 'httpd/unix-directory') { - //add the sizes of other mountpoints to the folder - $mountPoints = Filesystem::getMountPoints($path); - foreach ($mountPoints as $mountPoint) { - $subStorage = Filesystem::getStorage($mountPoint); - $subCache = $subStorage->getCache(); - $rootEntry = $subCache->get(''); - - $data['size'] += $rootEntry['size']; + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } else { + $watcher = new \OC\Files\Cache\Watcher($storage); + $watcher->checkUpdate($internalPath); } + + $data = $cache->get($internalPath); + + if ($data['mimetype'] === 'httpd/unix-directory') { + //add the sizes of other mountpoints to the folder + $mountPoints = Filesystem::getMountPoints($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = Filesystem::getStorage($mountPoint); + $subCache = $subStorage->getCache(); + $rootEntry = $subCache->get(''); + + $data['size'] += $rootEntry['size']; + } + } + + $permissionsCache = $storage->getPermissionsCache(); + $data['permissions'] = $permissionsCache->get($data['fileid'], \OC_User::getUser()); } - - $permissionsCache = $storage->getPermissionsCache(); - $data['permissions'] = $permissionsCache->get($data['fileid'], \OC_User::getUser()); - return $data; } @@ -714,75 +716,79 @@ class View { * @return array */ public function getDirectoryContent($directory, $mimetype_filter = '') { + $result = array(); $path = Filesystem::normalizePath($this->fakeRoot . '/' . $directory); /** * @var \OC\Files\Storage\Storage $storage * @var string $internalPath */ list($storage, $internalPath) = Filesystem::resolvePath($path); - $cache = $storage->getCache(); + if ($storage) { + $cache = $storage->getCache(); - if (!$cache->inCache($internalPath)) { - $scanner = $storage->getScanner(); - $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); - } else { - $watcher = new \OC\Files\Cache\Watcher($storage); - $watcher->checkUpdate($internalPath); - } - - $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter - - //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders - $mountPoints = Filesystem::getMountPoints($path); - $dirLength = strlen($path); - foreach ($mountPoints as $mountPoint) { - $subStorage = Filesystem::getStorage($mountPoint); - $subCache = $subStorage->getCache(); - $rootEntry = $subCache->get(''); - - $relativePath = trim(substr($mountPoint, $dirLength), '/'); - if ($pos = strpos($relativePath, '/')) { //mountpoint inside subfolder add size to the correct folder - $entryName = substr($relativePath, 0, $pos); - foreach ($files as &$entry) { - if ($entry['name'] === $entryName) { - $entry['size'] += $rootEntry['size']; - } - } - } else { //mountpoint in this folder, add an entry for it - $rootEntry['name'] = $relativePath; - $files[] = $rootEntry; + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } else { + $watcher = new \OC\Files\Cache\Watcher($storage); + $watcher->checkUpdate($internalPath); } - } - $ids = array(); + $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter - foreach ($files as $i => $file) { - $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; - $ids[] = $file['fileid']; - } - $permissionsCache = $storage->getPermissionsCache(); + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mountPoints = Filesystem::getMountPoints($path); + $dirLength = strlen($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = Filesystem::getStorage($mountPoint); + if ($subStorage) { + $subCache = $subStorage->getCache(); + $rootEntry = $subCache->get(''); - $permissions = $permissionsCache->getMultiple($ids, \OC_User::getUser()); - foreach ($files as $i => $file) { - $files[$i]['permissions'] = $permissions[$file['fileid']]; - } - - if ($mimetype_filter) { - foreach ($files as $file) { - if (strpos($mimetype_filter, '/')) { - if ($file['mimetype'] === $mimetype_filter) { - $result[] = $file; - } - } else { - if ($file['mimepart'] === $mimetype_filter) { - $result[] = $file; + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry['name'] === $entryName) { + $entry['size'] += $rootEntry['size']; + } + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $files[] = $rootEntry; } } } - } else { - $result = $files; - } + $ids = array(); + + foreach ($files as $i => $file) { + $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $ids[] = $file['fileid']; + } + $permissionsCache = $storage->getPermissionsCache(); + + $permissions = $permissionsCache->getMultiple($ids, \OC_User::getUser()); + foreach ($files as $i => $file) { + $files[$i]['permissions'] = $permissions[$file['fileid']]; + } + + if ($mimetype_filter) { + foreach ($files as $file) { + if (strpos($mimetype_filter, '/')) { + if ($file['mimetype'] === $mimetype_filter) { + $result[] = $file; + } + } else { + if ($file['mimepart'] === $mimetype_filter) { + $result[] = $file; + } + } + } + } else { + $result = $files; + } + } return $result; } @@ -802,14 +808,18 @@ class View { * @var string $internalPath */ list($storage, $internalPath) = Filesystem::resolvePath($path); - $cache = $storage->getCache(); + if ($storage) { + $cache = $storage->getCache(); - if (!$cache->inCache($internalPath)) { - $scanner = $storage->getScanner(); - $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } + + return $cache->put($internalPath, $data); + } else { + return -1; } - - return $cache->put($internalPath, $data); } /** @@ -843,29 +853,32 @@ class View { $mountPoint = Filesystem::getMountPoint($this->fakeRoot); $storage = Filesystem::getStorage($mountPoint); - $cache = $storage->getCache(); - - $results = $cache->$method($query); - foreach ($results as $result) { - if (substr($mountPoint . $result['path'], 0, $rootLength) === $this->fakeRoot) { - $result['path'] = substr($mountPoint . $result['path'], $rootLength); - $files[] = $result; - } - } - - $mountPoints = Filesystem::getMountPoints($this->fakeRoot); - foreach ($mountPoints as $mountPoint) { - $storage = Filesystem::getStorage($mountPoint); + if ($storage) { $cache = $storage->getCache(); - $relativeMountPoint = substr($mountPoint, $rootLength); $results = $cache->$method($query); foreach ($results as $result) { - $result['path'] = $relativeMountPoint . $result['path']; - $files[] = $result; + if (substr($mountPoint . $result['path'], 0, $rootLength) === $this->fakeRoot) { + $result['path'] = substr($mountPoint . $result['path'], $rootLength); + $files[] = $result; + } + } + + $mountPoints = Filesystem::getMountPoints($this->fakeRoot); + foreach ($mountPoints as $mountPoint) { + $storage = Filesystem::getStorage($mountPoint); + if ($storage) { + $cache = $storage->getCache(); + + $relativeMountPoint = substr($mountPoint, $rootLength); + $results = $cache->$method($query); + foreach ($results as $result) { + $result['path'] = $relativeMountPoint . $result['path']; + $files[] = $result; + } + } } } - return $files; } @@ -881,6 +894,10 @@ class View { * @var string $internalPath */ list($storage, $internalPath) = $this->resolvePath($path); - return $storage->getETag($internalPath); + if ($storage) { + return $storage->getETag($internalPath); + } else { + return null; + } } } From d3e37fa157faa59598e92a9aa02c6bbf818b60e0 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 23 Nov 2012 16:23:52 +0100 Subject: [PATCH 108/418] remove fileatime from common storage backend --- lib/fileproxy.php | 2 +- lib/files/storage/common.php | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/fileproxy.php b/lib/fileproxy.php index 2f81bde64a..52ec79b4bd 100644 --- a/lib/fileproxy.php +++ b/lib/fileproxy.php @@ -36,7 +36,7 @@ * The return value of the post-proxy will be used as the new result of the operation * The operations that have a post-proxy are: * file_get_contents, is_file, is_dir, file_exists, stat, is_readable, - * is_writable, fileatime, filemtime, filectime, file_get_contents, + * is_writable, filemtime, filectime, file_get_contents, * getMimeType, hash, fopen, free_space and search */ diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index 9978cea571..c891d0c3ad 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -69,10 +69,6 @@ abstract class Common implements \OC\Files\Storage\Storage { $stat = $this->stat($path); return $stat['mtime']; } - public function fileatime($path) { - $stat = $this->stat($path); - return $stat['atime']; - } public function file_get_contents($path) { $handle = $this->fopen($path, "r"); if(!$handle) { From 709aacfa0fef29692bff231f09625db5ba0bef6f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 24 Nov 2012 23:41:39 +0100 Subject: [PATCH 109/418] change behaviour of Filesystem::getMountPoint when a mountpoint is passed as path without trailing slash --- lib/files/filesystem.php | 8 +------- tests/lib/files/filesystem.php | 2 +- tests/lib/files/view.php | 12 ++++++++++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 9e8ce3ec18..4e3eb1989b 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -144,13 +144,7 @@ class Filesystem { */ static public function getMountPoint($path) { \OC_Hook::emit(self::CLASSNAME, 'get_mountpoint', array('path' => $path)); - if (!$path) { - $path = '/'; - } - if ($path[0] !== '/') { - $path = '/' . $path; - } - $path = str_replace('//', '/', $path); + $path = self::normalizePath($path) . '/'; $foundMountPoint = ''; $mountPoints = array_keys(self::$mounts); foreach ($mountPoints as $mountpoint) { diff --git a/tests/lib/files/filesystem.php b/tests/lib/files/filesystem.php index 363426511b..5837093fdd 100644 --- a/tests/lib/files/filesystem.php +++ b/tests/lib/files/filesystem.php @@ -60,7 +60,7 @@ class Filesystem extends \PHPUnit_Framework_TestCase { $this->assertEquals('/',\OC\Files\Filesystem::getMountPoint('/')); $this->assertEquals('/some/',\OC\Files\Filesystem::getMountPoint('/some/folder')); $this->assertEquals('/some/',\OC\Files\Filesystem::getMountPoint('/some/')); - $this->assertEquals('/',\OC\Files\Filesystem::getMountPoint('/some')); + $this->assertEquals('/some/',\OC\Files\Filesystem::getMountPoint('/some')); list( , $internalPath)=\OC\Files\Filesystem::resolvePath('/some/folder'); $this->assertEquals('folder',$internalPath); } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index ecfc803dc2..6f8d29c25b 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -71,6 +71,18 @@ class View extends \PHPUnit_Framework_TestCase { $this->assertEquals($storageSize + $textSize, $folderData[2]['size']); $this->assertEquals($storageSize, $folderData[3]['size']); + $folderData = $rootView->getDirectoryContent('/substorage'); + /** + * expected entries: + * foo.png + * foo.txt + * folder + */ + $this->assertEquals(3, count($folderData)); + $this->assertEquals('foo.png', $folderData[0]['name']); + $this->assertEquals('foo.txt', $folderData[1]['name']); + $this->assertEquals('folder', $folderData[2]['name']); + $folderView = new \OC\Files\View('/folder'); $this->assertEquals($rootView->getFileInfo('/folder'), $folderView->getFileInfo('/')); From cc5d8e56098189f49fbd68c598c11be2b8354846 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 24 Nov 2012 20:29:57 -0500 Subject: [PATCH 110/418] Check if data variable in scanner isn't null before using it --- lib/files/cache/scanner.php | 42 ++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 3c83b6528a..9ee509bb13 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -70,17 +70,19 @@ class Scanner { public function scanFile($file) { \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); $data = $this->getData($file); - if ($file !== '') { - $parent = dirname($file); - if ($parent === '.') { - $parent = ''; - } - if (!$this->cache->inCache($parent)) { - $this->scanFile($parent); + if ($data) { + if ($file !== '') { + $parent = dirname($file); + if ($parent === '.') { + $parent = ''; + } + if (!$this->cache->inCache($parent)) { + $this->scanFile($parent); + } } + $id = $this->cache->put($file, $data); + $this->permissionsCache->set($id, \OC_User::getUser(), $data['permissions']); } - $id = $this->cache->put($file, $data); - $this->permissionsCache->set($id, \OC_User::getUser(), $data['permissions']); return $data; } @@ -101,17 +103,19 @@ class Scanner { if ($file !== '.' and $file !== '..') { $child = ($path !== '') ? $path . '/' . $file : $file; $data = $this->scanFile($child); - if ($data['mimetype'] === 'httpd/unix-directory') { - if ($recursive === self::SCAN_RECURSIVE) { - $data['size'] = $this->scan($child, self::SCAN_RECURSIVE); - } else { - $data['size'] = -1; + if ($data) { + if ($data['mimetype'] === 'httpd/unix-directory') { + if ($recursive === self::SCAN_RECURSIVE) { + $data['size'] = $this->scan($child, self::SCAN_RECURSIVE); + } else { + $data['size'] = -1; + } + } + if ($data['size'] === -1) { + $size = -1; + } elseif ($size !== -1) { + $size += $data['size']; } - } - if ($data['size'] === -1) { - $size = -1; - } elseif ($size !== -1) { - $size += $data['size']; } } } From 0cfef83ed98951f852b07ce61dc10ee2e8266445 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 25 Nov 2012 16:08:35 +0100 Subject: [PATCH 111/418] sort output of getFolderContent by name --- lib/files/cache/cache.php | 2 +- tests/lib/files/view.php | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 6b93673097..bc52f21d91 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -87,7 +87,7 @@ class Cache { if ($fileId > -1) { $query = \OC_DB::prepare( 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` - FROM `*PREFIX*filecache` WHERE parent = ? ORDER BY `fileid` ASC'); + FROM `*PREFIX*filecache` WHERE parent = ? ORDER BY `name` ASC'); $result = $query->execute(array($fileId)); return $result->fetchAll(); } else { diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 6f8d29c25b..ed08dcc114 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -55,33 +55,33 @@ class View extends \PHPUnit_Framework_TestCase { $folderData = $rootView->getDirectoryContent('/'); /** * expected entries: + * folder * foo.png * foo.txt - * folder * substorage */ $this->assertEquals(4, count($folderData)); - $this->assertEquals('foo.png', $folderData[0]['name']); - $this->assertEquals('foo.txt', $folderData[1]['name']); - $this->assertEquals('folder', $folderData[2]['name']); + $this->assertEquals('folder', $folderData[0]['name']); + $this->assertEquals('foo.png', $folderData[1]['name']); + $this->assertEquals('foo.txt', $folderData[2]['name']); $this->assertEquals('substorage', $folderData[3]['name']); - $this->assertEquals($imageSize, $folderData[0]['size']); - $this->assertEquals($textSize, $folderData[1]['size']); - $this->assertEquals($storageSize + $textSize, $folderData[2]['size']); + $this->assertEquals($storageSize + $textSize, $folderData[0]['size']); + $this->assertEquals($imageSize, $folderData[1]['size']); + $this->assertEquals($textSize, $folderData[2]['size']); $this->assertEquals($storageSize, $folderData[3]['size']); $folderData = $rootView->getDirectoryContent('/substorage'); /** * expected entries: + * folder * foo.png * foo.txt - * folder */ $this->assertEquals(3, count($folderData)); - $this->assertEquals('foo.png', $folderData[0]['name']); - $this->assertEquals('foo.txt', $folderData[1]['name']); - $this->assertEquals('folder', $folderData[2]['name']); + $this->assertEquals('folder', $folderData[0]['name']); + $this->assertEquals('foo.png', $folderData[1]['name']); + $this->assertEquals('foo.txt', $folderData[2]['name']); $folderView = new \OC\Files\View('/folder'); $this->assertEquals($rootView->getFileInfo('/folder'), $folderView->getFileInfo('/')); From a609992a75d1dad15398f55e22ad2244c78650dc Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 25 Nov 2012 16:30:57 +0100 Subject: [PATCH 112/418] better check if we are passing a fileid to Cache::get --- lib/files/cache/cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index bc52f21d91..5aeb6f25af 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -47,7 +47,7 @@ class Cache { * @return array */ public function get($file) { - if (is_string($file)) { + if (is_string($file) or $file == '') { $where = 'WHERE `storage` = ? AND `path_hash` = ?'; $params = array($this->storageId, md5($file)); } else { //file id From dbbb357f62c1ba86bdff0fbe63e4cea9bc2977fc Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 30 Nov 2012 01:41:30 +0100 Subject: [PATCH 113/418] add upgrade path from old cache to preserve file id's --- apps/files/appinfo/app.php | 14 +++++++++-- apps/files/appinfo/version | 2 +- lib/files/cache/upgrade.php | 50 +++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 lib/files/cache/upgrade.php diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php index b431ddfec0..fb64a80ec0 100644 --- a/apps/files/appinfo/app.php +++ b/apps/files/appinfo/app.php @@ -1,8 +1,18 @@ "files_index", "order" => 0, "href" => OCP\Util::linkTo( "files", "index.php" ), "icon" => OCP\Util::imagePath( "core", "places/home.svg" ), "name" => $l->t("Files") )); +OCP\App::addNavigationEntry(array("id" => "files_index", "order" => 0, "href" => OCP\Util::linkTo("files", "index.php"), "icon" => OCP\Util::imagePath("core", "places/home.svg"), "name" => $l->t("Files"))); OC_Search::registerProvider('OC_Search_Provider_File'); + +if (OC_User::isLoggedIn()) { + // update OC4.5 filecache to OC5 filecache, can't do this in update.php since it needs to happen for each user individually + $cacheVersion = (int)OCP\Config::getUserValue(OC_User::getUser(), 'files', 'cache_version', 4); + if ($cacheVersion < 5) { + \OC_Log::write('files', 'updating filecache to 5.0 for user ' . OC_User::getUser(), \OC_Log::INFO); + \OC\Files\Cache\Upgrade::upgrade(); + OCP\Config::setUserValue(OC_User::getUser(), 'files', 'cache_version', 5); + } +} diff --git a/apps/files/appinfo/version b/apps/files/appinfo/version index 0664a8fd29..2bf1ca5f54 100644 --- a/apps/files/appinfo/version +++ b/apps/files/appinfo/version @@ -1 +1 @@ -1.1.6 +1.1.7 diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php new file mode 100644 index 0000000000..5be04b4207 --- /dev/null +++ b/lib/files/cache/upgrade.php @@ -0,0 +1,50 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Upgrade { + static $permissionsCaches = array(); + + static function upgrade() { + $insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` ) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + + $oldEntriesQuery = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` ORDER BY `id` ASC'); //sort ascending to ensure the parent gets inserted before a child + $oldEntriesResult = $oldEntriesQuery->execute(); + + while ($row = $oldEntriesResult->fetchRow()) { + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($row['path']); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath; + */ + $pathHash = md5($internalPath); + $storageId = $storage->getId(); + $parentId = ($internalPath === '') ? -1 : $row['parent']; + + $insertQuery->execute(array($row['id'], $storageId, $internalPath, $pathHash, $parentId, $row['name'], $row['mimetype'], $row['mimepart'], $row['size'], $row['mtime'], $row['encrypted'])); + + $permissions = ($row['writable']) ? \OCP\PERMISSION_ALL : \OCP\PERMISSION_READ; + $permissionsCache = self::getPermissionsCache($storage); + $permissionsCache->set($row['id'], $row['user'], $permissions); + } + } + + /** + * @param \OC\Files\Storage\Storage $storage + * @return Permissions + */ + static function getPermissionsCache($storage) { + $storageId = $storage->getId(); + if (!isset(self::$permissionsCaches[$storageId])) { + self::$permissionsCaches[$storageId] = $storage->getPermissionsCache(); + } + return self::$permissionsCaches[$storageId]; + } +} From 702444b2422326388e494091e815fd3d9b03cc87 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 1 Dec 2012 00:59:49 +0100 Subject: [PATCH 114/418] fail gracefully when no old filecache is present during upgrade --- lib/files/cache/upgrade.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php index 5be04b4207..899f6f7ac8 100644 --- a/lib/files/cache/upgrade.php +++ b/lib/files/cache/upgrade.php @@ -16,7 +16,14 @@ class Upgrade { VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); $oldEntriesQuery = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` ORDER BY `id` ASC'); //sort ascending to ensure the parent gets inserted before a child - $oldEntriesResult = $oldEntriesQuery->execute(); + try{ + $oldEntriesResult = $oldEntriesQuery->execute(); + }catch(\Exception $e){ + return; + } + if(!$oldEntriesResult){ + return; + } while ($row = $oldEntriesResult->fetchRow()) { list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($row['path']); From 01eb5d2790638ab836adc95a9726b6a1003562b9 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 2 Dec 2012 03:43:51 +0100 Subject: [PATCH 115/418] fix some edge cases while scanning the root of a storage --- lib/files/cache/scanner.php | 4 ++-- lib/files/filesystem.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 9ee509bb13..4c0ec9617f 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -71,7 +71,7 @@ class Scanner { \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); $data = $this->getData($file); if ($data) { - if ($file !== '') { + if ($file) { $parent = dirname($file); if ($parent === '.') { $parent = ''; @@ -101,7 +101,7 @@ class Scanner { if ($dh = $this->storage->opendir($path)) { while ($file = readdir($dh)) { if ($file !== '.' and $file !== '..') { - $child = ($path !== '') ? $path . '/' . $file : $file; + $child = ($path) ? $path . '/' . $file : $file; $data = $this->scanFile($child); if ($data) { if ($data['mimetype'] === 'httpd/unix-directory') { diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 4e3eb1989b..724c83b361 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -143,8 +143,8 @@ class Filesystem { * @return string */ static public function getMountPoint($path) { - \OC_Hook::emit(self::CLASSNAME, 'get_mountpoint', array('path' => $path)); $path = self::normalizePath($path) . '/'; + \OC_Hook::emit(self::CLASSNAME, 'get_mountpoint', array('path' => $path)); $foundMountPoint = ''; $mountPoints = array_keys(self::$mounts); foreach ($mountPoints as $mountpoint) { @@ -205,7 +205,7 @@ class Filesystem { $mountpoint = self::getMountPoint($path); if ($mountpoint) { $storage = self::getStorage($mountpoint); - if ($mountpoint === $path) { + if ($mountpoint === $path or $mountpoint . '/' === $path) { $internalPath = ''; } else { $internalPath = substr($path, strlen($mountpoint)); From 18663100d9f68400fba1fc344874aacb62bb4659 Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Sun, 2 Dec 2012 12:12:20 +0100 Subject: [PATCH 116/418] fixing syntax error + reformat the code --- apps/files_sharing/public.php | 358 ++++++++++++++++++---------------- 1 file changed, 185 insertions(+), 173 deletions(-) diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index fcf7063955..27d2b9dfa4 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -9,10 +9,10 @@ if (isset($_GET['token'])) { unset($_GET['file']); $qry = \OC_DB::prepare('SELECT `source` FROM `*PREFIX*sharing` WHERE `target` = ? LIMIT 1'); $filepath = $qry->execute(array($_GET['token']))->fetchOne(); - if(isset($filepath)) { + if (isset($filepath)) { $rootView = new \OC\Files\View(''); $info = $rootView->getFileInfo($filepath, ''); - if(strtolower($info['mimetype']) == 'httpd/unix-directory') { + if (strtolower($info['mimetype']) == 'httpd/unix-directory') { $_GET['dir'] = $filepath; } else { $_GET['file'] = $filepath; @@ -27,7 +27,7 @@ function getID($path) { if (substr(\OC\Files\Filesystem::getMountPoint($path), -7, 6) == "Shared") { $path_parts = explode('/', $path, 5); $user = $path_parts[1]; - $intPath = '/'.$path_parts[4]; + $intPath = '/' . $path_parts[4]; $query = \OC_DB::prepare('SELECT `item_source` FROM `*PREFIX*share` WHERE `uid_owner` = ? AND `file_target` = ? '); $result = $query->execute(array($user, $intPath)); $row = $result->fetchRow(); @@ -40,11 +40,12 @@ function getID($path) { return $fileSource; } + // Enf of backward compatibility /** * lookup file path and owner by fetching it from the fscache - * needed becaus OC_FileCache::getPath($id, $user) already requires the user + * needed because OC_FileCache::getPath($id, $user) already requires the user * @param int $id * @return array */ @@ -64,60 +65,62 @@ if (isset($_GET['t'])) { $type = $linkItem['item_type']; $fileSource = $linkItem['file_source']; $shareOwner = $linkItem['uid_owner']; - - if (OCP\User::userExists($shareOwner) && $fileSource != -1 ) { - + + if (OCP\User::userExists($shareOwner) && $fileSource != -1) { + $pathAndUser = getPathAndUser($linkItem['file_source']); $fileOwner = $pathAndUser['user']; - + //if this is a reshare check the file owner also exists - if ($shareOwner != $fileOwner && ! OCP\User::userExists($fileOwner)) { - OCP\Util::writeLog('share', 'original file owner '.$fileOwner.' does not exist for share '.$linkItem['id'], \OCP\Util::ERROR); - header('HTTP/1.0 404 Not Found'); - $tmpl = new OCP\Template('', '404', 'guest'); - $tmpl->printPage(); - exit(); + if ($shareOwner != $fileOwner && !OCP\User::userExists($fileOwner)) { + OCP\Util::writeLog('share', 'original file owner ' . $fileOwner . ' does not exist for share ' . $linkItem['id'], \OCP\Util::ERROR); + header('HTTP/1.0 404 Not Found'); + $tmpl = new OCP\Template('', '404', 'guest'); + $tmpl->printPage(); + exit(); } - + //mount filesystem of file owner OC_Util::setupFS($fileOwner); } } -} else if (isset($_GET['file']) || isset($_GET['dir'])) { - OCP\Util::writeLog('share', 'Missing token, trying fallback file/dir links', \OCP\Util::DEBUG); - if (isset($_GET['dir'])) { - $type = 'folder'; - $path = $_GET['dir']; - if(strlen($path)>1 and substr($path, -1, 1)==='/') { - $path=substr($path, 0, -1); +} else { + if (isset($_GET['file']) || isset($_GET['dir'])) { + OCP\Util::writeLog('share', 'Missing token, trying fallback file/dir links', \OCP\Util::DEBUG); + if (isset($_GET['dir'])) { + $type = 'folder'; + $path = $_GET['dir']; + if (strlen($path) > 1 and substr($path, -1, 1) === '/') { + $path = substr($path, 0, -1); + } + $baseDir = $path; + $dir = $baseDir; + } else { + $type = 'file'; + $path = $_GET['file']; + if (strlen($path) > 1 and substr($path, -1, 1) === '/') { + $path = substr($path, 0, -1); + } } - $baseDir = $path; - $dir = $baseDir; - } else { - $type = 'file'; - $path = $_GET['file']; - if(strlen($path)>1 and substr($path, -1, 1)==='/') { - $path=substr($path, 0, -1); - } - } - $shareOwner = substr($path, 1, strpos($path, '/', 1) - 1); - - if (OCP\User::userExists($shareOwner)) { - OC_Util::setupFS($shareOwner); - $fileSource = getId($path); - if ($fileSource != -1 ) { - $linkItem = OCP\Share::getItemSharedWithByLink($type, $fileSource, $shareOwner); - $pathAndUser['path'] = $path; - $path_parts = explode('/', $path, 5); - $pathAndUser['user'] = $path_parts[1]; - $fileOwner = $path_parts[1]; + $shareOwner = substr($path, 1, strpos($path, '/', 1) - 1); + + if (OCP\User::userExists($shareOwner)) { + OC_Util::setupFS($shareOwner); + $fileSource = getId($path); + if ($fileSource != -1) { + $linkItem = OCP\Share::getItemSharedWithByLink($type, $fileSource, $shareOwner); + $pathAndUser['path'] = $path; + $path_parts = explode('/', $path, 5); + $pathAndUser['user'] = $path_parts[1]; + $fileOwner = $path_parts[1]; + } } } } if ($linkItem) { if (!isset($linkItem['item_type'])) { - OCP\Util::writeLog('share', 'No item type set for share id: '.$linkItem['id'], \OCP\Util::ERROR); + OCP\Util::writeLog('share', 'No item type set for share id: ' . $linkItem['id'], \OCP\Util::ERROR); header('HTTP/1.0 404 Not Found'); $tmpl = new OCP\Template('', '404', 'guest'); $tmpl->printPage(); @@ -125,11 +128,13 @@ if ($linkItem) { } if (isset($linkItem['share_with'])) { // Authenticate share_with - $url = OCP\Util::linkToPublic('files').'&t='.$token; + $url = OCP\Util::linkToPublic('files') . '&t=' . $token; if (isset($_GET['file'])) { - $url .= '&file='.urlencode($_GET['file']); - } else if (isset($_GET['dir'])) { - $url .= '&dir='.urlencode($_GET['dir']); + $url .= '&file=' . urlencode($_GET['file']); + } else { + if (isset($_GET['dir'])) { + $url .= '&dir=' . urlencode($_GET['dir']); + } } if (isset($_POST['password'])) { $password = $_POST['password']; @@ -137,7 +142,7 @@ if ($linkItem) { // Check Password $forcePortable = (CRYPT_BLOWFISH != 1); $hasher = new PasswordHash(8, $forcePortable); - if (!($hasher->CheckPassword($password.OC_Config::getValue('passwordsalt', ''), $linkItem['share_with']))) { + if (!($hasher->CheckPassword($password . OC_Config::getValue('passwordsalt', ''), $linkItem['share_with']))) { $tmpl = new OCP\Template('files_sharing', 'authenticate', 'guest'); $tmpl->assign('URL', $url); $tmpl->assign('error', true); @@ -148,28 +153,30 @@ if ($linkItem) { $_SESSION['public_link_authenticated'] = $linkItem['id']; } } else { - OCP\Util::writeLog('share', 'Unknown share type '.$linkItem['share_type'].' for share id '.$linkItem['id'], \OCP\Util::ERROR); + OCP\Util::writeLog('share', 'Unknown share type ' . $linkItem['share_type'] . ' for share id ' . $linkItem['id'], \OCP\Util::ERROR); header('HTTP/1.0 404 Not Found'); $tmpl = new OCP\Template('', '404', 'guest'); $tmpl->printPage(); exit(); } - // Check if item id is set in session - } else if (!isset($_SESSION['public_link_authenticated']) || $_SESSION['public_link_authenticated'] !== $linkItem['id']) { - // Prompt for password - $tmpl = new OCP\Template('files_sharing', 'authenticate', 'guest'); - $tmpl->assign('URL', $url); - $tmpl->printPage(); - exit(); + // Check if item id is set in session + } else { + if (!isset($_SESSION['public_link_authenticated']) || $_SESSION['public_link_authenticated'] !== $linkItem['id']) { + // Prompt for password + $tmpl = new OCP\Template('files_sharing', 'authenticate', 'guest'); + $tmpl->assign('URL', $url); + $tmpl->printPage(); + exit(); + } } } - $basePath = substr($pathAndUser['path'] , strlen('/'.$fileOwner.'/files')); + $basePath = substr($pathAndUser['path'], strlen('/' . $fileOwner . '/files')); $path = $basePath; if (isset($_GET['path'])) { $path .= $_GET['path']; } if (!$path || !OC_Filesystem::isValidPath($path) || !OC_Filesystem::file_exists($path)) { - OCP\Util::writeLog('share', 'Invalid path '.$path.' for share id '.$linkItem['id'], \OCP\Util::ERROR); + OCP\Util::writeLog('share', 'Invalid path ' . $path . ' for share id ' . $linkItem['id'], \OCP\Util::ERROR); header('HTTP/1.0 404 Not Found'); $tmpl = new OCP\Template('', '404', 'guest'); $tmpl->printPage(); @@ -179,13 +186,15 @@ if ($linkItem) { $file = basename($path); // Download the file if (isset($_GET['download'])) { - if (isset($_GET['path']) && $_GET['path'] !== '' ) { - if ( isset($_GET['files']) ) { // download selected files + if (isset($_GET['path']) && $_GET['path'] !== '') { + if (isset($_GET['files'])) { // download selected files OC_Files::get($path, $_GET['files'], $_SERVER['REQUEST_METHOD'] == 'HEAD' ? true : false); - } else if (isset($_GET['path']) && $_GET['path'] != '' ) { // download a file from a shared directory - OC_Files::get($dir, $file, $_SERVER['REQUEST_METHOD'] == 'HEAD' ? true : false); - } else { // download the whole shared directory - OC_Files::get($dir, $file, $_SERVER['REQUEST_METHOD'] == 'HEAD' ? true : false); + } else { + if (isset($_GET['path']) && $_GET['path'] != '') { // download a file from a shared directory + OC_Files::get($dir, $file, $_SERVER['REQUEST_METHOD'] == 'HEAD' ? true : false); + } else { // download the whole shared directory + OC_Files::get($dir, $file, $_SERVER['REQUEST_METHOD'] == 'HEAD' ? true : false); + } } } else { // download a single shared file OC_Files::get($dir, $file, $_SERVER['REQUEST_METHOD'] == 'HEAD' ? true : false); @@ -206,7 +215,7 @@ if ($linkItem) { $getPath = ''; } // - $urlLinkIdentifiers= (isset($token)?'&t='.$token:'').(isset($_GET['dir'])?'&dir='.$_GET['dir']:'').(isset($_GET['file'])?'&file='.$_GET['file']:''); + $urlLinkIdentifiers = (isset($token) ? '&t=' . $token : '') . (isset($_GET['dir']) ? '&dir=' . $_GET['dir'] : '') . (isset($_GET['file']) ? '&file=' . $_GET['file'] : ''); // Show file list if (OC_Filesystem::is_dir($path)) { OCP\Util::addStyle('files', 'files'); @@ -219,9 +228,9 @@ if ($linkItem) { if ($i['type'] == 'file') { $fileinfo = pathinfo($i['name']); $i['basename'] = $fileinfo['filename']; - $i['extension'] = isset($fileinfo['extension']) ? ('.'.$fileinfo['extension']) : ''; + $i['extension'] = isset($fileinfo['extension']) ? ('.' . $fileinfo['extension']) : ''; } - $i['directory'] = '/'.substr($i['directory'], $rootLength); + $i['directory'] = '/' . substr($i['directory'], $rootLength); if ($i['directory'] == '/') { $i['directory'] = ''; } @@ -238,98 +247,31 @@ if ($linkItem) { //add subdir breadcrumbs foreach (explode('/', urldecode($_GET['path'])) as $i) { if ($i != '') { - $pathtohere .= '/'.$i; + $pathtohere .= '/' . $i; $breadcrumb[] = array('dir' => $pathtohere, 'name' => $i); - $path = $linkItem['path']; - if (isset($_GET['path'])) { - $path .= $_GET['path']; - $dir .= $_GET['path']; - if (!\OC\Files\Filesystem::file_exists($path)) { - header('HTTP/1.0 404 Not Found'); - $tmpl = new OCP\Template('', '404', 'guest'); - $tmpl->printPage(); - exit(); - } - } + $path = $linkItem['path']; + if (isset($_GET['path'])) { + $path .= $_GET['path']; + $dir .= $_GET['path']; + if (!\OC\Files\Filesystem::file_exists($path)) { + header('HTTP/1.0 404 Not Found'); + $tmpl = new OCP\Template('', '404', 'guest'); + $tmpl->printPage(); + exit(); + } + } - $list = new OCP\Template('files', 'part.list', ''); - $list->assign('files', $files, false); - $list->assign('publicListView', true); - $list->assign('baseURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&path=', false); - $list->assign('downloadURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&download&path=', false); - $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', '' ); - $breadcrumbNav->assign('breadcrumb', $breadcrumb, false); - $breadcrumbNav->assign('baseURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&path=', false); - $folder = new OCP\Template('files', 'index', ''); - $folder->assign('fileList', $list->fetchPage(), false); - $folder->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); - $folder->assign('isCreatable', false); - $folder->assign('permissions', 0); - $folder->assign('files', $files); - $folder->assign('uploadMaxFilesize', 0); - $folder->assign('uploadMaxHumanFilesize', 0); - $folder->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); - $tmpl->assign('folder', $folder->fetchPage(), false); - $tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); - $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&download&path='.urlencode($getPath)); - } else { - // Show file preview if viewer is available - if ($type == 'file') { - $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&download'); - } else { - OCP\Util::addStyle('files_sharing', 'public'); - OCP\Util::addScript('files_sharing', 'public'); - OCP\Util::addScript('files', 'fileactions'); - $tmpl = new OCP\Template('files_sharing', 'public', 'base'); - $tmpl->assign('owner', $uidOwner); - // Show file list - if (OC_Filesystem::is_dir($path)) { - OCP\Util::addStyle('files', 'files'); - OCP\Util::addScript('files', 'files'); - OCP\Util::addScript('files', 'filelist'); - $files = array(); - $rootLength = strlen($baseDir) + 1; - foreach (OC_Files::getDirectoryContent($path) as $i) { - $i['date'] = OCP\Util::formatDate($i['mtime']); - if ($i['type'] == 'file') { - $fileinfo = pathinfo($i['name']); - $i['basename'] = $fileinfo['filename']; - $i['extension'] = isset($fileinfo['extension']) ? ('.'.$fileinfo['extension']) : ''; - } - $i['directory'] = '/'.substr('/'.$uidOwner.'/files'.$i['directory'], $rootLength); - if ($i['directory'] == '/') { - $i['directory'] = ''; - } - $i['permissions'] = OCP\PERMISSION_READ; - $files[] = $i; - } - // Make breadcrumb - $breadcrumb = array(); - $pathtohere = ''; - $count = 1; - foreach (explode('/', $dir) as $i) { - if ($i != '') { - if ($i != $baseDir) { - $pathtohere .= '/'.$i; - } - if ( strlen($pathtohere) < strlen($_GET['dir'])) { - continue; - } - $breadcrumb[] = array('dir' => str_replace($_GET['dir'], "", $pathtohere, $count), 'name' => $i); - } - } $list = new OCP\Template('files', 'part.list', ''); $list->assign('files', $files, false); $list->assign('publicListView', true); - $list->assign('baseURL', OCP\Util::linkToPublic('files').'&dir='.urlencode($_GET['dir']).'&path=', false); - $list->assign('downloadURL', OCP\Util::linkToPublic('files').'&download&dir='.urlencode($_GET['dir']).'&path=', false); - $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', '' ); + $list->assign('baseURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&path=', false); + $list->assign('downloadURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&download&path=', false); + $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); $breadcrumbNav->assign('breadcrumb', $breadcrumb, false); - $breadcrumbNav->assign('baseURL', OCP\Util::linkToPublic('files').'&dir='.urlencode($_GET['dir']).'&path=', false); + $breadcrumbNav->assign('baseURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&path=', false); $folder = new OCP\Template('files', 'index', ''); $folder->assign('fileList', $list->fetchPage(), false); $folder->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); - $folder->assign('dir', basename($dir)); $folder->assign('isCreatable', false); $folder->assign('permissions', 0); $folder->assign('files', $files); @@ -337,43 +279,113 @@ if ($linkItem) { $folder->assign('uploadMaxHumanFilesize', 0); $folder->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); $tmpl->assign('folder', $folder->fetchPage(), false); - $tmpl->assign('uidOwner', $uidOwner); - $tmpl->assign('dir', basename($dir)); - $tmpl->assign('filename', basename($path)); - $tmpl->assign('mimetype', OC_Filesystem::getMimeType($path)); $tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); - if (isset($_GET['path'])) { - $getPath = $_GET['path']; - } else { - $getPath = ''; - } - $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files').'&download&dir='.urlencode($_GET['dir']).'&path='.urlencode($getPath), false); + $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&download&path=' . urlencode($getPath)); } else { // Show file preview if viewer is available - $tmpl->assign('uidOwner', $uidOwner); - $tmpl->assign('dir', dirname($path)); - $tmpl->assign('filename', basename($path)); - $tmpl->assign('mimetype', OC_Filesystem::getMimeType($path)); if ($type == 'file') { - $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files').'&file='.urlencode($_GET['file']).'&download', false); + $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files') . $urlLinkIdentifiers . '&download'); } else { - if (isset($_GET['path'])) { - $getPath = $_GET['path']; + OCP\Util::addStyle('files_sharing', 'public'); + OCP\Util::addScript('files_sharing', 'public'); + OCP\Util::addScript('files', 'fileactions'); + $tmpl = new OCP\Template('files_sharing', 'public', 'base'); + $tmpl->assign('owner', $uidOwner); + // Show file list + if (OC_Filesystem::is_dir($path)) { + OCP\Util::addStyle('files', 'files'); + OCP\Util::addScript('files', 'files'); + OCP\Util::addScript('files', 'filelist'); + $files = array(); + $rootLength = strlen($baseDir) + 1; + foreach (OC_Files::getDirectoryContent($path) as $i) { + $i['date'] = OCP\Util::formatDate($i['mtime']); + if ($i['type'] == 'file') { + $fileinfo = pathinfo($i['name']); + $i['basename'] = $fileinfo['filename']; + $i['extension'] = isset($fileinfo['extension']) ? ('.' . $fileinfo['extension']) : ''; + } + $i['directory'] = '/' . substr('/' . $uidOwner . '/files' . $i['directory'], $rootLength); + if ($i['directory'] == '/') { + $i['directory'] = ''; + } + $i['permissions'] = OCP\PERMISSION_READ; + $files[] = $i; + } + // Make breadcrumb + $breadcrumb = array(); + $pathtohere = ''; + $count = 1; + foreach (explode('/', $dir) as $i) { + if ($i != '') { + if ($i != $baseDir) { + $pathtohere .= '/' . $i; + } + if (strlen($pathtohere) < strlen($_GET['dir'])) { + continue; + } + $breadcrumb[] = array('dir' => str_replace($_GET['dir'], "", $pathtohere, $count), 'name' => $i); + } + } + $list = new OCP\Template('files', 'part.list', ''); + $list->assign('files', $files, false); + $list->assign('publicListView', true); + $list->assign('baseURL', OCP\Util::linkToPublic('files') . '&dir=' . urlencode($_GET['dir']) . '&path=', false); + $list->assign('downloadURL', OCP\Util::linkToPublic('files') . '&download&dir=' . urlencode($_GET['dir']) . '&path=', false); + $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); + $breadcrumbNav->assign('breadcrumb', $breadcrumb, false); + $breadcrumbNav->assign('baseURL', OCP\Util::linkToPublic('files') . '&dir=' . urlencode($_GET['dir']) . '&path=', false); + $folder = new OCP\Template('files', 'index', ''); + $folder->assign('fileList', $list->fetchPage(), false); + $folder->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); + $folder->assign('dir', basename($dir)); + $folder->assign('isCreatable', false); + $folder->assign('permissions', 0); + $folder->assign('files', $files); + $folder->assign('uploadMaxFilesize', 0); + $folder->assign('uploadMaxHumanFilesize', 0); + $folder->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); + $tmpl->assign('folder', $folder->fetchPage(), false); + $tmpl->assign('uidOwner', $uidOwner); + $tmpl->assign('dir', basename($dir)); + $tmpl->assign('filename', basename($path)); + $tmpl->assign('mimetype', OC_Filesystem::getMimeType($path)); + $tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); + if (isset($_GET['path'])) { + $getPath = $_GET['path']; + } else { + $getPath = ''; + } + $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files') . '&download&dir=' . urlencode($_GET['dir']) . '&path=' . urlencode($getPath), false); } else { - $getPath = ''; + // Show file preview if viewer is available + $tmpl->assign('uidOwner', $uidOwner); + $tmpl->assign('dir', dirname($path)); + $tmpl->assign('filename', basename($path)); + $tmpl->assign('mimetype', OC_Filesystem::getMimeType($path)); + if ($type == 'file') { + $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files') . '&file=' . urlencode($_GET['file']) . '&download', false); + } else { + if (isset($_GET['path'])) { + $getPath = $_GET['path']; + } else { + $getPath = ''; + } + $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files') . '&download&dir=' . urlencode($_GET['dir']) . '&path=' . urlencode($getPath), false); + } } - $tmpl->assign('downloadURL', OCP\Util::linkToPublic('files').'&download&dir='.urlencode($_GET['dir']).'&path='.urlencode($getPath), false); + $tmpl->printPage(); } } $tmpl->printPage(); } + exit(); + } else { + OCP\Util::writeLog('share', 'could not resolve linkItem', \OCP\Util::DEBUG); } - $tmpl->printPage(); } - exit(); -} else { - OCP\Util::writeLog('share', 'could not resolve linkItem', \OCP\Util::DEBUG); } header('HTTP/1.0 404 Not Found'); $tmpl = new OCP\Template('', '404', 'guest'); $tmpl->printPage(); + From 317cd4c70a3042c4e16424cafb4a8b34c8cd8c3c Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 6 Dec 2012 17:49:35 +0100 Subject: [PATCH 117/418] catch error if old filecache table is not present during upgrade --- lib/files/cache/upgrade.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php index 899f6f7ac8..ebac387de9 100644 --- a/lib/files/cache/upgrade.php +++ b/lib/files/cache/upgrade.php @@ -15,13 +15,17 @@ class Upgrade { $insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); - $oldEntriesQuery = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` ORDER BY `id` ASC'); //sort ascending to ensure the parent gets inserted before a child - try{ - $oldEntriesResult = $oldEntriesQuery->execute(); - }catch(\Exception $e){ + try { + $oldEntriesQuery = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` ORDER BY `id` ASC'); //sort ascending to ensure the parent gets inserted before a child + } catch (\Exception $e) { return; } - if(!$oldEntriesResult){ + try { + $oldEntriesResult = $oldEntriesQuery->execute(); + } catch (\Exception $e) { + return; + } + if (!$oldEntriesResult) { return; } From 8635699db932959ac147193473b014406b0e9e09 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 11 Dec 2012 01:06:21 +0100 Subject: [PATCH 118/418] fix cache behaviour for non existing files --- lib/files/view.php | 26 ++++++++++++++------------ tests/lib/files/cache/cache.php | 5 +++++ tests/lib/files/view.php | 3 +++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/files/view.php b/lib/files/view.php index 468808566a..994dbcc85c 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -691,20 +691,22 @@ class View { $data = $cache->get($internalPath); - if ($data['mimetype'] === 'httpd/unix-directory') { - //add the sizes of other mountpoints to the folder - $mountPoints = Filesystem::getMountPoints($path); - foreach ($mountPoints as $mountPoint) { - $subStorage = Filesystem::getStorage($mountPoint); - $subCache = $subStorage->getCache(); - $rootEntry = $subCache->get(''); + if ($data) { + if ($data['mimetype'] === 'httpd/unix-directory') { + //add the sizes of other mountpoints to the folder + $mountPoints = Filesystem::getMountPoints($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = Filesystem::getStorage($mountPoint); + $subCache = $subStorage->getCache(); + $rootEntry = $subCache->get(''); - $data['size'] += $rootEntry['size']; + $data['size'] += $rootEntry['size']; + } } - } - $permissionsCache = $storage->getPermissionsCache(); - $data['permissions'] = $permissionsCache->get($data['fileid'], \OC_User::getUser()); + $permissionsCache = $storage->getPermissionsCache(); + $data['permissions'] = $permissionsCache->get($data['fileid'], \OC_User::getUser()); + } } return $data; } @@ -888,7 +890,7 @@ class View { * @param string $path * @return string */ - public function getETag($path){ + public function getETag($path) { /** * @var Storage\Storage $storage * @var string $internalPath diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index e9105cd5ab..a2b131ac0a 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -192,6 +192,11 @@ class Cache extends \UnitTestCase { $this->assertEquals($file3, $this->cache->getIncomplete()); } + function testNonExisting() { + $this->assertFalse($this->cache->get('foo.txt')); + $this->assertEquals(array(), $this->cache->getFolderContents('foo')); + } + public function tearDown() { $this->cache->clear(); } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index ed08dcc114..1b8f6dc1e8 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -92,6 +92,9 @@ class View extends \PHPUnit_Framework_TestCase { $cachedData = $rootView->getFileInfo('/foo.txt'); $this->assertTrue($cachedData['encrypted']); $this->assertEquals($cachedData['fileid'], $id); + + $this->assertFalse($rootView->getFileInfo('/non/existing')); + $this->assertEquals(array(), $rootView->getDirectoryContent('/non/existing')); } public function testAutoScan() { From 438d3c21f651240e416b6e50ad49f72dd079a9ae Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 11 Dec 2012 01:24:53 +0100 Subject: [PATCH 119/418] actually connect the filesystem hooks to the cache updater --- lib/files/filesystem.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 724c83b361..c5b56ba9f0 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -685,4 +685,8 @@ class Filesystem { \OC_Hook::connect('OC_Filesystem', 'post_delete', 'OC_Filesystem', 'removeETagHook'); \OC_Hook::connect('OC_Filesystem', 'post_rename', 'OC_Filesystem', 'removeETagHook'); +\OC_Hook::connect('OC_Filesystem', 'post_write', '\OC\Files\Cache\Updater', 'writeHook'); +\OC_Hook::connect('OC_Filesystem', 'post_delete', '\OC\Files\Cache\Updater', 'deleteHook'); +\OC_Hook::connect('OC_Filesystem', 'post_rename', '\OC\Files\Cache\Updater', 'renameHook'); + \OC_Util::setupFS(); From cc0a0df88b3b6fdd2b1f9c85349683eb640f9670 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 11 Dec 2012 01:25:21 +0100 Subject: [PATCH 120/418] one additional test case for the cache updater --- tests/lib/files/cache/updater.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/lib/files/cache/updater.php b/tests/lib/files/cache/updater.php index b2eccf9130..d19966c191 100644 --- a/tests/lib/files/cache/updater.php +++ b/tests/lib/files/cache/updater.php @@ -99,6 +99,11 @@ class Updater extends \PHPUnit_Framework_TestCase { $this->assertFalse($this->cache->inCache('foo.txt')); $cachedData = $this->cache->get(''); $this->assertEquals(2 * $textSize + $imageSize, $cachedData['size']); + + Filesystem::mkdir('bar_folder'); + $this->assertTrue($this->cache->inCache('bar_folder')); + Filesystem::rmdir('bar_folder'); + $this->assertFalse($this->cache->inCache('bar_folder')); } public function testRename() { From c6a5ce54a706efe6d157ec30a3960a3ebf8719a7 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 15 Dec 2012 02:22:09 +0100 Subject: [PATCH 121/418] these functions should be static --- lib/files.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/files.php b/lib/files.php index 768a2ad219..2d7e335e96 100644 --- a/lib/files.php +++ b/lib/files.php @@ -28,11 +28,11 @@ class OC_Files { static $tmpFiles = array(); - public function getFileInfo($path){ + static public function getFileInfo($path){ return \OC\Files\Filesystem::getFileInfo($path); } - public function getDirectoryContent($path){ + static public function getDirectoryContent($path){ return \OC\Files\Filesystem::getDirectoryContent($path); } From bc52f121626bb1bb1f67bc1f876d0420d153b3d1 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 15 Dec 2012 02:29:34 +0100 Subject: [PATCH 122/418] dont insert and entry in the filecache during upgrade if the id already exists in the filecache most likely the result from an incompelte upgrade --- lib/files/cache/upgrade.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php index ebac387de9..9219deebef 100644 --- a/lib/files/cache/upgrade.php +++ b/lib/files/cache/upgrade.php @@ -29,7 +29,13 @@ class Upgrade { return; } + $checkExistingQuery = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `fileid` = ?'); + while ($row = $oldEntriesResult->fetchRow()) { + if($checkExistingQuery->execute(array($row['id']))->fetchRow()){ + continue; + } + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($row['path']); /** * @var \OC\Files\Storage\Storage $storage From 4be039e6cbc0a826b07fc2af1a6c6d94888a9187 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 15 Dec 2012 03:10:56 +0100 Subject: [PATCH 123/418] Filecache Watcher: use scan or scanFile based on the current item, not the cached item --- lib/files/cache/watcher.php | 6 ++++-- tests/lib/files/cache/watcher.php | 34 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/files/cache/watcher.php b/lib/files/cache/watcher.php index d6039d9945..31059ec7f5 100644 --- a/lib/files/cache/watcher.php +++ b/lib/files/cache/watcher.php @@ -44,12 +44,14 @@ class Watcher { public function checkUpdate($path) { $cachedEntry = $this->cache->get($path); if ($this->storage->hasUpdated($path, $cachedEntry['mtime'])) { - if ($cachedEntry['mimetype'] === 'httpd/unix-directory') { + if ($this->storage->is_dir($path)) { $this->scanner->scan($path, Scanner::SCAN_SHALLOW); - $this->cleanFolder($path); } else { $this->scanner->scanFile($path); } + if ($cachedEntry['mimetype'] === 'httpd/unix-directory') { + $this->cleanFolder($path); + } $this->cache->correctFolderSize($path); } } diff --git a/tests/lib/files/cache/watcher.php b/tests/lib/files/cache/watcher.php index 0125dd843b..07c8ac3640 100644 --- a/tests/lib/files/cache/watcher.php +++ b/tests/lib/files/cache/watcher.php @@ -63,6 +63,40 @@ class Watcher extends \PHPUnit_Framework_TestCase { $this->assertFalse($cache->inCache('folder/bar2.txt')); } + public function testFileToFolder() { + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = new \OC\Files\Cache\Watcher($storage); + + //set the mtime to the past so it can detect an mtime change + $cache->put('', array('mtime' => 10)); + + $storage->unlink('foo.txt'); + $storage->rename('folder','foo.txt'); + $updater->checkUpdate(''); + + $entry= $cache->get('foo.txt'); + $this->assertEquals(-1, $entry['size']); + $this->assertEquals('httpd/unix-directory', $entry['mimetype']); + $this->assertFalse($cache->inCache('folder')); + $this->assertFalse($cache->inCache('folder/bar.txt')); + + $storage = $this->getTestStorage(); + $cache = $storage->getCache(); + $updater = new \OC\Files\Cache\Watcher($storage); + + //set the mtime to the past so it can detect an mtime change + $cache->put('foo.txt', array('mtime' => 10)); + + $storage->unlink('foo.txt'); + $storage->rename('folder','foo.txt'); + $updater->checkUpdate('foo.txt'); + + $entry= $cache->get('foo.txt'); + $this->assertEquals('httpd/unix-directory', $entry['mimetype']); + $this->assertTrue($cache->inCache('foo.txt/bar.txt')); + } + /** * @param bool $scan * @return \OC\Files\Storage\Storage From cf3665057c0c195bb3a0e2d9f8f8746f5d2f5787 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 15 Dec 2012 03:20:50 +0100 Subject: [PATCH 124/418] make sure folders that are not fully scanned are at least shallow scanned when we open them the fact that they are in the cache does not mean they are scanned --- lib/files/view.php | 2 +- tests/lib/files/view.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/files/view.php b/lib/files/view.php index 994dbcc85c..6d917bb585 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -728,7 +728,7 @@ class View { if ($storage) { $cache = $storage->getCache(); - if (!$cache->inCache($internalPath)) { + if ($cache->getStatus($internalPath) < Cache\Cache::COMPLETE) { $scanner = $storage->getScanner(); $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } else { diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 1b8f6dc1e8..712166ab32 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -97,6 +97,19 @@ class View extends \PHPUnit_Framework_TestCase { $this->assertEquals(array(), $rootView->getDirectoryContent('/non/existing')); } + function testCacheIncompleteFolder() { + $storage1 = $this->getTestStorage(false); + \OC\Files\Filesystem::mount($storage1, array(), '/'); + $rootView = new \OC\Files\View(''); + + $entries = $rootView->getDirectoryContent('/'); + $this->assertEquals(3, count($entries)); + + // /folder will already be in the cache but not scanned + $entries = $rootView->getDirectoryContent('/folder'); + $this->assertEquals(1, count($entries)); + } + public function testAutoScan() { $storage1 = $this->getTestStorage(false); $storage2 = $this->getTestStorage(false); From 8951769cae5f1acc9b709ac676fffe26513d14f6 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 15 Dec 2012 17:16:26 -0500 Subject: [PATCH 125/418] Check sub storage isn't null or false --- lib/files/view.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/files/view.php b/lib/files/view.php index 6d917bb585..592c484a21 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -697,10 +697,11 @@ class View { $mountPoints = Filesystem::getMountPoints($path); foreach ($mountPoints as $mountPoint) { $subStorage = Filesystem::getStorage($mountPoint); - $subCache = $subStorage->getCache(); - $rootEntry = $subCache->get(''); - - $data['size'] += $rootEntry['size']; + if ($subStorage) { + $subCache = $subStorage->getCache(); + $rootEntry = $subCache->get(''); + $data['size'] += $rootEntry['size']; + } } } From b12abb2c94072ab5b84d0477ecb7ce9789bdda0d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 15 Dec 2012 23:28:07 +0100 Subject: [PATCH 126/418] use numeric ids for storages in the filecache --- db_structure.xml | 40 +++++++++++++++++++++++++++++++-- lib/files/cache/cache.php | 44 ++++++++++++++++++++++++++++--------- lib/files/cache/upgrade.php | 21 ++++++++++++++++-- lib/util.php | 2 +- 4 files changed, 92 insertions(+), 15 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index 2856ee4ff9..aa0916264c 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -58,6 +58,42 @@
+ + + *dbprefix*storages + + + + + id + text + + true + 64 + + + + numeric_id + integer + 0 + true + 1 + 4 + + + + storages_id_index + true + + id + ascending + + + + + +
+ *dbprefix*filecache @@ -75,10 +111,10 @@ storage - text + integer true - 64 + 4 diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 5aeb6f25af..3ebae9baa5 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -29,6 +29,13 @@ class Cache { */ private $storageId; + /** + * numeric storage id + * + * @var int $numericId + */ + private $numericId; + /** * @param \OC\Files\Storage\Storage|string $storage */ @@ -38,6 +45,20 @@ class Cache { } else { $this->storageId = $storage; } + + $query = \OC_DB::prepare('SELECT `numeric_id` FROM `*PREFIX*storages` WHERE `id` = ?'); + $result = $query->execute(array($this->storageId)); + if ($row = $result->fetchRow()) { + $this->numericId = $row['numeric_id']; + } else { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*storages`(`id`) VALUES(?)'); + $query->execute(array($this->storageId)); + $this->numericId = \OC_DB::insertid('*PREFIX*filecache'); + } + } + + public function getNumericStorageId() { + return $this->numericId; } /** @@ -49,7 +70,7 @@ class Cache { public function get($file) { if (is_string($file) or $file == '') { $where = 'WHERE `storage` = ? AND `path_hash` = ?'; - $params = array($this->storageId, md5($file)); + $params = array($this->numericId, md5($file)); } else { //file id $where = 'WHERE `fileid` = ?'; $params = array($file); @@ -128,7 +149,7 @@ class Cache { list($queryParts, $params) = $this->buildParts($data); $queryParts[] = '`storage`'; - $params[] = $this->storageId; + $params[] = $this->numericId; $valuesPlaceholder = array_fill(0, count($queryParts), '?'); $query = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ') VALUES(' . implode(', ', $valuesPlaceholder) . ')'); @@ -189,7 +210,7 @@ class Cache { $pathHash = md5($file); $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); - $result = $query->execute(array($this->storageId, $pathHash)); + $result = $query->execute(array($this->numericId, $pathHash)); if ($row = $result->fetchRow()) { return $row['fileid']; @@ -273,7 +294,10 @@ class Cache { * remove all entries for files that are stored on the storage from the cache */ public function clear() { - $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE storage=?'); + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE storage = ?'); + $query->execute(array($this->numericId)); + + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*storages` WHERE id = ?'); $query->execute(array($this->storageId)); } @@ -285,7 +309,7 @@ class Cache { public function getStatus($file) { $pathHash = md5($file); $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); - $result = $query->execute(array($this->storageId, $pathHash)); + $result = $query->execute(array($this->numericId, $pathHash)); if ($row = $result->fetchRow()) { if ((int)$row['size'] === -1) { return self::SHALLOW; @@ -312,7 +336,7 @@ class Cache { SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` FROM `*PREFIX*filecache` WHERE `name` LIKE ? AND `storage` = ?' ); - $result = $query->execute(array($pattern, $this->storageId)); + $result = $query->execute(array($pattern, $this->numericId)); $files = array(); while ($row = $result->fetchRow()) { $files[] = $row; @@ -336,7 +360,7 @@ class Cache { SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?' ); - $result = $query->execute(array($mimetype, $this->storageId)); + $result = $query->execute(array($mimetype, $this->numericId)); return $result->fetchAll(); } @@ -368,7 +392,7 @@ class Cache { return 0; } $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `parent` = ? AND `storage` = ?'); - $result = $query->execute(array($id, $this->storageId)); + $result = $query->execute(array($id, $this->numericId)); $totalSize = 0; $hasChilds = 0; while ($row = $result->fetchRow()) { @@ -395,7 +419,7 @@ class Cache { */ public function getAll() { $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?'); - $result = $query->execute(array($this->storageId)); + $result = $query->execute(array($this->numericId)); $ids = array(); while ($row = $result->fetchRow()) { $ids[] = $row['fileid']; @@ -414,7 +438,7 @@ class Cache { */ public function getIncomplete() { $query = \OC_DB::prepare('SELECT `path` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC LIMIT 1'); - $query->execute(array($this->storageId)); + $query->execute(array($this->numericId)); if ($row = $query->fetchRow()) { return $row['path']; } else { diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php index 9219deebef..77db4c2339 100644 --- a/lib/files/cache/upgrade.php +++ b/lib/files/cache/upgrade.php @@ -11,6 +11,8 @@ namespace OC\Files\Cache; class Upgrade { static $permissionsCaches = array(); + static $numericIds = array(); + static function upgrade() { $insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); @@ -32,7 +34,7 @@ class Upgrade { $checkExistingQuery = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `fileid` = ?'); while ($row = $oldEntriesResult->fetchRow()) { - if($checkExistingQuery->execute(array($row['id']))->fetchRow()){ + if ($checkExistingQuery->execute(array($row['id']))->fetchRow()) { continue; } @@ -42,7 +44,7 @@ class Upgrade { * @var string $internalPath; */ $pathHash = md5($internalPath); - $storageId = $storage->getId(); + $storageId = self::getNumericId($storage); $parentId = ($internalPath === '') ? -1 : $row['parent']; $insertQuery->execute(array($row['id'], $storageId, $internalPath, $pathHash, $parentId, $row['name'], $row['mimetype'], $row['mimepart'], $row['size'], $row['mtime'], $row['encrypted'])); @@ -64,4 +66,19 @@ class Upgrade { } return self::$permissionsCaches[$storageId]; } + + /** + * get the numeric storage id + * + * @param \OC\Files\Storage\Storage $storage + * @return int + */ + static function getNumericId($storage) { + $storageId = $storage->getId(); + if (!isset(self::$numericIds[$storageId])) { + $cache = new Cache($storage); + self::$numericIds[$storageId] = $cache->getNumericStorageId(); + } + return self::$numericIds[$storageId]; + } } diff --git a/lib/util.php b/lib/util.php index 0f6ead2419..4411b32731 100755 --- a/lib/util.php +++ b/lib/util.php @@ -75,7 +75,7 @@ class OC_Util { */ public static function getVersion() { // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.90.0. This is not visible to the user - return array(4,91,02); + return array(4,91,04); } /** From bf05ff351faa693337107ed4a316e36e9aacd296 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 15 Dec 2012 19:44:46 -0500 Subject: [PATCH 127/418] Initial support for file sharing with filesystem branch --- apps/files_sharing/appinfo/app.php | 3 + apps/files_sharing/lib/cache.php | 220 +++++++++++++++++++++++ apps/files_sharing/lib/permissions.php | 82 +++++++++ apps/files_sharing/lib/scanner.php | 69 +++++++ apps/files_sharing/lib/share/file.php | 74 ++++---- apps/files_sharing/lib/share/folder.php | 28 --- apps/files_sharing/lib/sharedstorage.php | 148 ++++++--------- lib/public/share.php | 8 +- 8 files changed, 475 insertions(+), 157 deletions(-) create mode 100644 apps/files_sharing/lib/cache.php create mode 100644 apps/files_sharing/lib/permissions.php create mode 100644 apps/files_sharing/lib/scanner.php diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 210c78ad17..189fd20cae 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -3,6 +3,9 @@ OC::$CLASSPATH['OC_Share_Backend_File'] = "apps/files_sharing/lib/share/file.php"; OC::$CLASSPATH['OC_Share_Backend_Folder'] = 'apps/files_sharing/lib/share/folder.php'; OC::$CLASSPATH['OC\Files\Storage\Shared'] = "apps/files_sharing/lib/sharedstorage.php"; +OC::$CLASSPATH['OC\Files\Cache\Shared_Cache'] = 'apps/files_sharing/lib/cache.php'; +OC::$CLASSPATH['OC\Files\Cache\Shared_Permissions'] = 'apps/files_sharing/lib/permissions.php'; +OC::$CLASSPATH['OC\Files\Cache\Shared_Scanner'] = 'apps/files_sharing/lib/scanner.php'; OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php new file mode 100644 index 0000000000..196e767cf6 --- /dev/null +++ b/apps/files_sharing/lib/cache.php @@ -0,0 +1,220 @@ +. +*/ + +namespace OC\Files\Cache; + +/** + * Metadata cache for shared files + * + * don't use this class directly if you need to get metadata, use \OC\Files\Filesystem::getFileInfo instead + */ +class Shared_Cache extends Cache { + + private $files = array(); + + /** + * @brief Get the source cache of a shared file or folder + * @param string Shared target file path + * @return \OC\Files\Storage\Cache + */ + private function getSourceCache($target) { + $source = \OC_Share_Backend_File::getSource($target); + if (isset($source['path'])) { + $source['path'] = '/'.$source['uid_owner'].'/'.$source['path']; + \OC\Files\Filesystem::initMountPoints($source['uid_owner']); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source['path']); + $this->files[$target] = $internalPath; + return $storage->getCache(); + } + return false; + } + + /** + * get the stored metadata of a file or folder + * + * @param string/int $file + * @return array + */ + public function get($file) { + if (is_string($file)) { + if ($cache = $this->getSourceCache($file)) { + return $cache->get($this->files[$file]); + } + } else { + $query = \OC_DB::prepare( + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` + FROM `*PREFIX*filecache` WHERE `fileid` = ?'); + $result = $query->execute(array($file)); + $data = $result->fetchRow(); + $data['fileid'] = (int)$data['fileid']; + $data['size'] = (int)$data['size']; + $data['mtime'] = (int)$data['mtime']; + $data['encrypted'] = (bool)$data['encrypted']; + return $data; + } + return false; + } + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return array + */ + public function getFolderContents($folder) { + if ($folder == '') { + return \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS); + } else { + return $this->getSourceCache($folder)->getFolderContents('/'.$this->files[$folder]); + } + } + + /** + * store meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + */ + public function put($file, array $data) { + if ($cache = $this->getSourceCache($file)) { + return $cache->put($this->files[$file]); + } + return false; + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + if ($cache = $this->getSourceCache($file)) { + return $cache->getId($this->files[$file]); + } + return -1; + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + if ($cache = $this->getSourceCache($file)) { + $cache->remove($this->files[$file]); + } + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + if ($cache = $this->getSourceCache($source)) { + $targetPath = \OC_Share_Backend_File::getSourcePath(dirname($target)); + if ($targetPath) { + $targetPath .= '/'.basename($target); + $cache->move($this->files[$source], $targetPath); + } + + } + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + // Not a valid action for Shared Cache + } + + /** + * @param string $file + * + * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + if ($cache = $this->getSourceCache($file)) { + return $cache->getStatus($this->files[$file]); + } + return false; + } + + /** + * search for files matching $pattern + * + * @param string $pattern + * @return array of file data + */ + public function search($pattern) { + // TODO + } + + /** + * search for files by mimetype + * + * @param string $part1 + * @param string $part2 + * @return array + */ + public function searchByMime($mimetype) { + if (strpos($mimetype, '/')) { + $where = '`mimetype` = ?'; + } else { + $where = '`mimepart` = ?'; + } + $ids = $this->getAll(); + $placeholders = join(',', array_fill(0, count($ids), '?')); + $query = \OC_DB::prepare(' + SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` + FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `fileid` IN = ('.$placeholders.')' + ); + $result = $query->execute(array_merge(array($mimetype), $ids)); + return $result->fetchAll(); + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @return int + */ + public function calculateFolderSize($path) { + if ($cache = $this->getSourceCache($path)) { + return $cache->calculateFolderSize($this->files[$path]); + } + return 0; + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + return OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_ALL); + } + +} \ No newline at end of file diff --git a/apps/files_sharing/lib/permissions.php b/apps/files_sharing/lib/permissions.php new file mode 100644 index 0000000000..51ae4cf102 --- /dev/null +++ b/apps/files_sharing/lib/permissions.php @@ -0,0 +1,82 @@ +. +*/ +namespace OC\Files\Cache; + +class Shared_Permissions { + + /** + * get the permissions for a single file + * + * @param int $fileId + * @param string $user + * @return int (-1 if file no permissions set) + */ + static public function get($fileId, $user) { + $source = \OCP\Share::getItemSharedWithBySource('file', $fileId, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + if ($source) { + return $source['permissions']; + } else { + return -1; + } + } + + /** + * set the permissions of a file + * + * @param int $fileId + * @param string $user + * @param int $permissions + */ + static public function set($fileId, $user, $permissions) { + // Not a valid action for Shared Permissions + } + + /** + * get the permissions of multiply files + * + * @param int[] $fileIds + * @param string $user + * @return int[] + */ + static public function getMultiple($fileIds, $user) { + if (count($fileIds) === 0) { + return array(); + } + foreach ($fileIds as $fileId) { + $filePermissions[$fileId] = self::get($fileId, $user); + } + return $filePermissions; + } + + /** + * remove the permissions for a file + * + * @param int $fileId + * @param string $user + */ + static public function remove($fileId, $user) { + // Not a valid action for Shared Permissions + } + + static public function removeMultiple($fileIds, $user) { + // Not a valid action for Shared Permissions + } +} diff --git a/apps/files_sharing/lib/scanner.php b/apps/files_sharing/lib/scanner.php new file mode 100644 index 0000000000..d13d2f9cbc --- /dev/null +++ b/apps/files_sharing/lib/scanner.php @@ -0,0 +1,69 @@ +. +*/ + +namespace OC\Files\Cache; + +class Shared_Scanner extends Scanner { + + public function __construct(\OC\Files\Storage\Storage $storage) { + + } + + /** + * get all the metadata of a file or folder + * * + * + * @param string $path + * @return array with metadata of the file + */ + public function getData($path) { + // Not a valid action for Shared Scanner + } + + /** + * scan a single file and store it in the cache + * + * @param string $file + * @return array with metadata of the scanned file + */ + public function scanFile($file) { + // Not a valid action for Shared Scanner + } + + /** + * scan all the files in a folder and store them in the cache + * + * @param string $path + * @param SCAN_RECURSIVE/SCAN_SHALLOW $recursive + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + public function scan($path, $recursive = self::SCAN_RECURSIVE) { + // Not a valid action for Shared Scanner + } + + /** + * walk over any folders that are not fully scanned yet and scan them + */ + public function backgroundScan() { + // Not a valid action for Shared Scanner + } + +} \ No newline at end of file diff --git a/apps/files_sharing/lib/share/file.php b/apps/files_sharing/lib/share/file.php index ac58523683..52220dc5b2 100644 --- a/apps/files_sharing/lib/share/file.php +++ b/apps/files_sharing/lib/share/file.php @@ -22,16 +22,17 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { const FORMAT_SHARED_STORAGE = 0; - const FORMAT_FILE_APP = 1; - const FORMAT_FILE_APP_ROOT = 2; + const FORMAT_GET_FOLDER_CONTENTS = 1; + const FORMAT_GET_ALL = 2; const FORMAT_OPENDIR = 3; private $path; public function isValidSource($itemSource, $uidOwner) { - $path = OC_FileCache::getPath($itemSource, $uidOwner); - if ($path) { - $this->path = $path; + $query = \OC_DB::prepare('SELECT `name` FROM `*PREFIX*filecache` WHERE `fileid` = ?'); + $result = $query->execute(array($itemSource)); + if ($row = $result->fetchRow()) { + $this->path = $row['name']; return true; } return false; @@ -70,50 +71,29 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { public function formatItems($items, $format, $parameters = null) { if ($format == self::FORMAT_SHARED_STORAGE) { // Only 1 item should come through for this format call - return array('path' => $items[key($items)]['path'], 'permissions' => $items[key($items)]['permissions']); - } else if ($format == self::FORMAT_FILE_APP) { - if (isset($parameters['mimetype_filter']) && $parameters['mimetype_filter']) { - $mimetype_filter = $parameters['mimetype_filter']; - } + return array('path' => $items[key($items)]['path'], 'permissions' => $items[key($items)]['permissions'], 'uid_owner' => $items[key($items)]['uid_owner']); + } else if ($format == self::FORMAT_GET_FOLDER_CONTENTS) { $files = array(); foreach ($items as $item) { - if (isset($mimetype_filter) - && strpos($item['mimetype'], $mimetype_filter) !== 0 - && $item['mimetype'] != 'httpd/unix-directory') { - continue; - } $file = array(); - $file['id'] = $item['file_source']; + $file['fileid'] = $item['file_source']; + $file['storage'] = $item['storage']; $file['path'] = $item['file_target']; + $file['parent'] = $item['file_parent']; $file['name'] = basename($item['file_target']); - $file['ctime'] = $item['ctime']; - $file['mtime'] = $item['mtime']; $file['mimetype'] = $item['mimetype']; $file['size'] = $item['size']; + $file['mtime'] = $item['mtime']; $file['encrypted'] = $item['encrypted']; - $file['versioned'] = $item['versioned']; - $file['directory'] = $parameters['folder']; - $file['type'] = ($item['mimetype'] == 'httpd/unix-directory') ? 'dir' : 'file'; - $file['permissions'] = $item['permissions']; - if ($file['type'] == 'file') { - // Remove Create permission if type is file - $file['permissions'] &= ~OCP\PERMISSION_CREATE; - } - // NOTE: Temporary fix to allow unsharing of files in root of Shared directory - $file['permissions'] |= OCP\PERMISSION_DELETE; $files[] = $file; } return $files; - } else if ($format == self::FORMAT_FILE_APP_ROOT) { - $mtime = 0; - $size = 0; + } else if ($format == self::FORMAT_GET_ALL) { + $ids = array(); foreach ($items as $item) { - if ($item['mtime'] > $mtime) { - $mtime = $item['mtime']; - } - $size += $item['size']; + $ids[] = $item['file_source']; } - return array(0 => array('id' => -1, 'name' => 'Shared', 'mtime' => $mtime, 'mimetype' => 'httpd/unix-directory', 'size' => $size, 'writable' => false, 'type' => 'dir', 'directory' => '', 'permissions' => OCP\PERMISSION_READ)); + return $ids; } else if ($format == self::FORMAT_OPENDIR) { $files = array(); foreach ($items as $item) { @@ -124,4 +104,26 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { return array(); } + public static function getSource($target) { + $target = '/'.$target; + $target = rtrim($target, '/'); + $pos = strpos($target, '/', 1); + // Get shared folder name + if ($pos !== false) { + $folder = substr($target, 0, $pos); + $source = \OCP\Share::getItemSharedWith('folder', $folder, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + if ($source) { + $source['path'] = $source['path'].substr($target, strlen($folder)); + return $source; + } + } else { + $source = \OCP\Share::getItemSharedWith('file', $target, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + if ($source) { + return $source; + } + } + \OCP\Util::writeLog('files_sharing', 'File source not found for: '.$target, \OCP\Util::ERROR); + return false; + } + } diff --git a/apps/files_sharing/lib/share/folder.php b/apps/files_sharing/lib/share/folder.php index d414fcf10f..a1bb843d72 100644 --- a/apps/files_sharing/lib/share/folder.php +++ b/apps/files_sharing/lib/share/folder.php @@ -21,34 +21,6 @@ class OC_Share_Backend_Folder extends OC_Share_Backend_File implements OCP\Share_Backend_Collection { - public function formatItems($items, $format, $parameters = null) { - if ($format == self::FORMAT_SHARED_STORAGE) { - // Only 1 item should come through for this format call - return array('path' => $items[key($items)]['path'], 'permissions' => $items[key($items)]['permissions']); - } else if ($format == self::FORMAT_FILE_APP && isset($parameters['folder'])) { - // Only 1 item should come through for this format call - $folder = $items[key($items)]; - if (isset($parameters['mimetype_filter'])) { - $mimetype_filter = $parameters['mimetype_filter']; - } else { - $mimetype_filter = ''; - } - $path = $folder['path'].substr($parameters['folder'], 7 + strlen($folder['file_target'])); - $files = OC_FileCache::getFolderContent($path, '', $mimetype_filter); - foreach ($files as &$file) { - $file['directory'] = $parameters['folder']; - $file['type'] = ($file['mimetype'] == 'httpd/unix-directory') ? 'dir' : 'file'; - $file['permissions'] = $folder['permissions']; - if ($file['type'] == 'file') { - // Remove Create permission if type is file - $file['permissions'] &= ~OCP\PERMISSION_CREATE; - } - } - return $files; - } - return array(); - } - public function getChildren($itemSource) { $children = array(); $parents = array($itemSource); diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 5abdbf29f0..960aa64099 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -34,41 +34,8 @@ class Shared extends \OC\Files\Storage\Common { $this->sharedFolder = $arguments['sharedFolder']; } - /** - * @brief Get the source file path and the permissions granted for a shared file - * @param string Shared target file path - * @return array with the keys path and permissions or false if not found - */ - private function getFile($target) { - $target = '/'.$target; - $target = rtrim($target, '/'); - if (isset($this->files[$target])) { - return $this->files[$target]; - } else { - $pos = strpos($target, '/', 1); - // Get shared folder name - if ($pos !== false) { - $folder = substr($target, 0, $pos); - if (isset($this->files[$folder])) { - $file = $this->files[$folder]; - } else { - $file = \OCP\Share::getItemSharedWith('folder', $folder, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); - } - if ($file) { - $this->files[$target]['path'] = $file['path'].substr($target, strlen($folder)); - $this->files[$target]['permissions'] = $file['permissions']; - return $this->files[$target]; - } - } else { - $file = \OCP\Share::getItemSharedWith('file', $target, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); - if ($file) { - $this->files[$target] = $file; - return $this->files[$target]; - } - } - \OCP\Util::writeLog('files_sharing', 'File source not found for: '.$target, \OCP\Util::ERROR); - return false; - } + public function getId(){ + return 'shared::' . $this->sharedFolder; } /** @@ -77,13 +44,13 @@ class Shared extends \OC\Files\Storage\Common { * @return string source file path or false if not found */ private function getSourcePath($target) { - $file = $this->getFile($target); - if (isset($file['path'])) { - $uid = substr($file['path'], 1, strpos($file['path'], '/', 1) - 1); - \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => \OC_User::getHome($uid)), $uid); - return $file['path']; + if (!isset($this->files[$target])) { + $source = \OC_Share_Backend_File::getSource($target); + $source['path'] = '/'.$source['uid_owner'].'/'.$source['path']; + $this->files[$target] = $source; + \OC\Files\Filesystem::initMountPoints($source['uid_owner']); } - return false; + return $this->files[$target]['path']; } /** @@ -92,26 +59,18 @@ class Shared extends \OC\Files\Storage\Common { * @return int CRUDS permissions granted or false if not found */ public function getPermissions($target) { - $file = $this->getFile($target); - if (isset($file['permissions'])) { - return $file['permissions']; + if (!isset($this->files[$target])) { + $source = \OC_Share_Backend_File::getSource($target); + $this->files[$target] = $source; } - return false; - } - - public function getOwner($target) { - $shared_item = \OCP\Share::getItemSharedWith('folder', $target, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); - if ($shared_item) { - return $shared_item[0]["uid_owner"]; - } - return null; + return $this->files[$target]['permissions']; } public function mkdir($path) { if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) { return false; } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->mkdir($internalPath); } return false; @@ -119,7 +78,7 @@ class Shared extends \OC\Files\Storage\Common { public function rmdir($path) { if (($source = $this->getSourcePath($path)) && $this->isDeletable($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->rmdir($internalPath); } return false; @@ -131,7 +90,7 @@ class Shared extends \OC\Files\Storage\Common { \OC_FakeDirStream::$dirs['shared'] = $files; return opendir('fakedir://shared'); } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->opendir($internalPath); } return false; @@ -141,7 +100,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/') { return true; } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->is_dir($internalPath); } return false; @@ -149,7 +108,7 @@ class Shared extends \OC\Files\Storage\Common { public function is_file($path) { if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->is_file($internalPath); } return false; @@ -161,7 +120,7 @@ class Shared extends \OC\Files\Storage\Common { $stat['mtime'] = $this->filemtime($path); return $stat; } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->stat($internalPath); } return false; @@ -171,7 +130,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/') { return 'dir'; } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->filetype($internalPath); } return false; @@ -181,7 +140,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '' || $path == '/' || $this->is_dir($path)) { return 0; } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->filesize($internalPath); } return false; @@ -191,7 +150,7 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '') { return false; } - return ($this->getPermissions($path) & OCP\PERMISSION_CREATE); + return ($this->getPermissions($path) & \OCP\PERMISSION_CREATE); } public function isReadable($path) { @@ -202,28 +161,28 @@ class Shared extends \OC\Files\Storage\Common { if ($path == '') { return false; } - return ($this->getPermissions($path) & OCP\PERMISSION_UPDATE); + return ($this->getPermissions($path) & \OCP\PERMISSION_UPDATE); } public function isDeletable($path) { if ($path == '') { return true; } - return ($this->getPermissions($path) & OCP\PERMISSION_DELETE); + return ($this->getPermissions($path) & \OCP\PERMISSION_DELETE); } public function isSharable($path) { if ($path == '') { return false; } - return ($this->getPermissions($path) & OCP\PERMISSION_SHARE); + return ($this->getPermissions($path) & \OCP\PERMISSION_SHARE); } public function file_exists($path) { if ($path == '' || $path == '/') { return true; } else if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->file_exists($internalPath); } return false; @@ -244,7 +203,7 @@ class Shared extends \OC\Files\Storage\Common { } else { $source = $this->getSourcePath($path); if ($source) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->filemtime($internalPath); } } @@ -258,7 +217,7 @@ class Shared extends \OC\Files\Storage\Common { 'source' => $source, ); \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->file_get_contents($internalPath); } } @@ -274,7 +233,7 @@ class Shared extends \OC\Files\Storage\Common { 'source' => $source, ); \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); $result = $storage->file_put_contents($internalPath, $data); return $result; } @@ -285,7 +244,7 @@ class Shared extends \OC\Files\Storage\Common { // Delete the file if DELETE permission is granted if ($source = $this->getSourcePath($path)) { if ($this->isDeletable($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->unlink($internalPath); } else if (dirname($path) == '/' || dirname($path) == '.') { // Unshare the file from the user if in the root of the Shared folder @@ -309,8 +268,8 @@ class Shared extends \OC\Files\Storage\Common { if (dirname($path1) == dirname($path2)) { // Rename the file if UPDATE permission is granted if ($this->isUpdatable($path1)) { - list($storage, $oldInternalPath)=\OC\Files\Filesystem::resolvePath($oldSource); - list( , $newInternalPath)=\OC\Files\Filesystem::resolvePath($newSource); + list($storage, $oldInternalPath) = \OC\Files\Filesystem::resolvePath($oldSource); + list( , $newInternalPath) = \OC\Files\Filesystem::resolvePath($newSource); return $storage->rename($oldInternalPath, $newInternalPath); } } else { @@ -325,8 +284,8 @@ class Shared extends \OC\Files\Storage\Common { return $this->unlink($path1); } } else { - list($storage, $oldInternalPath)=\OC\Files\Filesystem::resolvePath($oldSource); - list( , $newInternalPath)=\OC\Files\Filesystem::resolvePath($newSource); + list($storage, $oldInternalPath) = \OC\Files\Filesystem::resolvePath($oldSource); + list( , $newInternalPath) = \OC\Files\Filesystem::resolvePath($newSource); return $storage->rename($oldInternalPath, $newInternalPath); } } @@ -372,7 +331,7 @@ class Shared extends \OC\Files\Storage\Common { 'mode' => $mode, ); \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info); - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->fopen($internalPath, $mode); } return false; @@ -383,7 +342,7 @@ class Shared extends \OC\Files\Storage\Common { return 'httpd/unix-directory'; } if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->getMimeType($internalPath); } return false; @@ -392,21 +351,21 @@ class Shared extends \OC\Files\Storage\Common { public function free_space($path) { $source = $this->getSourcePath($path); if ($source) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->free_space($internalPath); } } public function getLocalFile($path) { if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->getLocalFile($internalPath); } return false; } public function touch($path, $mtime = null) { if ($source = $this->getSourcePath($path)) { - list($storage, $internalPath)=\OC\Files\Filesystem::resolvePath($source); + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); return $storage->touch($internalPath, $mtime); } return false; @@ -417,17 +376,28 @@ class Shared extends \OC\Files\Storage\Common { \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared', array('sharedFolder' => '/Shared'), $user_dir.'/Shared/'); } - /** - * check if a file or folder has been updated since $time - * @param int $time - * @return bool - */ - public function hasUpdated($path, $time) { - //TODO - return false; + public function getCache() { + return new \OC\Files\Cache\Shared_Cache($this); } - public function getId(){ - return 'shared::' . $this->sharedFolder; + public function getScanner(){ + return new \OC\Files\Cache\Shared_Scanner($this); } + + public function getPermissionsCache() { + return new \OC\Files\Cache\Shared_Permissions($this); + } + + public function getOwner($path) { + if (!isset($this->files[$path])) { + $source = \OC_Share_Backend_File::getSource($path); + $this->files[$path] = $source; + } + return $this->files[$path]['uid_owner']; + } + + public function getETag($path) { + + } + } diff --git a/lib/public/share.php b/lib/public/share.php index 9bb64a38c8..8981de1b50 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -552,7 +552,7 @@ class Share { // Get filesystem root to add it to the file target and remove from the file source, match file_source with the file cache if ($itemType == 'file' || $itemType == 'folder') { $root = \OC\Files\Filesystem::getRoot(); - $where = 'INNER JOIN `*PREFIX*fscache` ON `file_source` = `*PREFIX*fscache`.`id`'; + $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid`'; if (!isset($item)) { $where .= ' WHERE `file_target` IS NOT NULL'; } @@ -569,7 +569,7 @@ class Share { $itemTypes = $collectionTypes; } $placeholders = join(',', array_fill(0, count($itemTypes), '?')); - $where .= ' WHERE `item_type` IN ('.$placeholders.'))'; + $where = ' WHERE `item_type` IN ('.$placeholders.'))'; $queryArgs = $itemTypes; } else { $where = ' WHERE `item_type` = ?'; @@ -681,8 +681,8 @@ class Share { } } else { if ($fileDependent) { - if (($itemType == 'file' || $itemType == 'folder') && $format == \OC_Share_Backend_File::FORMAT_FILE_APP || $format == \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT) { - $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `uid_owner`, `share_type`, `share_with`, `file_source`, `path`, `file_target`, `permissions`, `expiration`, `name`, `ctime`, `mtime`, `mimetype`, `size`, `encrypted`, `versioned`, `writable`'; + if (($itemType == 'file' || $itemType == 'folder') && $format == \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS) { + $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `uid_owner`, `share_type`, `share_with`, `file_source`, `path`, `file_target`, `permissions`, `expiration`, `*PREFIX*filecache`.`parent` as `file_parent`, `name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`'; } else { $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`, `*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`, `file_source`, `path`, `file_target`, `permissions`, `stime`, `expiration`, `token`'; } From 74b4fefda87feeaf51633ea7794ac498f7dce892 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Wed, 19 Dec 2012 00:10:03 +0100 Subject: [PATCH 128/418] remove padding from header --- core/css/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/css/styles.css b/core/css/styles.css index 2b55cc28c1..9e8bb2c006 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -16,7 +16,7 @@ body { background:#fefefe; font:normal .8em/1.6em "Lucida Grande", Arial, Verdan /* HEADERS */ -#body-user #header, #body-settings #header { position:fixed; top:0; left:0; right:0; z-index:100; height:45px; line-height:2.5em; padding:.5em; background:#1d2d44; -moz-box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; -webkit-box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; } +#body-user #header, #body-settings #header { position:fixed; top:0; left:0; right:0; z-index:100; height:45px; line-height:2.5em; background:#1d2d44; -moz-box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; -webkit-box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; box-shadow:0 0 10px rgba(0, 0, 0, .5), inset 0 -2px 10px #222; } #body-login #header { margin: -2em auto 0; text-align:center; height:10em; padding:1em 0 .5em; -moz-box-shadow:0 0 1em rgba(0, 0, 0, .5); -webkit-box-shadow:0 0 1em rgba(0, 0, 0, .5); box-shadow:0 0 1em rgba(0, 0, 0, .5); background:#1d2d44; /* Old browsers */ From b4515d874e5259647886d148db902f9753bbeb81 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Wed, 26 Dec 2012 15:36:50 -0500 Subject: [PATCH 129/418] Only folders have Create permission --- lib/files/storage/common.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index c891d0c3ad..3cf960d05d 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -38,7 +38,10 @@ abstract class Common implements \OC\Files\Storage\Storage { } } public function isCreatable($path) { - return $this->isUpdatable($path); + if ($this->is_dir($path) && $this->isUpdatable($path)) { + return true; + } + return false; } public function isDeletable($path) { return $this->isUpdatable($path); From 1910057900476c5b7509a456a460d70664c01610 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Wed, 26 Dec 2012 16:20:10 -0500 Subject: [PATCH 130/418] Fix shared statuses inside folders --- apps/files_sharing/lib/share/folder.php | 4 ++-- lib/public/share.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/files_sharing/lib/share/folder.php b/apps/files_sharing/lib/share/folder.php index a1bb843d72..bbe4c130bd 100644 --- a/apps/files_sharing/lib/share/folder.php +++ b/apps/files_sharing/lib/share/folder.php @@ -26,11 +26,11 @@ class OC_Share_Backend_Folder extends OC_Share_Backend_File implements OCP\Share $parents = array($itemSource); while (!empty($parents)) { $parents = "'".implode("','", $parents)."'"; - $query = OC_DB::prepare('SELECT `id`, `name`, `mimetype` FROM `*PREFIX*fscache` WHERE `parent` IN ('.$parents.')'); + $query = OC_DB::prepare('SELECT `fileid`, `name`, `mimetype` FROM `*PREFIX*filecache` WHERE `parent` IN ('.$parents.')'); $result = $query->execute(); $parents = array(); while ($file = $result->fetchRow()) { - $children[] = array('source' => $file['id'], 'file_path' => $file['name']); + $children[] = array('source' => $file['fileid'], 'file_path' => $file['name']); // If a child folder is found look inside it if ($file['mimetype'] == 'httpd/unix-directory') { $parents[] = $file['id']; diff --git a/lib/public/share.php b/lib/public/share.php index 8981de1b50..2775a07f13 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -739,7 +739,8 @@ class Share { if (isset($row['parent'])) { $row['path'] = '/Shared/'.basename($row['path']); } else { - $row['path'] = substr($row['path'], $root); + // Strip 'files' from path + $row['path'] = substr($row['path'], 5); } } if (isset($row['expiration'])) { From 65f1e521079968a4450468917649b241b0fb6b24 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 27 Dec 2012 15:49:48 -0500 Subject: [PATCH 131/418] Change old is_readable to isReadable, fixes downloads --- lib/files.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files.php b/lib/files.php index e12797bd61..15bfbc3fc2 100644 --- a/lib/files.php +++ b/lib/files.php @@ -100,7 +100,7 @@ class OC_Files { $filename = $dir . '/' . $files; } OC_Util::obEnd(); - if($zip or \OC\Files\Filesystem::is_readable($filename)) { + if ($zip or \OC\Files\Filesystem::isReadable($filename)) { if ( preg_match( "/MSIE/", $_SERVER["HTTP_USER_AGENT"] ) ) { header( 'Content-Disposition: attachment; filename="' . rawurlencode( basename($filename) ) . '"' ); } else { From b03562670973fd924a042064644994639a59667d Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 27 Dec 2012 16:53:32 -0500 Subject: [PATCH 132/418] Retrieve storage correctly, filename is not the mountpoint --- lib/files.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files.php b/lib/files.php index 15bfbc3fc2..e1a31c6f03 100644 --- a/lib/files.php +++ b/lib/files.php @@ -117,7 +117,7 @@ class OC_Files { }else{ header('Content-Type: '.\OC\Files\Filesystem::getMimeType($filename)); header("Content-Length: ".\OC\Files\Filesystem::filesize($filename)); - $storage = \OC\Files\Filesystem::getStorage($filename); + list($storage) = \OC\Files\Filesystem::resolvePath($filename); if ($storage instanceof \OC\File\Storage\Local) { self::addSendfileHeader(\OC\Files\Filesystem::getLocalFile($filename)); } From 8bdfb040565eafb33bc27305463eb08374f14e32 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 28 Dec 2012 15:06:12 -0500 Subject: [PATCH 133/418] Fix shared folders --- apps/files_sharing/lib/cache.php | 17 ++++++++++++++--- lib/files/filesystem.php | 1 + lib/util.php | 1 - 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 196e767cf6..d2ac8ccaaa 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -30,6 +30,10 @@ class Shared_Cache extends Cache { private $files = array(); + public function __construct($storage) { + + } + /** * @brief Get the source cache of a shared file or folder * @param string Shared target file path @@ -41,8 +45,12 @@ class Shared_Cache extends Cache { $source['path'] = '/'.$source['uid_owner'].'/'.$source['path']; \OC\Files\Filesystem::initMountPoints($source['uid_owner']); list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source['path']); - $this->files[$target] = $internalPath; - return $storage->getCache(); + if ($storage) { + $this->files[$target] = $internalPath; + $cache = $storage->getCache(); + $this->numericId = $cache->getNumericStorageId(); + return $cache; + } } return false; } @@ -83,8 +91,11 @@ class Shared_Cache extends Cache { if ($folder == '') { return \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS); } else { - return $this->getSourceCache($folder)->getFolderContents('/'.$this->files[$folder]); + if ($cache = $this->getSourceCache($folder)) { + return $cache->getFolderContents($this->files[$folder]); + } } + return false; } /** diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index c5b56ba9f0..8183b8ff99 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -276,6 +276,7 @@ class Filesystem { } // Load personal mount points $root = \OC_User::getHome($user); + self::mount('\OC\Files\Storage\Local', array('datadir' => $root), $user); if (is_file($root.'/mount.php')) { $mountConfig = include $root.'/mount.php'; if (isset($mountConfig['user'][$user])) { diff --git a/lib/util.php b/lib/util.php index 4ebc2564d1..c5a495234d 100755 --- a/lib/util.php +++ b/lib/util.php @@ -51,7 +51,6 @@ class OC_Util { mkdir( $userdirectory, 0755, true ); } //jail the user into his "home" directory - \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $user_root), $user); \OC\Files\Filesystem::init($user_dir); $quotaProxy=new OC_FileProxy_Quota(); From fb053f8e73056108acfd1574973d7b18581fc833 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 29 Dec 2012 11:09:57 -0500 Subject: [PATCH 134/418] Fix retrieving of shared source paths --- apps/files_sharing/lib/sharedstorage.php | 41 ++++++++++++++++-------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 960aa64099..cb9b36482f 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -38,19 +38,34 @@ class Shared extends \OC\Files\Storage\Common { return 'shared::' . $this->sharedFolder; } + /** + * @brief Get the source file path, permissions, and owner for a shared file + * @param string Shared target file path + * @return Returns array with the keys path, permissions, and owner or false if not found + */ + private function getFile($target) { + if (!isset($this->files[$target])) { + $source = \OC_Share_Backend_File::getSource($target); + if ($source) { + $source['path'] = '/'.$source['uid_owner'].'/'.$source['path']; + } + $this->files[$target] = $source; + } + return $this->files[$target]; + } + /** * @brief Get the source file path for a shared file * @param string Shared target file path * @return string source file path or false if not found */ private function getSourcePath($target) { - if (!isset($this->files[$target])) { - $source = \OC_Share_Backend_File::getSource($target); - $source['path'] = '/'.$source['uid_owner'].'/'.$source['path']; - $this->files[$target] = $source; + $source = $this->getFile($target); + if ($source) { \OC\Files\Filesystem::initMountPoints($source['uid_owner']); + return $source['path']; } - return $this->files[$target]['path']; + return false; } /** @@ -59,11 +74,11 @@ class Shared extends \OC\Files\Storage\Common { * @return int CRUDS permissions granted or false if not found */ public function getPermissions($target) { - if (!isset($this->files[$target])) { - $source = \OC_Share_Backend_File::getSource($target); - $this->files[$target] = $source; + $source = $this->getFile($target); + if ($source) { + return $source['permissions']; } - return $this->files[$target]['permissions']; + return false; } public function mkdir($path) { @@ -389,11 +404,11 @@ class Shared extends \OC\Files\Storage\Common { } public function getOwner($path) { - if (!isset($this->files[$path])) { - $source = \OC_Share_Backend_File::getSource($path); - $this->files[$path] = $source; + $source = $this->getFile($path); + if ($source) { + return $source['uid_owner']; } - return $this->files[$path]['uid_owner']; + return false; } public function getETag($path) { From cfc3526b25f1b96ab7a989f1c89f5b3911bc18fc Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 29 Dec 2012 11:45:13 -0500 Subject: [PATCH 135/418] Fix data for shared root folder --- apps/files_sharing/lib/cache.php | 4 +++- apps/files_sharing/lib/permissions.php | 3 +++ apps/files_sharing/lib/share/file.php | 26 +++++++++++++++++++------- lib/public/share.php | 2 +- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index d2ac8ccaaa..a22e7af7f5 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -62,7 +62,9 @@ class Shared_Cache extends Cache { * @return array */ public function get($file) { - if (is_string($file)) { + if ($file == '') { + return \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT); + } else if (is_string($file)) { if ($cache = $this->getSourceCache($file)) { return $cache->get($this->files[$file]); } diff --git a/apps/files_sharing/lib/permissions.php b/apps/files_sharing/lib/permissions.php index 51ae4cf102..6eaed34b33 100644 --- a/apps/files_sharing/lib/permissions.php +++ b/apps/files_sharing/lib/permissions.php @@ -30,6 +30,9 @@ class Shared_Permissions { * @return int (-1 if file no permissions set) */ static public function get($fileId, $user) { + if ($fileId == -1) { + return \OCP\PERMISSION_READ; + } $source = \OCP\Share::getItemSharedWithBySource('file', $fileId, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); if ($source) { return $source['permissions']; diff --git a/apps/files_sharing/lib/share/file.php b/apps/files_sharing/lib/share/file.php index 52220dc5b2..5e98c455d3 100644 --- a/apps/files_sharing/lib/share/file.php +++ b/apps/files_sharing/lib/share/file.php @@ -23,8 +23,9 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { const FORMAT_SHARED_STORAGE = 0; const FORMAT_GET_FOLDER_CONTENTS = 1; - const FORMAT_GET_ALL = 2; + const FORMAT_FILE_APP_ROOT = 2; const FORMAT_OPENDIR = 3; + const FORMAT_GET_ALL = 4; private $path; @@ -82,24 +83,35 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { $file['parent'] = $item['file_parent']; $file['name'] = basename($item['file_target']); $file['mimetype'] = $item['mimetype']; + $file['mimepart'] = $item['mimepart']; $file['size'] = $item['size']; $file['mtime'] = $item['mtime']; $file['encrypted'] = $item['encrypted']; $files[] = $file; } return $files; + } else if ($format == self::FORMAT_FILE_APP_ROOT) { + $mtime = 0; + $size = 0; + foreach ($items as $item) { + if ($item['mtime'] > $mtime) { + $mtime = $item['mtime']; + } + $size += (int)$item['size']; + } + return array('fileid' => -1, 'name' => 'Shared', 'mtime' => $mtime, 'mimetype' => 'httpd/unix-directory', 'size' => $size); + } else if ($format == self::FORMAT_OPENDIR) { + $files = array(); + foreach ($items as $item) { + $files[] = basename($item['file_target']); + } + return $files; } else if ($format == self::FORMAT_GET_ALL) { $ids = array(); foreach ($items as $item) { $ids[] = $item['file_source']; } return $ids; - } else if ($format == self::FORMAT_OPENDIR) { - $files = array(); - foreach ($items as $item) { - $files[] = basename($item['file_target']); - } - return $files; } return array(); } diff --git a/lib/public/share.php b/lib/public/share.php index 2775a07f13..e438386ca3 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -681,7 +681,7 @@ class Share { } } else { if ($fileDependent) { - if (($itemType == 'file' || $itemType == 'folder') && $format == \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS) { + if (($itemType == 'file' || $itemType == 'folder') && $format == \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS || $format == \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT) { $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `uid_owner`, `share_type`, `share_with`, `file_source`, `path`, `file_target`, `permissions`, `expiration`, `*PREFIX*filecache`.`parent` as `file_parent`, `name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`'; } else { $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`, `*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`, `file_source`, `path`, `file_target`, `permissions`, `stime`, `expiration`, `token`'; From d275725e233092f21247a62bde3e46a352d5a1b5 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 29 Dec 2012 13:43:44 -0500 Subject: [PATCH 136/418] No longer need to create folders locally for external storage mount points --- apps/files_external/lib/config.php | 41 ------------------------------ 1 file changed, 41 deletions(-) diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 0e0bc56ad0..323e4060a4 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -178,22 +178,6 @@ class OC_Mount_Config { return $personal; } - /** - * Add directory for mount point to the filesystem - * @param OC_Fileview instance $view - * @param string path to mount point - */ - private static function addMountPointDirectory($view, $path) { - $dir = ''; - foreach ( explode('/', $path) as $pathPart) { - $dir = $dir.'/'.$pathPart; - if ( !$view->file_exists($dir)) { - $view->mkdir($dir); - } - } - } - - /** * Add a mount point to the filesystem * @param string Mount point @@ -216,33 +200,8 @@ class OC_Mount_Config { if ($applicable != OCP\User::getUser() || $class == '\OC\Files\Storage\Local') { return false; } - $view = new \OC\Files\View('/'.OCP\User::getUser().'/files'); - self::addMountPointDirectory($view, ltrim($mountPoint, '/')); $mountPoint = '/'.$applicable.'/files/'.ltrim($mountPoint, '/'); } else { - $view = new \OC\Files\View('/'); - switch ($mountType) { - case 'user': - if ($applicable == "all") { - $users = OCP\User::getUsers(); - foreach ( $users as $user ) { - $path = $user.'/files/'.ltrim($mountPoint, '/'); - self::addMountPointDirectory($view, $path); - } - } else { - $path = $applicable.'/files/'.ltrim($mountPoint, '/'); - self::addMountPointDirectory($view, $path); - } - break; - case 'group' : - $groupMembers = OC_Group::usersInGroups(array($applicable)); - foreach ( $groupMembers as $user ) { - $path = $user.'/files/'.ltrim($mountPoint, '/'); - self::addMountPointDirectory($view, $path); - } - break; - } - $mountPoint = '/$user/files/'.ltrim($mountPoint, '/'); } $mount = array($applicable => array($mountPoint => array('class' => $class, 'options' => $classOptions))); From 2c23e143d33e231e62dccb450bf1f1b0f3938ccd Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 30 Dec 2012 16:32:55 -0500 Subject: [PATCH 137/418] Store etags in the file cache --- db_structure.xml | 8 ++++++++ lib/files/cache/cache.php | 10 +++++----- lib/files/cache/scanner.php | 1 + lib/util.php | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index aa0916264c..7c67ca78f4 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -189,6 +189,14 @@ 4 + + etag + text + + true + 250 + + storage_path_hash true diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 3ebae9baa5..d105f865eb 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -76,7 +76,7 @@ class Cache { $params = array($file); } $query = \OC_DB::prepare( - 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` FROM `*PREFIX*filecache` ' . $where); $result = $query->execute($params); $data = $result->fetchRow(); @@ -107,7 +107,7 @@ class Cache { $fileId = $this->getId($folder); if ($fileId > -1) { $query = \OC_DB::prepare( - 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` FROM `*PREFIX*filecache` WHERE parent = ? ORDER BY `name` ASC'); $result = $query->execute(array($fileId)); return $result->fetchAll(); @@ -180,7 +180,7 @@ class Cache { * @return array */ static function buildParts(array $data) { - $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'encrypted'); + $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'encrypted', 'etag'); $params = array(); $queryParts = array(); @@ -333,7 +333,7 @@ class Cache { */ public function search($pattern) { $query = \OC_DB::prepare(' - SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` + SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` FROM `*PREFIX*filecache` WHERE `name` LIKE ? AND `storage` = ?' ); $result = $query->execute(array($pattern, $this->numericId)); @@ -357,7 +357,7 @@ class Cache { $where = '`mimepart` = ?'; } $query = \OC_DB::prepare(' - SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` + SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?' ); $result = $query->execute(array($mimetype, $this->numericId)); diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 4c0ec9617f..e7bfb1898e 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -58,6 +58,7 @@ class Scanner { $data['size'] = $this->storage->filesize($path); $data['permissions'] = $this->storage->getPermissions($path); } + $data['etag'] = $this->storage->getETag($path); return $data; } diff --git a/lib/util.php b/lib/util.php index c5a495234d..df26a825d1 100755 --- a/lib/util.php +++ b/lib/util.php @@ -74,7 +74,7 @@ class OC_Util { */ public static function getVersion() { // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.90.0. This is not visible to the user - return array(4,91,04); + return array(4,91,05); } /** From a5cb7363a5031d25d3e4cbd46817930c19893143 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 30 Dec 2012 19:18:05 -0500 Subject: [PATCH 138/418] Use etags from file cache in SabreDAV connector --- lib/connector/sabre/directory.php | 2 +- lib/connector/sabre/node.php | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/connector/sabre/directory.php b/lib/connector/sabre/directory.php index 8d4dd92a3d..a720157936 100644 --- a/lib/connector/sabre/directory.php +++ b/lib/connector/sabre/directory.php @@ -121,8 +121,8 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa $paths = array(); foreach($folder_content as $info) { $paths[] = $this->path.'/'.$info['name']; + $properties[$this->path.'/'.$info['name']][self::GETETAG_PROPERTYNAME] = $info['etag']; } - $properties = array_fill_keys($paths, array()); if(count($paths)>0) { // // the number of arguments within IN conditions are limited in most databases diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php index ad08bd434f..d4023dfad7 100644 --- a/lib/connector/sabre/node.php +++ b/lib/connector/sabre/node.php @@ -190,6 +190,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr while( $row = $result->fetchRow()) { $this->property_cache[$row['propertyname']] = $row['propertyvalue']; } + $this->property_cache[self::GETETAG_PROPERTYNAME] = $this->getETagPropertyForPath($this->path); } // if the array was empty, we need to return everything @@ -210,14 +211,11 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr * @return string|null Returns null if the ETag can not effectively be determined */ static public function getETagPropertyForPath($path) { - $tag = \OC\Files\Filesystem::getETag($path); - if (empty($tag)) { - return null; + $data = \OC\Files\Filesystem::getFileInfo($path); + if (isset($data['etag'])) { + return $data['etag']; } - $etag = '"'.$tag.'"'; - $query = OC_DB::prepare( 'INSERT INTO `*PREFIX*properties` (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)' ); - $query->execute( array( OC_User::getUser(), $path, self::GETETAG_PROPERTYNAME, $etag )); - return $etag; + return null; } /** From 83064aca51db7d8382282743ade2ab9da2a6e1b0 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 30 Dec 2012 19:23:31 -0500 Subject: [PATCH 139/418] Remove old etag code --- lib/connector/sabre/node.php | 24 ------------------------ lib/files/filesystem.php | 21 --------------------- lib/files/view.php | 2 -- lib/filesystem.php | 7 ------- 4 files changed, 54 deletions(-) diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php index d4023dfad7..dd8ae152ff 100644 --- a/lib/connector/sabre/node.php +++ b/lib/connector/sabre/node.php @@ -218,28 +218,4 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr return null; } - /** - * @brief Remove the ETag from the cache. - * @param string $path Path of the file - */ - static public function removeETagPropertyForPath($path) { - // remove tags from this and parent paths - $paths = array(); - while ($path != '/' && $path != '.' && $path != '' && $path != '\\') { - $paths[] = $path; - $path = dirname($path); - } - if (empty($paths)) { - return; - } - $paths[] = $path; - $path_placeholders = join(',', array_fill(0, count($paths), '?')); - $query = OC_DB::prepare( 'DELETE FROM `*PREFIX*properties`' - .' WHERE `userid` = ?' - .' AND `propertyname` = ?' - .' AND `propertypath` IN ('.$path_placeholders.')' - ); - $vals = array( OC_User::getUser(), self::GETETAG_PROPERTYNAME ); - $query->execute(array_merge( $vals, $paths )); - } } diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 8183b8ff99..d62b5186cb 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -582,23 +582,6 @@ class Filesystem { return self::$defaultInstance->hasUpdated($path, $time); } - static public function removeETagHook($params, $root = false) { - if (isset($params['path'])) { - $path = $params['path']; - } else { - $path = $params['oldpath']; - } - - if ($root) { // reduce path to the required part of it (no 'username/files') - $fakeRootView = new View($root); - $count = 1; - $path = str_replace(\OC_App::getStorage("files")->getAbsolutePath(''), "", $fakeRootView->getAbsolutePath($path), $count); - } - - $path = self::normalizePath($path); - \OC_Connector_Sabre_Node::removeETagPropertyForPath($path); - } - /** * normalize a path * @@ -682,10 +665,6 @@ class Filesystem { } } -\OC_Hook::connect('OC_Filesystem', 'post_write', 'OC_Filesystem', 'removeETagHook'); -\OC_Hook::connect('OC_Filesystem', 'post_delete', 'OC_Filesystem', 'removeETagHook'); -\OC_Hook::connect('OC_Filesystem', 'post_rename', 'OC_Filesystem', 'removeETagHook'); - \OC_Hook::connect('OC_Filesystem', 'post_write', '\OC\Files\Cache\Updater', 'writeHook'); \OC_Hook::connect('OC_Filesystem', 'post_delete', '\OC\Files\Cache\Updater', 'deleteHook'); \OC_Hook::connect('OC_Filesystem', 'post_rename', '\OC\Files\Cache\Updater', 'renameHook'); diff --git a/lib/files/view.php b/lib/files/view.php index 592c484a21..77146895e6 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -462,8 +462,6 @@ class View { Filesystem::signal_post_write, array(Filesystem::signal_param_path => $path2) ); - } else { // no real copy, file comes from somewhere else, e.g. version rollback -> just update the file cache and the webdav properties without all the other post_write actions - Filesystem::removeETagHook(array("path" => $path2), $this->fakeRoot); } return $result; } else { diff --git a/lib/filesystem.php b/lib/filesystem.php index 20b5ab2790..57cca90230 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -398,13 +398,6 @@ class OC_Filesystem { return \OC\Files\Filesystem::hasUpdated($path, $time); } - /** - * @deprecated OC_Filesystem is replaced by \OC\Files\Filesystem - */ - static public function removeETagHook($params, $root = false) { - \OC\Files\Filesystem::removeETagHook($params, $root); - } - /** * normalize a path * From 77f12c526bf0010219e363844b588ffae27f1251 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 30 Dec 2012 19:54:51 -0500 Subject: [PATCH 140/418] Update etags in parent folders --- lib/files/cache/updater.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index fb9783023e..d57e4a1abe 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -34,6 +34,7 @@ class Updater { $scanner = new Scanner($storage); $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); $cache->correctFolderSize($internalPath); + self::eTagUpdate($path); } static public function deleteUpdate($path) { @@ -45,6 +46,29 @@ class Updater { $cache = new Cache($storage); $cache->remove($internalPath); $cache->correctFolderSize($internalPath); + self::eTagUpdate($path); + } + + static public function eTagUpdate($path) { + if ($path !== '') { + $parent = dirname($path); + if ($parent === '.') { + $parent = ''; + } + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($parent); + if ($storage) { + $cache = $storage->getCache(); + $id = $cache->getId($internalPath); + if ($id !== -1) { + $cache->update($id, array('etag' => $storage->getETag($internalPath))); + self::updateFolderETags($parent); + } + } + } } /** From 3ee3323b8766992a0b60bb76b909e49fc1ea76d2 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 30 Dec 2012 20:27:38 -0500 Subject: [PATCH 141/418] Fix retrieving of cache and scanner in Updater class --- lib/files/cache/updater.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index fb9783023e..c8c96a97ee 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -30,10 +30,12 @@ class Updater { * @var string $internalPath */ list($storage, $internalPath) = self::resolvePath($path); - $cache = new Cache($storage); - $scanner = new Scanner($storage); - $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); - $cache->correctFolderSize($internalPath); + if ($storage) { + $cache = $storage->getCache(); + $scanner = $storage->getScanner(); + $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); + $cache->correctFolderSize($internalPath); + } } static public function deleteUpdate($path) { @@ -42,9 +44,11 @@ class Updater { * @var string $internalPath */ list($storage, $internalPath) = self::resolvePath($path); - $cache = new Cache($storage); - $cache->remove($internalPath); - $cache->correctFolderSize($internalPath); + if ($storage) { + $cache = $storage->getCache(); + $cache->remove($internalPath); + $cache->correctFolderSize($internalPath); + } } /** From 96e08a1d962b42782d03721009970cbab352eec2 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 30 Dec 2012 21:23:17 -0500 Subject: [PATCH 142/418] Fix function name --- lib/files/cache/updater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index d57e4a1abe..7fc9bd382f 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -65,7 +65,7 @@ class Updater { $id = $cache->getId($internalPath); if ($id !== -1) { $cache->update($id, array('etag' => $storage->getETag($internalPath))); - self::updateFolderETags($parent); + self::eTagUpdate($parent); } } } From 29b82ccdf32f8a13723e7f49be0c3cbf7e64a404 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sun, 30 Dec 2012 21:23:54 -0500 Subject: [PATCH 143/418] Change length of etag field to 40 --- db_structure.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db_structure.xml b/db_structure.xml index 7c67ca78f4..527b53040d 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -194,7 +194,7 @@ text true - 250 + 40 From d0a50fae8317e4b4871027ee4b2940ab5443961f Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Mon, 31 Dec 2012 18:16:44 -0500 Subject: [PATCH 144/418] Fix eTagUpdate and add tests --- lib/files/cache/updater.php | 2 +- tests/lib/files/cache/updater.php | 28 ++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index 2604159009..cfc1ec731e 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -54,7 +54,7 @@ class Updater { } static public function eTagUpdate($path) { - if ($path !== '') { + if ($path !== '' && $path !== '/') { $parent = dirname($path); if ($parent === '.') { $parent = ''; diff --git a/tests/lib/files/cache/updater.php b/tests/lib/files/cache/updater.php index d19966c191..cad3d9d46f 100644 --- a/tests/lib/files/cache/updater.php +++ b/tests/lib/files/cache/updater.php @@ -70,14 +70,18 @@ class Updater extends \PHPUnit_Framework_TestCase { public function testWrite() { $textSize = strlen("dummy file data\n"); $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); - $cachedData = $this->cache->get(''); - $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + $rootCachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $rootCachedData['size']); + $fooCachedData = $this->cache->get('foo.txt'); Filesystem::file_put_contents('foo.txt', 'asd'); $cachedData = $this->cache->get('foo.txt'); $this->assertEquals(3, $cachedData['size']); + $this->assertNotEquals($fooCachedData['etag'], $cachedData['etag']); $cachedData = $this->cache->get(''); $this->assertEquals(2 * $textSize + $imageSize + 3, $cachedData['size']); + $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); + $rootCachedData = $cachedData; $this->assertFalse($this->cache->inCache('bar.txt')); Filesystem::file_put_contents('bar.txt', 'asd'); @@ -86,38 +90,50 @@ class Updater extends \PHPUnit_Framework_TestCase { $this->assertEquals(3, $cachedData['size']); $cachedData = $this->cache->get(''); $this->assertEquals(2 * $textSize + $imageSize + 2 * 3, $cachedData['size']); + $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); } public function testDelete() { $textSize = strlen("dummy file data\n"); $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); - $cachedData = $this->cache->get(''); - $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + $rootCachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $rootCachedData['size']); $this->assertTrue($this->cache->inCache('foo.txt')); Filesystem::unlink('foo.txt', 'asd'); $this->assertFalse($this->cache->inCache('foo.txt')); $cachedData = $this->cache->get(''); $this->assertEquals(2 * $textSize + $imageSize, $cachedData['size']); + $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); + $rootCachedData = $cachedData; Filesystem::mkdir('bar_folder'); $this->assertTrue($this->cache->inCache('bar_folder')); + $cachedData = $this->cache->get(''); + $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); + $rootCachedData = $cachedData; Filesystem::rmdir('bar_folder'); $this->assertFalse($this->cache->inCache('bar_folder')); + $cachedData = $this->cache->get(''); + $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); } public function testRename() { $textSize = strlen("dummy file data\n"); $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); - $cachedData = $this->cache->get(''); - $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + $rootCachedData = $this->cache->get(''); + $this->assertEquals(3 * $textSize + $imageSize, $rootCachedData['size']); $this->assertTrue($this->cache->inCache('foo.txt')); + $fooCachedData = $this->cache->get('foo.txt'); $this->assertFalse($this->cache->inCache('bar.txt')); Filesystem::rename('foo.txt', 'bar.txt'); $this->assertFalse($this->cache->inCache('foo.txt')); $this->assertTrue($this->cache->inCache('bar.txt')); + $cachedData = $this->cache->get('foo.txt'); + $this->assertNotEquals($fooCachedData['etag'], $cachedData['etag']); $cachedData = $this->cache->get(''); $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); + $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); } } From f2ca7023e1ece60dc3cebc5cd770c7373f53c93a Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Tue, 1 Jan 2013 11:19:33 -0500 Subject: [PATCH 145/418] Fix Shared root problems with Watcher and Quota proxy --- apps/files_sharing/lib/cache.php | 3 +++ apps/files_sharing/lib/sharedstorage.php | 13 +++++++++++++ lib/fileproxy/quota.php | 4 +++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index a22e7af7f5..60f29ce5ee 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -169,6 +169,9 @@ class Shared_Cache extends Cache { * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE */ public function getStatus($file) { + if ($file == '') { + return self::COMPLETE; + } if ($cache = $this->getSourceCache($file)) { return $cache->getStatus($this->files[$file]); } diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index cb9b36482f..3a1d7ef101 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -364,6 +364,9 @@ class Shared extends \OC\Files\Storage\Common { } public function free_space($path) { + if ($path == '') { + return -1; + } $source = $this->getSourcePath($path); if ($source) { list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); @@ -391,6 +394,13 @@ class Shared extends \OC\Files\Storage\Common { \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared', array('sharedFolder' => '/Shared'), $user_dir.'/Shared/'); } + public function hasUpdated($path, $time) { + if ($path == '') { + return false; + } + return $this->filemtime($path) > $time; + } + public function getCache() { return new \OC\Files\Cache\Shared_Cache($this); } @@ -404,6 +414,9 @@ class Shared extends \OC\Files\Storage\Common { } public function getOwner($path) { + if ($path == '') { + return false; + } $source = $this->getFile($path); if ($source) { return $source['uid_owner']; diff --git a/lib/fileproxy/quota.php b/lib/fileproxy/quota.php index 80270728ab..c333efa6cd 100644 --- a/lib/fileproxy/quota.php +++ b/lib/fileproxy/quota.php @@ -63,6 +63,9 @@ class OC_FileProxy_Quota extends OC_FileProxy{ */ list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); $owner=$storage->getOwner($internalPath); + if (!$owner) { + return -1; + } $totalSpace=$this->getQuota($owner); if($totalSpace==-1) { @@ -73,7 +76,6 @@ class OC_FileProxy_Quota extends OC_FileProxy{ $rootInfo=$view->getFileInfo('/'); $usedSpace=isset($rootInfo['size'])?$rootInfo['size']:0; - $usedSpace=isset($sharedInfo['size'])?$usedSpace-$sharedInfo['size']:$usedSpace; return $totalSpace-$usedSpace; } From b41189de4420ea8a48adaaf7bb59f9227124b70a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 1 Jan 2013 18:04:29 +0100 Subject: [PATCH 146/418] Cache: allow storage backends to overwrite Watcher --- lib/files/storage/common.php | 7 +++++++ lib/files/storage/storage.php | 5 +++++ lib/files/view.php | 4 ++-- tests/lib/files/cache/watcher.php | 14 +++++++------- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index 3cf960d05d..ab167f2864 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -249,6 +249,13 @@ abstract class Common implements \OC\Files\Storage\Storage { return new \OC\Files\Cache\Permissions($this); } + /** + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher(){ + return new \OC\Files\Cache\Watcher($this); + } + /** * get the owner of a path * @param string $path The path to get the owner diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index 73dcb8fe36..b603381dc9 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -69,6 +69,11 @@ interface Storage{ */ public function getPermissionsCache(); + /** + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher(); + /** * get the ETag for a file or folder * diff --git a/lib/files/view.php b/lib/files/view.php index 592c484a21..d3f93d39f5 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -685,7 +685,7 @@ class View { $scanner = $storage->getScanner(); $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } else { - $watcher = new \OC\Files\Cache\Watcher($storage); + $watcher = $storage->getWatcher(); $watcher->checkUpdate($internalPath); } @@ -733,7 +733,7 @@ class View { $scanner = $storage->getScanner(); $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } else { - $watcher = new \OC\Files\Cache\Watcher($storage); + $watcher = $storage->getWatcher(); $watcher->checkUpdate($internalPath); } diff --git a/tests/lib/files/cache/watcher.php b/tests/lib/files/cache/watcher.php index 07c8ac3640..e8a1689cab 100644 --- a/tests/lib/files/cache/watcher.php +++ b/tests/lib/files/cache/watcher.php @@ -32,7 +32,7 @@ class Watcher extends \PHPUnit_Framework_TestCase { function testWatcher() { $storage = $this->getTestStorage(); $cache = $storage->getCache(); - $updater = new \OC\Files\Cache\Watcher($storage); + $updater = $storage->getWatcher(); //set the mtime to the past so it can detect an mtime change $cache->put('', array('mtime' => 10)); @@ -66,16 +66,16 @@ class Watcher extends \PHPUnit_Framework_TestCase { public function testFileToFolder() { $storage = $this->getTestStorage(); $cache = $storage->getCache(); - $updater = new \OC\Files\Cache\Watcher($storage); + $updater = $storage->getWatcher(); //set the mtime to the past so it can detect an mtime change $cache->put('', array('mtime' => 10)); $storage->unlink('foo.txt'); - $storage->rename('folder','foo.txt'); + $storage->rename('folder', 'foo.txt'); $updater->checkUpdate(''); - $entry= $cache->get('foo.txt'); + $entry = $cache->get('foo.txt'); $this->assertEquals(-1, $entry['size']); $this->assertEquals('httpd/unix-directory', $entry['mimetype']); $this->assertFalse($cache->inCache('folder')); @@ -83,16 +83,16 @@ class Watcher extends \PHPUnit_Framework_TestCase { $storage = $this->getTestStorage(); $cache = $storage->getCache(); - $updater = new \OC\Files\Cache\Watcher($storage); + $updater = $storage->getWatcher(); //set the mtime to the past so it can detect an mtime change $cache->put('foo.txt', array('mtime' => 10)); $storage->unlink('foo.txt'); - $storage->rename('folder','foo.txt'); + $storage->rename('folder', 'foo.txt'); $updater->checkUpdate('foo.txt'); - $entry= $cache->get('foo.txt'); + $entry = $cache->get('foo.txt'); $this->assertEquals('httpd/unix-directory', $entry['mimetype']); $this->assertTrue($cache->inCache('foo.txt/bar.txt')); } From 4b65dd608a70117141c52749f4a06e6c5466ff7b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 1 Jan 2013 18:07:10 +0100 Subject: [PATCH 147/418] Share: small phpdoc fixes --- apps/files_sharing/lib/cache.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 60f29ce5ee..679e42d94d 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -36,8 +36,8 @@ class Shared_Cache extends Cache { /** * @brief Get the source cache of a shared file or folder - * @param string Shared target file path - * @return \OC\Files\Storage\Cache + * @param string $target Shared target file path + * @return \OC\Files\Cache\Cache */ private function getSourceCache($target) { $source = \OC_Share_Backend_File::getSource($target); @@ -230,7 +230,7 @@ class Shared_Cache extends Cache { * @return int[] */ public function getAll() { - return OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_ALL); + return \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_ALL); } -} \ No newline at end of file +} From 0ac78a64110b628832d94061c78fb8f1b89405cb Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 1 Jan 2013 18:10:38 +0100 Subject: [PATCH 148/418] Share: fix cache put function --- apps/files_sharing/lib/cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 679e42d94d..07c305bb95 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -110,7 +110,7 @@ class Shared_Cache extends Cache { */ public function put($file, array $data) { if ($cache = $this->getSourceCache($file)) { - return $cache->put($this->files[$file]); + return $cache->put($this->files[$file], $data); } return false; } From f4e4a06826459befc4a7df429fe02081a14348f3 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Tue, 1 Jan 2013 11:26:04 -0500 Subject: [PATCH 149/418] Forgot to select storage from filecache --- lib/public/share.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/share.php b/lib/public/share.php index e438386ca3..c74960b94c 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -682,7 +682,7 @@ class Share { } else { if ($fileDependent) { if (($itemType == 'file' || $itemType == 'folder') && $format == \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS || $format == \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT) { - $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `uid_owner`, `share_type`, `share_with`, `file_source`, `path`, `file_target`, `permissions`, `expiration`, `*PREFIX*filecache`.`parent` as `file_parent`, `name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`'; + $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `uid_owner`, `share_type`, `share_with`, `file_source`, `path`, `file_target`, `permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, `name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`'; } else { $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`, `*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`, `file_source`, `path`, `file_target`, `permissions`, `stime`, `expiration`, `token`'; } From 3b67613afc6d0b74c0e49e567f5e22ec020172d3 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Tue, 1 Jan 2013 12:43:38 -0500 Subject: [PATCH 150/418] Remove Shared_Scanner and add Shared_Watcher instead --- apps/files_sharing/appinfo/app.php | 2 +- apps/files_sharing/lib/scanner.php | 69 ------------------------ apps/files_sharing/lib/sharedstorage.php | 8 +-- 3 files changed, 5 insertions(+), 74 deletions(-) delete mode 100644 apps/files_sharing/lib/scanner.php diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 189fd20cae..d3e05cc62d 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -5,7 +5,7 @@ OC::$CLASSPATH['OC_Share_Backend_Folder'] = 'apps/files_sharing/lib/share/folder OC::$CLASSPATH['OC\Files\Storage\Shared'] = "apps/files_sharing/lib/sharedstorage.php"; OC::$CLASSPATH['OC\Files\Cache\Shared_Cache'] = 'apps/files_sharing/lib/cache.php'; OC::$CLASSPATH['OC\Files\Cache\Shared_Permissions'] = 'apps/files_sharing/lib/permissions.php'; -OC::$CLASSPATH['OC\Files\Cache\Shared_Scanner'] = 'apps/files_sharing/lib/scanner.php'; +OC::$CLASSPATH['OC\Files\Cache\Shared_Watcher'] = 'apps/files_sharing/lib/watcher.php'; OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); diff --git a/apps/files_sharing/lib/scanner.php b/apps/files_sharing/lib/scanner.php deleted file mode 100644 index d13d2f9cbc..0000000000 --- a/apps/files_sharing/lib/scanner.php +++ /dev/null @@ -1,69 +0,0 @@ -. -*/ - -namespace OC\Files\Cache; - -class Shared_Scanner extends Scanner { - - public function __construct(\OC\Files\Storage\Storage $storage) { - - } - - /** - * get all the metadata of a file or folder - * * - * - * @param string $path - * @return array with metadata of the file - */ - public function getData($path) { - // Not a valid action for Shared Scanner - } - - /** - * scan a single file and store it in the cache - * - * @param string $file - * @return array with metadata of the scanned file - */ - public function scanFile($file) { - // Not a valid action for Shared Scanner - } - - /** - * scan all the files in a folder and store them in the cache - * - * @param string $path - * @param SCAN_RECURSIVE/SCAN_SHALLOW $recursive - * @return int the size of the scanned folder or -1 if the size is unknown at this stage - */ - public function scan($path, $recursive = self::SCAN_RECURSIVE) { - // Not a valid action for Shared Scanner - } - - /** - * walk over any folders that are not fully scanned yet and scan them - */ - public function backgroundScan() { - // Not a valid action for Shared Scanner - } - -} \ No newline at end of file diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 3a1d7ef101..8504a2d894 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -405,14 +405,14 @@ class Shared extends \OC\Files\Storage\Common { return new \OC\Files\Cache\Shared_Cache($this); } - public function getScanner(){ - return new \OC\Files\Cache\Shared_Scanner($this); - } - public function getPermissionsCache() { return new \OC\Files\Cache\Shared_Permissions($this); } + public function getWatcher() { + return new \OC\Files\Cache\Shared_Watcher($this); + } + public function getOwner($path) { if ($path == '') { return false; From 268c7acfc47eb44ab154eb90cfbb4484bd64b296 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Tue, 1 Jan 2013 13:16:42 -0500 Subject: [PATCH 151/418] Actually add Shared_Watcher --- apps/files_sharing/lib/watcher.php | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 apps/files_sharing/lib/watcher.php diff --git a/apps/files_sharing/lib/watcher.php b/apps/files_sharing/lib/watcher.php new file mode 100644 index 0000000000..e67d1ee908 --- /dev/null +++ b/apps/files_sharing/lib/watcher.php @@ -0,0 +1,51 @@ +. +*/ + +namespace OC\Files\Cache; + +/** + * check the storage backends for updates and change the cache accordingly + */ +class Shared_Watcher extends Watcher { + + /** + * check $path for updates + * + * @param string $path + */ + public function checkUpdate($path) { + if ($path != '') { + parent::checkUpdate($path); + } + } + + /** + * remove deleted files in $path from the cache + * + * @param string $path + */ + public function cleanFolder($path) { + if ($path != '') { + parent::cleanFolder($path); + } + } + +} \ No newline at end of file From 04f83e3b539c6a20ce819bbe4f66c1996e7169f7 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 1 Jan 2013 20:11:39 +0100 Subject: [PATCH 152/418] Cache: optional path argument for getCache/Scanner/etc --- lib/files/storage/common.php | 11 ++++------- lib/files/storage/storage.php | 12 ++++++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index ab167f2864..e859d447f3 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -237,22 +237,19 @@ abstract class Common implements \OC\Files\Storage\Storage { return $this->filemtime($path)>$time; } - public function getCache(){ + public function getCache($path=''){ return new \OC\Files\Cache\Cache($this); } - public function getScanner(){ + public function getScanner($path=''){ return new \OC\Files\Cache\Scanner($this); } - public function getPermissionsCache(){ + public function getPermissionsCache($path=''){ return new \OC\Files\Cache\Permissions($this); } - /** - * @return \OC\Files\Cache\Watcher - */ - public function getWatcher(){ + public function getWatcher($path=''){ return new \OC\Files\Cache\Watcher($this); } diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php index b603381dc9..2cc835236b 100644 --- a/lib/files/storage/storage.php +++ b/lib/files/storage/storage.php @@ -54,25 +54,29 @@ interface Storage{ public function hasUpdated($path,$time); /** + * @param string $path * @return \OC\Files\Cache\Cache */ - public function getCache(); + public function getCache($path=''); /** + * @param string $path * @return \OC\Files\Cache\Scanner */ - public function getScanner(); + public function getScanner($path=''); public function getOwner($path); /** + * @param string $path * @return \OC\Files\Cache\Permissions */ - public function getPermissionsCache(); + public function getPermissionsCache($path=''); /** + * @param string $path * @return \OC\Files\Cache\Watcher */ - public function getWatcher(); + public function getWatcher($path=''); /** * get the ETag for a file or folder From a164fd160fb0d3da548dfe01ec27df67d76b0b59 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 1 Jan 2013 20:19:53 +0100 Subject: [PATCH 153/418] Cache: provide path hints to getCache/etc where available --- lib/files/cache/updater.php | 6 +++--- lib/files/view.php | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index c8c96a97ee..c14adba3e5 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -31,8 +31,8 @@ class Updater { */ list($storage, $internalPath) = self::resolvePath($path); if ($storage) { - $cache = $storage->getCache(); - $scanner = $storage->getScanner(); + $cache = $storage->getCache($internalPath); + $scanner = $storage->getScanner($internalPath); $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); $cache->correctFolderSize($internalPath); } @@ -45,7 +45,7 @@ class Updater { */ list($storage, $internalPath) = self::resolvePath($path); if ($storage) { - $cache = $storage->getCache(); + $cache = $storage->getCache($internalPath); $cache->remove($internalPath); $cache->correctFolderSize($internalPath); } diff --git a/lib/files/view.php b/lib/files/view.php index d3f93d39f5..8558b03fe1 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -679,13 +679,13 @@ class View { */ list($storage, $internalPath) = Filesystem::resolvePath($path); if ($storage) { - $cache = $storage->getCache(); + $cache = $storage->getCache($internalPath); if (!$cache->inCache($internalPath)) { - $scanner = $storage->getScanner(); + $scanner = $storage->getScanner($internalPath); $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } else { - $watcher = $storage->getWatcher(); + $watcher = $storage->getWatcher($internalPath); $watcher->checkUpdate($internalPath); } @@ -698,14 +698,14 @@ class View { foreach ($mountPoints as $mountPoint) { $subStorage = Filesystem::getStorage($mountPoint); if ($subStorage) { - $subCache = $subStorage->getCache(); + $subCache = $subStorage->getCache(''); $rootEntry = $subCache->get(''); $data['size'] += $rootEntry['size']; } } } - $permissionsCache = $storage->getPermissionsCache(); + $permissionsCache = $storage->getPermissionsCache($internalPath); $data['permissions'] = $permissionsCache->get($data['fileid'], \OC_User::getUser()); } } @@ -727,13 +727,13 @@ class View { */ list($storage, $internalPath) = Filesystem::resolvePath($path); if ($storage) { - $cache = $storage->getCache(); + $cache = $storage->getCache($internalPath); if ($cache->getStatus($internalPath) < Cache\Cache::COMPLETE) { - $scanner = $storage->getScanner(); + $scanner = $storage->getScanner($internalPath); $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } else { - $watcher = $storage->getWatcher(); + $watcher = $storage->getWatcher($internalPath); $watcher->checkUpdate($internalPath); } @@ -745,7 +745,7 @@ class View { foreach ($mountPoints as $mountPoint) { $subStorage = Filesystem::getStorage($mountPoint); if ($subStorage) { - $subCache = $subStorage->getCache(); + $subCache = $subStorage->getCache(''); $rootEntry = $subCache->get(''); $relativePath = trim(substr($mountPoint, $dirLength), '/'); @@ -769,7 +769,7 @@ class View { $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; $ids[] = $file['fileid']; } - $permissionsCache = $storage->getPermissionsCache(); + $permissionsCache = $storage->getPermissionsCache($internalPath); $permissions = $permissionsCache->getMultiple($ids, \OC_User::getUser()); foreach ($files as $i => $file) { @@ -812,10 +812,10 @@ class View { */ list($storage, $internalPath) = Filesystem::resolvePath($path); if ($storage) { - $cache = $storage->getCache(); + $cache = $storage->getCache($path); if (!$cache->inCache($internalPath)) { - $scanner = $storage->getScanner(); + $scanner = $storage->getScanner($internalPath); $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); } @@ -857,7 +857,7 @@ class View { $mountPoint = Filesystem::getMountPoint($this->fakeRoot); $storage = Filesystem::getStorage($mountPoint); if ($storage) { - $cache = $storage->getCache(); + $cache = $storage->getCache(''); $results = $cache->$method($query); foreach ($results as $result) { @@ -871,7 +871,7 @@ class View { foreach ($mountPoints as $mountPoint) { $storage = Filesystem::getStorage($mountPoint); if ($storage) { - $cache = $storage->getCache(); + $cache = $storage->getCache(''); $relativeMountPoint = substr($mountPoint, $rootLength); $results = $cache->$method($query); From fe90130618e4ce9b1829b4ce8e87311a15f53b83 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Tue, 1 Jan 2013 14:13:05 -0500 Subject: [PATCH 154/418] Initialize storageId variable in shared cache --- apps/files_sharing/lib/cache.php | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 07c305bb95..0534d6dd89 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -48,6 +48,7 @@ class Shared_Cache extends Cache { if ($storage) { $this->files[$target] = $internalPath; $cache = $storage->getCache(); + $this->storageId = $storage->getId(); $this->numericId = $cache->getNumericStorageId(); return $cache; } From a1f7c28e28e221a6939a74eb5ae7b43c02a1d8e6 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Tue, 1 Jan 2013 14:47:25 -0500 Subject: [PATCH 155/418] Use the source Scanner for shared storage when the path is specified --- apps/files_sharing/lib/sharedstorage.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 8504a2d894..3a5755c01f 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -401,15 +401,25 @@ class Shared extends \OC\Files\Storage\Common { return $this->filemtime($path) > $time; } - public function getCache() { + public function getCache($path = '') { return new \OC\Files\Cache\Shared_Cache($this); } - public function getPermissionsCache() { + public function getScanner($path = '') { + if ($path != '' && ($source = $this->getSourcePath($path))) { + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); + if ($storage) { + return $storage->getScanner($internalPath); + } + } + return new \OC\Files\Cache\Scanner($this); + } + + public function getPermissionsCache($path = '') { return new \OC\Files\Cache\Shared_Permissions($this); } - public function getWatcher() { + public function getWatcher($path = '') { return new \OC\Files\Cache\Shared_Watcher($this); } From a068ddff64bb6071de204b948129c4ca23c16d26 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Wed, 2 Jan 2013 14:40:06 -0500 Subject: [PATCH 156/418] Use the sub storage's permission cache for retrieving the correct permission --- lib/files/view.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/files/view.php b/lib/files/view.php index 8558b03fe1..9ba3eea3cf 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -739,6 +739,18 @@ class View { $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter + $ids = array(); + foreach ($files as $i => $file) { + $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $ids[] = $file['fileid']; + } + + $permissionsCache = $storage->getPermissionsCache($internalPath); + $permissions = $permissionsCache->getMultiple($ids, \OC_User::getUser()); + foreach ($files as $i => $file) { + $files[$i]['permissions'] = $permissions[$file['fileid']]; + } + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders $mountPoints = Filesystem::getMountPoints($path); $dirLength = strlen($path); @@ -758,24 +770,14 @@ class View { } } else { //mountpoint in this folder, add an entry for it $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $subPermissionsCache = $subStorage->getPermissionsCache(''); + $rootEntry['permissions'] = $subPermissionsCache->get($rootEntry['fileid'], \OC_User::getUser()); $files[] = $rootEntry; } } } - $ids = array(); - - foreach ($files as $i => $file) { - $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; - $ids[] = $file['fileid']; - } - $permissionsCache = $storage->getPermissionsCache($internalPath); - - $permissions = $permissionsCache->getMultiple($ids, \OC_User::getUser()); - foreach ($files as $i => $file) { - $files[$i]['permissions'] = $permissions[$file['fileid']]; - } - if ($mimetype_filter) { foreach ($files as $file) { if (strpos($mimetype_filter, '/')) { From 6a6d6756b7a6495d044d44c8d6081505880d96b9 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 3 Jan 2013 00:29:10 +0100 Subject: [PATCH 157/418] Cache: rename index to prevent colissions with old fscache indexes --- db_structure.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index aa0916264c..e47cf3c629 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -190,7 +190,7 @@ - storage_path_hash + fs_storage_path_hash true storage @@ -203,7 +203,7 @@ - parent_name_hash + fs_parent_name_hash parent ascending From f144be8857e3356495acefca787637b5672f73ef Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Wed, 2 Jan 2013 19:20:34 -0500 Subject: [PATCH 158/418] Don't mount shared storage unless there are shared files --- apps/files_sharing/lib/sharedstorage.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 3a5755c01f..24096e0c10 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -390,8 +390,10 @@ class Shared extends \OC\Files\Storage\Common { } public static function setup($options) { - $user_dir = $options['user_dir']; - \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared', array('sharedFolder' => '/Shared'), $user_dir.'/Shared/'); + if (\OCP\Share::getItemsSharedWith('file')) { + $user_dir = $options['user_dir']; + \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared', array('sharedFolder' => '/Shared'), $user_dir.'/Shared/'); + } } public function hasUpdated($path, $time) { From 38876fc98a11a917e68780357d7f4b5a146ba89a Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 3 Jan 2013 12:07:04 -0500 Subject: [PATCH 159/418] Update old storage classes names to the new namespace during mounting --- apps/files_external/lib/config.php | 12 ++++++++++++ lib/files/filesystem.php | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 323e4060a4..d7286c52c0 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -120,6 +120,10 @@ class OC_Mount_Config { if (isset($mountPoints[self::MOUNT_TYPE_GROUP])) { foreach ($mountPoints[self::MOUNT_TYPE_GROUP] as $group => $mounts) { foreach ($mounts as $mountPoint => $mount) { + // Update old classes to new namespace + if (strpos($mount['class'], 'OC_Filestorage_') !== false) { + $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15, strlen($mount['class']) - 15); + } // Remove '/$user/files/' from mount point $mountPoint = substr($mountPoint, 13); // Merge the mount point into the current mount points @@ -139,6 +143,10 @@ class OC_Mount_Config { if (isset($mountPoints[self::MOUNT_TYPE_USER])) { foreach ($mountPoints[self::MOUNT_TYPE_USER] as $user => $mounts) { foreach ($mounts as $mountPoint => $mount) { + // Update old classes to new namespace + if (strpos($mount['class'], 'OC_Filestorage_') !== false) { + $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15, strlen($mount['class']) - 15); + } // Remove '/$user/files/' from mount point $mountPoint = substr($mountPoint, 13); // Merge the mount point into the current mount points @@ -169,6 +177,10 @@ class OC_Mount_Config { $personal = array(); if (isset($mountPoints[self::MOUNT_TYPE_USER][$uid])) { foreach ($mountPoints[self::MOUNT_TYPE_USER][$uid] as $mountPoint => $mount) { + // Update old classes to new namespace + if (strpos($mount['class'], 'OC_Filestorage_') !== false) { + $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15, strlen($mount['class']) - 15); + } // Remove '/uid/files/' from mount point $personal[substr($mountPoint, strlen($uid) + 8)] = array('class' => $mount['class'], 'backend' => $backends[$mount['class']]['backend'], diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 8183b8ff99..b9fd6a0376 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -364,7 +364,10 @@ class Filesystem { if (strlen($mountpoint) > 1) { $mountpoint .= '/'; } - + // Update old classes to new namespace + if (strpos($class, 'OC_Filestorage_') !== false) { + $class = '\OC\Files\Storage\\'.substr($class, 15, strlen($class) - 15); + } if ($class instanceof \OC\Files\Storage\Storage) { self::$mounts[$mountpoint] = array('class' => get_class($class), 'arguments' => $arguments); self::$storages[$mountpoint] = $class; From 1137723b2a46c37d61e7f501694c73562b21ef74 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 3 Jan 2013 12:13:45 -0500 Subject: [PATCH 160/418] Remove unnecessary length parameter from last commit --- apps/files_external/lib/config.php | 6 +++--- lib/files/filesystem.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index d7286c52c0..3bb512493d 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -122,7 +122,7 @@ class OC_Mount_Config { foreach ($mounts as $mountPoint => $mount) { // Update old classes to new namespace if (strpos($mount['class'], 'OC_Filestorage_') !== false) { - $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15, strlen($mount['class']) - 15); + $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15); } // Remove '/$user/files/' from mount point $mountPoint = substr($mountPoint, 13); @@ -145,7 +145,7 @@ class OC_Mount_Config { foreach ($mounts as $mountPoint => $mount) { // Update old classes to new namespace if (strpos($mount['class'], 'OC_Filestorage_') !== false) { - $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15, strlen($mount['class']) - 15); + $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15); } // Remove '/$user/files/' from mount point $mountPoint = substr($mountPoint, 13); @@ -179,7 +179,7 @@ class OC_Mount_Config { foreach ($mountPoints[self::MOUNT_TYPE_USER][$uid] as $mountPoint => $mount) { // Update old classes to new namespace if (strpos($mount['class'], 'OC_Filestorage_') !== false) { - $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15, strlen($mount['class']) - 15); + $mount['class'] = '\OC\Files\Storage\\'.substr($mount['class'], 15); } // Remove '/uid/files/' from mount point $personal[substr($mountPoint, strlen($uid) + 8)] = array('class' => $mount['class'], diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index b9fd6a0376..d9487bde80 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -366,7 +366,7 @@ class Filesystem { } // Update old classes to new namespace if (strpos($class, 'OC_Filestorage_') !== false) { - $class = '\OC\Files\Storage\\'.substr($class, 15, strlen($class) - 15); + $class = '\OC\Files\Storage\\'.substr($class, 15); } if ($class instanceof \OC\Files\Storage\Storage) { self::$mounts[$mountpoint] = array('class' => get_class($class), 'arguments' => $arguments); From 0ca5047da56c1b6d0614174b01d42bace85d53ee Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 7 Jan 2013 00:36:01 +0100 Subject: [PATCH 161/418] Autoload namespaced test classes --- lib/base.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/base.php b/lib/base.php index 3d3e7d59f9..80e5c5ed77 100644 --- a/lib/base.php +++ b/lib/base.php @@ -105,6 +105,8 @@ class OC $path = str_replace('\\', '/', $className) . '.php'; } elseif (strpos($className, 'Test_') === 0) { $path = 'tests/lib/' . strtolower(str_replace('_', '/', substr($className, 5)) . '.php'); + } elseif (strpos($className, 'Test\\') === 0) { + $path = 'tests/lib/' . strtolower(str_replace('\\', '/', substr($className, 5)) . '.php'); } else { return false; } From 457dc270f5147863c00b55355542298ba4d41762 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 7 Jan 2013 00:36:39 +0100 Subject: [PATCH 162/418] Fix messed up mounts --- lib/files/filesystem.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index d9487bde80..b3ba62c3a4 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -364,14 +364,14 @@ class Filesystem { if (strlen($mountpoint) > 1) { $mountpoint .= '/'; } - // Update old classes to new namespace - if (strpos($class, 'OC_Filestorage_') !== false) { - $class = '\OC\Files\Storage\\'.substr($class, 15); - } if ($class instanceof \OC\Files\Storage\Storage) { self::$mounts[$mountpoint] = array('class' => get_class($class), 'arguments' => $arguments); self::$storages[$mountpoint] = $class; } else { + // Update old classes to new namespace + if (strpos($class, 'OC_Filestorage_') !== false) { + $class = '\OC\Files\Storage\\'.substr($class, 15); + } self::$mounts[$mountpoint] = array('class' => $class, 'arguments' => $arguments); } } From 439578288facbae3144f131aca85a7235f622053 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 7 Jan 2013 01:03:11 +0100 Subject: [PATCH 163/418] Cache: split permission cache scanning and cache scanning --- lib/files/cache/scanner.php | 9 --------- lib/files/view.php | 30 ++++++++++++++++++++++-------- tests/lib/files/view.php | 1 + 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 4c0ec9617f..526d4a2aab 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -24,11 +24,6 @@ class Scanner { */ private $cache; - /** - * @var \OC\Files\Cache\Permissions $permissionsCache - */ - private $permissionsCache; - const SCAN_RECURSIVE = true; const SCAN_SHALLOW = false; @@ -36,7 +31,6 @@ class Scanner { $this->storage = $storage; $this->storageId = $this->storage->getId(); $this->cache = $storage->getCache(); - $this->permissionsCache = $storage->getPermissionsCache(); } /** @@ -53,10 +47,8 @@ class Scanner { $data['mtime'] = $this->storage->filemtime($path); if ($data['mimetype'] == 'httpd/unix-directory') { $data['size'] = -1; //unknown - $data['permissions'] = $this->storage->getPermissions($path . '/'); } else { $data['size'] = $this->storage->filesize($path); - $data['permissions'] = $this->storage->getPermissions($path); } return $data; } @@ -81,7 +73,6 @@ class Scanner { } } $id = $this->cache->put($file, $data); - $this->permissionsCache->set($id, \OC_User::getUser(), $data['permissions']); } return $data; } diff --git a/lib/files/view.php b/lib/files/view.php index 9ba3eea3cf..124345f3c6 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -680,6 +680,8 @@ class View { list($storage, $internalPath) = Filesystem::resolvePath($path); if ($storage) { $cache = $storage->getCache($internalPath); + $permissionsCache = $storage->getPermissionsCache($internalPath); + $user = \OC_User::getUser(); if (!$cache->inCache($internalPath)) { $scanner = $storage->getScanner($internalPath); @@ -705,8 +707,12 @@ class View { } } - $permissionsCache = $storage->getPermissionsCache($internalPath); - $data['permissions'] = $permissionsCache->get($data['fileid'], \OC_User::getUser()); + $permissions = $permissionsCache->get($data['fileid'], $user); + if ($permissions === -1) { + $permissions = $storage->getPermissions($internalPath); + $permissionsCache->set($data['fileid'], $user, $permissions); + } + $data['permissions'] = $permissions; } } return $data; @@ -728,6 +734,8 @@ class View { list($storage, $internalPath) = Filesystem::resolvePath($path); if ($storage) { $cache = $storage->getCache($internalPath); + $permissionsCache = $storage->getPermissionsCache($internalPath); + $user = \OC_User::getUser(); if ($cache->getStatus($internalPath) < Cache\Cache::COMPLETE) { $scanner = $storage->getScanner($internalPath); @@ -743,12 +751,13 @@ class View { foreach ($files as $i => $file) { $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; $ids[] = $file['fileid']; - } - $permissionsCache = $storage->getPermissionsCache($internalPath); - $permissions = $permissionsCache->getMultiple($ids, \OC_User::getUser()); - foreach ($files as $i => $file) { - $files[$i]['permissions'] = $permissions[$file['fileid']]; + $permissions = $permissionsCache->get($file['fileid'], $user); + if ($permissions === -1) { + $permissions = $storage->getPermissions($file['path']); + $permissionsCache->set($file['fileid'], $user, $permissions); + } + $files[$i]['permissions'] = $permissions; } //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders @@ -772,7 +781,12 @@ class View { $rootEntry['name'] = $relativePath; $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; $subPermissionsCache = $subStorage->getPermissionsCache(''); - $rootEntry['permissions'] = $subPermissionsCache->get($rootEntry['fileid'], \OC_User::getUser()); + $permissions = $subPermissionsCache->get($rootEntry['fileid'], $user); + if ($permissions === -1) { + $permissions = $subStorage->getPermissions($rootEntry['path']); + $subPermissionsCache->set($rootEntry['fileid'], $user, $permissions); + } + $rootEntry['permissions'] = $subPermissionsCache; $files[] = $rootEntry; } } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 712166ab32..4b0abc2201 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -43,6 +43,7 @@ class View extends \PHPUnit_Framework_TestCase { $cachedData = $rootView->getFileInfo('/foo.txt'); $this->assertEquals($textSize, $cachedData['size']); $this->assertEquals('text/plain', $cachedData['mimetype']); + $this->assertEquals(\OCP\PERMISSION_ALL ^ \OCP\PERMISSION_CREATE, $cachedData['permissions']); $cachedData = $rootView->getFileInfo('/'); $this->assertEquals($storageSize * 3, $cachedData['size']); From ad3badeabff9dde839827558c281a691c611cf87 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 7 Jan 2013 01:03:11 +0100 Subject: [PATCH 164/418] Cache: split permission cache scanning and cache scanning --- tests/lib/files/view.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 4b0abc2201..5327114267 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -43,7 +43,7 @@ class View extends \PHPUnit_Framework_TestCase { $cachedData = $rootView->getFileInfo('/foo.txt'); $this->assertEquals($textSize, $cachedData['size']); $this->assertEquals('text/plain', $cachedData['mimetype']); - $this->assertEquals(\OCP\PERMISSION_ALL ^ \OCP\PERMISSION_CREATE, $cachedData['permissions']); + $this->assertNotEquals(-1, $cachedData['permissions']); $cachedData = $rootView->getFileInfo('/'); $this->assertEquals($storageSize * 3, $cachedData['size']); From d0377b1951a156e218ca0200340e2bcfb51ac0c8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 7 Jan 2013 01:40:09 +0100 Subject: [PATCH 165/418] Cache: normalize mimetypes --- db_structure.xml | 44 ++++++++++++++++++++++++++--- lib/files/cache/cache.php | 59 +++++++++++++++++++++++++++++++++++---- lib/files/view.php | 2 +- lib/util.php | 2 +- 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index e47cf3c629..3022983473 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -94,6 +94,42 @@
+ + + *dbprefix*mimetypes + + + + + id + integer + 0 + true + 1 + 4 + + + + mimetype + text + + true + 64 + + + + mimetype_id_index + true + + mimetype + ascending + + + + + +
+ *dbprefix*filecache @@ -151,18 +187,18 @@ mimetype - text + integer true - 64 + 4 mimepart - text + integer true - 32 + 4 diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php index 3ebae9baa5..0001c2752d 100644 --- a/lib/files/cache/cache.php +++ b/lib/files/cache/cache.php @@ -36,6 +36,9 @@ class Cache { */ private $numericId; + private $mimetypeIds = array(); + private $mimetypes = array(); + /** * @param \OC\Files\Storage\Storage|string $storage */ @@ -61,6 +64,41 @@ class Cache { return $this->numericId; } + /** + * normalize mimetypes + * + * @param string $mime + * @return int + */ + public function getMimetypeId($mime) { + if (!isset($this->mimetypeIds[$mime])) { + $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?'); + $result = $query->execute(array($mime)); + if ($row = $result->fetchRow()) { + $this->mimetypeIds[$mime] = $row['id']; + } else { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)'); + $query->execute(array($mime)); + $this->mimetypeIds[$mime] = \OC_DB::insertid('*PREFIX*mimetypes'); + } + $this->mimetypes[$this->mimetypeIds[$mime]] = $mime; + } + return $this->mimetypeIds[$mime]; + } + + public function getMimetype($id) { + if (!isset($this->mimetypes[$id])) { + $query = \OC_DB::prepare('SELECT `mimetype` FROM `*PREFIX*mimetypes` WHERE `id` = ?'); + $result = $query->execute(array($id)); + if ($row = $result->fetchRow()) { + $this->mimetypes[$id] = $row['mimetype']; + } else { + return null; + } + } + return $this->mimetypes[$id]; + } + /** * get the stored metadata of a file or folder * @@ -92,6 +130,8 @@ class Cache { $data['size'] = (int)$data['size']; $data['mtime'] = (int)$data['mtime']; $data['encrypted'] = (bool)$data['encrypted']; + $data['mimetype'] = $this->getMimetype($data['mimetype']); + $data['mimepart'] = $this->getMimetype($data['mimepart']); } return $data; @@ -110,7 +150,12 @@ class Cache { 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` FROM `*PREFIX*filecache` WHERE parent = ? ORDER BY `name` ASC'); $result = $query->execute(array($fileId)); - return $result->fetchAll(); + $files = $result->fetchAll(); + foreach ($files as &$file) { + $file['mimetype'] = $this->getMimetype($file['mimetype']); + $file['mimepart'] = $this->getMimetype($file['mimepart']); + } + return $files; } else { return array(); } @@ -179,22 +224,23 @@ class Cache { * @param array $data * @return array */ - static function buildParts(array $data) { + function buildParts(array $data) { $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'encrypted'); $params = array(); $queryParts = array(); foreach ($data as $name => $value) { if (array_search($name, $fields) !== false) { - $params[] = $value; - $queryParts[] = '`' . $name . '`'; if ($name === 'path') { $params[] = md5($value); $queryParts[] = '`path_hash`'; } elseif ($name === 'mimetype') { - $params[] = substr($value, 0, strpos($value, '/')); + $params[] = $this->getMimetypeId(substr($value, 0, strpos($value, '/'))); $queryParts[] = '`mimepart`'; + $value = $this->getMimetypeId($value); } + $params[] = $value; + $queryParts[] = '`' . $name . '`'; } } return array($queryParts, $params); @@ -339,6 +385,8 @@ class Cache { $result = $query->execute(array($pattern, $this->numericId)); $files = array(); while ($row = $result->fetchRow()) { + $row['mimetype'] = $this->getMimetype($row['mimetype']); + $row['mimepart'] = $this->getMimetype($row['mimepart']); $files[] = $row; } return $files; @@ -360,6 +408,7 @@ class Cache { SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?' ); + $mimetype = $this->getMimetypeId($mimetype); $result = $query->execute(array($mimetype, $this->numericId)); return $result->fetchAll(); } diff --git a/lib/files/view.php b/lib/files/view.php index 124345f3c6..8303a080bd 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -693,7 +693,7 @@ class View { $data = $cache->get($internalPath); - if ($data) { + if ($data and $data['fileid']) { if ($data['mimetype'] === 'httpd/unix-directory') { //add the sizes of other mountpoints to the folder $mountPoints = Filesystem::getMountPoints($path); diff --git a/lib/util.php b/lib/util.php index 805ef6f18b..93c0d0f26d 100755 --- a/lib/util.php +++ b/lib/util.php @@ -74,7 +74,7 @@ class OC_Util { */ public static function getVersion() { // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.90.0. This is not visible to the user - return array(4,91,04); + return array(4,91,05); } /** From 5174eda23270463f09c82db77f2f61d3496f752f Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Mon, 7 Jan 2013 15:21:38 -0500 Subject: [PATCH 166/418] Fix permissions for mount point --- lib/files/view.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files/view.php b/lib/files/view.php index 8303a080bd..94c89603ae 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -786,7 +786,7 @@ class View { $permissions = $subStorage->getPermissions($rootEntry['path']); $subPermissionsCache->set($rootEntry['fileid'], $user, $permissions); } - $rootEntry['permissions'] = $subPermissionsCache; + $rootEntry['permissions'] = $permissions; $files[] = $rootEntry; } } From a7d4d042239236082d7f2c2679249ea7d48e596c Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Mon, 7 Jan 2013 15:27:22 -0500 Subject: [PATCH 167/418] Fix mimetypes in shared cache --- apps/files_sharing/lib/cache.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 0534d6dd89..8b989db3b0 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -79,6 +79,8 @@ class Shared_Cache extends Cache { $data['size'] = (int)$data['size']; $data['mtime'] = (int)$data['mtime']; $data['encrypted'] = (bool)$data['encrypted']; + $data['mimetype'] = $this->getMimetype($data['mimetype']); + $data['mimepart'] = $this->getMimetype($data['mimepart']); return $data; } return false; @@ -92,7 +94,12 @@ class Shared_Cache extends Cache { */ public function getFolderContents($folder) { if ($folder == '') { - return \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS); + $files = \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS); + foreach ($files as &$file) { + $file['mimetype'] = $this->getMimetype($file['mimetype']); + $file['mimepart'] = $this->getMimetype($file['mimepart']); + } + return $files; } else { if ($cache = $this->getSourceCache($folder)) { return $cache->getFolderContents($this->files[$folder]); From 8f8a5bbfb750b3c9091da810749a43cada2740b2 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Mon, 7 Jan 2013 18:17:14 -0500 Subject: [PATCH 168/418] Maked Shared_Permissions extend Permissions class --- apps/files_sharing/lib/permissions.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/files_sharing/lib/permissions.php b/apps/files_sharing/lib/permissions.php index 6eaed34b33..508c3a384f 100644 --- a/apps/files_sharing/lib/permissions.php +++ b/apps/files_sharing/lib/permissions.php @@ -20,7 +20,7 @@ */ namespace OC\Files\Cache; -class Shared_Permissions { +class Shared_Permissions extends Permissions { /** * get the permissions for a single file @@ -29,7 +29,7 @@ class Shared_Permissions { * @param string $user * @return int (-1 if file no permissions set) */ - static public function get($fileId, $user) { + public function get($fileId, $user) { if ($fileId == -1) { return \OCP\PERMISSION_READ; } @@ -48,7 +48,7 @@ class Shared_Permissions { * @param string $user * @param int $permissions */ - static public function set($fileId, $user, $permissions) { + public function set($fileId, $user, $permissions) { // Not a valid action for Shared Permissions } @@ -59,7 +59,7 @@ class Shared_Permissions { * @param string $user * @return int[] */ - static public function getMultiple($fileIds, $user) { + public function getMultiple($fileIds, $user) { if (count($fileIds) === 0) { return array(); } @@ -75,11 +75,11 @@ class Shared_Permissions { * @param int $fileId * @param string $user */ - static public function remove($fileId, $user) { + public function remove($fileId, $user) { // Not a valid action for Shared Permissions } - static public function removeMultiple($fileIds, $user) { + public function removeMultiple($fileIds, $user) { // Not a valid action for Shared Permissions } } From e8b195bf109d702402735e628b2d239b199088e5 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Mon, 7 Jan 2013 20:52:51 -0500 Subject: [PATCH 169/418] Almost fix Shared scanner... --- apps/files_sharing/lib/cache.php | 13 +++++++++++++ apps/files_sharing/lib/permissions.php | 2 +- apps/files_sharing/lib/share/file.php | 3 +++ apps/files_sharing/lib/share/folder.php | 11 +++++++++-- apps/files_sharing/lib/sharedstorage.php | 6 ------ lib/public/share.php | 5 ++++- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 8b989db3b0..d35a5148de 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -136,6 +136,19 @@ class Shared_Cache extends Cache { return -1; } + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + if ($file == '') { + return true; + } + return parent::inCache($file); + } + /** * remove a file or folder from the cache * diff --git a/apps/files_sharing/lib/permissions.php b/apps/files_sharing/lib/permissions.php index 508c3a384f..2b068ff935 100644 --- a/apps/files_sharing/lib/permissions.php +++ b/apps/files_sharing/lib/permissions.php @@ -33,7 +33,7 @@ class Shared_Permissions extends Permissions { if ($fileId == -1) { return \OCP\PERMISSION_READ; } - $source = \OCP\Share::getItemSharedWithBySource('file', $fileId, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE); + $source = \OCP\Share::getItemSharedWithBySource('file', $fileId, \OC_Share_Backend_File::FORMAT_SHARED_STORAGE, null, true); if ($source) { return $source['permissions']; } else { diff --git a/apps/files_sharing/lib/share/file.php b/apps/files_sharing/lib/share/file.php index 5e98c455d3..6d3c55a008 100644 --- a/apps/files_sharing/lib/share/file.php +++ b/apps/files_sharing/lib/share/file.php @@ -117,6 +117,9 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { } public static function getSource($target) { + if ($target == '') { + return false; + } $target = '/'.$target; $target = rtrim($target, '/'); $pos = strpos($target, '/', 1); diff --git a/apps/files_sharing/lib/share/folder.php b/apps/files_sharing/lib/share/folder.php index bbe4c130bd..11c8c6b1e8 100644 --- a/apps/files_sharing/lib/share/folder.php +++ b/apps/files_sharing/lib/share/folder.php @@ -24,6 +24,13 @@ class OC_Share_Backend_Folder extends OC_Share_Backend_File implements OCP\Share public function getChildren($itemSource) { $children = array(); $parents = array($itemSource); + $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?'); + $result = $query->execute(array('httpd/unix-directory')); + if ($row = $result->fetchRow()) { + $mimetype = $row['id']; + } else { + $mimetype = -1; + } while (!empty($parents)) { $parents = "'".implode("','", $parents)."'"; $query = OC_DB::prepare('SELECT `fileid`, `name`, `mimetype` FROM `*PREFIX*filecache` WHERE `parent` IN ('.$parents.')'); @@ -32,8 +39,8 @@ class OC_Share_Backend_Folder extends OC_Share_Backend_File implements OCP\Share while ($file = $result->fetchRow()) { $children[] = array('source' => $file['fileid'], 'file_path' => $file['name']); // If a child folder is found look inside it - if ($file['mimetype'] == 'httpd/unix-directory') { - $parents[] = $file['id']; + if ($file['mimetype'] == $mimetype) { + $parents[] = $file['fileid']; } } } diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 24096e0c10..c8756af8ed 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -408,12 +408,6 @@ class Shared extends \OC\Files\Storage\Common { } public function getScanner($path = '') { - if ($path != '' && ($source = $this->getSourcePath($path))) { - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); - if ($storage) { - return $storage->getScanner($internalPath); - } - } return new \OC\Files\Cache\Scanner($this); } diff --git a/lib/public/share.php b/lib/public/share.php index c74960b94c..7722e0b86c 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -756,7 +756,7 @@ class Share { $collectionItems = array(); foreach ($items as &$row) { // Return only the item instead of a 2-dimensional array - if ($limit == 1 && $row['item_type'] == $itemType && $row[$column] == $item) { + if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) { if ($format == self::FORMAT_NONE) { return $row; } else { @@ -823,6 +823,9 @@ class Share { if (!empty($collectionItems)) { $items = array_merge($items, $collectionItems); } + if (empty($items) && $limit == 1) { + return false; + } if ($format == self::FORMAT_NONE) { return $items; } else if ($format == self::FORMAT_STATUSES) { From 464dafd7d2627d59732c09e01986d705d604ee56 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Wed, 9 Jan 2013 22:17:39 -0500 Subject: [PATCH 170/418] Make Google Drive storage id unique, before it was anonymousanonymous --- apps/files_external/lib/google.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php index bbb315c491..4c485773e7 100644 --- a/apps/files_external/lib/google.php +++ b/apps/files_external/lib/google.php @@ -41,7 +41,7 @@ class Google extends \OC\Files\Storage\Common { ) { $consumer_key = isset($params['consumer_key']) ? $params['consumer_key'] : 'anonymous'; $consumer_secret = isset($params['consumer_secret']) ? $params['consumer_secret'] : 'anonymous'; - $this->id = 'google::' . $consumer_key . $consumer_secret; + $this->id = 'google::' . $params['token']; $this->consumer = new \OAuthConsumer($consumer_key, $consumer_secret); $this->oauth_token = new \OAuthToken($params['token'], $params['token_secret']); $this->sig_method = new \OAuthSignatureMethod_HMAC_SHA1(); From 106541361c3857ed8e35c6869c91faffb8ae984d Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Wed, 9 Jan 2013 22:57:42 -0500 Subject: [PATCH 171/418] Change length of mimetypes to 255, the maximum length according to RFC 4288 --- db_structure.xml | 2 +- lib/util.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index 3022983473..7b6829aa30 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -114,7 +114,7 @@ text true - 64 + 255 diff --git a/lib/util.php b/lib/util.php index 93c0d0f26d..e814a3a32d 100755 --- a/lib/util.php +++ b/lib/util.php @@ -74,7 +74,7 @@ class OC_Util { */ public static function getVersion() { // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.90.0. This is not visible to the user - return array(4,91,05); + return array(4,91,06); } /** From aa15fcf22f4c32026eca5ff8ae5e5df244f2c53e Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 10 Jan 2013 12:09:55 -0500 Subject: [PATCH 172/418] Scan mount points in root before adding a entry --- lib/files/view.php | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/lib/files/view.php b/lib/files/view.php index 94c89603ae..703cda5123 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -767,27 +767,37 @@ class View { $subStorage = Filesystem::getStorage($mountPoint); if ($subStorage) { $subCache = $subStorage->getCache(''); - $rootEntry = $subCache->get(''); - $relativePath = trim(substr($mountPoint, $dirLength), '/'); - if ($pos = strpos($relativePath, '/')) { //mountpoint inside subfolder add size to the correct folder - $entryName = substr($relativePath, 0, $pos); - foreach ($files as &$entry) { - if ($entry['name'] === $entryName) { - $entry['size'] += $rootEntry['size']; + if ($subCache->getStatus('') < Cache\Cache::COMPLETE) { + $subScanner = $subStorage->getScanner(''); + $subScanner->scan('', Cache\Scanner::SCAN_SHALLOW); + } else { + $subWatcher = $subStorage->getWatcher(''); + $subWatcher->checkUpdate(''); + } + + $rootEntry = $subCache->get(''); + if ($rootEntry) { + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry['name'] === $entryName) { + $entry['size'] += $rootEntry['size']; + } } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $subPermissionsCache = $subStorage->getPermissionsCache(''); + $permissions = $subPermissionsCache->get($rootEntry['fileid'], $user); + if ($permissions === -1) { + $permissions = $subStorage->getPermissions($rootEntry['path']); + $subPermissionsCache->set($rootEntry['fileid'], $user, $permissions); + } + $rootEntry['permissions'] = $permissions; + $files[] = $rootEntry; } - } else { //mountpoint in this folder, add an entry for it - $rootEntry['name'] = $relativePath; - $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; - $subPermissionsCache = $subStorage->getPermissionsCache(''); - $permissions = $subPermissionsCache->get($rootEntry['fileid'], $user); - if ($permissions === -1) { - $permissions = $subStorage->getPermissions($rootEntry['path']); - $subPermissionsCache->set($rootEntry['fileid'], $user, $permissions); - } - $rootEntry['permissions'] = $permissions; - $files[] = $rootEntry; } } } From 0784bcb8d74214448e3908e8c05a8c6be38ef457 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 10 Jan 2013 23:30:26 +0100 Subject: [PATCH 173/418] introduce configPrefix to allow settings for multiple LDAP servers --- apps/user_ldap/ajax/testConfiguration.php | 4 +- apps/user_ldap/appinfo/app.php | 2 +- apps/user_ldap/lib/connection.php | 58 ++++++++++++----------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/apps/user_ldap/ajax/testConfiguration.php b/apps/user_ldap/ajax/testConfiguration.php index a82f7e4c17..fd72485268 100644 --- a/apps/user_ldap/ajax/testConfiguration.php +++ b/apps/user_ldap/ajax/testConfiguration.php @@ -4,7 +4,7 @@ * ownCloud - user_ldap * * @author Arthur Schiwon - * @copyright 2012 Arthur Schiwon blizzz@owncloud.com + * @copyright 2012, 2013 Arthur Schiwon blizzz@owncloud.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -26,7 +26,7 @@ OCP\JSON::checkAdminUser(); OCP\JSON::checkAppEnabled('user_ldap'); OCP\JSON::callCheck(); -$connection = new \OCA\user_ldap\lib\Connection(null); +$connection = new \OCA\user_ldap\lib\Connection('', null); if($connection->setConfiguration($_POST)) { //Configuration is okay if($connection->bind()) { diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index ce3079da0b..9e72e388e6 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -23,7 +23,7 @@ OCP\App::registerAdmin('user_ldap', 'settings'); -$connector = new OCA\user_ldap\lib\Connection('user_ldap'); +$connector = new OCA\user_ldap\lib\Connection('', 'user_ldap'); $userBackend = new OCA\user_ldap\USER_LDAP(); $userBackend->setConnector($connector); $groupBackend = new OCA\user_ldap\GROUP_LDAP(); diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 7046cbbfc7..21b2d7560c 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -4,7 +4,7 @@ * ownCloud – LDAP Access * * @author Arthur Schiwon - * @copyright 2012 Arthur Schiwon blizzz@owncloud.com + * @copyright 2012, 2013 Arthur Schiwon blizzz@owncloud.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -25,6 +25,7 @@ namespace OCA\user_ldap\lib; class Connection { private $ldapConnectionRes = null; + private $configPrefix; private $configID; private $configured = false; @@ -59,7 +60,8 @@ class Connection { 'hasPagedResultSupport' => false, ); - public function __construct($configID = 'user_ldap') { + public function __construct($configPrefix = '', $configID = 'user_ldap') { + $this->configPrefix = $configPrefix; $this->configID = $configID; $this->cache = \OC_Cache::getGlobalCache(); $this->config['hasPagedResultSupport'] = (function_exists('ldap_control_paged_result') && function_exists('ldap_control_paged_result_response')); @@ -89,7 +91,7 @@ class Connection { \OCP\Util::writeLog('user_ldap', 'Set config ldapUuidAttribute to '.$value, \OCP\Util::DEBUG); $this->config[$name] = $value; if(!empty($this->configID)) { - \OCP\Config::setAppValue($this->configID, 'ldap_uuid_attribute', $value); + \OCP\Config::setAppValue($this->configID, $this->configPrefix.'ldap_uuid_attribute', $value); } $changed = true; } @@ -126,7 +128,7 @@ class Connection { } private function getCacheKey($key) { - $prefix = 'LDAP-'.$this->configID.'-'; + $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-'; if(is_null($key)) { return $prefix; } @@ -183,30 +185,30 @@ class Connection { \OCP\Util::writeLog('user_ldap', 'Checking conf state: isConfigured? '.print_r($this->configured, true).' isForce? '.print_r($force, true).' configID? '.print_r($this->configID, true), \OCP\Util::DEBUG); if((!$this->configured || $force) && !is_null($this->configID)) { \OCP\Util::writeLog('user_ldap', 'Reading the configuration', \OCP\Util::DEBUG); - $this->config['ldapHost'] = \OCP\Config::getAppValue($this->configID, 'ldap_host', ''); - $this->config['ldapPort'] = \OCP\Config::getAppValue($this->configID, 'ldap_port', 389); - $this->config['ldapAgentName'] = \OCP\Config::getAppValue($this->configID, 'ldap_dn', ''); - $this->config['ldapAgentPassword'] = base64_decode(\OCP\Config::getAppValue($this->configID, 'ldap_agent_password', '')); - $this->config['ldapBase'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, 'ldap_base', '')); - $this->config['ldapBaseUsers'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, 'ldap_base_users', $this->config['ldapBase'])); - $this->config['ldapBaseGroups'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, 'ldap_base_groups', $this->config['ldapBase'])); - $this->config['ldapTLS'] = \OCP\Config::getAppValue($this->configID, 'ldap_tls', 0); - $this->config['ldapNoCase'] = \OCP\Config::getAppValue($this->configID, 'ldap_nocase', 0); - $this->config['turnOffCertCheck'] = \OCP\Config::getAppValue($this->configID, 'ldap_turn_off_cert_check', 0); - $this->config['ldapUserDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, 'ldap_display_name', 'uid'), 'UTF-8'); - $this->config['ldapUserFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_userlist_filter', 'objectClass=person'); - $this->config['ldapGroupFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_group_filter', '(objectClass=posixGroup)'); - $this->config['ldapLoginFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_login_filter', '(uid=%uid)'); - $this->config['ldapGroupDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, 'ldap_group_display_name', 'uid'), 'UTF-8'); - $this->config['ldapQuotaAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_quota_attr', ''); - $this->config['ldapQuotaDefault'] = \OCP\Config::getAppValue($this->configID, 'ldap_quota_def', ''); - $this->config['ldapEmailAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_email_attr', ''); - $this->config['ldapGroupMemberAssocAttr'] = \OCP\Config::getAppValue($this->configID, 'ldap_group_member_assoc_attribute', 'uniqueMember'); + $this->config['ldapHost'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_host', ''); + $this->config['ldapPort'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_port', 389); + $this->config['ldapAgentName'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_dn', ''); + $this->config['ldapAgentPassword'] = base64_decode(\OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_agent_password', '')); + $this->config['ldapBase'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base', '')); + $this->config['ldapBaseUsers'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base_users', $this->config['ldapBase'])); + $this->config['ldapBaseGroups'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base_groups', $this->config['ldapBase'])); + $this->config['ldapTLS'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_tls', 0); + $this->config['ldapNoCase'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_nocase', 0); + $this->config['turnOffCertCheck'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_turn_off_cert_check', 0); + $this->config['ldapUserDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, '$this->configPrefix.ldap_display_name', 'uid'), 'UTF-8'); + $this->config['ldapUserFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_userlist_filter', 'objectClass=person'); + $this->config['ldapGroupFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_group_filter', '(objectClass=posixGroup)'); + $this->config['ldapLoginFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_login_filter', '(uid=%uid)'); + $this->config['ldapGroupDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_group_display_name', 'uid'), 'UTF-8'); + $this->config['ldapQuotaAttribute'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_quota_attr', ''); + $this->config['ldapQuotaDefault'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_quota_def', ''); + $this->config['ldapEmailAttribute'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_email_attr', ''); + $this->config['ldapGroupMemberAssocAttr'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_group_member_assoc_attribute', 'uniqueMember'); $this->config['ldapIgnoreNamingRules'] = \OCP\Config::getSystemValue('ldapIgnoreNamingRules', false); - $this->config['ldapCacheTTL'] = \OCP\Config::getAppValue($this->configID, 'ldap_cache_ttl', 10*60); - $this->config['ldapUuidAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_uuid_attribute', 'auto'); - $this->config['ldapOverrideUuidAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_override_uuid_attribute', 0); - $this->config['homeFolderNamingRule'] = \OCP\Config::getAppValue($this->configID, 'home_folder_naming_rule', 'opt:username'); + $this->config['ldapCacheTTL'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_cache_ttl', 10*60); + $this->config['ldapUuidAttribute'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_uuid_attribute', 'auto'); + $this->config['ldapOverrideUuidAttribute'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_override_uuid_attribute', 0); + $this->config['homeFolderNamingRule'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'home_folder_naming_rule', 'opt:username'); $this->configured = $this->validateConfiguration(); } @@ -264,7 +266,7 @@ class Connection { \OCP\Util::writeLog('user_ldap', 'No group filter is specified, LDAP group feature will not be used.', \OCP\Util::INFO); } if(!in_array($this->config['ldapUuidAttribute'], array('auto', 'entryuuid', 'nsuniqueid', 'objectguid')) && (!is_null($this->configID))) { - \OCP\Config::setAppValue($this->configID, 'ldap_uuid_attribute', 'auto'); + \OCP\Config::setAppValue($this->configID, $this->configPrefix.'ldap_uuid_attribute', 'auto'); \OCP\Util::writeLog('user_ldap', 'Illegal value for the UUID Attribute, reset to autodetect.', \OCP\Util::INFO); } From fab5817f67a9e9dde245d522838fee3b928fcbd8 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 10 Jan 2013 23:34:24 +0100 Subject: [PATCH 174/418] documentation for the Connection constructor --- apps/user_ldap/lib/connection.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 21b2d7560c..803ac34f59 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -60,6 +60,11 @@ class Connection { 'hasPagedResultSupport' => false, ); + /** + * @brief Constructor + * @param $configPrefix a string with the prefix for the configkey column (appconfig table) + * @param $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections + */ public function __construct($configPrefix = '', $configID = 'user_ldap') { $this->configPrefix = $configPrefix; $this->configID = $configID; From 4835525c469d5ac75104e92c2dfbbb049d62890c Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 10 Jan 2013 22:28:50 -0500 Subject: [PATCH 175/418] Switch scan to scanFile for root of mount points --- lib/files/view.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/files/view.php b/lib/files/view.php index 703cda5123..fa031b7478 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -768,9 +768,9 @@ class View { if ($subStorage) { $subCache = $subStorage->getCache(''); - if ($subCache->getStatus('') < Cache\Cache::COMPLETE) { + if ($subCache->getStatus('') === Cache\Cache::NOT_FOUND) { $subScanner = $subStorage->getScanner(''); - $subScanner->scan('', Cache\Scanner::SCAN_SHALLOW); + $subScanner->scanFile(''); } else { $subWatcher = $subStorage->getWatcher(''); $subWatcher->checkUpdate(''); From 36cac7f924ad07738a3cb72fee06f21d07baad42 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Thu, 10 Jan 2013 22:29:47 -0500 Subject: [PATCH 176/418] Return NOT_FOUND in shared cache --- apps/files_sharing/lib/cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index d35a5148de..0b187a3c3f 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -196,7 +196,7 @@ class Shared_Cache extends Cache { if ($cache = $this->getSourceCache($file)) { return $cache->getStatus($this->files[$file]); } - return false; + return self::NOT_FOUND; } /** From 09c54722a877352713d8cefdb6a0a92860633898 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 11 Jan 2013 18:13:22 +0100 Subject: [PATCH 177/418] add LDAP User and Group proxies to suppoer multiple servers --- apps/user_ldap/group_proxy.php | 178 +++++++++++++++++++++++++++++++++ apps/user_ldap/lib/proxy.php | 104 +++++++++++++++++++ apps/user_ldap/user_proxy.php | 159 +++++++++++++++++++++++++++++ 3 files changed, 441 insertions(+) create mode 100644 apps/user_ldap/group_proxy.php create mode 100644 apps/user_ldap/lib/proxy.php create mode 100644 apps/user_ldap/user_proxy.php diff --git a/apps/user_ldap/group_proxy.php b/apps/user_ldap/group_proxy.php new file mode 100644 index 0000000000..5aa1aef0e0 --- /dev/null +++ b/apps/user_ldap/group_proxy.php @@ -0,0 +1,178 @@ +. + * + */ + +namespace OCA\user_ldap; + +class Group_Proxy extends lib\Proxy implements \OCP\GroupInterface { + private $backends = array(); + private $refBackend = null; + + /** + * @brief Constructor + * @param $serverConfigPrefixes array containing the config Prefixes + */ + public function __construct($serverConfigPrefixes) { + parent::__construct(); + foreach($serverConfigPrefixes as $configPrefix) { + $this->backends[$configPrefix] = new \OCA\user_ldap\GROUP_LDAP(); + $connector = $this->getConnector($configPrefix); + $this->backends[$configPrefix]->setConnector($connector); + if(is_null($this->refBackend)) { + $this->refBackend = &$this->backends[$configPrefix]; + } + } + } + + /** + * @brief Tries the backends one after the other until a positive result is returned from the specified method + * @param $gid string, the gid connected to the request + * @param $method string, the method of the group backend that shall be called + * @param $parameters an array of parameters to be passed + * @return mixed, the result of the method or false + */ + protected function walkBackends($gid, $method, $parameters) { + $cacheKey = $this->getGroupCacheKey($gid); + foreach($this->backends as $configPrefix => $backend) { + if($result = call_user_func_array(array($backend, $method), $parameters)) { + $this->writeToCache($cacheKey, $configPrefix); + return $result; + } + } + return false; + } + + /** + * @brief Asks the backend connected to the server that supposely takes care of the gid from the request. + * @param $gid string, the gid connected to the request + * @param $method string, the method of the group backend that shall be called + * @param $parameters an array of parameters to be passed + * @return mixed, the result of the method or false + */ + protected function callOnLastSeenOn($gid, $method, $parameters) { + $cacheKey = $this->getGroupCacheKey($gid);; + $prefix = $this->getFromCache($cacheKey); + //in case the uid has been found in the past, try this stored connection first + if(!is_null($prefix)) { + if(isset($this->backends[$prefix])) { + $result = call_user_func_array(array($this->backends[$prefix], $method), $parameters); + if(!$result) { + //not found here, reset cache to null + $this->writeToCache($cacheKey, null); + } + return $result; + } + } + return false; + } + + /** + * @brief is user in group? + * @param $uid uid of the user + * @param $gid gid of the group + * @returns true/false + * + * Checks whether the user is member of a group or not. + */ + public function inGroup($uid, $gid) { + return $this->handleRequest($gid, 'inGroup', array($uid, $gid)); + } + + /** + * @brief Get all groups a user belongs to + * @param $uid Name of the user + * @returns array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups($uid) { + $groups = array(); + + foreach($this->backends as $backend) { + $backendGroups = $backend->getUserGroups($uid); + if (is_array($backendGroups)) { + $groups = array_merge($groups, $backendGroups); + } + } + + return $groups; + } + + /** + * @brief get a list of all users in a group + * @returns array with user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $users = array(); + + foreach($this->backends as $backend) { + $backendUsers = $backend->usersInGroup($gid, $search, $limit, $offset); + if (is_array($backendUsers)) { + $users = array_merge($users, $backendUsers); + } + } + + return $users; + } + + /** + * @brief get a list of all groups + * @returns array with group names + * + * Returns a list with all groups + */ + public function getGroups($search = '', $limit = -1, $offset = 0) { + $groups = array(); + + foreach($this->backends as $backend) { + $backendGroups = $backend->getGroups($search, $limit, $offset); + if (is_array($backendGroups)) { + $groups = array_merge($groups, $backendGroups); + } + } + + return $groups; + } + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + return $this->handleRequest($gid, 'groupExists', array($gid)); + } + + /** + * @brief Check if backend implements actions + * @param $actions bitwise-or'ed actions + * @returns boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions) { + //it's the same across all our user backends obviously + return $this->refBackend->implementsActions($actions); + } +} \ No newline at end of file diff --git a/apps/user_ldap/lib/proxy.php b/apps/user_ldap/lib/proxy.php new file mode 100644 index 0000000000..c80e216347 --- /dev/null +++ b/apps/user_ldap/lib/proxy.php @@ -0,0 +1,104 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +abstract class Proxy { + static private $connectors = array(); + + public function __construct() { + $this->cache = \OC_Cache::getGlobalCache(); + } + + private function addConnector($configPrefix) { + self::$connectors[$configPrefix] = new \OCA\user_ldap\lib\Connection($configPrefix); + } + + protected function getConnector($configPrefix) { + if(!isset(self::$connectors[$configPrefix])) { + $this->addConnector($configPrefix); + } + return self::$connectors[$configPrefix]; + } + + protected function getConnectors() { + return self::$connectors; + } + + protected function getUserCacheKey($uid) { + return 'user-'.$uid.'-lastSeenOn'; + } + + protected function getGroupCacheKey($gid) { + return 'group-'.$gid.'-lastSeenOn'; + } + + abstract protected function callOnLastSeenOn($id, $method, $parameters); + abstract protected function walkBackends($id, $method, $parameters); + + /** + * @brief Takes care of the request to the User backend + * @param $uid string, the uid connected to the request + * @param $method string, the method of the user backend that shall be called + * @param $parameters an array of parameters to be passed + * @return mixed, the result of the specified method + */ + protected function handleRequest($id, $method, $parameters) { + if(!$result = $this->callOnLastSeenOn($id, $method, $parameters)) { + $result = $this->walkBackends($id, $method, $parameters); + } + return $result; + } + + private function getCacheKey($key) { + $prefix = 'LDAP-Proxy-'; + if(is_null($key)) { + return $prefix; + } + return $prefix.md5($key); + } + + public function getFromCache($key) { + if(!$this->isCached($key)) { + return null; + } + $key = $this->getCacheKey($key); + + return unserialize(base64_decode($this->cache->get($key))); + } + + public function isCached($key) { + $key = $this->getCacheKey($key); + return $this->cache->hasKey($key); + } + + public function writeToCache($key, $value) { + $key = $this->getCacheKey($key); + $value = base64_encode(serialize($value)); + $this->cache->set($key, $value, '2592000'); + } + + public function clearCache() { + $this->cache->clear($this->getCacheKey(null)); + } +} \ No newline at end of file diff --git a/apps/user_ldap/user_proxy.php b/apps/user_ldap/user_proxy.php new file mode 100644 index 0000000000..47f901ddb5 --- /dev/null +++ b/apps/user_ldap/user_proxy.php @@ -0,0 +1,159 @@ +. + * + */ + +namespace OCA\user_ldap; + +class User_Proxy extends lib\Proxy implements \OCP\UserInterface { + private $backends = array(); + private $refBackend = null; + + /** + * @brief Constructor + * @param $serverConfigPrefixes array containing the config Prefixes + */ + public function __construct($serverConfigPrefixes) { + parent::__construct(); + foreach($serverConfigPrefixes as $configPrefix) { + $this->backends[$configPrefix] = new \OCA\user_ldap\USER_LDAP(); + $connector = $this->getConnector($configPrefix); + $this->backends[$configPrefix]->setConnector($connector); + if(is_null($this->refBackend)) { + $this->refBackend = &$this->backends[$configPrefix]; + } + } + } + + /** + * @brief Tries the backends one after the other until a positive result is returned from the specified method + * @param $uid string, the uid connected to the request + * @param $method string, the method of the user backend that shall be called + * @param $parameters an array of parameters to be passed + * @return mixed, the result of the method or false + */ + protected function walkBackends($uid, $method, $parameters) { + $cacheKey = $this->getUserCacheKey($uid); + foreach($this->backends as $configPrefix => $backend) { + if($result = call_user_func_array(array($backend, $method), $parameters)) { + $this->writeToCache($cacheKey, $configPrefix); + return $result; + } + } + return false; + } + + /** + * @brief Asks the backend connected to the server that supposely takes care of the uid from the request. + * @param $uid string, the uid connected to the request + * @param $method string, the method of the user backend that shall be called + * @param $parameters an array of parameters to be passed + * @return mixed, the result of the method or false + */ + protected function callOnLastSeenOn($uid, $method, $parameters) { + $cacheKey = $this->getUserCacheKey($uid); + $prefix = $this->getFromCache($cacheKey); + //in case the uid has been found in the past, try this stored connection first + if(!is_null($prefix)) { + if(isset($this->backends[$prefix])) { + $result = call_user_func_array(array($this->backends[$prefix], $method), $parameters); + if(!$result) { + //not found here, reset cache to null + $this->writeToCache($cacheKey, null); + } + return $result; + } + } + return false; + } + + /** + * @brief Check if backend implements actions + * @param $actions bitwise-or'ed actions + * @returns boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions) { + //it's the same across all our user backends obviously + return $this->refBackend->implementsActions($actions); + } + + /** + * @brief Get a list of all users + * @returns array with all uids + * + * Get a list of all users. + */ + public function getUsers($search = '', $limit = 10, $offset = 0) { + //we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends + $users = array(); + foreach($this->backends as $backend) { + $backendUsers = $backend->getUsers($search, $limit, $offset); + if (is_array($backendUsers)) { + $users = array_merge($users, $backendUsers); + } + } + return $users; + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + return $this->handleRequest($uid, 'userExists', array($uid)); + } + + /** + * @brief Check if the password is correct + * @param $uid The username + * @param $password The password + * @returns true/false + * + * Check if the password is correct without logging in the user + */ + public function checkPassword($uid, $password) { + return $this->handleRequest($uid, 'checkPassword', array($uid, $password)); + } + + /** + * @brief get the user's home directory + * @param string $uid the username + * @return boolean + */ + public function getHome($uid) { + return $this->handleRequest($uid, 'getHome', array($uid)); + } + + /** + * @brief delete a user + * @param $uid The username of the user to delete + * @returns true/false + * + * Deletes a user + */ + public function deleteUser($uid) { + return false; + } +} \ No newline at end of file From 8a63bcc1e85499335ade1df459f0c9f467161404 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 11 Jan 2013 20:56:36 -0500 Subject: [PATCH 178/418] Don't use more entropy for etags --- lib/files/storage/common.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php index e859d447f3..591803f044 100644 --- a/lib/files/storage/common.php +++ b/lib/files/storage/common.php @@ -274,7 +274,7 @@ abstract class Common implements \OC\Files\Storage\Storage { $hash = call_user_func($ETagFunction, $path); return $hash; }else{ - return uniqid('', true); + return uniqid(); } } } From 9e2f3a53244e353cb75f9927e1c69ef40f589db7 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 11 Jan 2013 20:59:53 -0500 Subject: [PATCH 179/418] Remove old create etag function --- lib/connector/sabre/file.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/connector/sabre/file.php b/lib/connector/sabre/file.php index 1770b49128..e0723e1230 100644 --- a/lib/connector/sabre/file.php +++ b/lib/connector/sabre/file.php @@ -101,15 +101,6 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D return $this->getETagPropertyForPath($this->path); } - /** - * Creates a ETag for this path. - * @param string $path Path of the file - * @return string|null Returns null if the ETag can not effectively be determined - */ - static protected function createETag($path) { - return \OC\Files\Filesystem::hash('md5', $path); - } - /** * Returns the mime-type for a file * From a00b9e0a03a7cdb1103415d33d2c3f22e86edffb Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 11 Jan 2013 21:01:28 -0500 Subject: [PATCH 180/418] Bump version --- lib/util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.php b/lib/util.php index e814a3a32d..faae962859 100755 --- a/lib/util.php +++ b/lib/util.php @@ -74,7 +74,7 @@ class OC_Util { */ public static function getVersion() { // hint: We only can count up. So the internal version number of ownCloud 4.5 will be 4.90.0. This is not visible to the user - return array(4,91,06); + return array(4,91,07); } /** From b30648cb7dc258208fad4dadb297aa1d792fa7ef Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 11 Jan 2013 21:09:01 -0500 Subject: [PATCH 181/418] Don't waste time making another call since we know it doesn't exist --- lib/connector/sabre/file.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connector/sabre/file.php b/lib/connector/sabre/file.php index e0723e1230..1c18a39174 100644 --- a/lib/connector/sabre/file.php +++ b/lib/connector/sabre/file.php @@ -98,7 +98,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D if (isset($properties[self::GETETAG_PROPERTYNAME])) { return $properties[self::GETETAG_PROPERTYNAME]; } - return $this->getETagPropertyForPath($this->path); + return null; } /** From 094a852bff378c03c873b6e8fd94587d2a2c784e Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 11 Jan 2013 21:09:58 -0500 Subject: [PATCH 182/418] Wrap the etag in double quotes --- lib/connector/sabre/node.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php index dd8ae152ff..93d8fc477d 100644 --- a/lib/connector/sabre/node.php +++ b/lib/connector/sabre/node.php @@ -213,7 +213,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr static public function getETagPropertyForPath($path) { $data = \OC\Files\Filesystem::getFileInfo($path); if (isset($data['etag'])) { - return $data['etag']; + return '"'.$data['etag'].'"'; } return null; } From ebcf41b420d3cd2f013b1b834b3c55d31c39041f Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 12 Jan 2013 23:35:13 -0500 Subject: [PATCH 183/418] Move data directory permission checks after data directory existence checks --- lib/util.php | 57 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/lib/util.php b/lib/util.php index 7b1de094ea..bc8d22a9e9 100755 --- a/lib/util.php +++ b/lib/util.php @@ -207,35 +207,7 @@ class OC_Util { in owncloud or disabling the appstore in the config file."); } } - $CONFIG_DATADIRECTORY = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); - //check for correct file permissions - if(!stristr(PHP_OS, 'WIN')) { - $permissionsModHint="Please change the permissions to 0770 so that the directory cannot be listed by other users."; - $prems=substr(decoct(@fileperms($CONFIG_DATADIRECTORY)), -3); - if(substr($prems, -1)!='0') { - OC_Helper::chmodr($CONFIG_DATADIRECTORY, 0770); - clearstatcache(); - $prems=substr(decoct(@fileperms($CONFIG_DATADIRECTORY)), -3); - if(substr($prems, 2, 1)!='0') { - $errors[]=array('error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') is readable for other users
', 'hint'=>$permissionsModHint); - } - } - if( OC_Config::getValue( "enablebackup", false )) { - $CONFIG_BACKUPDIRECTORY = OC_Config::getValue( "backupdirectory", OC::$SERVERROOT."/backup" ); - $prems=substr(decoct(@fileperms($CONFIG_BACKUPDIRECTORY)), -3); - if(substr($prems, -1)!='0') { - OC_Helper::chmodr($CONFIG_BACKUPDIRECTORY, 0770); - clearstatcache(); - $prems=substr(decoct(@fileperms($CONFIG_BACKUPDIRECTORY)), -3); - if(substr($prems, 2, 1)!='0') { - $errors[]=array('error'=>'Data directory ('.$CONFIG_BACKUPDIRECTORY.') is readable for other users
', 'hint'=>$permissionsModHint); - } - } - } - }else{ - //TODO: permissions checks for windows hosts - } // Create root dir. if(!is_dir($CONFIG_DATADIRECTORY)) { $success=@mkdir($CONFIG_DATADIRECTORY); @@ -244,8 +216,35 @@ class OC_Util { } } else if(!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) { $errors[]=array('error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') not writable by ownCloud
', 'hint'=>$permissionsHint); + } else { + //check for correct file permissions + if(!stristr(PHP_OS, 'WIN')) { + $permissionsModHint="Please change the permissions to 0770 so that the directory cannot be listed by other users."; + $prems=substr(decoct(@fileperms($CONFIG_DATADIRECTORY)), -3); + if(substr($prems, -1)!='0') { + OC_Helper::chmodr($CONFIG_DATADIRECTORY, 0770); + clearstatcache(); + $prems=substr(decoct(@fileperms($CONFIG_DATADIRECTORY)), -3); + if(substr($prems, 2, 1)!='0') { + $errors[]=array('error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') is readable for other users
', 'hint'=>$permissionsModHint); + } + } + if( OC_Config::getValue( "enablebackup", false )) { + $CONFIG_BACKUPDIRECTORY = OC_Config::getValue( "backupdirectory", OC::$SERVERROOT."/backup" ); + $prems=substr(decoct(@fileperms($CONFIG_BACKUPDIRECTORY)), -3); + if(substr($prems, -1)!='0') { + OC_Helper::chmodr($CONFIG_BACKUPDIRECTORY, 0770); + clearstatcache(); + $prems=substr(decoct(@fileperms($CONFIG_BACKUPDIRECTORY)), -3); + if(substr($prems, 2, 1)!='0') { + $errors[]=array('error'=>'Data directory ('.$CONFIG_BACKUPDIRECTORY.') is readable for other users
', 'hint'=>$permissionsModHint); + } + } + } + } else { + //TODO: permissions checks for windows hosts + } } - // check if all required php modules are present if(!class_exists('ZipArchive')) { $errors[]=array('error'=>'PHP module zip not installed.
', 'hint'=>'Please ask your server administrator to install the module.'); From 94068e5d08cba776e410d925e26037d442b5bc62 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 3 Jan 2013 00:26:13 +0100 Subject: [PATCH 184/418] Cache: show upgrade progress --- apps/files/ajax/upgrade.php | 42 ++++++++ apps/files/appinfo/app.php | 10 -- apps/files/css/files.css | 9 ++ apps/files/index.php | 72 ++++++++------ apps/files/js/upgrade.js | 17 ++++ apps/files/templates/upgrade.php | 4 + lib/files/cache/legacy.php | 73 ++++++++++++++ lib/files/cache/upgrade.php | 161 ++++++++++++++++++++++--------- 8 files changed, 305 insertions(+), 83 deletions(-) create mode 100644 apps/files/ajax/upgrade.php create mode 100644 apps/files/js/upgrade.js create mode 100644 apps/files/templates/upgrade.php create mode 100644 lib/files/cache/legacy.php diff --git a/apps/files/ajax/upgrade.php b/apps/files/ajax/upgrade.php new file mode 100644 index 0000000000..965c0073b8 --- /dev/null +++ b/apps/files/ajax/upgrade.php @@ -0,0 +1,42 @@ +hasItems()) { + OC_Hook::connect('\OC\Files\Cache\Upgrade', 'migrate_path', $listener, 'upgradePath'); + + $upgrade = new \OC\Files\Cache\Upgrade($legacy); + $count = $legacy->getCount(); + $eventSource->send('total', $count); + $upgrade->upgradePath('/' . $user . '/files'); +} +\OC\Files\Cache\Upgrade::upgradeDone($user); +$eventSource->send('done', true); +$eventSource->close(); + +class UpgradeListener { + /** + * @var OC_EventSource $eventSource + */ + private $eventSource; + + private $count = 0; + private $lastSend = 0; + + public function __construct($eventSource) { + $this->eventSource = $eventSource; + } + + public function upgradePath($path) { + $this->count++; + if ($this->count > ($this->lastSend + 5)) { + $this->lastSend = $this->count; + $this->eventSource->send('count', $this->count); + } + } +} diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php index 643d8ed18a..ab2f3b01a2 100644 --- a/apps/files/appinfo/app.php +++ b/apps/files/appinfo/app.php @@ -10,13 +10,3 @@ OCP\App::addNavigationEntry( array( "id" => "files_index", "name" => $l->t("Files") )); OC_Search::registerProvider('OC_Search_Provider_File'); - -if (OC_User::isLoggedIn()) { - // update OC4.5 filecache to OC5 filecache, can't do this in update.php since it needs to happen for each user individually - $cacheVersion = (int)OCP\Config::getUserValue(OC_User::getUser(), 'files', 'cache_version', 4); - if ($cacheVersion < 5) { - \OC_Log::write('files', 'updating filecache to 5.0 for user ' . OC_User::getUser(), \OC_Log::INFO); - \OC\Files\Cache\Upgrade::upgrade(); - OCP\Config::setUserValue(OC_User::getUser(), 'files', 'cache_version', 5); - } -} diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 36a1e5c954..0c130efe47 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -122,3 +122,12 @@ a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; } #scanning-message{ top:40%; left:40%; position:absolute; display:none; } div.crumb a{ padding:0.9em 0 0.7em 0; } + +#upgrade { + width: 400px; + position: absolute; + top: 200px; + left: 50%; + text-align: center; + margin-left: -200px; +} diff --git a/apps/files/index.php b/apps/files/index.php index 993d8b4dcf..0dce768696 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -28,7 +28,6 @@ OCP\User::checkLoggedIn(); OCP\Util::addStyle('files', 'files'); OCP\Util::addscript('files', 'jquery.iframe-transport'); OCP\Util::addscript('files', 'jquery.fileupload'); -OCP\Util::addscript('files', 'files'); OCP\Util::addscript('files', 'filelist'); OCP\Util::addscript('files', 'fileactions'); OCP\Util::addscript('files', 'keyboardshortcuts'); @@ -37,8 +36,8 @@ OCP\App::setActiveNavigationEntry('files_index'); // Load the files $dir = isset($_GET['dir']) ? stripslashes($_GET['dir']) : ''; // Redirect if directory does not exist -if(!\OC\Files\Filesystem::is_dir($dir . '/')) { - header('Location: '.$_SERVER['SCRIPT_NAME'].''); +if (!\OC\Files\Filesystem::is_dir($dir . '/')) { + header('Location: ' . $_SERVER['SCRIPT_NAME'] . ''); exit(); } @@ -53,16 +52,25 @@ function fileCmp($a, $b) { } $files = array(); -foreach( \OC\Files\Filesystem::getDirectoryContent( $dir ) as $i ) { - $i['date'] = OCP\Util::formatDate($i['mtime'] ); - if($i['type'] == 'file') { +$user = OC_User::getUser(); +if (\OC\Files\Cache\Upgrade::needUpgrade($user)) { //dont load anything if we need to upgrade the cache + $content = array(); + $needUpgrade = true; + $freeSpace = 0; +} else { + $content = \OC\Files\Filesystem::getDirectoryContent($dir); + $freeSpace = \OC\Files\Filesystem::free_space($dir); + $needUpgrade = false; +} +foreach ($content as $i) { + $i['date'] = OCP\Util::formatDate($i['mtime']); + if ($i['type'] == 'file') { $fileinfo = pathinfo($i['name']); $i['basename'] = $fileinfo['filename']; if (!empty($fileinfo['extension'])) { - $i['extension']='.' . $fileinfo['extension']; - } - else { - $i['extension']=''; + $i['extension'] = '.' . $fileinfo['extension']; + } else { + $i['extension'] = ''; } } $i['directory'] = $dir; @@ -74,10 +82,10 @@ usort($files, "fileCmp"); // Make breadcrumb $breadcrumb = array(); $pathtohere = ''; -foreach( explode( '/', $dir ) as $i ) { - if( $i != '' ) { +foreach (explode('/', $dir) as $i) { + if ($i != '') { $pathtohere .= '/' . $i; - $breadcrumb[] = array( 'dir' => $pathtohere, 'name' => $i ); + $breadcrumb[] = array('dir' => $pathtohere, 'name' => $i); } } @@ -94,29 +102,35 @@ $upload_max_filesize = OCP\Util::computerFileSize(ini_get('upload_max_filesize') $post_max_size = OCP\Util::computerFileSize(ini_get('post_max_size')); $maxUploadFilesize = min($upload_max_filesize, $post_max_size); -$freeSpace = \OC\Files\Filesystem::free_space($dir); -$freeSpace = max($freeSpace,0); +$freeSpace = max($freeSpace, 0); $maxUploadFilesize = min($maxUploadFilesize, $freeSpace); $permissions = OCP\PERMISSION_READ; if (\OC\Files\Filesystem::isUpdatable($dir . '/')) { - $permissions |= OCP\PERMISSION_UPDATE; + $permissions |= OCP\PERMISSION_UPDATE; } if (\OC\Files\Filesystem::isDeletable($dir . '/')) { - $permissions |= OCP\PERMISSION_DELETE; + $permissions |= OCP\PERMISSION_DELETE; } if (\OC\Files\Filesystem::isSharable($dir . '/')) { - $permissions |= OCP\PERMISSION_SHARE; + $permissions |= OCP\PERMISSION_SHARE; } -$tmpl = new OCP\Template( 'files', 'index', 'user' ); -$tmpl->assign( 'fileList', $list->fetchPage(), false ); -$tmpl->assign( 'breadcrumb', $breadcrumbNav->fetchPage(), false ); -$tmpl->assign( 'dir', \OC\Files\Filesystem::normalizePath($dir)); -$tmpl->assign( 'isCreatable', \OC\Files\Filesystem::isCreatable($dir . '/')); -$tmpl->assign('permissions', $permissions); -$tmpl->assign('files', $files); -$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); -$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize)); -$tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); -$tmpl->printPage(); +if ($needUpgrade) { + OCP\Util::addscript('files', 'upgrade'); + $tmpl = new OCP\Template('files', 'upgrade', 'user'); + $tmpl->printPage(); +} else { + OCP\Util::addscript('files', 'files'); + $tmpl = new OCP\Template('files', 'index', 'user'); + $tmpl->assign('fileList', $list->fetchPage(), false); + $tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); + $tmpl->assign('dir', \OC\Files\Filesystem::normalizePath($dir)); + $tmpl->assign('isCreatable', \OC\Files\Filesystem::isCreatable($dir . '/')); + $tmpl->assign('permissions', $permissions); + $tmpl->assign('files', $files); + $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); + $tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize)); + $tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); + $tmpl->printPage(); +} diff --git a/apps/files/js/upgrade.js b/apps/files/js/upgrade.js new file mode 100644 index 0000000000..02d57fc9e6 --- /dev/null +++ b/apps/files/js/upgrade.js @@ -0,0 +1,17 @@ +$(document).ready(function () { + var eventSource, total, bar = $('#progressbar'); + console.log('start'); + bar.progressbar({value: 0}); + eventSource = new OC.EventSource(OC.filePath('files', 'ajax', 'upgrade.php')); + eventSource.listen('total', function (count) { + total = count; + console.log(count + ' files needed to be migrated'); + }); + eventSource.listen('count', function (count) { + bar.progressbar({value: (count / total) * 100}); + console.log(count); + }); + eventSource.listen('done', function () { + document.location.reload(); + }); +}); diff --git a/apps/files/templates/upgrade.php b/apps/files/templates/upgrade.php new file mode 100644 index 0000000000..de6cc71302 --- /dev/null +++ b/apps/files/templates/upgrade.php @@ -0,0 +1,4 @@ +
+ t('Upgrading filesystem cache...');?> +
+
diff --git a/lib/files/cache/legacy.php b/lib/files/cache/legacy.php new file mode 100644 index 0000000000..ee10a1c135 --- /dev/null +++ b/lib/files/cache/legacy.php @@ -0,0 +1,73 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * Provide read only support for the old filecache + */ +class Legacy { + private $user; + + public function __construct($user) { + $this->user = $user; + } + + function getCount() { + $query = \OC_DB::prepare('SELECT COUNT(`id`) AS `count` FROM `*PREFIX*fscache` WHERE `user` = ?'); + $result = $query->execute(array($this->user)); + if ($row = $result->fetchRow()) { + return $row['count']; + } else { + return 0; + } + } + + /** + * check if a legacy cache is present and holds items + * + * @return bool + */ + function hasItems() { + try { + $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*fscache` WHERE `user` = ? LIMIT 1'); + } catch (\Exception $e) { + return false; + } + try { + $result = $query->execute(array($this->user)); + } catch (\Exception $e) { + return false; + } + return (bool)$result->fetchRow(); + } + + /** + * @param string|int $path + * @return array + */ + function get($path) { + if (is_numeric($path)) { + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` WHERE `id` = ?'); + } else { + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` WHERE `path` = ?'); + } + $result = $query->execute(array($path)); + return $result->fetchRow(); + } + + /** + * @param int $id + * @return array + */ + function getChildren($id) { + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` WHERE `parent` = ?'); + $result = $query->execute(array($id)); + return $result->fetchAll(); + } +} diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php index 77db4c2339..1032e0a844 100644 --- a/lib/files/cache/upgrade.php +++ b/lib/files/cache/upgrade.php @@ -9,62 +9,102 @@ namespace OC\Files\Cache; class Upgrade { - static $permissionsCaches = array(); + /** + * @var Legacy $legacy + */ + private $legacy; - static $numericIds = array(); + private $permissionsCaches = array(); - static function upgrade() { - $insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` ) - VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + private $numericIds = array(); - try { - $oldEntriesQuery = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` ORDER BY `id` ASC'); //sort ascending to ensure the parent gets inserted before a child - } catch (\Exception $e) { - return; - } - try { - $oldEntriesResult = $oldEntriesQuery->execute(); - } catch (\Exception $e) { - return; - } - if (!$oldEntriesResult) { + private $mimeTypeIds = array(); + + /** + * @param Legacy $legacy + */ + public function __construct($legacy) { + $this->legacy = $legacy; + } + + /** + * Preform a shallow upgrade + * + * @param string $path + * @param int $mode + */ + function upgradePath($path, $mode = Scanner::SCAN_RECURSIVE) { + if (!$this->legacy->hasItems()) { return; } + \OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $path); - $checkExistingQuery = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `fileid` = ?'); + if ($row = $this->legacy->get($path)) { + $data = $this->getNewData($row); + $this->insert($data); - while ($row = $oldEntriesResult->fetchRow()) { - if ($checkExistingQuery->execute(array($row['id']))->fetchRow()) { - continue; + $children = $this->legacy->getChildren($data['id']); + foreach ($children as $child) { + if ($mode == Scanner::SCAN_SHALLOW) { + $childData = $this->getNewData($child); + \OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $child['path']); + $this->insert($childData); + } else { + $this->upgradePath($child['path']); + } } - - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($row['path']); - /** - * @var \OC\Files\Storage\Storage $storage - * @var string $internalPath; - */ - $pathHash = md5($internalPath); - $storageId = self::getNumericId($storage); - $parentId = ($internalPath === '') ? -1 : $row['parent']; - - $insertQuery->execute(array($row['id'], $storageId, $internalPath, $pathHash, $parentId, $row['name'], $row['mimetype'], $row['mimepart'], $row['size'], $row['mtime'], $row['encrypted'])); - - $permissions = ($row['writable']) ? \OCP\PERMISSION_ALL : \OCP\PERMISSION_READ; - $permissionsCache = self::getPermissionsCache($storage); - $permissionsCache->set($row['id'], $row['user'], $permissions); } } + /** + * @param array $data the data for the new cache + */ + function insert($data) { + $insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache` + ( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` ) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + + $insertQuery->execute(array($data['id'], $data['storage'], $data['path'], $data['path_hash'], $data['parent'], $data['name'], + $data['mimetype'], $data['mimepart'], $data['size'], $data['mtime'], $data['encrypted'])); + + $permissionsCache = $this->getPermissionsCache($data['storage_object']); + $permissionsCache->set($data['id'], $data['user'], $data['permissions']); + } + + /** + * get the new data array from the old one + * + * @param array $data the data from the old cache + * @return array + */ + function getNewData($data) { + $newData = $data; + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($data['path']); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath; + */ + $newData['path_hash'] = md5($internalPath); + $newData['path'] = $internalPath; + $newData['storage'] = $this->getNumericId($storage); + $newData['parent'] = ($internalPath === '') ? -1 : $data['parent']; + $newData['permissions'] = ($data['writable']) ? \OCP\PERMISSION_ALL : \OCP\PERMISSION_READ; + $newData['storage_object'] = $storage; + $newData['mimetype'] = $this->getMimetypeId($newData['mimetype'], $storage); + $newData['mimepart'] = $this->getMimetypeId($newData['mimepart'], $storage); + return $newData; + } + /** * @param \OC\Files\Storage\Storage $storage * @return Permissions */ - static function getPermissionsCache($storage) { + function getPermissionsCache($storage) { $storageId = $storage->getId(); - if (!isset(self::$permissionsCaches[$storageId])) { - self::$permissionsCaches[$storageId] = $storage->getPermissionsCache(); + if (!isset($this->permissionsCaches[$storageId])) { + $this->permissionsCaches[$storageId] = $storage->getPermissionsCache(); } - return self::$permissionsCaches[$storageId]; + return $this->permissionsCaches[$storageId]; } /** @@ -73,12 +113,45 @@ class Upgrade { * @param \OC\Files\Storage\Storage $storage * @return int */ - static function getNumericId($storage) { + function getNumericId($storage) { $storageId = $storage->getId(); - if (!isset(self::$numericIds[$storageId])) { - $cache = new Cache($storage); - self::$numericIds[$storageId] = $cache->getNumericStorageId(); + if (!isset($this->numericIds[$storageId])) { + $cache = $storage->getCache(); + $this->numericIds[$storageId] = $cache->getNumericStorageId(); } - return self::$numericIds[$storageId]; + return $this->numericIds[$storageId]; + } + + /** + * @param string $mimetype + * @param \OC\Files\Storage\Storage $storage + * @return int + */ + function getMimetypeId($mimetype, $storage) { + if (!isset($this->mimeTypeIds[$mimetype])) { + $cache = new Cache($storage); + $this->mimeTypeIds[$mimetype] = $cache->getMimetypeId($mimetype); + } + return $this->mimeTypeIds[$mimetype]; + } + + /** + * check if a cache upgrade is required for $user + * + * @param string $user + * @return bool + */ + static function needUpgrade($user) { + $cacheVersion = (int)\OCP\Config::getUserValue($user, 'files', 'cache_version', 4); + return $cacheVersion < 5; + } + + /** + * mark the filecache as upgrade + * + * @param string $user + */ + static function upgradeDone($user) { + \OCP\Config::setUserValue($user, 'files', 'cache_version', 5); } } From 7debfac0dc8f602168d8db480cfd4757b4d612b0 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 15 Jan 2013 19:11:12 +0100 Subject: [PATCH 185/418] Cache: more efficient upgrading --- apps/files/ajax/upgrade.php | 2 ++ lib/files/cache/legacy.php | 10 +++++++++- lib/files/cache/upgrade.php | 24 +++++++++++++++--------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/apps/files/ajax/upgrade.php b/apps/files/ajax/upgrade.php index 965c0073b8..7237b02c0b 100644 --- a/apps/files/ajax/upgrade.php +++ b/apps/files/ajax/upgrade.php @@ -10,10 +10,12 @@ $legacy = new \OC\Files\Cache\Legacy($user); if ($legacy->hasItems()) { OC_Hook::connect('\OC\Files\Cache\Upgrade', 'migrate_path', $listener, 'upgradePath'); + OC_DB::beginTransaction(); $upgrade = new \OC\Files\Cache\Upgrade($legacy); $count = $legacy->getCount(); $eventSource->send('total', $count); $upgrade->upgradePath('/' . $user . '/files'); + OC_DB::commit(); } \OC\Files\Cache\Upgrade::upgradeDone($user); $eventSource->send('done', true); diff --git a/lib/files/cache/legacy.php b/lib/files/cache/legacy.php index ee10a1c135..33d4b8e7c9 100644 --- a/lib/files/cache/legacy.php +++ b/lib/files/cache/legacy.php @@ -14,6 +14,8 @@ namespace OC\Files\Cache; class Legacy { private $user; + private $cacheHasItems = null; + public function __construct($user) { $this->user = $user; } @@ -34,17 +36,23 @@ class Legacy { * @return bool */ function hasItems() { + if (!is_null($this->cacheHasItems)) { + return $this->cacheHasItems; + } try { $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*fscache` WHERE `user` = ? LIMIT 1'); } catch (\Exception $e) { + $this->cacheHasItems = false; return false; } try { $result = $query->execute(array($this->user)); } catch (\Exception $e) { + $this->cacheHasItems = false; return false; } - return (bool)$result->fetchRow(); + $this->cacheHasItems = (bool)$result->fetchRow(); + return $this->cacheHasItems; } /** diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php index 1032e0a844..cd9a9e91a8 100644 --- a/lib/files/cache/upgrade.php +++ b/lib/files/cache/upgrade.php @@ -43,15 +43,21 @@ class Upgrade { $data = $this->getNewData($row); $this->insert($data); - $children = $this->legacy->getChildren($data['id']); - foreach ($children as $child) { - if ($mode == Scanner::SCAN_SHALLOW) { - $childData = $this->getNewData($child); - \OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $child['path']); - $this->insert($childData); - } else { - $this->upgradePath($child['path']); - } + $this->upgradeChilds($data['id'], $mode); + } + } + + /** + * @param int $id + */ + function upgradeChilds($id, $mode = Scanner::SCAN_RECURSIVE) { + $children = $this->legacy->getChildren($id); + foreach ($children as $child) { + $childData = $this->getNewData($child); + \OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $child['path']); + $this->insert($childData); + if ($mode == Scanner::SCAN_RECURSIVE) { + $this->upgradeChilds($child['id']); } } } From 73d45e79a7066b56581072c732f38ca375a4fc29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Tue, 15 Jan 2013 20:35:15 +0100 Subject: [PATCH 186/418] add trash button to web interface --- apps/files/css/files.css | 2 ++ apps/files/index.php | 1 + apps/files/js/files.js | 5 +++++ apps/files/templates/index.php | 6 +++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 36a1e5c954..6355a8cde1 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -23,6 +23,8 @@ #new>ul>li>p { cursor:pointer; } #new>ul>li>form>input { padding:0.3em; margin:-0.3em; } +#trash { height:17px; margin:0 0 0 1em; z-index:1010; position:absolute; right:13.5em; } + #upload { height:27px; padding:0; margin-left:0.2em; overflow:hidden; } diff --git a/apps/files/index.php b/apps/files/index.php index b64bde44cc..24f58bbd07 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -105,6 +105,7 @@ $tmpl->assign('dir', OC_Filesystem::normalizePath($dir)); $tmpl->assign('isCreatable', OC_Filesystem::isCreatable($dir . '/')); $tmpl->assign('permissions', $permissions); $tmpl->assign('files', $files); +$tmpl->assign('trash', \OCP\App::isEnabled('files_trashbin')); $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); $tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize)); $tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); diff --git a/apps/files/js/files.js b/apps/files/js/files.js index bb298431e8..c13d7a5961 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -82,6 +82,11 @@ $(document).ready(function() { $(this).parent().children('#file_upload_start').trigger('click'); return false; }); + + // Show Trash bin + $('#trash a').live('click', function() { + console.log("hello"); + }); var lastChecked; diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 2e0772443f..f6b4c29d5a 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -35,6 +35,11 @@
+ + +
-
From ad1113c2cb06f1c35102c50e24d803fa1bd2d367 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 16 Jan 2013 14:56:57 +0100 Subject: [PATCH 187/418] LDAP: fix parameter passed not as expected --- apps/user_ldap/lib/access.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 422e43fc00..27c7444697 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -912,7 +912,7 @@ abstract class Access { $reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit; //a bit recursive, $offset of 0 is the exit \OCP\Util::writeLog('user_ldap', 'Looking for cookie L/O '.$limit.'/'.$reOffset, \OCP\Util::INFO); - $this->search($filter, $base, $attr, $limit, $reOffset, true); + $this->search($filter, array($base), $attr, $limit, $reOffset, true); $cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset); //still no cookie? obviously, the server does not like us. Let's skip paging efforts. //TODO: remember this, probably does not change in the next request... From 4699f36e4406a50e3b44a2b69b3ecc37a93c321c Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 16 Jan 2013 14:58:49 +0100 Subject: [PATCH 188/418] LDAP: fix read configuration, remove unnecessary debug output --- apps/user_ldap/lib/connection.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 803ac34f59..1dc1d1510a 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -70,7 +70,6 @@ class Connection { $this->configID = $configID; $this->cache = \OC_Cache::getGlobalCache(); $this->config['hasPagedResultSupport'] = (function_exists('ldap_control_paged_result') && function_exists('ldap_control_paged_result_response')); - \OCP\Util::writeLog('user_ldap', 'PHP supports paged results? '.print_r($this->config['hasPagedResultSupport'], true), \OCP\Util::INFO); } public function __destruct() { @@ -187,20 +186,20 @@ class Connection { * Caches the general LDAP configuration. */ private function readConfiguration($force = false) { - \OCP\Util::writeLog('user_ldap', 'Checking conf state: isConfigured? '.print_r($this->configured, true).' isForce? '.print_r($force, true).' configID? '.print_r($this->configID, true), \OCP\Util::DEBUG); if((!$this->configured || $force) && !is_null($this->configID)) { - \OCP\Util::writeLog('user_ldap', 'Reading the configuration', \OCP\Util::DEBUG); $this->config['ldapHost'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_host', ''); $this->config['ldapPort'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_port', 389); $this->config['ldapAgentName'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_dn', ''); $this->config['ldapAgentPassword'] = base64_decode(\OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_agent_password', '')); - $this->config['ldapBase'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base', '')); - $this->config['ldapBaseUsers'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base_users', $this->config['ldapBase'])); - $this->config['ldapBaseGroups'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base_groups', $this->config['ldapBase'])); + $rawLdapBase = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base', ''); + $this->config['ldapBase'] = preg_split('/\r\n|\r|\n/', $rawLdapBase); + $this->config['ldapBaseUsers'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base_users', $rawLdapBase)); + $this->config['ldapBaseGroups'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base_groups', $rawLdapBase)); + unset($rawLdapBase); $this->config['ldapTLS'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_tls', 0); $this->config['ldapNoCase'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_nocase', 0); $this->config['turnOffCertCheck'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_turn_off_cert_check', 0); - $this->config['ldapUserDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, '$this->configPrefix.ldap_display_name', 'uid'), 'UTF-8'); + $this->config['ldapUserDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_display_name', 'uid'), 'UTF-8'); $this->config['ldapUserFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_userlist_filter', 'objectClass=person'); $this->config['ldapGroupFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_group_filter', '(objectClass=posixGroup)'); $this->config['ldapLoginFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_login_filter', '(uid=%uid)'); From 6063ce9c8d3f32a7c9d53a0aa77d2cbfa0798f7b Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 16 Jan 2013 14:59:41 +0100 Subject: [PATCH 189/418] LDAP: enable support for multiple LDAP/AD servers --- apps/user_ldap/appinfo/app.php | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index 9e72e388e6..69860ba143 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -23,11 +23,27 @@ OCP\App::registerAdmin('user_ldap', 'settings'); -$connector = new OCA\user_ldap\lib\Connection('', 'user_ldap'); -$userBackend = new OCA\user_ldap\USER_LDAP(); -$userBackend->setConnector($connector); -$groupBackend = new OCA\user_ldap\GROUP_LDAP(); -$groupBackend->setConnector($connector); +$query = \OCP\DB::prepare(' + SELECT DISTINCT `configkey` + FROM `*PREFIX*appconfig` + WHERE `configkey` LIKE ? +'); +$serverConnections = $query->execute(array('%ldap_login_filter'))->fetchAll(); +if(count($serverConnections) == 1) { + $prefix = substr($serverConnections[0]['configkey'], 0, strlen($serverConnections[0]['configkey'])- strlen('ldap_login_filter')); + $connector = new OCA\user_ldap\lib\Connection($prefix); + $userBackend = new OCA\user_ldap\USER_LDAP(); + $userBackend->setConnector($connector); + $groupBackend = new OCA\user_ldap\GROUP_LDAP(); + $groupBackend->setConnector($connector); +} else { + $prefixes = array(); + foreach($serverConnections as $serverConnection) { + $prefixes[] = substr($serverConnection['configkey'], 0, strlen($serverConnection['configkey'])- strlen('ldap_login_filter')); + } + $userBackend = new OCA\user_ldap\User_Proxy($prefixes); + $groupBackend = new OCA\user_ldap\Group_Proxy($prefixes); +} // register user backend OC_User::useBackend($userBackend); From c494eb79ab049d71113c786375b1ee11338e5edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Wed, 16 Jan 2013 16:01:11 +0100 Subject: [PATCH 190/418] listen to post delete event to allow the trash bin to create a copy of the version first --- apps/files_versions/appinfo/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_versions/appinfo/app.php b/apps/files_versions/appinfo/app.php index afc0a67edb..edd0a2f702 100644 --- a/apps/files_versions/appinfo/app.php +++ b/apps/files_versions/appinfo/app.php @@ -12,5 +12,5 @@ OCP\Util::addscript('files_versions', 'versions'); // Listen to write signals OCP\Util::connectHook('OC_Filesystem', 'write', "OCA_Versions\Hooks", "write_hook"); // Listen to delete and rename signals -OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA_Versions\Hooks", "remove_hook"); +OCP\Util::connectHook('OC_Filesystem', 'post-delete', "OCA_Versions\Hooks", "remove_hook"); OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA_Versions\Hooks", "rename_hook"); \ No newline at end of file From 29ec00797948d98ffa5dcb0baa1518630ab3ef56 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 16 Jan 2013 19:11:33 +0100 Subject: [PATCH 191/418] Cache: dont migrate permissions, cache them on demain instead --- lib/files/cache/upgrade.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php index cd9a9e91a8..19e3d9ad57 100644 --- a/lib/files/cache/upgrade.php +++ b/lib/files/cache/upgrade.php @@ -14,8 +14,6 @@ class Upgrade { */ private $legacy; - private $permissionsCaches = array(); - private $numericIds = array(); private $mimeTypeIds = array(); @@ -72,9 +70,6 @@ class Upgrade { $insertQuery->execute(array($data['id'], $data['storage'], $data['path'], $data['path_hash'], $data['parent'], $data['name'], $data['mimetype'], $data['mimepart'], $data['size'], $data['mtime'], $data['encrypted'])); - - $permissionsCache = $this->getPermissionsCache($data['storage_object']); - $permissionsCache->set($data['id'], $data['user'], $data['permissions']); } /** @@ -101,18 +96,6 @@ class Upgrade { return $newData; } - /** - * @param \OC\Files\Storage\Storage $storage - * @return Permissions - */ - function getPermissionsCache($storage) { - $storageId = $storage->getId(); - if (!isset($this->permissionsCaches[$storageId])) { - $this->permissionsCaches[$storageId] = $storage->getPermissionsCache(); - } - return $this->permissionsCaches[$storageId]; - } - /** * get the numeric storage id * From f9c42a196f03bb193b07a8f5f8ecf42b911ef4b5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 16 Jan 2013 21:36:04 +0100 Subject: [PATCH 192/418] Cache: no longer using this file --- lib/files/file.php | 61 ---------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 lib/files/file.php diff --git a/lib/files/file.php b/lib/files/file.php deleted file mode 100644 index 0d33cea7ee..0000000000 --- a/lib/files/file.php +++ /dev/null @@ -1,61 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -namespace OC\Files; - -/** - * representation of the location a file or folder is stored - */ - -class File{ - /** - * @var Storage\Storage $storage - */ - private $storage; - /** - * @var string internalPath - */ - private $internalPath; - - public function __construct(Storage\Storage $storage, $internalPath){ - $this->storage = $storage; - $this->internalPath = $internalPath; - } - - public static function resolve($fullPath){ - $storage = null; - $internalPath = ''; - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($fullPath); - return new File($storage, $internalPath); - } - - /** - * get the internal path of the file inside the filestorage - * @return string - */ - public function getInternalPath(){ - return $this->internalPath; - } - - /** - * get the storage the file is stored in - * @return \OC\Files\Storage\Storage - */ - public function getStorage(){ - return $this->storage; - } - - /** - * get the id of the storage the file is stored in - * @return string - */ - public function getStorageId(){ - return $this->storage->getId(); - } - -} From bb43cf378b5e7c6a18a30d8fbb226b72f1e4eb88 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 16 Jan 2013 21:36:25 +0100 Subject: [PATCH 193/418] Files: make sure keybinds js is loaded after files js --- apps/files/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files/index.php b/apps/files/index.php index 0dce768696..854218eee5 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -30,7 +30,6 @@ OCP\Util::addscript('files', 'jquery.iframe-transport'); OCP\Util::addscript('files', 'jquery.fileupload'); OCP\Util::addscript('files', 'filelist'); OCP\Util::addscript('files', 'fileactions'); -OCP\Util::addscript('files', 'keyboardshortcuts'); OCP\App::setActiveNavigationEntry('files_index'); // Load the files @@ -122,6 +121,7 @@ if ($needUpgrade) { $tmpl->printPage(); } else { OCP\Util::addscript('files', 'files'); + OCP\Util::addscript('files', 'keyboardshortcuts'); $tmpl = new OCP\Template('files', 'index', 'user'); $tmpl->assign('fileList', $list->fetchPage(), false); $tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); From 6871a150bd1309af0ca22e45487043d9640bb356 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 16 Jan 2013 21:58:17 +0100 Subject: [PATCH 194/418] Cache: use a database transition for scanning each folder gives a massive speed improvement while scanning files --- lib/files/cache/scanner.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index b62a093cec..bf0ef01d6b 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -83,14 +83,19 @@ class Scanner { * * @param string $path * @param SCAN_RECURSIVE/SCAN_SHALLOW $recursive + * @param bool $onlyChilds * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ - public function scan($path, $recursive = self::SCAN_RECURSIVE) { + public function scan($path, $recursive = self::SCAN_RECURSIVE, $onlyChilds = false) { \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_folder', array('path' => $path, 'storage' => $this->storageId)); - $this->scanFile($path); + $childQueue = array(); + if (!$onlyChilds) { + $this->scanFile($path); + } $size = 0; if ($dh = $this->storage->opendir($path)) { + \OC_DB::beginTransaction(); while ($file = readdir($dh)) { if ($file !== '.' and $file !== '..') { $child = ($path) ? $path . '/' . $file : $file; @@ -98,10 +103,12 @@ class Scanner { if ($data) { if ($data['mimetype'] === 'httpd/unix-directory') { if ($recursive === self::SCAN_RECURSIVE) { - $data['size'] = $this->scan($child, self::SCAN_RECURSIVE); + $childQueue[] = $child; + $data['size'] = 0; } else { $data['size'] = -1; } + } else { } if ($data['size'] === -1) { $size = -1; @@ -111,6 +118,15 @@ class Scanner { } } } + \OC_DB::commit(); + foreach ($childQueue as $child) { + $childSize = $this->scan($child, self::SCAN_RECURSIVE, true); + if ($childSize === -1) { + $size = -1; + } else { + $size += $childSize; + } + } if ($size !== -1) { $this->cache->put($path, array('size' => $size)); } From de41813792249ae9cbd193d3df8f645c9f5f723c Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Thu, 17 Jan 2013 11:54:31 +0700 Subject: [PATCH 195/418] new app icon style for new design --- core/css/styles.css | 16 ++++++----- core/img/places/files.png | Bin 884 -> 428 bytes core/img/places/files.svg | 49 ++++++++++----------------------- core/img/places/music.png | Bin 802 -> 708 bytes core/img/places/music.svg | 24 ++++++++-------- core/img/places/picture.png | Bin 323 -> 271 bytes core/img/places/picture.svg | 20 +++++++------- core/templates/layout.user.php | 2 +- 8 files changed, 46 insertions(+), 65 deletions(-) diff --git a/core/css/styles.css b/core/css/styles.css index 825d949d82..1895ce7177 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -184,13 +184,15 @@ fieldset.warning legend { color:#b94a48 !important; } /* NAVIGATION ------------------------------------------------------------- */ #navigation { position:fixed; top:3.5em; float:left; width:64px; padding:0; z-index:75; height:100%; background:#30343a url('../img/noise.png') repeat; border-right:1px #333 solid; -moz-box-shadow:0 0 7px #000; -webkit-box-shadow:0 0 7px #000; box-shadow:0 0 7px #000; overflow:hidden;} -#navigation a { display:block; padding:8px 0 4px; text-decoration:none; font-size:10px; text-align:center; color:#000; text-shadow:#444 0 1px 0; } -#navigation .icon { width:32px; height:32px; background-position:0 0; background-repeat:no-repeat; margin:0 16px 0; } -#navigation li:hover div, #navigation li a.active div { background-position:-32px 0; } -#navigation li:first-child a { padding-top:16px; } -#navigation a img { display:block; width:32px; height:32px; margin:0 auto; } -#navigation a.active, #navigation a:hover, #navigation a:focus { color:#888; text-shadow:#000 0 -1px 0; } -#navigation a.active { } +#navigation a { + display:block; padding:8px 0 4px; + text-decoration:none; font-size:10px; text-align:center; + color:#fff; text-shadow:#000 0 -1px 0; opacity:.3; +} + #navigation a:hover, #navigation a:focus { opacity:.8; } + #navigation a.active { opacity:1; } + #navigation .icon { display:block; width:32px; height:32px; margin:0 16px 0; } + #navigation li:first-child a { padding-top:16px; } #settings { float:right; margin-top:7px; color:#bbb; text-shadow:0 -1px 0 #000; } #expand { padding:15px; cursor:pointer; font-weight:bold; } #expand:hover, #expand:focus, #expand:active { color:#fff; } diff --git a/core/img/places/files.png b/core/img/places/files.png index a18264d3cea241068207fc0623ab853a83fcfa71..eb5a7905e237e8fbac10851d58633a9e92a88b8d 100644 GIT binary patch delta 363 zcmV-x0hIpq2CM@iiBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPPauUDsiZ`IhuGYmlV77vR{ot%1Yg&;$F+p2pVISu^CW z<2d?D-}l<@_dW2T>nsldJlAzcf2Vg{r`>MXJuCq5JQ^;9E+lOntY%vK}!KABpsh_ z|K(()t2d&xuS#a@;i4PS8f*buz!tCtYyngMhNIu^wR!mo`~V6cZHn7$zia>i002ov JPDHLkV1f}orfC2G delta 823 zcmV-71IYZW1M~(ViBL{Q4GJ0x0000DNk~Le0000$0000W2nGNE0HU3Kgpnake**DI zL_t(&f$dpKYZGAbL`Te~Kp}QdLzU zqGtoXeN)X0TuD%0g1n>>$+wb#)6MIpIOB%4FE9Yw|D>(4bGd^bv902gR8vA8HMf{a!{^u(u z3 zL8EOz&; - @@ -73,7 +62,7 @@ image/svg+xml - + @@ -83,24 +72,14 @@ id="layer1" transform="translate(573.14286,110.2963)"> + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="path3802" + d="m -557.14286,-109.23532 -16,15.93882 6,0 0,12.0002 20,0 0,-12.0002 6,0 -6,-6.061183 0,-7.939047 -6,0 0,2.16234 -4,-4.10093 z" + inkscape:connector-curvature="0" /> - - + style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> diff --git a/core/img/places/music.png b/core/img/places/music.png index 71a6ac0755fc975ebebadfeac73a87f1d5d4806e..5b71e19ee3c885b88695e4067fd66b5531af9ae8 100644 GIT binary patch delta 609 zcmV-n0-pV%2E+xBZht{ZL_t(oh3!{8s}n&KJ+o^T(`GT9kYe3sNfA4nCkW}b7t*JD zA%DQ)na<`RNI(#=jD?+@5JYS&Oc5|PN>~jR(GWr+Mq-lLWAUQob7%9CbU83EEce`V zW_CXA2$I+*gb)b;_N+N`{S<&lB+usKuUt|j4!~L`lt?5@+kduU+cq4>!M}bgl|s2( zz6W5zfQUK+LWmOp_W{`Pc>KC$Sz?`#X_^~zjK^abh9MR{l8n@{+3dG!waQkj#UKc{ z0~Yc|o`GhwNz1Y##VW8c1CiE%5WVF-Q! z_z0i@;KQuFJ%5ye&ohB}u`d9&I@xwH;OxNtkAwZ&qk*G=qk*W{_elr<5JJQNWB>%J z+jg%8gb*5lvsf&4n#p9Y1wo)Dg%u&VxVw|-bo!>MumGUxy8gUWDzVe)L@r3e#62NI zJ(*0>bzNm5!!YhvDwVA(qD|tbp69V%uSZUl&*wiyQGZt&0C2ldD9CLS)oQgrDnhT< z`>iyP%jI59r_-%R(r&kv1{#gVyJg`?d%sn&8o-DkI!ZhxOiL_t(oh3%KWZ__{=#ozbt>?3v(Csn|WDFdQi$W)1mC929& zkXTqiVyHSGfz*M70VMWHObql-K#e+-0YzfT08=Ce)B&j*5h)VC6c}igkdkAuqmsHg zCv}oWjeDy5$P(|>;)03tF0zpRoa2t=0oS zq#p+0&!|2}nS!rJ7~5Cuuhj{_3BU=!sEYMd9LWE1od=+7+x~w5{yyO*fNPxdOq4N0 z0er)BB9U+y8$Mfp-;!yX^Fjz7b<=Vv01;gRaGQuOw13-eSeB*px-6x+&W`y zH>A7D<$rRoH#av|UDw^w0$7&S7#1#sXe})*tq-#%@!9I3mY37E4^Ot;3-Ck?+XhH&(_w~>HvBnejI@9*8)HX zz%D}8OkYZ=!6Hhf((L5qa?(o5ngF$Em+B`Pf-*QkQ)I0000 @@ -27,9 +27,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="15.839192" - inkscape:cx="19.074656" - inkscape:cy="17.628465" + inkscape:zoom="11.2" + inkscape:cx="7.9636746" + inkscape:cy="12.572189" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" @@ -38,9 +38,9 @@ fit-margin-right="0" fit-margin-bottom="0" inkscape:window-width="1280" - inkscape:window-height="800" + inkscape:window-height="745" inkscape:window-x="0" - inkscape:window-y="-31" + inkscape:window-y="27" inkscape:window-maximized="1" /> @@ -50,7 +50,7 @@ image/svg+xml - + @@ -60,14 +60,14 @@ id="layer1" transform="translate(581.71429,-2.0764682)"> + style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" /> diff --git a/core/img/places/picture.png b/core/img/places/picture.png index a478876d6a705506819809d4407aa61e2300c4f8..a278240a6d6fd073b0b94ca43dc3356a84c4e77d 100644 GIT binary patch delta 169 zcmX@i)Xy{_ufE6A#W5tJ_3d*g1C_iWRs;gFv%bWq3W R@$qz!c28G7mvv4FO#m1#Mymh- delta 221 zcmeBYI?Oa7ul|6ii(^Pc>)YwJTup{NZTBu7-w8#6kN-2rlqnvEzZ6mN_oSzcRhy5 zo>gnNP74zIzV#W$l84Ucr%S)z$E>x0369R1KhzDge7=!kRxOa5sEj&QyO=wDi52 Ut(%Wv5d#o-y85}Sb4q9e04|JNGXMYp diff --git a/core/img/places/picture.svg b/core/img/places/picture.svg index e48a5568a3..aba68e6206 100644 --- a/core/img/places/picture.svg +++ b/core/img/places/picture.svg @@ -27,9 +27,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="7.9195959" - inkscape:cx="32.552959" - inkscape:cy="-0.73426477" + inkscape:zoom="11.2" + inkscape:cx="13.989783" + inkscape:cy="8.9886524" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" @@ -38,9 +38,9 @@ fit-margin-right="0" fit-margin-bottom="0" inkscape:window-width="1280" - inkscape:window-height="800" + inkscape:window-height="745" inkscape:window-x="0" - inkscape:window-y="-31" + inkscape:window-y="27" inkscape:window-maximized="1" /> @@ -50,7 +50,7 @@ image/svg+xml - + @@ -62,11 +62,11 @@ + id="path3770" + d="m -575.01366,3.0805705 c -0.39495,0.0765 -0.70712,0.466654 -0.70001,0.874878 l -6.2e-4,26.2461415 c 10e-6,0.458082 0.41045,0.874866 0.86155,0.874878 l 20.28048,0 c 0.4511,-1.2e-5 0.86154,-0.416796 0.86155,-0.874878 l 6.1e-4,-25.9212225 c -6.5e-4,-0.672871 -0.53099,-1.203711 -1.03374,-1.199797 0,0 -15.52067,0 -20.26982,0 z m 1.29978,19.9958975 18,0 8.2e-4,6 -18.00082,0 z" + style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99992161999999996;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" />
  • class="active"> -
    +
  • From 09d3db5eb7395a21009d840b9edb30bcce44187b Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Thu, 17 Jan 2013 12:45:33 +0700 Subject: [PATCH 196/418] new settings menu for new design --- core/css/styles.css | 8 +-- core/img/actions/logout.png | Bin 706 -> 613 bytes core/img/actions/logout.svg | 17 ++--- settings/img/admin.png | Bin 224 -> 228 bytes settings/img/admin.svg | 120 +++++------------------------------- settings/img/apps.png | Bin 229 -> 219 bytes settings/img/apps.svg | 81 +++++------------------- settings/img/help.png | Bin 423 -> 477 bytes settings/img/help.svg | 46 +++++--------- settings/img/personal.png | Bin 504 -> 499 bytes settings/img/personal.svg | 46 ++++++-------- settings/img/users.png | Bin 639 -> 598 bytes settings/img/users.svg | 52 +++++----------- 13 files changed, 95 insertions(+), 275 deletions(-) diff --git a/core/css/styles.css b/core/css/styles.css index 1895ce7177..0124fa854e 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -198,10 +198,10 @@ fieldset.warning legend { color:#b94a48 !important; } #expand:hover, #expand:focus, #expand:active { color:#fff; } #expand img { opacity:.5; margin-bottom:-2px; } #expand:hover img, #expand:focus img, #expand:active img { opacity:1; } -#expanddiv { position:absolute; right:0; top:45px; background-color:#eee; border-bottom-left-radius:7px; box-shadow: 0 0 20px #888888; z-index:76; } -#expanddiv a { display:block; color:#222; text-shadow:0 1px 0 #fff; padding:0 8px; } -#expanddiv a img { margin-bottom:-3px; } -#expanddiv a:hover, #expanddiv a:focus, #expanddiv a:active { color:#555; background-color:#ddd; } +#expanddiv { position:absolute; right:0; top:45px; background-color:#444; border-bottom-left-radius:7px; box-shadow: 0 0 20px rgb(29,45,68); z-index:76; } + #expanddiv a { display:block; color:#fff; text-shadow:0 -1px 0 #000; padding:0 8px; opacity:.6; } + #expanddiv a img { margin-bottom:-3px; } + #expanddiv a:hover, #expanddiv a:focus, #expanddiv a:active { opacity:1; } /* VARIOUS REUSABLE SELECTORS */ .hidden { display:none; } diff --git a/core/img/actions/logout.png b/core/img/actions/logout.png index 37f62543ac2489fecd1b629a8cecdffaa63511cd..e2f4b7af12ef73d2b72006d461bbd731f7cf0410 100644 GIT binary patch delta 514 zcmV+d0{#8M1?2>gZGQpcNkl3_jf^1t*4=V^>a>*swn}QcT^$(kEOl3d``O_|D#-I5Lt4t8AEy)7aEtCD0SH6?j~qJJo|Q54yH@0p}VF%PKN ze9ss&Xf~TvtJTkCz}D8*uSTPRq`ss(*8ra9eIE=408A#6y)qyS!=C{CexJ3qwXatI zl6H=dj{(TCYy?!x0O09zxjYBh-`_{lixr^NYMlT?Q8WV9FX<(KfJbp02LRjK+rt&W z_x%e1-}kRvQh$;p^CU^;#kC*^ngEA~hw%zN_ei%O-E*6Ve8y&h?rVy(5n>uV6y%6#m{zZAGCfNE6IF1=b(Fc-$NSv6M zxJnZFeEu!TWWY}l1kE4_nnQeb*VosdIp^qkUI_rk`VZsIIn2z={2V5@TrSrKL=S9Z zV`DQ(k|dYQO=fFUO0}JHC=?23j%;X^=OVZ zMFRlFn3p86y}kWC(?O+DxfMmxfl`W6%7>$5tCV`6lwuS`2Xk|Ccd{)2II+06xU#pm z=Y$Z6l=7*R@)Aj(j@Vd7gKrP$=B4*XuWn#o~p0K0gJ3LI@QELD*;?X?1MZ0c0000image/svg+xml - + @@ -163,15 +163,16 @@ x2="8.4964771" y2="15.216674" /> + - diff --git a/settings/img/admin.png b/settings/img/admin.png index 13d653f92a80ed3820332f3b514ec37f4a771720..d883f0b61a329a011a151ef382a0f3284bde9d15 100644 GIT binary patch delta 125 zcmV-@0D}ME0ptOYZgycwL_t(IjqOv*4S-Mx1LH{SKS7zPo*?@CgeM+tLc*n`ZGr-j z4we>MMAlf$jIjgIX#q0%;_e5*E5wo82qYip - - - - - - - - - - - - - - - - + + diff --git a/settings/img/apps.png b/settings/img/apps.png index e9845d012be91e4341da4ed105a5502ed60386f0..de5ccbd2c5fc6276a7ea359fd8b708b85baff6df 100644 GIT binary patch delta 116 zcmV-)0E_?S0owtPZf#geL_t(IjqOn}3IH(()6ysT{}&{0vQrOR>QEfITtHBwAwdNo z6|~Z|i1gW*nMVSE#yR6Z2kySqj*KrycrT#;8}A~JRFaFco-Lm+V?+m60;g#2-#7pu We^ZAQ-g?gf0000$-@=6xVT(K#^?s%l7}n!|NnnBGDbH5m;6YJMwtJ}jz(;3lA=*X gMP)Mx53Xng0Ctr!lT-vW-2eap07*qoM6N<$g2Ki!Q~&?~ diff --git a/settings/img/apps.svg b/settings/img/apps.svg index cda246bc4a..d341592120 100644 --- a/settings/img/apps.svg +++ b/settings/img/apps.svg @@ -14,9 +14,9 @@ width="16" height="16" id="svg11300" - inkscape:version="0.48.1 r9760" - sodipodi:docname="file.svg" - inkscape:export-filename="/home/jancborchardt/jancborchardt/ownCloud/icons/file.png" + inkscape:version="0.48.3.1 r9886" + sodipodi:docname="apps.svg" + inkscape:export-filename="apps.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> - - - - - - - - - - - - - - + + diff --git a/settings/img/help.png b/settings/img/help.png index 37ccb3568309a284ff95ee6bb2433ca6dbf4d302..c0200096735bb7394db96e70170a18b3dbf3b127 100644 GIT binary patch delta 376 zcmV-;0f+vl1Kk6VZhu-yL_t(Ijg^tHOT$1Ehkut7p%^Ikh9lVFAhaEX7W9pRlhVH+ zTO9;T2Pek}4i4h#Sj0^nD?t!P2Zs(9iga&+tv}+pk+w`kPN7g3+O~a@G!t7uL~8)99LG6qx7)1O z>uqNe2q7-}{r-9Y;B>*yOp;D>L0l;-Ebyu63 zn%)Tv49p|TfVp$$>mr*mv>bMPITE`2YXEDG>VuF)vvLh>D8x@bU2p;gTn$*+x=Q za<#IuauZ1gC@3g!h>MHoX=!O4ba8RH7aktIjU)q9RaG+%9e+Ahn3a`v&dbZ|VRd!& zGLj4c23RN%OZoWtoB`s8lP6EECOHf=G&F?0y}j@F`T0HEyLYb>$p!#zb`A;(dKecM zcMIqmL6QvsYAg>433*snR(1qmOyQJ>kB?sx78drfv$J!awzjqw5Id1%Kw@IzfvBjc zhd{3!j*N^f`#bdMlQl^Obai!YEG{m-eCpJx#lSEK0%8`D3;=2f2jW^FR>2nr0HQHO U0K7Vyp8x;=07*qoM6N<$f{`MO8UO$Q diff --git a/settings/img/help.svg b/settings/img/help.svg index 1e07aed852..55b68e6baf 100644 --- a/settings/img/help.svg +++ b/settings/img/help.svg @@ -14,9 +14,9 @@ width="16" height="16" id="svg11300" - inkscape:version="0.48.1 r9760" - sodipodi:docname="users.svg" - inkscape:export-filename="/home/jancborchardt/jancborchardt/ownCloud/icons/users.png" + inkscape:version="0.48.3.1 r9886" + sodipodi:docname="help.svg" + inkscape:export-filename="help.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> - - - - - + + diff --git a/settings/img/personal.png b/settings/img/personal.png index 8edc5a16cd6f22bed3f4ce643160755682e3e278..c80fed1b62b38d9270aefa3ad8de5d494cbe14c4 100644 GIT binary patch delta 399 zcmV;A0dW5K1M>rrZhvq|L_t(Ijg3;VO2beTJpjZrTv!orQlS7N-;x|YMIOGQ$ zItYT44i0{S8G@^8`~e3;kPbpe;bF75WO3;d?BFDYm_C;z*e0bpaN)hfJ?Gtfc#Lz7 zO%TyOfJ*=u3H1ozfph+|*}Ioh15gy@YBU<%`@Y}zeZM~*kALlEvw2pT}Vs<{m6up<1o}mf^MlMooqpG9wZpXxTCLVW88HCV>vcI7qNZt;vIdHxWV-C2s_LSwLA%|~K2`R&8Dm`l tX8_It90O=11pterJ^(y1#$Hn0AMI+bfAL}rO#lD@00>D%PDHLkV1hH$v=IOR delta 404 zcmV;F0c-yA1NZ}wZhv)2L_t(I%VRim<_rTm1_Cx_W@djTCZsTZjg5`> z5^aFHyZagg1A}wO7$iot0TmS$r9d0cA>*>LvT~vg`2YXEQh!21!V)txvvVLE6BDxx zD5gTR0U(^2nOSOSX?YHWv$C=(aH&I&egsxq7z|Xq3c~>*RLZNFJ9bKR8+JyF)?vRWMt&QprD{rAPi!I#6WUD(D45K`ydPhfC+^K yh~7sI1q> - - - - - - + + diff --git a/settings/img/users.png b/settings/img/users.png index 79ad3d667e1fb01394ff36bba75a0c2245be1582..a811af47c1c25837f54996a0edfd645290b5233c 100644 GIT binary patch delta 498 zcmV)nkQE?;;s z@6CIkdGm&0LI~^==llS`F@VQ$=q-R(LWo~UPD&}ran9|;&wpmKPdlB?3)giIUDrKy z9OopT&$n)zB7{KP#c?i|+t{{E00u$`bi3Veq^Q^HT_!a`h%12OIA1rL4G|Fy1_J_6 z@H{V20iNfD*8y76NvqY$=(-L7XfzryP4jL#oeqJk8~})8TcxgTTqbMA7^>Ck7vJ~K z)WNE;Ex<60mw(B6yzN9$EbC+kz&Wqzx^AylD*}LWxvX){>r%=$Qp!(B z@@zIcl~TS|q0cLo3Jr%t@_nC(h$fQ>)$4TvFmE=SQ8K`GyZr~i2taN73jKb6=cy9S z=W`0faO+1=M5ED&YPA{x7z4PE#bOcd1N=+l@t8`b(rbhTLGWACv;*~Cx6yPu{hrb^ z?FoSQ06YLc{|AuCWbQEaGZhMj$TZE=o|+q4E|f*NcK`qY delta 540 zcmV+%0^|MG1pfq(Zh!nqL_t(I%Z-!Gi&9|}#zmu0)I}*2e&Gj#--4+4g`$Ee=yb0j zB`!2qx`-Bzxg4gTO}Gez_Etn0K`pv8*fcma%^>eT;=;8ndrr)antKCZ_`!Lf^WNt@ z_ndnb%jHt>Kd`Q=RH{9tQu%_w{w}!j&zUO^=rkJ59dVC%&wpSr9NX>obHZpe9&5GQ zx~u^XmvuVbC%s<(MXgppB+X{C&AeVO`x|HF+mU-q`}0|Zns~NMzmV3H_%)X1FO~QGnq_}5{bmmWHQNI zF4uFj*&L%>uh-j$&InXFl8hQ%ec*PxzoNX;R4ToIC5AI=v3mCDhl*?rawOUP({6MJWJDk}r zG#U-5!D_X-Kb=kw2ZO=uPNyR@o6VDYy?#MZOd92|4%X{6FEOAm5LiJ9Q~*b>b_sW> eC4mK~eiZ>vp3a+yyNW^p0000 - - - - - - - + + From d5ee4352539f2165eebde5dc983113849b6a1a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Thu, 17 Jan 2013 13:17:48 +0100 Subject: [PATCH 197/418] rename "publicListView" option to "disableSharing", this is more meaningful also because it is not only useful for the public list view --- apps/files/templates/part.list.php | 6 +++--- apps/files_sharing/js/share.js | 2 +- apps/files_sharing/public.php | 2 +- apps/files_sharing/templates/public.php | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/files/templates/part.list.php b/apps/files/templates/part.list.php index dfac43d1b1..1970a80e1b 100644 --- a/apps/files/templates/part.list.php +++ b/apps/files/templates/part.list.php @@ -1,8 +1,8 @@ diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 8a546d6216..59faf82e26 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -1,6 +1,6 @@ $(document).ready(function() { - if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined' && !publicListView) { + if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined' && !disableSharing) { FileActions.register('all', 'Share', OC.PERMISSION_READ, OC.imagePath('core', 'actions/share'), function(filename) { if ($('#dir').val() == '/') { diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index 487b9e7996..7e1959cd95 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -257,7 +257,7 @@ if ($linkItem) { $list = new OCP\Template('files', 'part.list', ''); $list->assign('files', $files, false); - $list->assign('publicListView', true); + $list->assign('disableSharing', true); $list->assign('baseURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&path=', false); $list->assign('downloadURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&download&path=', false); $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', '' ); diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 647e1e08a3..bfcc521e10 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -1,8 +1,8 @@ From 3d56cf3a5b29e0c75f98646eafd22ee8cb7749dc Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 17 Jan 2013 13:31:14 +0100 Subject: [PATCH 198/418] LDAP: add support for backup/replica servers --- apps/user_ldap/lib/connection.php | 42 +++++++++++++++++++++------ apps/user_ldap/settings.php | 2 +- apps/user_ldap/templates/settings.php | 3 ++ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 1dc1d1510a..a22246c709 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -36,6 +36,8 @@ class Connection { protected $config = array( 'ldapHost' => null, 'ldapPort' => null, + 'ldapBackupHost' => null, + 'ldapBackupPort' => null, 'ldapBase' => null, 'ldapBaseUsers' => null, 'ldapBaseGroups' => null, @@ -56,6 +58,7 @@ class Connection { 'ldapCacheTTL' => null, 'ldapUuidAttribute' => null, 'ldapOverrideUuidAttribute' => null, + 'ldapOverrideMainServer' => false, 'homeFolderNamingRule' => null, 'hasPagedResultSupport' => false, ); @@ -188,7 +191,10 @@ class Connection { private function readConfiguration($force = false) { if((!$this->configured || $force) && !is_null($this->configID)) { $this->config['ldapHost'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_host', ''); + $this->config['ldapBackupHost'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_backup_host', ''); $this->config['ldapPort'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_port', 389); + $this->config['ldapBackupPort'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_backup_port', $this->config['ldapPort']); + $this->config['ldapOverrideMainServer']= \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_override_main_server', false); $this->config['ldapAgentName'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_dn', ''); $this->config['ldapAgentPassword'] = base64_decode(\OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_agent_password', '')); $rawLdapBase = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base', ''); @@ -229,7 +235,7 @@ class Connection { return false; } - $params = array('ldap_host'=>'ldapHost', 'ldap_port'=>'ldapPort', 'ldap_dn'=>'ldapAgentName', 'ldap_agent_password'=>'ldapAgentPassword', 'ldap_base'=>'ldapBase', 'ldap_base_users'=>'ldapBaseUsers', 'ldap_base_groups'=>'ldapBaseGroups', 'ldap_userlist_filter'=>'ldapUserFilter', 'ldap_login_filter'=>'ldapLoginFilter', 'ldap_group_filter'=>'ldapGroupFilter', 'ldap_display_name'=>'ldapUserDisplayName', 'ldap_group_display_name'=>'ldapGroupDisplayName', + $params = array('ldap_host'=>'ldapHost', 'ldap_port'=>'ldapPort', 'ldap_backup_host'=>'ldapBackupHost', 'ldap_backup_port'=>'ldapBackupPort', 'ldapOverrideMainServer' => 'ldap_override_main_server', 'ldap_dn'=>'ldapAgentName', 'ldap_agent_password'=>'ldapAgentPassword', 'ldap_base'=>'ldapBase', 'ldap_base_users'=>'ldapBaseUsers', 'ldap_base_groups'=>'ldapBaseGroups', 'ldap_userlist_filter'=>'ldapUserFilter', 'ldap_login_filter'=>'ldapLoginFilter', 'ldap_group_filter'=>'ldapGroupFilter', 'ldap_display_name'=>'ldapUserDisplayName', 'ldap_group_display_name'=>'ldapGroupDisplayName', 'ldap_tls'=>'ldapTLS', 'ldap_nocase'=>'ldapNoCase', 'ldap_quota_def'=>'ldapQuotaDefault', 'ldap_quota_attr'=>'ldapQuotaAttribute', 'ldap_email_attr'=>'ldapEmailAttribute', 'ldap_group_member_assoc_attribute'=>'ldapGroupMemberAssocAttr', 'ldap_cache_ttl'=>'ldapCacheTTL', 'home_folder_naming_rule' => 'homeFolderNamingRule'); @@ -342,16 +348,34 @@ class Connection { \OCP\Util::writeLog('user_ldap', 'Could not turn off SSL certificate validation.', \OCP\Util::WARN); } } - $this->ldapConnectionRes = ldap_connect($this->config['ldapHost'], $this->config['ldapPort']); - if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) { - if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) { - if($this->config['ldapTLS']) { - ldap_start_tls($this->ldapConnectionRes); - } - } + if(!$this->config['ldapOverrideMainServer'] && !$this->getFromCache('overrideMainServer')) { + $this->doConnect($this->config['ldapHost'], $this->config['ldapPort']); + $bindStatus = $this->bind(); } - return $this->bind(); + $error = null; + //if LDAP server is not reachable, try the Backup (Replica!) Server + if((!$bindStatus && ($error = ldap_errno($this->ldapConnectionRes)) == -1) + || $this->config['ldapOverrideMainServer'] + || $this->getFromCache('overrideMainServer')) { + $this->doConnect($this->config['ldapBackupHost'], $this->config['ldapBackupPort']); + $bindStatus = $this->bind(); + if($bindStatus && $error == -1) { + $this->writeToCache('overrideMainServer', true); + } + } + return $bindStatus; + } + } + + private function doConnect($host, $port) { + $this->ldapConnectionRes = ldap_connect($host, $port); + if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) { + if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) { + if($this->config['ldapTLS']) { + ldap_start_tls($this->ldapConnectionRes); + } + } } } diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index 58ec8e7f7a..e49f37da2d 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -23,7 +23,7 @@ OC_Util::checkAdminUser(); -$params = array('ldap_host', 'ldap_port', 'ldap_dn', 'ldap_agent_password', 'ldap_base', 'ldap_base_users', 'ldap_base_groups', 'ldap_userlist_filter', 'ldap_login_filter', 'ldap_group_filter', 'ldap_display_name', 'ldap_group_display_name', 'ldap_tls', 'ldap_turn_off_cert_check', 'ldap_nocase', 'ldap_quota_def', 'ldap_quota_attr', 'ldap_email_attr', 'ldap_group_member_assoc_attribute', 'ldap_cache_ttl', 'home_folder_naming_rule'); +$params = array('ldap_host', 'ldap_port', 'ldap_backup_host', 'ldap_backup_port', 'ldap_override_main_server', 'ldap_dn', 'ldap_agent_password', 'ldap_base', 'ldap_base_users', 'ldap_base_groups', 'ldap_userlist_filter', 'ldap_login_filter', 'ldap_group_filter', 'ldap_display_name', 'ldap_group_display_name', 'ldap_tls', 'ldap_turn_off_cert_check', 'ldap_nocase', 'ldap_quota_def', 'ldap_quota_attr', 'ldap_email_attr', 'ldap_group_member_assoc_attribute', 'ldap_cache_ttl', 'home_folder_naming_rule'); OCP\Util::addscript('user_ldap', 'settings'); OCP\Util::addstyle('user_ldap', 'settings'); diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index b24c6e2f02..030fbff4aa 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -22,6 +22,9 @@

    +

    +

    +

    title="t('When switched on, ownCloud will only connect to the replica server.');?>" />

    From 59a6068246c4012955e3b2b49e44426eedcc923b Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 17 Jan 2013 13:46:32 +0100 Subject: [PATCH 199/418] fix undeclared variable --- apps/user_ldap/lib/connection.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index a22246c709..9eab692bef 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -351,6 +351,8 @@ class Connection { if(!$this->config['ldapOverrideMainServer'] && !$this->getFromCache('overrideMainServer')) { $this->doConnect($this->config['ldapHost'], $this->config['ldapPort']); $bindStatus = $this->bind(); + } else { + $bindStatus = false; } $error = null; From d8be83029b107359884f9e23dd5bded71fea8999 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 17 Jan 2013 13:56:37 +0100 Subject: [PATCH 200/418] make sure port is used as backup port if not specified. documentation. determine connection error earlier. --- apps/user_ldap/lib/connection.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 9eab692bef..55234f4ac0 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -279,6 +279,10 @@ class Connection { \OCP\Config::setAppValue($this->configID, $this->configPrefix.'ldap_uuid_attribute', 'auto'); \OCP\Util::writeLog('user_ldap', 'Illegal value for the UUID Attribute, reset to autodetect.', \OCP\Util::INFO); } + if(empty($this->config['ldapBackupPort'])) { + //force default + $this->config['ldapBackupPort'] = $this->config['ldapPort']; + } //second step: critical checks. If left empty or filled wrong, set as unconfigured and give a warning. @@ -351,18 +355,22 @@ class Connection { if(!$this->config['ldapOverrideMainServer'] && !$this->getFromCache('overrideMainServer')) { $this->doConnect($this->config['ldapHost'], $this->config['ldapPort']); $bindStatus = $this->bind(); + $error = ldap_errno($this->ldapConnectionRes); } else { $bindStatus = false; + $error = null; } $error = null; //if LDAP server is not reachable, try the Backup (Replica!) Server - if((!$bindStatus && ($error = ldap_errno($this->ldapConnectionRes)) == -1) + if((!$bindStatus && ($error == -1)) || $this->config['ldapOverrideMainServer'] || $this->getFromCache('overrideMainServer')) { $this->doConnect($this->config['ldapBackupHost'], $this->config['ldapBackupPort']); $bindStatus = $this->bind(); if($bindStatus && $error == -1) { + //when bind to backup server succeeded and failed to main server, + //skip contacting him until next cache refresh $this->writeToCache('overrideMainServer', true); } } From a53addf8250ea47a36837463f15122339123aeff Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 18 Jan 2013 01:23:15 +0100 Subject: [PATCH 201/418] LDAP: first basics for multiserver config ui --- .../ajax/getNewServerConfigPrefix.php | 39 +++++++++++++++++++ apps/user_ldap/js/settings.js | 33 ++++++++++++++++ apps/user_ldap/settings.php | 1 + apps/user_ldap/templates/settings.php | 5 +++ 4 files changed, 78 insertions(+) create mode 100644 apps/user_ldap/ajax/getNewServerConfigPrefix.php diff --git a/apps/user_ldap/ajax/getNewServerConfigPrefix.php b/apps/user_ldap/ajax/getNewServerConfigPrefix.php new file mode 100644 index 0000000000..1a5f78cf21 --- /dev/null +++ b/apps/user_ldap/ajax/getNewServerConfigPrefix.php @@ -0,0 +1,39 @@ +. + * + */ + +// Check user and app status +OCP\JSON::checkAdminUser(); +OCP\JSON::checkAppEnabled('user_ldap'); +OCP\JSON::callCheck(); + +$query = \OCP\DB::prepare(' + SELECT DISTINCT `configkey` + FROM `*PREFIX*appconfig` + WHERE `configkey` LIKE ? +'); +$serverConnections = $query->execute(array('%ldap_login_filter'))->fetchAll(); +sort($serverConnections); +$lk = array_pop($serverConnections); +$ln = intval(str_replace('s', '', $lk)); +$nk = 's'.str_pad($ln+1, 2, '0', STR_PAD_LEFT); +OCP\JSON::success(array('configPrefix' => $nk)); \ No newline at end of file diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 7063eead96..8cd31301f2 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -21,4 +21,37 @@ $(document).ready(function() { } ); }); + + $('#ldap_serverconfig_chooser').change(function(event) { + value = $('#ldap_serverconfig_chooser option:selected:first').attr('value'); + if(value == 'NEW') { + $.post( + OC.filePath('user_ldap','ajax','getNewServerConfigPrefix.php'), + function (result) { + if(result.status == 'success') { + OC.dialogs.confirm( + 'Take over settings from recent server configuration?', + 'Keep settings?', + function(keep) { + if(!keep) { + $('#ldap').find('input[type=text], input[type=password], textarea, select').val(''); + $('#ldap').find('input[type=checkbox]').removeAttr('checked'); + } + } + ); + $('#ldap_serverconfig_chooser option:selected:first').removeAttr('selected'); + var html = ''; + $('#ldap_serverconfig_chooser option:last').before(html); + } else { + OC.dialogs.alert( + result.message, + 'Cannot add server configuration' + ); + } + } + ); + } else { + alert(value); + } + }); }); \ No newline at end of file diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index e49f37da2d..35233dc987 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -76,5 +76,6 @@ $tmpl->assign( 'ldap_cache_ttl', OCP\Config::getAppValue('user_ldap', 'ldap_cach $hfnr = OCP\Config::getAppValue('user_ldap', 'home_folder_naming_rule', 'opt:username'); $hfnr = ($hfnr == 'opt:username') ? '' : substr($hfnr, strlen('attr:')); $tmpl->assign( 'home_folder_naming_rule', $hfnr, ''); +$tmpl->assign('serverConfigurationOptions', '', false); return $tmpl->fetchPage(); diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 030fbff4aa..c3ec20fc84 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -12,6 +12,11 @@ } ?>
    +

    From 82d4da0d3d0dc09c22d2cd2220d418372206099e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 10:23:31 +0100 Subject: [PATCH 202/418] call the trash bin view --- apps/files/js/files.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files/js/files.js b/apps/files/js/files.js index c13d7a5961..359b92a6fd 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -83,9 +83,9 @@ $(document).ready(function() { return false; }); - // Show Trash bin + // Show trash bin $('#trash a').live('click', function() { - console.log("hello"); + window.location=OC.filePath('files_trashbin', '', 'index.php'); }); var lastChecked; From 1318450791146e7a075c01d8a39df6d333b619f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 10:51:13 +0100 Subject: [PATCH 203/418] introduce option to disable download action --- apps/files/js/fileactions.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 80b9c01f83..093b6204c3 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -147,15 +147,19 @@ $(document).ready(function () { } else { var downloadScope = 'file'; } - FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { - return OC.imagePath('core', 'actions/download'); - }, function (filename) { - window.location = OC.filePath('files', 'ajax', 'download.php') + '?files=' + encodeURIComponent(filename) + '&dir=' + encodeURIComponent($('#dir').val()); - }); - + + if (typeof disableDownloadActions == 'undefined' || !disableDownloadActions) { + FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { + return OC.imagePath('core', 'actions/download'); + }, function (filename) { + window.location = OC.filePath('files', 'ajax', 'download.php') + '?files=' + encodeURIComponent(filename) + '&dir=' + encodeURIComponent($('#dir').val()); + }); + } + $('#fileList tr').each(function(){ FileActions.display($(this).children('td.filename')); }); + }); FileActions.register('all', 'Delete', OC.PERMISSION_DELETE, function () { From 1c19e66712958930cd772485bb96c68c0c00c011 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 18 Jan 2013 11:42:34 +0100 Subject: [PATCH 204/418] coding style --- apps/user_ldap/settings.php | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index 35233dc987..73e4f0b6f4 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -23,12 +23,22 @@ OC_Util::checkAdminUser(); -$params = array('ldap_host', 'ldap_port', 'ldap_backup_host', 'ldap_backup_port', 'ldap_override_main_server', 'ldap_dn', 'ldap_agent_password', 'ldap_base', 'ldap_base_users', 'ldap_base_groups', 'ldap_userlist_filter', 'ldap_login_filter', 'ldap_group_filter', 'ldap_display_name', 'ldap_group_display_name', 'ldap_tls', 'ldap_turn_off_cert_check', 'ldap_nocase', 'ldap_quota_def', 'ldap_quota_attr', 'ldap_email_attr', 'ldap_group_member_assoc_attribute', 'ldap_cache_ttl', 'home_folder_naming_rule'); +$params = array('ldap_host', 'ldap_port', 'ldap_backup_host', + 'ldap_backup_port', 'ldap_override_main_server', 'ldap_dn', + 'ldap_agent_password', 'ldap_base', 'ldap_base_users', + 'ldap_base_groups', 'ldap_userlist_filter', + 'ldap_login_filter', 'ldap_group_filter', 'ldap_display_name', + 'ldap_group_display_name', 'ldap_tls', + 'ldap_turn_off_cert_check', 'ldap_nocase', 'ldap_quota_def', + 'ldap_quota_attr', 'ldap_email_attr', + 'ldap_group_member_assoc_attribute', 'ldap_cache_ttl', + 'home_folder_naming_rule' + ); OCP\Util::addscript('user_ldap', 'settings'); OCP\Util::addstyle('user_ldap', 'settings'); -if ($_POST) { +if($_POST) { $clearCache = false; foreach($params as $param) { if(isset($_POST[$param])) { @@ -60,22 +70,22 @@ if ($_POST) { } // fill template -$tmpl = new OCP\Template( 'user_ldap', 'settings'); +$tmpl = new OCP\Template('user_ldap', 'settings'); foreach($params as $param) { - $value = OCP\Config::getAppValue('user_ldap', $param, ''); - $tmpl->assign($param, $value); + $value = OCP\Config::getAppValue('user_ldap', $param, ''); + $tmpl->assign($param, $value); } // settings with default values -$tmpl->assign( 'ldap_port', OCP\Config::getAppValue('user_ldap', 'ldap_port', '389')); -$tmpl->assign( 'ldap_display_name', OCP\Config::getAppValue('user_ldap', 'ldap_display_name', 'uid')); -$tmpl->assign( 'ldap_group_display_name', OCP\Config::getAppValue('user_ldap', 'ldap_group_display_name', 'cn')); -$tmpl->assign( 'ldap_group_member_assoc_attribute', OCP\Config::getAppValue('user_ldap', 'ldap_group_member_assoc_attribute', 'uniqueMember')); -$tmpl->assign( 'ldap_agent_password', base64_decode(OCP\Config::getAppValue('user_ldap', 'ldap_agent_password'))); -$tmpl->assign( 'ldap_cache_ttl', OCP\Config::getAppValue('user_ldap', 'ldap_cache_ttl', '600')); +$tmpl->assign('ldap_port', OCP\Config::getAppValue('user_ldap', 'ldap_port', '389')); +$tmpl->assign('ldap_display_name', OCP\Config::getAppValue('user_ldap', 'ldap_display_name', 'uid')); +$tmpl->assign('ldap_group_display_name', OCP\Config::getAppValue('user_ldap', 'ldap_group_display_name', 'cn')); +$tmpl->assign('ldap_group_member_assoc_attribute', OCP\Config::getAppValue('user_ldap', 'ldap_group_member_assoc_attribute', 'uniqueMember')); +$tmpl->assign('ldap_agent_password', base64_decode(OCP\Config::getAppValue('user_ldap', 'ldap_agent_password'))); +$tmpl->assign('ldap_cache_ttl', OCP\Config::getAppValue('user_ldap', 'ldap_cache_ttl', '600')); $hfnr = OCP\Config::getAppValue('user_ldap', 'home_folder_naming_rule', 'opt:username'); $hfnr = ($hfnr == 'opt:username') ? '' : substr($hfnr, strlen('attr:')); -$tmpl->assign( 'home_folder_naming_rule', $hfnr, ''); +$tmpl->assign('home_folder_naming_rule', $hfnr, ''); $tmpl->assign('serverConfigurationOptions', '', false); return $tmpl->fetchPage(); From d5151fa61c561544ee64472a2c778cbe4fe086f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 13:11:29 +0100 Subject: [PATCH 205/418] first version of the trash bin app --- apps/files_trashbin/ajax/undelete.php | 13 ++ apps/files_trashbin/appinfo/app.php | 7 + apps/files_trashbin/appinfo/database.xml | 92 ++++++++++ apps/files_trashbin/appinfo/info.xml | 14 ++ apps/files_trashbin/appinfo/version | 1 + apps/files_trashbin/css/trash.css | 78 ++++++++ apps/files_trashbin/download.php | 52 ++++++ apps/files_trashbin/index.php | 55 ++++++ apps/files_trashbin/js/trash.js | 37 ++++ apps/files_trashbin/lib/hooks.php | 45 +++++ apps/files_trashbin/lib/trash.php | 190 ++++++++++++++++++++ apps/files_trashbin/templates/index.php | 36 ++++ apps/files_trashbin/templates/part.list.php | 70 ++++++++ 13 files changed, 690 insertions(+) create mode 100644 apps/files_trashbin/ajax/undelete.php create mode 100644 apps/files_trashbin/appinfo/app.php create mode 100644 apps/files_trashbin/appinfo/database.xml create mode 100644 apps/files_trashbin/appinfo/info.xml create mode 100644 apps/files_trashbin/appinfo/version create mode 100644 apps/files_trashbin/css/trash.css create mode 100644 apps/files_trashbin/download.php create mode 100644 apps/files_trashbin/index.php create mode 100644 apps/files_trashbin/js/trash.js create mode 100644 apps/files_trashbin/lib/hooks.php create mode 100644 apps/files_trashbin/lib/trash.php create mode 100644 apps/files_trashbin/templates/index.php create mode 100644 apps/files_trashbin/templates/part.list.php diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php new file mode 100644 index 0000000000..c548034828 --- /dev/null +++ b/apps/files_trashbin/ajax/undelete.php @@ -0,0 +1,13 @@ + array('content'=>'foo', 'id' => 'bar'))); \ No newline at end of file diff --git a/apps/files_trashbin/appinfo/app.php b/apps/files_trashbin/appinfo/app.php new file mode 100644 index 0000000000..3741d42c78 --- /dev/null +++ b/apps/files_trashbin/appinfo/app.php @@ -0,0 +1,7 @@ + + + + *dbname* + true + false + + utf8 + +
    + + *dbprefix*files_trash + + + + + id + text + + true + 50 + + + + user + text + + true + 50 + + + + timestamp + text + + true + 12 + + + + location + text + + true + 200 + + + + type + text + + true + 4 + + + + mime + text + + true + 30 + + + + id_index + + id + ascending + + + + + timestamp_index + + timestamp + ascending + + + + + user_index + + user + ascending + + + + + +
    + + diff --git a/apps/files_trashbin/appinfo/info.xml b/apps/files_trashbin/appinfo/info.xml new file mode 100644 index 0000000000..9b48612636 --- /dev/null +++ b/apps/files_trashbin/appinfo/info.xml @@ -0,0 +1,14 @@ + + + files_trashbin + Trash + Trash bin + AGPL + Bjoern Schiessle + true + 4.9 + + + + + diff --git a/apps/files_trashbin/appinfo/version b/apps/files_trashbin/appinfo/version new file mode 100644 index 0000000000..49d59571fb --- /dev/null +++ b/apps/files_trashbin/appinfo/version @@ -0,0 +1 @@ +0.1 diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css new file mode 100644 index 0000000000..bd6341c6fb --- /dev/null +++ b/apps/files_trashbin/css/trash.css @@ -0,0 +1,78 @@ +body { + background:#ddd; +} + +#header { + background:#1d2d44; + box-shadow:0 0 10px rgba(0,0,0,.5), inset 0 -2px 10px #222; + height:2.5em; + left:0; + line-height:2.5em; + position:fixed; + right:0; + top:0; + z-index:100; + padding:.5em; +} + +#details { + color:#fff; +} + +#header #download { + font-weight:700; + margin-left:2em; +} + +#header #download img { + padding-left:.1em; + padding-right:.3em; + vertical-align:text-bottom; +} + +#preview { + background:#eee; + border-bottom:1px solid #f8f8f8; + min-height:30em; + padding-top:2em; + text-align:center; + margin:50px auto; +} + +#noPreview { + display:none; + padding-top:5em; +} + +p.info { + color:#777; + text-align:center; + text-shadow:#fff 0 1px 0; + width:22em; + margin:2em auto; +} + +p.info a { + color:#777; + font-weight:700; +} + +#imgframe { + height:75%; + padding-bottom:2em; + width:80%; + margin:0 auto; +} + +#imgframe img { + max-height:100%; + max-width:100%; +} + +table td.filename .name { + display:block; + height:1.5em; + vertical-align:middle; + margin-left:3em; + /*font-weight:bold;*/ +} \ No newline at end of file diff --git a/apps/files_trashbin/download.php b/apps/files_trashbin/download.php new file mode 100644 index 0000000000..a987dd4fd1 --- /dev/null +++ b/apps/files_trashbin/download.php @@ -0,0 +1,52 @@ +. +* +*/ + +// Check if we are a user +OCP\User::checkLoggedIn(); + +$filename = $_GET["file"]; + +$view = new OC_FilesystemView('/'.\OCP\User::getUser().'/files_trashbin'); + +if(!$view->file_exists($filename)) { + error_log("file does not exist... " . $view->getInternalPath($filename)); + header("HTTP/1.0 404 Not Found"); + $tmpl = new OCP\Template( '', '404', 'guest' ); + $tmpl->assign('file', $filename); + $tmpl->printPage(); + exit; +} + +$ftype=$view->getMimeType( $filename ); + +header('Content-Type:'.$ftype);if ( preg_match( "/MSIE/", $_SERVER["HTTP_USER_AGENT"] ) ) { + header( 'Content-Disposition: attachment; filename="' . rawurlencode( basename($filename) ) . '"' ); +} else { + header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode( basename($filename) ) + . '; filename="' . rawurlencode( basename($filename) ) . '"' ); +} +OCP\Response::disableCaching(); +header('Content-Length: '. $view->filesize($filename)); + +OC_Util::obEnd(); +$view->readfile( $filename ); diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php new file mode 100644 index 0000000000..28414cc1ce --- /dev/null +++ b/apps/files_trashbin/index.php @@ -0,0 +1,55 @@ +execute(array($user))->fetchAll(); + +$files = array(); +foreach ($result as $r) { + $i = array(); + $i['name'] = $r['id']; + $i['date'] = OCP\Util::formatDate($r['timestamp']); + $i['timestamp'] = $r['timestamp']; + $i['mimetype'] = $r['mime']; + $i['type'] = $r['type']; + if ($i['type'] == 'file') { + $fileinfo = pathinfo($r['id']); + $i['basename'] = $fileinfo['filename']; + $i['extension'] = isset($fileinfo['extension']) ? ('.'.$fileinfo['extension']) : ''; + } + $i['directory'] = $r['location']; + if ($i['directory'] == '/') { + $i['directory'] = ''; + } + $i['permissions'] = OCP\PERMISSION_READ; + $files[] = $i; +} + +$breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); +$breadcrumbNav->assign('breadcrumb', array(array('dir' => '', 'name' => 'Trash')), false); +$breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir=', false); + +$list = new OCP\Template('files_trashbin', 'part.list', ''); +$list->assign('files', $files, false); +$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir=', false); +$list->assign('downloadURL', OCP\Util::linkTo('files_trashbin', 'download.php') . '?file=', false); +$list->assign('disableSharing', true); +$list->assign('disableDownloadActions', true); +$tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); +$tmpl->assign('fileList', $list->fetchPage(), false); +$tmpl->assign('dir', OC_Filesystem::normalizePath($view->getAbsolutePath())); + +$tmpl->printPage(); diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js new file mode 100644 index 0000000000..37e9a4bf10 --- /dev/null +++ b/apps/files_trashbin/js/trash.js @@ -0,0 +1,37 @@ +// Override download path to files_sharing/public.php +function fileDownloadPath(dir, file) { + var url = $('#downloadURL').val(); + if (url.indexOf('&path=') != -1) { + url += '/'+file; + } + return url; +} + +$(document).ready(function() { + + if (typeof FileActions !== 'undefined') { + FileActions.register('all', 'Undelete', OC.PERMISSION_READ, '', function(filename) { + var tr=$('tr').filterAttr('data-file', filename); + console.log("tr: " + tr.attr('data-timestamp') + " name: " + name); + $.post(OC.filePath('files_trashbin','ajax','undelete.php'), + {timestamp:tr.attr('data-timestamp'),filename:tr.attr('data-filename')}, + function(result){ + if (result.status == 'success') { + return; + var date=new Date(); + FileList.addFile(name,0,date,false,hidden); + var tr=$('tr').filterAttr('data-file',name); + tr.data('mime','text/plain').data('id',result.data.id); + tr.attr('data-id', result.data.id); + getMimeIcon('text/plain',function(path){ + tr.find('td.filename').attr('style','background-image:url('+path+')'); + }); + } else { + OC.dialogs.alert(result.data.message, 'Error'); + } + }); + + }); + }; + +}); \ No newline at end of file diff --git a/apps/files_trashbin/lib/hooks.php b/apps/files_trashbin/lib/hooks.php new file mode 100644 index 0000000000..d3bee105b5 --- /dev/null +++ b/apps/files_trashbin/lib/hooks.php @@ -0,0 +1,45 @@ +. + * + */ + +/** + * This class contains all hooks. + */ + +namespace OCA_Trash; + +class Hooks { + + /** + * @brief Copy files to trash bin + * @param array + * + * This function is connected to the delete signal of OC_Filesystem + * to copy the file to the trash bin + */ + public static function remove_hook($params) { + + if ( \OCP\App::isEnabled('files_trashbin') ) { + $path = $params['path']; + Trashbin::move2trash($path); + } + } +} diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php new file mode 100644 index 0000000000..384a865ce0 --- /dev/null +++ b/apps/files_trashbin/lib/trash.php @@ -0,0 +1,190 @@ +. + * + */ + +namespace OCA_Trash; + +class Trashbin { + + /** + * move file to the trash bin + * + * @param $file_path path to the deleted file/directory relative to the files root directory + */ + public static function move2trash($file_path) { + $user = \OCP\User::getUser(); + $view = new \OC_FilesystemView('/'. $user); + if (!$view->is_dir('files_trashbin')) { + $view->mkdir('files_trashbin'); + $view->mkdir("versions_trashbin"); + } + + $path_parts = pathinfo($file_path); + + $deleted = $path_parts['basename']; + $location = $path_parts['dirname']; + $timestamp = time(); + $mime = $view->getMimeType('files'.$file_path); + + if ( $view->is_dir('files'.$file_path) ) { + $type = 'dir'; + } else { + $type = 'file'; + } + + self::copy_recursive($file_path, 'files_trashbin/'.$deleted.'.d'.$timestamp, $view); + + $query = \OC_DB::prepare("INSERT INTO *PREFIX*files_trash (id,timestamp,location,type,mime,user) VALUES (?,?,?,?,?,?)"); + $result = $query->execute(array($deleted, $timestamp, $location, $type, $mime, $user)); + + if ( \OCP\App::isEnabled('files_versions') ) { + if ( $view->is_dir('files_versions'.$file_path) ) { + $view->rename('files_versions'.$file_path, 'versions_trashbin/'. $deleted.'.d'.$timestamp); + } else if ( $versions = \OCA_Versions\Storage::getVersions($file_path) ) { + foreach ($versions as $v) { + $view->rename('files_versions'.$v['path'].'.v'.$v['version'], 'versions_trashbin/'. $deleted.'.v'.$v['version'].'.d'.$timestamp); + } + } + } + + self::expire(); + } + + + /** + * restore files from trash bin + * @param $filename name of the file + * @param $timestamp time when the file was deleted + */ + public static function restore($filename, $timestamp) { + + $user = \OCP\User::getUser(); + $view = new \OC_FilesystemView('/'.$user); + + $query = \OC_DB::prepare('SELECT location,type FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); + $result = $query->execute(array($user,$filename,$timestamp))->fetchAll(); + + if ( count($result) != 1 ) { + \OC_Log::write('files_trashbin', 'trash bin database inconsistent!', OC_Log::ERROR); + return false; + } + + $location = $result[0]['location']; + if ( $result[0]['location'] != '/' && !$view->is_dir('files'.$result[0]['location']) ) { + $location = '/'; + } + + $source = 'files_trashbin/'.$filename.'.d'.$timestamp; + $target = \OC_Filesystem::normalizePath('files/'.$location.'/'.$filename); + + $ext = self::getUniqueExtension($location, $filename, $view); + + $view->rename($source, $target.$ext); + + if ( \OCP\App::isEnabled('files_versions') ) { + if ( $result[0][type] == 'dir' ) { + $view->rename('versions_trashbin/'. $filename.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext); + } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { + foreach ($versions as $v) { + $view->rename('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + } + } + } + + $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); + $query->execute(array($user,$filename,$timestamp)); + + } + + /** + * clean up the trash bin + */ + private static function expire() { + //TODO: implement expire function + return true; + } + + /** + * recursive copy to copy a whole directory + * + * @param $source source path, relative to the users files directory + * @param $destination destination path relative to the users root directoy + * @param $view file view for the users root directory + * @param $location location of the source files, either "fscache" or "local" + */ + private static function copy_recursive( $source, $destination, $view, $location='fscache' ) { + if ( $view->is_dir( 'files'.$source ) ) { + $view->mkdir( $destination ); + foreach ( \OC_Files::getDirectoryContent($source) as $i ) { + $pathDir = $source.'/'.$i['name']; + if ( $view->is_dir('files'.$pathDir) ) { + self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view); + } else { + $view->copy( 'files'.$pathDir, $destination . '/' . $i['name'] ); + } + } + } else { + $view->copy( 'files'.$source, $destination ); + } + } + + /** + * find all versions which belong to the file we want to restore + * @param $filename name of the file which should be restored + * @param $timestamp timestamp when the file was deleted + */ + private static function getVersionsFromTrash($filename, $timestamp) { + $view = new \OC_FilesystemView('/'.\OCP\User::getUser().'/versions_trashbin'); + $versionsName = \OCP\Config::getSystemValue('datadirectory').$view->getAbsolutePath($filename); + $versions = array(); + + // fetch for old versions + $matches = glob( $versionsName.'.v*.d'.$timestamp ); + + foreach( $matches as $ma ) { + $parts = explode( '.v', substr($ma, 0, -strlen($timestamp)-2) ); + $versions[] = ( end( $parts ) ); + } + return $versions; + } + + /** + * find unique extension for restored file if a file with the same name already exists + * @param $location where the file should be restored + * @param $filename name of the file + * @param $view filesystem view relative to users root directory + * @return string with unique extension + */ + private static function getUniqueExtension($location, $filename, $view) { + $ext = ''; + if ( $view->file_exists('files'.$location.'/'.$filename) ) { + $tmpext = '.restored'; + $ext = $tmpext; + $i = 1; + while ( $view->file_exists('files'.$location.'/'.$filename.$ext) ) { + $ext = $tmpext.$i; + $i++; + } + } + return $ext; + } + +} diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php new file mode 100644 index 0000000000..a412379d53 --- /dev/null +++ b/apps/files_trashbin/templates/index.php @@ -0,0 +1,36 @@ + +
    + +
    +
    +
    + + +
    t('Nothing in here. Upload something!')?>
    + + + + + + + + + + + + +
    + + t( 'Name' ); ?> + + + + Download" /> + t('Download')?> + + + + + t( 'Deleted' ); ?> +
    diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php new file mode 100644 index 0000000000..d022854330 --- /dev/null +++ b/apps/files_trashbin/templates/part.list.php @@ -0,0 +1,70 @@ + + +200) $relative_date_color = 200; + $name = str_replace('+', '%20', urlencode($file['name'])); + $name = str_replace('%2F', '/', $name); + $directory = str_replace('+', '%20', urlencode($file['directory'])); + $directory = str_replace('%2F', '/', $directory); ?> + ' + data-permissions=''> + + style="background-image:url()" + + style="background-image:url()" + + > + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Fri, 18 Jan 2013 13:35:40 +0100 Subject: [PATCH 206/418] LDAP: gather defaults in one place, simplify readConfiguration --- apps/user_ldap/lib/connection.php | 119 +++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 28 deletions(-) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 55234f4ac0..b6ed500cb1 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -185,40 +185,69 @@ class Connection { $this->cache->clear($this->getCacheKey(null)); } + private function getValue($varname) { + static $defaults; + if(is_null($defaults)){ + $defaults = $this->getDefaults(); + } + return \OCP\Config::getAppValue($this->configID, + $this->configPrefix.$varname, + $defaults[$varname]); + } + /** * Caches the general LDAP configuration. */ private function readConfiguration($force = false) { if((!$this->configured || $force) && !is_null($this->configID)) { - $this->config['ldapHost'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_host', ''); - $this->config['ldapBackupHost'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_backup_host', ''); - $this->config['ldapPort'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_port', 389); - $this->config['ldapBackupPort'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_backup_port', $this->config['ldapPort']); - $this->config['ldapOverrideMainServer']= \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_override_main_server', false); - $this->config['ldapAgentName'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_dn', ''); - $this->config['ldapAgentPassword'] = base64_decode(\OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_agent_password', '')); - $rawLdapBase = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base', ''); - $this->config['ldapBase'] = preg_split('/\r\n|\r|\n/', $rawLdapBase); - $this->config['ldapBaseUsers'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base_users', $rawLdapBase)); - $this->config['ldapBaseGroups'] = preg_split('/\r\n|\r|\n/', \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_base_groups', $rawLdapBase)); + $defaults = $this->getDefaults(); + $v = 'getValue'; + $this->config['ldapHost'] = $this->$v('ldap_host'); + $this->config['ldapBackupHost'] = $this->$v('ldap_backup_host'); + $this->config['ldapPort'] = $this->$v('ldap_port'); + $this->config['ldapBackupPort'] = $this->$v('ldapPort'); + $this->config['ldapOverrideMainServer'] + = $this->$v('ldap_override_main_server'); + $this->config['ldapAgentName'] = $this->$v('ldap_dn'); + $this->config['ldapAgentPassword'] + = base64_decode($this->$v('ldap_agent_password')); + $rawLdapBase = $this->$v('ldap_base'); + $this->config['ldapBase'] + = preg_split('/\r\n|\r|\n/', $rawLdapBase); + $this->config['ldapBaseUsers'] + = preg_split('/\r\n|\r|\n/', ($this->$v('ldap_base_users'))); + $this->config['ldapBaseGroups'] + = preg_split('/\r\n|\r|\n/', $this->$v('ldap_base_groups')); unset($rawLdapBase); - $this->config['ldapTLS'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_tls', 0); - $this->config['ldapNoCase'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_nocase', 0); - $this->config['turnOffCertCheck'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_turn_off_cert_check', 0); - $this->config['ldapUserDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_display_name', 'uid'), 'UTF-8'); - $this->config['ldapUserFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_userlist_filter', 'objectClass=person'); - $this->config['ldapGroupFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_group_filter', '(objectClass=posixGroup)'); - $this->config['ldapLoginFilter'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_login_filter', '(uid=%uid)'); - $this->config['ldapGroupDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_group_display_name', 'uid'), 'UTF-8'); - $this->config['ldapQuotaAttribute'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_quota_attr', ''); - $this->config['ldapQuotaDefault'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_quota_def', ''); - $this->config['ldapEmailAttribute'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_email_attr', ''); - $this->config['ldapGroupMemberAssocAttr'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_group_member_assoc_attribute', 'uniqueMember'); - $this->config['ldapIgnoreNamingRules'] = \OCP\Config::getSystemValue('ldapIgnoreNamingRules', false); - $this->config['ldapCacheTTL'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_cache_ttl', 10*60); - $this->config['ldapUuidAttribute'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_uuid_attribute', 'auto'); - $this->config['ldapOverrideUuidAttribute'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'ldap_override_uuid_attribute', 0); - $this->config['homeFolderNamingRule'] = \OCP\Config::getAppValue($this->configID, $this->configPrefix.'home_folder_naming_rule', 'opt:username'); + $this->config['ldapTLS'] = $this->$v('ldap_tls'); + $this->config['ldapNoCase'] = $this->$v('ldap_nocase'); + $this->config['turnOffCertCheck'] + = $this->$v('ldap_turn_off_cert_check'); + $this->config['ldapUserDisplayName'] + = mb_strtolower($this->$v('ldap_display_name'),'UTF-8'); + $this->config['ldapUserFilter'] + = $this->$v('ldap_userlist_filter'); + $this->config['ldapGroupFilter'] = $this->$v('ldap_group_filter'); + $this->config['ldapLoginFilter'] = $this->$v('ldap_login_filter'); + $this->config['ldapGroupDisplayName'] + = mb_strtolower($this->$v('ldap_group_display_name'), 'UTF-8'); + $this->config['ldapQuotaAttribute'] + = $this->$v('ldap_quota_attr'); + $this->config['ldapQuotaDefault'] + = $this->$v('ldap_quota_def'); + $this->config['ldapEmailAttribute'] + = $this->$v('ldap_email_attr'); + $this->config['ldapGroupMemberAssocAttr'] + = $this->$v('ldap_group_member_assoc_attribute'); + $this->config['ldapIgnoreNamingRules'] + = \OCP\Config::getSystemValue('ldapIgnoreNamingRules', false); + $this->config['ldapCacheTTL'] = $this->$v('ldap_cache_ttl'); + $this->config['ldapUuidAttribute'] + = $this->$v('ldap_uuid_attribute'); + $this->config['ldapOverrideUuidAttribute'] + = $this->$v('ldap_override_uuid_attribute'); + $this->config['homeFolderNamingRule'] + = $this->$v('home_folder_naming_rule'); $this->configured = $this->validateConfiguration(); } @@ -326,6 +355,40 @@ class Connection { return $configurationOK; } + /** + * @returns an associted array with the default values. Keys are correspond + * to configvalue entries in the database table + */ + public function getDefaults() { + return array( + 'ldap_host' => '', + 'ldap_port' => '389', + 'ldap_backup_host' => '', + 'ldap_backup_port' => '', + 'ldap_override_main_server' => '', + 'ldap_dn' => '', + 'ldap_agent_password' => '', + 'ldap_base' => '', + 'ldap_base_users' => '', + 'ldap_base_groups' => '', + 'ldap_userlist_filter' => 'objectClass=person', + 'ldap_login_filter' => 'uid=%uid', + 'ldap_group_filter' => 'objectClass=posixGroup', + 'ldap_display_name' => 'cn', + 'ldap_group_display_name' => 'cn', + 'ldap_tls' => 1, + 'ldap_nocase' => 0, + 'ldap_quota_def' => '', + 'ldap_quota_attr' => '', + 'ldap_email_attr' => '', + 'ldap_group_member_assoc_attribute' => 'uniqueMember', + 'ldap_cache_ttl' => 600, + 'ldap_uuid_attribute' => 'auto', + 'ldap_override_uuid_attribute' => 0, + 'home_folder_naming_rule' => '', + ); + } + /** * Connects and Binds to LDAP */ From 805f900b1980ae1f5d69395fd646a9a4605b7c68 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 18 Jan 2013 13:45:39 +0100 Subject: [PATCH 207/418] LDAP: pass defaults to settings form, and restore them on creating a new configuration if wanted --- apps/user_ldap/js/settings.js | 12 +++++-- apps/user_ldap/settings.php | 13 +++++++- apps/user_ldap/templates/settings.php | 48 +++++++++++++-------------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 8cd31301f2..0b8f141dfa 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -34,8 +34,16 @@ $(document).ready(function() { 'Keep settings?', function(keep) { if(!keep) { - $('#ldap').find('input[type=text], input[type=password], textarea, select').val(''); - $('#ldap').find('input[type=checkbox]').removeAttr('checked'); + $('#ldap').find('input[type=text], input[type=number], input[type=password], textarea, select').each(function() { + $(this).val($(this).attr('data-default')); + }); + $('#ldap').find('input[type=checkbox]').each(function() { + if($(this).attr('data-default') == 1) { + $(this).attr('checked', 'checked'); + } else { + $(this).removeAttr('checked'); + } + }); } } ); diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index 73e4f0b6f4..f3f0826739 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -64,7 +64,7 @@ if($_POST) { } } if($clearCache) { - $ldap = new \OCA\user_ldap\lib\Connection('user_ldap'); + $ldap = new \OCA\user_ldap\lib\Connection(); $ldap->clearCache(); } } @@ -88,4 +88,15 @@ $hfnr = ($hfnr == 'opt:username') ? '' : substr($hfnr, strlen('attr:')); $tmpl->assign('home_folder_naming_rule', $hfnr, ''); $tmpl->assign('serverConfigurationOptions', '', false); +// assign default values +if(!isset($ldap)) { + $ldap = new \OCA\user_ldap\lib\Connection(); +} +$defaults = $ldap->getDefaults(); +foreach($defaults as $key => $default) { + $tmpl->assign($key.'_default', $default); +} + +// $tmpl->assign(); + return $tmpl->fetchPage(); diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index c3ec20fc84..90a46a1733 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -17,32 +17,32 @@

    -

    -

    -

    -

    -


    t('use %%uid placeholder, e.g. "uid=%%uid"');?>

    -


    t('without any placeholder, e.g. "objectClass=person".');?>

    -


    t('without any placeholder, e.g. "objectClass=posixGroup".');?>

    +

    +

    +

    +

    +


    t('use %%uid placeholder, e.g. "uid=%%uid"');?>

    +


    t('without any placeholder, e.g. "objectClass=person".');?>

    +


    t('without any placeholder, e.g. "objectClass=posixGroup".');?>

    -

    -

    -

    -

    title="t('When switched on, ownCloud will only connect to the replica server.');?>" />

    -

    -

    -

    -

    title="t('Do not use it for SSL connections, it will fail.');?>" />

    -

    >

    -

    >
    t('Not recommended, use for testing only.');?>

    -

    -

    -

    -

    -

    -

    -

    +

    +

    +

    +

    data-default="" title="t('When switched on, ownCloud will only connect to the replica server.');?>" />

    +

    +

    +

    +

    data-default="" title="t('Do not use it for SSL connections, it will fail.');?>" />

    +

    >

    +

    >
    t('Not recommended, use for testing only.');?>

    +

    +

    +

    +

    +

    +

    +

    t('Help');?> From 229a25f41a5cf1b9eeac8cccedaa7196975328b4 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 18 Jan 2013 13:53:26 +0100 Subject: [PATCH 208/418] fix mixed key and value --- apps/user_ldap/lib/connection.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index b6ed500cb1..926691c2d9 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -264,7 +264,7 @@ class Connection { return false; } - $params = array('ldap_host'=>'ldapHost', 'ldap_port'=>'ldapPort', 'ldap_backup_host'=>'ldapBackupHost', 'ldap_backup_port'=>'ldapBackupPort', 'ldapOverrideMainServer' => 'ldap_override_main_server', 'ldap_dn'=>'ldapAgentName', 'ldap_agent_password'=>'ldapAgentPassword', 'ldap_base'=>'ldapBase', 'ldap_base_users'=>'ldapBaseUsers', 'ldap_base_groups'=>'ldapBaseGroups', 'ldap_userlist_filter'=>'ldapUserFilter', 'ldap_login_filter'=>'ldapLoginFilter', 'ldap_group_filter'=>'ldapGroupFilter', 'ldap_display_name'=>'ldapUserDisplayName', 'ldap_group_display_name'=>'ldapGroupDisplayName', + $params = array('ldap_host'=>'ldapHost', 'ldap_port'=>'ldapPort', 'ldap_backup_host'=>'ldapBackupHost', 'ldap_backup_port'=>'ldapBackupPort', 'ldap_override_main_server' => 'ldapOverrideMainServer', 'ldap_dn'=>'ldapAgentName', 'ldap_agent_password'=>'ldapAgentPassword', 'ldap_base'=>'ldapBase', 'ldap_base_users'=>'ldapBaseUsers', 'ldap_base_groups'=>'ldapBaseGroups', 'ldap_userlist_filter'=>'ldapUserFilter', 'ldap_login_filter'=>'ldapLoginFilter', 'ldap_group_filter'=>'ldapGroupFilter', 'ldap_display_name'=>'ldapUserDisplayName', 'ldap_group_display_name'=>'ldapGroupDisplayName', 'ldap_tls'=>'ldapTLS', 'ldap_nocase'=>'ldapNoCase', 'ldap_quota_def'=>'ldapQuotaDefault', 'ldap_quota_attr'=>'ldapQuotaAttribute', 'ldap_email_attr'=>'ldapEmailAttribute', 'ldap_group_member_assoc_attribute'=>'ldapGroupMemberAssocAttr', 'ldap_cache_ttl'=>'ldapCacheTTL', 'home_folder_naming_rule' => 'homeFolderNamingRule'); @@ -287,6 +287,14 @@ class Connection { return $this->configured; } + /** + * @brief get the current LDAP configuration + * @return array + */ + public function getConfiguration() { + return $this->config; + } + /** * @brief Validates the user specified configuration * @returns true if configuration seems OK, false otherwise From d16164b0cd90cc52758b2d7c7b847e99fb892247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 14:09:22 +0100 Subject: [PATCH 209/418] remove item in the trash bin view after successful undelete --- apps/files_trashbin/ajax/undelete.php | 9 +++--- apps/files_trashbin/js/trash.js | 12 ++----- apps/files_trashbin/lib/trash.php | 35 ++++++++++++--------- apps/files_trashbin/templates/part.list.php | 3 +- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php index c548034828..f55629d695 100644 --- a/apps/files_trashbin/ajax/undelete.php +++ b/apps/files_trashbin/ajax/undelete.php @@ -7,7 +7,8 @@ if(!OC_User::isLoggedIn()) { $timestamp = isset( $_REQUEST['timestamp'] ) ? $_REQUEST['timestamp'] : ''; $filename = isset( $_REQUEST['filename'] ) ? trim($_REQUEST['filename'], '/\\') : ''; -OCA_Trash\Trashbin::restore($filename, $timestamp); - -//TODO: return useful data after succsessful restore operation and remove restored files from the list view -OCP\JSON::success(array("data" => array('content'=>'foo', 'id' => 'bar'))); \ No newline at end of file +if ( OCA_Trash\Trashbin::restore($filename, $timestamp) ) { + OCP\JSON::success(array("data" => array('filename'=>$filename, 'timestamp' => $timestamp))); +} else { + OCP\JSON::error(array("data" => array("message" => "Couldn't restore ".$filename))); +} diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 37e9a4bf10..b9088944fd 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -12,20 +12,12 @@ $(document).ready(function() { if (typeof FileActions !== 'undefined') { FileActions.register('all', 'Undelete', OC.PERMISSION_READ, '', function(filename) { var tr=$('tr').filterAttr('data-file', filename); - console.log("tr: " + tr.attr('data-timestamp') + " name: " + name); $.post(OC.filePath('files_trashbin','ajax','undelete.php'), {timestamp:tr.attr('data-timestamp'),filename:tr.attr('data-filename')}, function(result){ if (result.status == 'success') { - return; - var date=new Date(); - FileList.addFile(name,0,date,false,hidden); - var tr=$('tr').filterAttr('data-file',name); - tr.data('mime','text/plain').data('id',result.data.id); - tr.attr('data-id', result.data.id); - getMimeIcon('text/plain',function(path){ - tr.find('td.filename').attr('style','background-image:url('+path+')'); - }); + var row = document.getElementById(result.data.filename+'.d'+result.data.timestamp); + row.parentNode.removeChild(row); } else { OC.dialogs.alert(result.data.message, 'Error'); } diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 384a865ce0..322f5679b7 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -86,7 +86,8 @@ class Trashbin { \OC_Log::write('files_trashbin', 'trash bin database inconsistent!', OC_Log::ERROR); return false; } - + + // if location no longer exists, restore file in the root directory $location = $result[0]['location']; if ( $result[0]['location'] != '/' && !$view->is_dir('files'.$result[0]['location']) ) { $location = '/'; @@ -95,23 +96,29 @@ class Trashbin { $source = 'files_trashbin/'.$filename.'.d'.$timestamp; $target = \OC_Filesystem::normalizePath('files/'.$location.'/'.$filename); + // we need a extension in case a file/dir with the same name already exists $ext = self::getUniqueExtension($location, $filename, $view); - $view->rename($source, $target.$ext); - - if ( \OCP\App::isEnabled('files_versions') ) { - if ( $result[0][type] == 'dir' ) { - $view->rename('versions_trashbin/'. $filename.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext); - } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { - foreach ($versions as $v) { - $view->rename('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + if( $view->rename($source, $target.$ext) ) { + + // if versioning app is enabled, copy versions from the trash bin back to the original location + if ( $return && \OCP\App::isEnabled('files_versions') ) { + if ( $result[0][type] == 'dir' ) { + $view->rename('versions_trashbin/'. $filename.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext); + } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { + foreach ($versions as $v) { + $view->rename('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + } } - } + } + + $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); + $query->execute(array($user,$filename,$timestamp)); + + return true; } - - $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); - $query->execute(array($user,$filename,$timestamp)); - + + return false; } /** diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php index d022854330..c9a641a2e2 100644 --- a/apps/files_trashbin/templates/part.list.php +++ b/apps/files_trashbin/templates/part.list.php @@ -24,7 +24,8 @@ $name = str_replace('%2F', '/', $name); $directory = str_replace('+', '%20', urlencode($file['directory'])); $directory = str_replace('%2F', '/', $directory); ?> - Date: Fri, 18 Jan 2013 14:11:55 +0100 Subject: [PATCH 210/418] remove unneeded function --- apps/files_trashbin/js/trash.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index b9088944fd..075dc6c315 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -1,12 +1,3 @@ -// Override download path to files_sharing/public.php -function fileDownloadPath(dir, file) { - var url = $('#downloadURL').val(); - if (url.indexOf('&path=') != -1) { - url += '/'+file; - } - return url; -} - $(document).ready(function() { if (typeof FileActions !== 'undefined') { From 8fdcd72d7f5fa9a0f2f84c63a1bc91211265cb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 15:12:38 +0100 Subject: [PATCH 211/418] expire files in trash bin after 30 days --- apps/files_trashbin/lib/trash.php | 32 ++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 322f5679b7..1b0b9cef4b 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -23,6 +23,8 @@ namespace OCA_Trash; class Trashbin { + + const DELETEAFTER=30; // how long do we keep files in the trash bin (number of days) /** * move file to the trash bin @@ -124,9 +126,33 @@ class Trashbin { /** * clean up the trash bin */ - private static function expire() { - //TODO: implement expire function - return true; + private static function expire() { + + $view = new \OC_FilesystemView('/'.\OCP\User::getUser()); + $user = \OCP\User::getUser(); + + $query = \OC_DB::prepare('SELECT location,type,id,timestamp FROM *PREFIX*files_trash WHERE user=?'); + $result = $query->execute(array($user))->fetchAll(); + + $limit = time() - (self::DELETEAFTER * 86400); + + foreach ( $result as $r ) { + $timestamp = $r['timestamp']; + $filename = $r['id']; + if ( $r['timestamp'] < $limit ) { + $view->unlink('files_trashbin/'.$filename.'.d'.$timestamp); + if ($r['type'] == 'dir') { + $view->unlink('versions_trashbin/'.$filename.'.d'.$timestamp); + } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { + foreach ($versions as $v) { + $view->unlink('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp); + } + } + } + } + + $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND timestampexecute(array($user,$limit)); } /** From adbc576439faf19515b84d0fe4bb48ba52c4450a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 17:50:44 +0100 Subject: [PATCH 212/418] handle group restore --- apps/files_trashbin/ajax/undelete.php | 35 ++++-- apps/files_trashbin/css/trash.css | 4 +- apps/files_trashbin/js/trash.js | 135 +++++++++++++++++++++++- apps/files_trashbin/lib/trash.php | 2 +- apps/files_trashbin/templates/index.php | 9 +- 5 files changed, 166 insertions(+), 19 deletions(-) diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php index f55629d695..05b5e7a5ee 100644 --- a/apps/files_trashbin/ajax/undelete.php +++ b/apps/files_trashbin/ajax/undelete.php @@ -4,11 +4,34 @@ if(!OC_User::isLoggedIn()) { exit; } -$timestamp = isset( $_REQUEST['timestamp'] ) ? $_REQUEST['timestamp'] : ''; -$filename = isset( $_REQUEST['filename'] ) ? trim($_REQUEST['filename'], '/\\') : ''; +$files = $_REQUEST['files']; +$list = explode(';', $files); + +$error = array(); + +$i = 0; +foreach ($list as $file) { + $delimiter = strrpos($file, '.d'); + $filename = substr($file, 0, $delimiter); + $timestamp = substr($file, $delimiter+2); + + if ( !OCA_Trash\Trashbin::restore($filename, $timestamp) ) { + $error[] = $filename; + } else { + $success[$i]['filename'] = $filename; + $success[$i]['timestamp'] = $timestamp; + $i++; + } -if ( OCA_Trash\Trashbin::restore($filename, $timestamp) ) { - OCP\JSON::success(array("data" => array('filename'=>$filename, 'timestamp' => $timestamp))); -} else { - OCP\JSON::error(array("data" => array("message" => "Couldn't restore ".$filename))); } + +if ( $error ) { + $filelist = ''; + foreach ( $error as $e ) { + $filelist .= $e.', '; + } + OCP\JSON::error(array("data" => array("message" => "Couldn't restore ".rtrim($filelist,', '), "success" => $success, "error" => $error))); +} else { + OCP\JSON::success(array("data" => array("success" => $success))); +} + diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css index bd6341c6fb..e0dd8c6e93 100644 --- a/apps/files_trashbin/css/trash.css +++ b/apps/files_trashbin/css/trash.css @@ -19,12 +19,12 @@ body { color:#fff; } -#header #download { +#header #undelete { font-weight:700; margin-left:2em; } -#header #download img { +#header #undelete img { padding-left:.1em; padding-right:.3em; vertical-align:text-bottom; diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 075dc6c315..8f3786f15e 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -1,20 +1,145 @@ + $(document).ready(function() { if (typeof FileActions !== 'undefined') { FileActions.register('all', 'Undelete', OC.PERMISSION_READ, '', function(filename) { var tr=$('tr').filterAttr('data-file', filename); $.post(OC.filePath('files_trashbin','ajax','undelete.php'), - {timestamp:tr.attr('data-timestamp'),filename:tr.attr('data-filename')}, + {files:tr.attr('data-filename')+'.d'+tr.attr('data-timestamp')}, function(result){ - if (result.status == 'success') { - var row = document.getElementById(result.data.filename+'.d'+result.data.timestamp); + for (var i = 0; i < result.data.success.length; i++) { + var row = document.getElementById(result.data.success[i].filename+'.d'+result.data.success[i].timestamp); row.parentNode.removeChild(row); - } else { + } + if (result.status != 'success') { OC.dialogs.alert(result.data.message, 'Error'); } }); }); }; + + // Sets the select_all checkbox behaviour : + $('#select_all').click(function() { + if($(this).attr('checked')){ + // Check all + $('td.filename input:checkbox').attr('checked', true); + $('td.filename input:checkbox').parent().parent().addClass('selected'); + }else{ + // Uncheck all + $('td.filename input:checkbox').attr('checked', false); + $('td.filename input:checkbox').parent().parent().removeClass('selected'); + } + procesSelection(); + }); -}); \ No newline at end of file + $('td.filename input:checkbox').live('change',function(event) { + if (event.shiftKey) { + var last = $(lastChecked).parent().parent().prevAll().length; + var first = $(this).parent().parent().prevAll().length; + var start = Math.min(first, last); + var end = Math.max(first, last); + var rows = $(this).parent().parent().parent().children('tr'); + for (var i = start; i < end; i++) { + $(rows).each(function(index) { + if (index == i) { + var checkbox = $(this).children().children('input:checkbox'); + $(checkbox).attr('checked', 'checked'); + $(checkbox).parent().parent().addClass('selected'); + } + }); + } + } + var selectedCount=$('td.filename input:checkbox:checked').length; + $(this).parent().parent().toggleClass('selected'); + if(!$(this).attr('checked')){ + $('#select_all').attr('checked',false); + }else{ + if(selectedCount==$('td.filename input:checkbox').length){ + $('#select_all').attr('checked',true); + } + } + procesSelection(); + }); + + $('.undelete').click('click',function(event) { + var fileslist=getSelectedFiles('file').join(';'); + $.post(OC.filePath('files_trashbin','ajax','undelete.php'), + {files:fileslist}, + function(result){ + for (var i = 0; i < result.data.success.length; i++) { + var row = document.getElementById(result.data.success[i].filename+'.d'+result.data.success[i].timestamp); + row.parentNode.removeChild(row); + } + if (result.status != 'success') { + OC.dialogs.alert(result.data.message, 'Error'); + } + }); + }); + + +}); + +function procesSelection(){ + var selected=getSelectedFiles(); + var selectedFiles=selected.filter(function(el){return el.type=='file'}); + var selectedFolders=selected.filter(function(el){return el.type=='dir'}); + if(selectedFiles.length==0 && selectedFolders.length==0) { + $('#headerName>span.name').text(t('files','Name')); + $('#modified').text(t('files','Deleted')); + $('table').removeClass('multiselect'); + $('.selectedActions').hide(); + } + else { + $('.selectedActions').show(); + var selection=''; + if(selectedFolders.length>0){ + if(selectedFolders.length==1){ + selection+=t('files','1 folder'); + }else{ + selection+=t('files','{count} folders',{count: selectedFolders.length}); + } + if(selectedFiles.length>0){ + selection+=' & '; + } + } + if(selectedFiles.length>0){ + if(selectedFiles.length==1){ + selection+=t('files','1 file'); + }else{ + selection+=t('files','{count} files',{count: selectedFiles.length}); + } + } + $('#headerName>span.name').text(selection); + $('#modified').text(''); + $('table').addClass('multiselect'); + } +} + +/** + * @brief get a list of selected files + * @param string property (option) the property of the file requested + * @return array + * + * possible values for property: name, mime, size and type + * if property is set, an array with that property for each file is returnd + * if it's ommited an array of objects with all properties is returned + */ +function getSelectedFiles(property){ + var elements=$('td.filename input:checkbox:checked').parent().parent(); + var files=[]; + elements.each(function(i,element){ + var file={ + name:$(element).attr('data-filename'), + file:$(element).attr('data-file'), + timestamp:$(element).attr('data-timestamp'), + type:$(element).attr('data-type') + }; + if(property){ + files.push(file[property]); + }else{ + files.push(file); + } + }); + return files; +} \ No newline at end of file diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 1b0b9cef4b..abfcf847ac 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -77,7 +77,7 @@ class Trashbin { * @param $timestamp time when the file was deleted */ public static function restore($filename, $timestamp) { - + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView('/'.$user); diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php index a412379d53..a9cb216b1f 100644 --- a/apps/files_trashbin/templates/index.php +++ b/apps/files_trashbin/templates/index.php @@ -16,13 +16,12 @@ t( 'Name' ); ?> - - - Download + + t('Undelete')?> - From da34e58589152f0c696f4eb802f8a4e3b900de0d Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 18 Jan 2013 23:30:56 -0500 Subject: [PATCH 213/418] Move permissions check to new function checkDataDirectoryPermissions() --- lib/util.php | 55 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/util.php b/lib/util.php index bc8d22a9e9..d46e68e991 100755 --- a/lib/util.php +++ b/lib/util.php @@ -211,39 +211,15 @@ class OC_Util { // Create root dir. if(!is_dir($CONFIG_DATADIRECTORY)) { $success=@mkdir($CONFIG_DATADIRECTORY); - if(!$success) { + if ($success) { + $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); + } else { $errors[]=array('error'=>"Can't create data directory (".$CONFIG_DATADIRECTORY.")", 'hint'=>"You can usually fix this by giving the webserver write access to the ownCloud directory '".OC::$SERVERROOT."' (in a terminal, use the command 'chown -R www-data:www-data /path/to/your/owncloud/install/data' "); } } else if(!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) { $errors[]=array('error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') not writable by ownCloud
    ', 'hint'=>$permissionsHint); } else { - //check for correct file permissions - if(!stristr(PHP_OS, 'WIN')) { - $permissionsModHint="Please change the permissions to 0770 so that the directory cannot be listed by other users."; - $prems=substr(decoct(@fileperms($CONFIG_DATADIRECTORY)), -3); - if(substr($prems, -1)!='0') { - OC_Helper::chmodr($CONFIG_DATADIRECTORY, 0770); - clearstatcache(); - $prems=substr(decoct(@fileperms($CONFIG_DATADIRECTORY)), -3); - if(substr($prems, 2, 1)!='0') { - $errors[]=array('error'=>'Data directory ('.$CONFIG_DATADIRECTORY.') is readable for other users
    ', 'hint'=>$permissionsModHint); - } - } - if( OC_Config::getValue( "enablebackup", false )) { - $CONFIG_BACKUPDIRECTORY = OC_Config::getValue( "backupdirectory", OC::$SERVERROOT."/backup" ); - $prems=substr(decoct(@fileperms($CONFIG_BACKUPDIRECTORY)), -3); - if(substr($prems, -1)!='0') { - OC_Helper::chmodr($CONFIG_BACKUPDIRECTORY, 0770); - clearstatcache(); - $prems=substr(decoct(@fileperms($CONFIG_BACKUPDIRECTORY)), -3); - if(substr($prems, 2, 1)!='0') { - $errors[]=array('error'=>'Data directory ('.$CONFIG_BACKUPDIRECTORY.') is readable for other users
    ', 'hint'=>$permissionsModHint); - } - } - } - } else { - //TODO: permissions checks for windows hosts - } + $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY)); } // check if all required php modules are present if(!class_exists('ZipArchive')) { @@ -295,6 +271,29 @@ class OC_Util { return $errors; } + /** + * Check for correct file permissions of data directory + * @return array arrays with error messages and hints + */ + public static function checkDataDirectoryPermissions($dataDirectory) { + $errors = array(); + if (stristr(PHP_OS, 'WIN')) { + //TODO: permissions checks for windows hosts + } else { + $permissionsModHint = 'Please change the permissions to 0770 so that the directory cannot be listed by other users.'; + $prems = substr(decoct(@fileperms($dataDirectory)), -3); + if (substr($prems, -1) != '0') { + OC_Helper::chmodr($dataDirectory, 0770); + clearstatcache(); + $prems = substr(decoct(@fileperms($dataDirectory)), -3); + if (substr($prems, 2, 1) != '0') { + $errors[] = array('error' => 'Data directory ('.$dataDirectory.') is readable for other users
    ', 'hint' => $permissionsModHint); + } + } + } + return $errors; + } + public static function displayLoginPage($errors = array()) { $parameters = array(); foreach( $errors as $key => $value ) { From 5df6f9d14d5a05d91d6706b420c8ad9a387a9f6d Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 18 Jan 2013 23:56:47 -0500 Subject: [PATCH 214/418] Fix merge --- lib/public/share.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/public/share.php b/lib/public/share.php index 9d2a64b459..5f9ad7fc09 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -684,13 +684,13 @@ class Share { } else { if ($fileDependent) { if (($itemType == 'file' || $itemType == 'folder') - && $format == \OC_Share_Backend_File::FORMAT_FILE_APP + && $format == \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS || $format == \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT ) { $select = '`*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `uid_owner`, ' - .'`share_type`, `share_with`, `file_source`, `path`, `file_target`, `permissions`, ' - .'`expiration`, `name`, `ctime`, `mtime`, `mimetype`, `size`, `encrypted`, ' - .'`versioned`, `writable`'; + .'`share_type`, `share_with`, `file_source`, `path`, `file_target`, ' + .'`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, ' + .'`name` `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`'; } else { $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`, `*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`, `file_source`, `path`, `file_target`, `permissions`, `stime`, `expiration`, `token`'; } From cd8d8360b0bcc07e22c4c9b86252ef1676b123dc Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 18 Jan 2013 23:57:13 -0500 Subject: [PATCH 215/418] Disable fancy folder sharing for now --- lib/public/share.php | 56 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/public/share.php b/lib/public/share.php index 5f9ad7fc09..f65d272ff1 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -300,36 +300,36 @@ class Share { throw new \Exception($message); } // If the item is a folder, scan through the folder looking for equivalent item types - if ($itemType == 'folder') { - $parentFolder = self::put('folder', $itemSource, $shareType, $shareWith, $uidOwner, $permissions, true); - if ($parentFolder && $files = \OC\Files\Filesystem::getDirectoryContent($itemSource)) { - for ($i = 0; $i < count($files); $i++) { - $name = substr($files[$i]['name'], strpos($files[$i]['name'], $itemSource) - strlen($itemSource)); - if ($files[$i]['mimetype'] == 'httpd/unix-directory' - && $children = \OC\Files\Filesystem::getDirectoryContent($name, '/') - ) { - // Continue scanning into child folders - array_push($files, $children); - } else { - // Check file extension for an equivalent item type to convert to - $extension = strtolower(substr($itemSource, strrpos($itemSource, '.') + 1)); - foreach (self::$backends as $type => $backend) { - if (isset($backend->dependsOn) && $backend->dependsOn == 'file' && isset($backend->supportedFileExtensions) && in_array($extension, $backend->supportedFileExtensions)) { - $itemType = $type; - break; - } - } - // Pass on to put() to check if this item should be converted, the item won't be inserted into the database unless it can be converted - self::put($itemType, $name, $shareType, $shareWith, $uidOwner, $permissions, $parentFolder); - } - } - return true; - } - return false; - } else { +// if ($itemType == 'folder') { +// $parentFolder = self::put('folder', $itemSource, $shareType, $shareWith, $uidOwner, $permissions, true); +// if ($parentFolder && $files = \OC\Files\Filesystem::getDirectoryContent($itemSource)) { +// for ($i = 0; $i < count($files); $i++) { +// $name = substr($files[$i]['name'], strpos($files[$i]['name'], $itemSource) - strlen($itemSource)); +// if ($files[$i]['mimetype'] == 'httpd/unix-directory' +// && $children = \OC\Files\Filesystem::getDirectoryContent($name, '/') +// ) { +// // Continue scanning into child folders +// array_push($files, $children); +// } else { +// // Check file extension for an equivalent item type to convert to +// $extension = strtolower(substr($itemSource, strrpos($itemSource, '.') + 1)); +// foreach (self::$backends as $type => $backend) { +// if (isset($backend->dependsOn) && $backend->dependsOn == 'file' && isset($backend->supportedFileExtensions) && in_array($extension, $backend->supportedFileExtensions)) { +// $itemType = $type; +// break; +// } +// } +// // Pass on to put() to check if this item should be converted, the item won't be inserted into the database unless it can be converted +// self::put($itemType, $name, $shareType, $shareWith, $uidOwner, $permissions, $parentFolder); +// } +// } +// return true; +// } +// return false; +// } else { // Put the item into the database return self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions); - } +// } } /** From 8ca30d244c19b33e7e3b0da247b70160a3acc44f Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 19 Jan 2013 00:02:40 -0500 Subject: [PATCH 216/418] Implement getETag() in shared storage --- apps/files_sharing/lib/sharedstorage.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index c8756af8ed..b0be6843c5 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -431,7 +431,14 @@ class Shared extends \OC\Files\Storage\Common { } public function getETag($path) { - + if ($path == '') { + return parent::getETag($path); + } + if ($source = $this->getSourcePath($path)) { + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source); + return $storage->getETag($internalPath); + } + return null; } } From 316eef3ded233c53ccb1f40aa339372ed9c644d9 Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 19 Jan 2013 01:50:02 -0500 Subject: [PATCH 217/418] Fix sharing issue with collection and children mismatches --- lib/public/share.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/public/share.php b/lib/public/share.php index cda583aa07..920ce26700 100644 --- a/lib/public/share.php +++ b/lib/public/share.php @@ -681,7 +681,8 @@ class Share { $collectionTypes[] = $type; } } - if (!self::getBackend($itemType) instanceof Share_Backend_Collection) { + // TODO Add option for collections to be collection of themselves, only 'folder' does it now... + if (!self::getBackend($itemType) instanceof Share_Backend_Collection || $itemType != 'folder') { unset($collectionTypes[0]); } // Return array if collections were found or the item type is a collection itself From 664f33a29c81701fbf6a7fb18eda81e1c4665ecc Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Sat, 19 Jan 2013 21:04:25 -0500 Subject: [PATCH 218/418] Update mtimes of all parent folders after change inside --- lib/files/cache/updater.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index 8b0d383503..d04541c219 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -35,7 +35,7 @@ class Updater { $scanner = $storage->getScanner($internalPath); $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); $cache->correctFolderSize($internalPath); - self::eTagUpdate($path); + self::correctFolder($path, $storage->filemtime($internalPath)); } } @@ -49,11 +49,17 @@ class Updater { $cache = $storage->getCache($internalPath); $cache->remove($internalPath); $cache->correctFolderSize($internalPath); - self::eTagUpdate($path); + self::correctFolder($path, time()); } } - static public function eTagUpdate($path) { + /** + * Update the mtime and ETag of all parent folders + * + * @param string $path + * @param string $time + */ + static public function correctFolder($path, $time) { if ($path !== '' && $path !== '/') { $parent = dirname($path); if ($parent === '.') { @@ -68,8 +74,8 @@ class Updater { $cache = $storage->getCache(); $id = $cache->getId($internalPath); if ($id !== -1) { - $cache->update($id, array('etag' => $storage->getETag($internalPath))); - self::eTagUpdate($parent); + $cache->update($id, array('mtime' => $time, 'etag' => $storage->getETag($internalPath))); + self::correctFolder($parent, $time); } } } From 6d84aa93d3ddf4f7d3c8599cba17bb02fd6df9e9 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Sun, 20 Jan 2013 18:02:44 +0100 Subject: [PATCH 219/418] Ajaxifiy Settings Save --- apps/user_ldap/ajax/setConfiguration.php | 33 +++++++++++++++ apps/user_ldap/js/settings.js | 14 +++++++ apps/user_ldap/lib/connection.php | 51 ++++++++++++++++++++++-- apps/user_ldap/templates/settings.php | 2 +- 4 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 apps/user_ldap/ajax/setConfiguration.php diff --git a/apps/user_ldap/ajax/setConfiguration.php b/apps/user_ldap/ajax/setConfiguration.php new file mode 100644 index 0000000000..206487c7e0 --- /dev/null +++ b/apps/user_ldap/ajax/setConfiguration.php @@ -0,0 +1,33 @@ +. + * + */ + +// Check user and app status +OCP\JSON::checkAdminUser(); +OCP\JSON::checkAppEnabled('user_ldap'); +OCP\JSON::callCheck(); + +$prefix = $_POST['ldap_serverconfig_chooser']; +$connection = new \OCA\user_ldap\lib\Connection($prefix); +$connection->setConfiguration($_POST); +$connection->saveConfiguration(); +OCP\JSON::success(); \ No newline at end of file diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 0b8f141dfa..a07d140cf8 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -22,6 +22,20 @@ $(document).ready(function() { ); }); + $('#ldap_submit').click(function(event) { + event.preventDefault(); + $.post( + OC.filePath('user_ldap','ajax','setConfiguration.php'), + $('#ldap').serialize(), + function (result) { + if (result.status == 'success') { + $('#notification').text(t('user_ldap', 'LDAP Configuration Saved')); + $('#notification').fadeIn(); + } + } + ); + }); + $('#ldap_serverconfig_chooser').change(function(event) { value = $('#ldap_serverconfig_chooser option:selected:first').attr('value'); if(value == 'NEW') { diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 926691c2d9..ebc46bf3b9 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -195,6 +195,12 @@ class Connection { $defaults[$varname]); } + private function setValue($varname, $value) { + \OCP\Config::setAppValue($this->configID, + $this->configPrefix.$varname, + $value); + } + /** * Caches the general LDAP configuration. */ @@ -205,7 +211,7 @@ class Connection { $this->config['ldapHost'] = $this->$v('ldap_host'); $this->config['ldapBackupHost'] = $this->$v('ldap_backup_host'); $this->config['ldapPort'] = $this->$v('ldap_port'); - $this->config['ldapBackupPort'] = $this->$v('ldapPort'); + $this->config['ldapBackupPort'] = $this->$v('ldap_backup_port'); $this->config['ldapOverrideMainServer'] = $this->$v('ldap_override_main_server'); $this->config['ldapAgentName'] = $this->$v('ldap_dn'); @@ -253,6 +259,13 @@ class Connection { } } + private function getConfigTranslationArray() { + static $array = array('ldap_host'=>'ldapHost', 'ldap_port'=>'ldapPort', 'ldap_backup_host'=>'ldapBackupHost', 'ldap_backup_port'=>'ldapBackupPort', 'ldap_override_main_server' => 'ldapOverrideMainServer', 'ldap_dn'=>'ldapAgentName', 'ldap_agent_password'=>'ldapAgentPassword', 'ldap_base'=>'ldapBase', 'ldap_base_users'=>'ldapBaseUsers', 'ldap_base_groups'=>'ldapBaseGroups', 'ldap_userlist_filter'=>'ldapUserFilter', 'ldap_login_filter'=>'ldapLoginFilter', 'ldap_group_filter'=>'ldapGroupFilter', 'ldap_display_name'=>'ldapUserDisplayName', 'ldap_group_display_name'=>'ldapGroupDisplayName', + + 'ldap_tls'=>'ldapTLS', 'ldap_nocase'=>'ldapNoCase', 'ldap_quota_def'=>'ldapQuotaDefault', 'ldap_quota_attr'=>'ldapQuotaAttribute', 'ldap_email_attr'=>'ldapEmailAttribute', 'ldap_group_member_assoc_attribute'=>'ldapGroupMemberAssocAttr', 'ldap_cache_ttl'=>'ldapCacheTTL', 'home_folder_naming_rule' => 'homeFolderNamingRule', 'turn_off_cert_check' => 'turnOffCertCheck'); + return $array; + } + /** * @brief set LDAP configuration with values delivered by an array, not read from configuration * @param $config array that holds the config parameters in an associated array @@ -264,9 +277,7 @@ class Connection { return false; } - $params = array('ldap_host'=>'ldapHost', 'ldap_port'=>'ldapPort', 'ldap_backup_host'=>'ldapBackupHost', 'ldap_backup_port'=>'ldapBackupPort', 'ldap_override_main_server' => 'ldapOverrideMainServer', 'ldap_dn'=>'ldapAgentName', 'ldap_agent_password'=>'ldapAgentPassword', 'ldap_base'=>'ldapBase', 'ldap_base_users'=>'ldapBaseUsers', 'ldap_base_groups'=>'ldapBaseGroups', 'ldap_userlist_filter'=>'ldapUserFilter', 'ldap_login_filter'=>'ldapLoginFilter', 'ldap_group_filter'=>'ldapGroupFilter', 'ldap_display_name'=>'ldapUserDisplayName', 'ldap_group_display_name'=>'ldapGroupDisplayName', - - 'ldap_tls'=>'ldapTLS', 'ldap_nocase'=>'ldapNoCase', 'ldap_quota_def'=>'ldapQuotaDefault', 'ldap_quota_attr'=>'ldapQuotaAttribute', 'ldap_email_attr'=>'ldapEmailAttribute', 'ldap_group_member_assoc_attribute'=>'ldapGroupMemberAssocAttr', 'ldap_cache_ttl'=>'ldapCacheTTL', 'home_folder_naming_rule' => 'homeFolderNamingRule'); + $params = $this->getConfigTranslationArray(); foreach($config as $parameter => $value) { if(isset($this->config[$parameter])) { @@ -287,11 +298,42 @@ class Connection { return $this->configured; } + public function saveConfiguration() { + $trans = array_flip($this->getConfigTranslationArray()); + foreach($this->config as $key => $value) { + \OCP\Util::writeLog('user_ldap', 'LDAP: storing key '.$key.' value '.$value, \OCP\Util::DEBUG); + switch ($key) { + case 'ldap_agent_password': + $value = base64_encode($value); + break; + case 'home_folder_naming_rule': + $value = empty($value) ? 'opt:username' : 'attr:'.$value; + break; + case 'ldapIgnoreNamingRules': + case 'ldapOverrideUuidAttribute': + case 'hasPagedResultSupport': + continue; + default: + if(is_null($value)) { + $value = 0; + } + } + + $this->setValue($trans[$key], $value); + } + } + /** * @brief get the current LDAP configuration * @return array */ public function getConfiguration() { + $trans = $this->getConfigTranslationArray(); + $config = array(); + foreach($trans as $classKey => $dbKey) { + $config[$dbKey] = $this->config[$classKey]; + } + return $this->config; } @@ -394,6 +436,7 @@ class Connection { 'ldap_uuid_attribute' => 'auto', 'ldap_override_uuid_attribute' => 0, 'home_folder_naming_rule' => '', + 'ldap_turn_off_cert_check' => 0, ); } diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 90a46a1733..6b95f8660e 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -44,7 +44,7 @@

    - t('Help');?> + t('Help');?> From 0c3466325b858013acf799fa1cd65acaad48d723 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Sun, 20 Jan 2013 18:27:39 +0100 Subject: [PATCH 220/418] fix continue in switch, add another key to skip --- apps/user_ldap/lib/connection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index ebc46bf3b9..bf36db8223 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -311,8 +311,9 @@ class Connection { break; case 'ldapIgnoreNamingRules': case 'ldapOverrideUuidAttribute': + case 'ldapUuidAttribute': case 'hasPagedResultSupport': - continue; + continue 2; default: if(is_null($value)) { $value = 0; From 10876aba896be188be2883dbc059b93fea96bbf5 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Sun, 20 Jan 2013 18:30:14 +0100 Subject: [PATCH 221/418] fix more config keys for save settings handling --- apps/user_ldap/lib/connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index bf36db8223..7d9f82cf83 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -303,10 +303,10 @@ class Connection { foreach($this->config as $key => $value) { \OCP\Util::writeLog('user_ldap', 'LDAP: storing key '.$key.' value '.$value, \OCP\Util::DEBUG); switch ($key) { - case 'ldap_agent_password': + case 'ldapAgentPassword': $value = base64_encode($value); break; - case 'home_folder_naming_rule': + case 'homeFolderNamingRule': $value = empty($value) ? 'opt:username' : 'attr:'.$value; break; case 'ldapIgnoreNamingRules': From 3fd28632c470e89d7c9334379da3d8fa6faf0de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Mon, 21 Jan 2013 13:07:43 +0100 Subject: [PATCH 222/418] allow to look into deleted directories --- apps/files/js/fileactions.js | 1 + apps/files_trashbin/index.php | 48 +++++++++++++++++++-- apps/files_trashbin/templates/part.list.php | 13 +++++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 093b6204c3..d20f9e835b 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -189,6 +189,7 @@ FileActions.register('all', 'Rename', OC.PERMISSION_UPDATE, function () { FileList.rename(filename); }); + FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { window.location = OC.linkTo('files', 'index.php') + '?dir=' + encodeURIComponent($('#dir').val()).replace(/%2F/g, '/') + '/' + encodeURIComponent(filename); }); diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php index 28414cc1ce..2925223197 100644 --- a/apps/files_trashbin/index.php +++ b/apps/files_trashbin/index.php @@ -14,8 +14,37 @@ $view = new OC_Filesystemview('/'.$user.'/files_trashbin'); OCP\Util::addStyle('files', 'files'); OCP\Util::addScript('files', 'filelist'); -$query = \OC_DB::prepare('SELECT id,location,timestamp,type,mime FROM *PREFIX*files_trash WHERE user=?'); -$result = $query->execute(array($user))->fetchAll(); +$dir = isset($_GET['dir']) ? stripslashes($_GET['dir']) : ''; + +if ($dir) { + $dirlisting = true; + $view = new \OC_FilesystemView('/'.\OCP\User::getUser().'/files_trashbin'); + $fullpath = \OCP\Config::getSystemValue('datadirectory').$view->getAbsolutePath($dir); + $dirContent = opendir($fullpath); + $i = 0; + while($entryName = readdir($dirContent)) { + if ( $entryName != '.' && $entryName != '..' ) { + $pos = strpos($dir.'/', '/', 1); + $tmp = substr($dir, 0, $pos); + $pos = strrpos($tmp, '.d'); + $timestamp = substr($tmp,$pos+2); + error_log("timestamp: $timestamp"); + $result[] = array( + 'id' => $entryName, + 'timestamp' => $timestamp, + 'mime' => $view->getMimeType($dir.'/'.$entryName), + 'type' => $view->is_dir($dir.'/'.$entryName) ? 'dir' : 'file', + 'location' => $dir, + ); + } + } + closedir($fullpath); + +} else { + $dirlisting = false; + $query = \OC_DB::prepare('SELECT id,location,timestamp,type,mime FROM *PREFIX*files_trash WHERE user=?'); + $result = $query->execute(array($user))->fetchAll(); +} $files = array(); foreach ($result as $r) { @@ -38,15 +67,26 @@ foreach ($result as $r) { $files[] = $i; } +// Make breadcrumb +$breadcrumb = array('dir' => '', 'name' => 'Trash'); +$pathtohere = ''; +foreach (explode('/', $dir) as $i) { + if ($i != '') { + $pathtohere .= '/' . $i; + $breadcrumb[] = array('dir' => $pathtohere, 'name' => $i); + } +} + $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); $breadcrumbNav->assign('breadcrumb', array(array('dir' => '', 'name' => 'Trash')), false); $breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir=', false); $list = new OCP\Template('files_trashbin', 'part.list', ''); $list->assign('files', $files, false); -$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir=', false); -$list->assign('downloadURL', OCP\Util::linkTo('files_trashbin', 'download.php') . '?file=', false); +$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir='.$dir, false); +$list->assign('downloadURL', OCP\Util::linkTo('files_trashbin', 'download.php') . '?file='.$dir, false); $list->assign('disableSharing', true); +$list->assign('dirlisting', $dirlisting); $list->assign('disableDownloadActions', true); $tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); $tmpl->assign('fileList', $list->fetchPage(), false); diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php index c9a641a2e2..72359da299 100644 --- a/apps/files_trashbin/templates/part.list.php +++ b/apps/files_trashbin/templates/part.list.php @@ -40,9 +40,17 @@ > - + + + + + - + + + + + @@ -69,3 +77,4 @@ Date: Mon, 21 Jan 2013 20:40:23 +0100 Subject: [PATCH 223/418] we are getting closer. updating is not working yet. --- lib/app.php | 16 +++++- lib/installer.php | 99 ++++++++++++++++++++++++++++++++++--- lib/ocsclient.php | 2 + settings/ajax/apps/ocs.php | 1 + settings/ajax/updateapp.php | 17 +++++++ settings/apps.php | 5 +- settings/js/apps.js | 30 +++++++++++ settings/routes.php | 2 + settings/templates/apps.php | 4 +- 9 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 settings/ajax/updateapp.php diff --git a/lib/app.php b/lib/app.php index e60bce2a20..13f54d6cf7 100644 --- a/lib/app.php +++ b/lib/app.php @@ -142,6 +142,8 @@ class OC_App{ * check if app is shipped * @param string $appid the id of the app to check * @return bool + * + * Check if an app that is installed is a shipped app or installed from the appstore. */ public static function isShipped($appid){ $info = self::getAppInfo($appid); @@ -197,9 +199,10 @@ class OC_App{ if(!is_numeric($app)) { $app = OC_Installer::installShippedApp($app); }else{ + $appdata=OC_OCSClient::getApplication($app); $download=OC_OCSClient::getApplicationDownload($app, 1); if(isset($download['downloadlink']) and $download['downloadlink']!='') { - $app=OC_Installer::installApp(array('source'=>'http', 'href'=>$download['downloadlink'])); + $app=OC_Installer::installApp(array('source'=>'http', 'href'=>$download['downloadlink'],'appdata'=>$appdata)); } } } @@ -212,6 +215,7 @@ class OC_App{ return false; }else{ OC_Appconfig::setValue( $app, 'enabled', 'yes' ); + if(isset($appdata['id'])) OC_Appconfig::setValue( $app, 'ocsid', $appdata['id'] ); return true; } }else{ @@ -229,6 +233,14 @@ class OC_App{ public static function disable( $app ) { // check if app is a shiped app or not. if not delete OC_Appconfig::setValue( $app, 'enabled', 'no' ); + + // check if app is a shiped app or not. if not delete + if(!OC_App::isShipped( $app )){ +// error_log($app.' not shipped'); + OC_Installer::removeApp( $app ); + }else{ +// error_log($app.' shipped'); + } } /** @@ -609,6 +621,8 @@ class OC_App{ $app1[$i]['author'] = $app['personid']; $app1[$i]['ocs_id'] = $app['id']; $app1[$i]['internal'] = $app1[$i]['active'] = 0; + $app1[$i]['update'] = false; + // rating img if($app['score']>=0 and $app['score']<5) $img=OC_Helper::imagePath( "core", "rating/s1.png" ); diff --git a/lib/installer.php b/lib/installer.php index 7dc8b0cef8..f4094a5d4c 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -141,6 +141,20 @@ class OC_Installer{ return false; } + // check if shipped tag is set which is only allowed for apps that are shipped with ownCloud + if(isset($info['shipped']) and ($info['shipped']=='true')) { + OC_Log::write('core', 'App can\'t be installed because it contains the true tag which is not allowed for non shipped apps', OC_Log::ERROR); + OC_Helper::rmdirr($extractDir); + return false; + } + + // check if the ocs version is the same as the version in info.xml/version + if(!isset($info['version']) or ($info['version']<>$data['appdata']['version'])) { + OC_Log::write('core', 'App can\'t be installed because the version in info.xml/version is not the same as the version reported from the app store', OC_Log::ERROR); + OC_Helper::rmdirr($extractDir); + return false; + } + //check if an app with the same id is already installed if(self::isInstalled( $info['id'] )) { OC_Log::write('core', 'App already installed', OC_Log::WARN); @@ -226,7 +240,6 @@ class OC_Installer{ /** * @brief Update an application * @param $data array with all information - * @returns integer * * This function installs an app. All information needed are passed in the * associative array $data. @@ -250,11 +263,57 @@ class OC_Installer{ * * upgrade.php can determine the current installed version of the app using "OC_Appconfig::getValue($appid, 'installed_version')" */ - public static function upgradeApp( $data = array()) { - // TODO: write function - return true; + public static function updateApp( $app ) { + error_log('updater!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); + return(true); + if(OC_Installer::isDownloaded( $name )) { + } } + /** + * @brief Check if an update for the app is available + * @param $name name of the application + * @returns emptry string is no update available or the version number of the update + * + * The function will check if an update for a version is available + */ + public static function isUpdateAvailable( $app ) { + //debug + return('1.1'); + + $ocsid=OC_Appconfig::getValue( $app, 'ocsid', ''); + + if($ocsid<>''){ + + $ocsdata=OC_OCSClient::getApplication($ocsid); + $ocsversion=$ocsdata['version']; + $currentversion=OC_App::getAppVersion($app); + +//error_log('bb'.$app.' '.$ocsversion); + return($ocsversion); + + }else{ + return(''); + } + + } + + /** + * @brief Check if app is already downloaded + * @param $name name of the application to remove + * @returns true/false + * + * The function will check if the app is already downloaded in the apps repository + */ + public static function isDownloaded( $name ) { + + $downloaded=false; + foreach(OC::$APPSROOTS as $dir) { + if(is_dir($dir['path'].'/'.$name)) $downloaded=true; + } + return($downloaded); + } + /** * @brief Removes an app * @param $name name of the application to remove @@ -276,8 +335,36 @@ class OC_Installer{ * this has to be done by the function oc_app_uninstall(). */ public static function removeApp( $name, $options = array()) { - // TODO: write function - return true; + + if(isset($options['keeppreferences']) and $options['keeppreferences']==false ){ + // todo + // remove preferences + } + + if(isset($options['keepappconfig']) and $options['keepappconfig']==false ){ + // todo + // remove app config + } + + if(isset($options['keeptables']) and $options['keeptables']==false ){ + // todo + // remove app database tables + } + + if(isset($options['keepfiles']) and $options['keepfiles']==false ){ + // todo + // remove user files + } + + if(OC_Installer::isDownloaded( $name )) { + $appdir=OC_App::getInstallPath().'/'.$name; + OC_Helper::rmdirr($appdir); + + }else{ + OC_Log::write('core', 'can\'t remove app '.$name.'. It is not installed.', OC_Log::ERROR); + + } + } /** diff --git a/lib/ocsclient.php b/lib/ocsclient.php index 24081425f1..3693078877 100644 --- a/lib/ocsclient.php +++ b/lib/ocsclient.php @@ -123,6 +123,7 @@ class OC_OCSClient{ $app=array(); $app['id']=(string)$tmp[$i]->id; $app['name']=(string)$tmp[$i]->name; + $app['version']=(string)$tmp[$i]->version; $app['type']=(string)$tmp[$i]->typeid; $app['typename']=(string)$tmp[$i]->typename; $app['personid']=(string)$tmp[$i]->personid; @@ -162,6 +163,7 @@ class OC_OCSClient{ $app=array(); $app['id']=$tmp->id; $app['name']=$tmp->name; + $app['version']=$tmp->version; $app['type']=$tmp->typeid; $app['typename']=$tmp->typename; $app['personid']=$tmp->personid; diff --git a/settings/ajax/apps/ocs.php b/settings/ajax/apps/ocs.php index 1ffba26ad1..6e09785d23 100644 --- a/settings/ajax/apps/ocs.php +++ b/settings/ajax/apps/ocs.php @@ -54,6 +54,7 @@ if(is_array($catagoryNames)) { 'preview'=>$pre, 'internal'=>false, 'internallabel'=>'3rd Party App', + 'update'=>false, ); } } diff --git a/settings/ajax/updateapp.php b/settings/ajax/updateapp.php new file mode 100644 index 0000000000..68c2bbf7f0 --- /dev/null +++ b/settings/ajax/updateapp.php @@ -0,0 +1,17 @@ + array('appid' => $appid))); +} else { + $l = OC_L10N::get('settings'); + OC_JSON::error(array("data" => array( "message" => $l->t("Could update app. ") ))); +} + + + diff --git a/settings/apps.php b/settings/apps.php index 99a3094399..a2015801e5 100644 --- a/settings/apps.php +++ b/settings/apps.php @@ -68,13 +68,16 @@ foreach ( $installedApps as $app ) { $info['internal']=true; $info['internallabel']='Internal App'; + + $info['update']=false; }else{ $info['internal']=false; $info['internallabel']='3rd Party App'; - + + $info['update']=OC_Installer::isUpdateAvailable($app); } $info['preview'] = OC_Helper::imagePath('settings', 'trans.png'); diff --git a/settings/js/apps.js b/settings/js/apps.js index c4c36b4bb1..8d214bd114 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -24,6 +24,14 @@ OC.Settings.Apps = OC.Settings.Apps || { page.find('span.author').text(app.author); page.find('span.licence').text(app.licence); + if (app.update != false) { + page.find('input.update').show(); + page.find('input.update').data('appid', app.id); + page.find('input.update').attr('value',t('settings', 'Update to ')+app.update); + } else { + page.find('input.update').hide(); + } + page.find('input.enable').show(); page.find('input.enable').val((app.active) ? t('settings', 'Disable') : t('settings', 'Enable')); page.find('input.enable').data('appid', app.id); @@ -44,6 +52,7 @@ OC.Settings.Apps = OC.Settings.Apps || { appData = appitem.data('app'); appData.active = !active; appitem.data('app', appData); + element.val(t('settings','Please wait....')); if(active) { $.post(OC.filePath('settings','ajax','disableapp.php'),{appid:appid},function(result) { if(!result || result.status!='success') { @@ -70,6 +79,20 @@ OC.Settings.Apps = OC.Settings.Apps || { $('#leftcontent li[data-id="'+appid+'"]').addClass('active'); } }, + updateApp:function(appid, element) { + console.log('updateApp:', appid, element); + element.val(t('settings','Updateing....')); + $.post(OC.filePath('settings','ajax','updateapp.php'),{appid:appid},function(result) { + if(!result || result.status!='success') { + OC.dialogs.alert('Error while updating app','Error'); + } + else { + element.val(t('settings','Updated')); + element.hide(); + } + },'json'); + }, + insertApp:function(appdata) { var applist = $('#leftcontent li'); var app = @@ -154,6 +177,13 @@ $(document).ready(function(){ OC.Settings.Apps.enableApp(appid, active, element); } }); + $('#rightcontent input.update').click(function(){ + var element = $(this); + var appid=$(this).data('appid'); + if(appid) { + OC.Settings.Apps.updateApp(appid, element); + } + }); if(appid) { var item = $('#leftcontent li[data-id="'+appid+'"]'); diff --git a/settings/routes.php b/settings/routes.php index 9b5bf80923..fa78f56652 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -51,6 +51,8 @@ $this->create('settings_ajax_enableapp', '/settings/ajax/enableapp.php') ->actionInclude('settings/ajax/enableapp.php'); $this->create('settings_ajax_disableapp', '/settings/ajax/disableapp.php') ->actionInclude('settings/ajax/disableapp.php'); +$this->create('settings_ajax_updateapp', '/settings/ajax/updateapp.php') + ->actionInclude('settings/ajax/updateapp.php'); $this->create('settings_ajax_navigationdetect', '/settings/ajax/navigationdetect.php') ->actionInclude('settings/ajax/navigationdetect.php'); // admin diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 179ce9c540..8654547ecb 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -7,7 +7,7 @@ var appid = '';