Files
Isaac ConnorandClaude Opus 5.5 1779e7acae fix: store the computed event disk space when Storage.DiskSpace is NULL refs #5191
Storage.DiskSpace defaults to NULL on fresh installs, and zmc's
DiskSpace = DiskSpace + N leaves it NULL. Storage::event_disk_space()
treated NULL as uncached and ran SUM(DiskSpace) over Events on every
navbar render and status refresh, taking seconds per page on large
Events tables.

Write the computed sum back to the Storage row (only while it is still
NULL, so a value set by zmaudit is not overwritten). Later page loads use
the stored value and zmc keeps it current. COALESCE the sum so a storage
area with no events stores 0 instead of NULL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:50:55 -04:00

173 lines
5.5 KiB
PHP

<?php
namespace ZM;
require_once('database.php');
require_once('Event.php');
require_once('Object.php');
class Storage extends ZM_Object {
protected static $table = 'Storage';
public $Id;
public $Path;
public $Name;
public $Type = 'local';
public $Url;
public $DiskSpace;
public $Scheme = 'Medium';
public $ServerId;
public $DoDelete = 1;
public $Enabled = 1;
protected $defaults = array(
'Id' => null,
'Path' => array('type'=>'text','filter_regexp'=>array('/[^\w\-\.\(\)\:\/ ]/','/\/$/'), 'default'=>''),
'Name' => array('type'=>'text','filter_regexp'=>'/[^\w\-\.\(\)\:\/ ]/', 'default'=>'Storage'),
'Type' => 'local',
'Url' => '',
'DiskSpace' => null,
'Scheme' => 'Medium',
'ServerId' => 0,
'DoDelete' => 1,
'Enabled' => 1,
);
public static function find($parameters = array(), $options = array()) {
return ZM_Object::_find(self::class, $parameters, $options);
}
public static function find_one($parameters = array(), $options = array()) {
return ZM_Object::_find_one(self::class, $parameters, $options);
}
public function Path($new=null) {
if ( $new ) $this->{'Path'} = $new;
if ( isset($this->{'Path'}) and ( $this->{'Path'} != '' ) ) {
return $this->{'Path'};
} else if ( ! isset($this->{'Id'}) ) {
$path = ZM_DIR_EVENTS;
if ( $path[0] != '/' ) {
$this->{'Path'} = ZM_PATH_WEB.'/'.ZM_DIR_EVENTS;
} else {
$this->{'Path'} = ZM_DIR_EVENTS;
}
return $this->{'Path'};
}
return $this->{'Name'};
}
public function Name($new=null) {
if ( $new )
$this->{'Name'} = $new;
if ( isset($this->{'Name'}) and ( $this->{'Name'} != '' ) ) {
return $this->{'Name'};
} else if ( ! isset($this->{'Id'}) ) {
return 'Default';
}
return $this->{'Name'};
}
public function Events() {
if ( $this->{'Id'} and ! isset($this->{'Events'}) ) {
$this->{'Events'} = Event::find(array('StorageId'=>$this->{'Id'}));
}
if ( ! isset($this->{'Events'}) ) {
$this->{'Events'} = array();
}
return $this->{'Events'};
}
public function EventCount() {
if ( (! property_exists($this, 'EventCount')) or (!$this->{'EventCount'}) ) {
$this->{'EventCount'} = dbFetchOne('SELECT COUNT(*) AS EventCount FROM Events WHERE StorageId=?', 'EventCount', array($this->Id()));
}
return $this->{'EventCount'};
}
public function disk_used_blocks() {
$df = shell_exec('df '.escapeshellarg($this->Path()));
$space = -1;
if ( preg_match('/\s(\d+)\s+\d+\s+\d+%/ms', $df, $matches) )
$space = $matches[1];
return $space;
}
public function disk_usage_percent() {
$path = $this->Path();
if ( ! $path ) {
Warning('Storage::disk_usage_percent: path is empty');
return 0;
} else if ( ! file_exists($path) ) {
Warning("Storage::disk_usage_percent: path $path does not exist");
return 0;
}
$total = $this->disk_total_space();
if ( ! $total ) {
Error('disk_total_space returned false for ' . $path);
return 0;
}
$used = $this->disk_used_space();
$usage = round(($used / $total) * 100);
//Debug("Used $usage = round( ( $used / $total ) * 100 )");
return $usage;
}
public function disk_total_space() {
if ( !property_exists($this, 'disk_total_space') ) {
$path = $this->Path();
if ( file_exists($path) ) {
$this->{'disk_total_space'} = disk_total_space($path);
} else {
Error("Path $path does not exist.");
$this->{'disk_total_space'} = 0;
}
}
return $this->{'disk_total_space'};
}
public function disk_used_space() {
# This isn't a function like this in php, so we have to add up the space used in each event.
if ( ( !property_exists($this, 'disk_used_space')) or !$this->{'disk_used_space'} ) {
if ( $this->Type() == 's3fs' ) {
$this->{'disk_used_space'} = $this->event_disk_space();
} else {
$path = $this->Path();
if ( file_exists($path) ) {
$this->{'disk_used_space'} = disk_total_space($path) - disk_free_space($path);
} else {
Error("Path $path does not exist.");
$this->{'disk_used_space'} = 0;
}
}
}
return $this->{'disk_used_space'};
} // end function disk_used_space
public function event_disk_space() {
# This isn't a function like this in php, so we have to add up the space used in each event.
if ( (! property_exists($this, 'DiskSpace')) or (!isset($this->{'DiskSpace'})) ) {
$this->{'DiskSpace'} = dbFetchOne('SELECT COALESCE(SUM(DiskSpace),0) AS DiskSpace FROM Events WHERE StorageId=? AND DiskSpace IS NOT NULL', 'DiskSpace', array($this->Id()));
# Store it so the sum is not recomputed on every page load. zmc only adds to a non-NULL value.
dbQuery('UPDATE Storage SET DiskSpace=? WHERE Id=? AND DiskSpace IS NULL', array($this->{'DiskSpace'}, $this->Id()));
}
return $this->{'DiskSpace'};
} // end function event_disk_space
public function Server() {
if ( ! property_exists($this, 'Server') ) {
if ( property_exists($this, 'ServerId') ) {
$this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'}));
if ( !$this->{'Server'} ) {
if ( $this->{'ServerId'} )
Error('No Server record found for server id ' . $this->{'ServerId'});
$this->{'Server'} = new Server();
}
} else {
$this->{'Server'} = new Server();
}
}
return $this->{'Server'};
}
} // end class Storage
?>