foldershare-8.x-1.2/src/ManageUsageStatistics.php
src/ManageUsageStatistics.php
<?php
namespace Drupal\foldershare;
use Drupal\Core\Database\Database;
use Drupal\foldershare\Entity\FolderShare;
use Drupal\foldershare\Entity\FolderShareScheduledTask;
/**
* Manages usage statistics for FolderShare entities.
*
* This class provides static methods to manage collecting and retrieving
* usage statistics about FolderShare entities. Supported operations include:
* - Clearing usage statistics.
* - Deleting usage statistics for specific users.
* - Getting usage statistics.
* - Updating usage statistics.
*
* This method also provides methods to query configuration settings for
* usage statistics updates, and the total number of FolderShare entities
* of various types.
*
* <B>Access control</B>
* This class's methods do not do access control. The caller should check
* access as needed by their situation.
*
* @ingroup foldershare
*
* @see \Drupal\foldershare\Entity\FolderShare
*/
final class ManageUsageStatistics {
/*--------------------------------------------------------------------
*
* Constants.
*
*-------------------------------------------------------------------*/
/**
* The name of the per-user usage tracking database table.
*/
const USAGE_TABLE = 'foldershare_usage';
/*--------------------------------------------------------------------
*
* Table definition.
*
*-------------------------------------------------------------------*/
/**
* Returns the database schema that defines the user usage table.
*
* The table contains one record for each user. Record fields include:
*
* - The user ID.
* - The number of subfolders they own.
* - The number of files they own.
* - The total storage used by the user.
*
* @return array
* Returns an array describing the user usage table.
*/
public static function getSchema() {
$schema[self::USAGE_TABLE] = [
'description' => 'Stores file and folder usage information for users.',
'fields' => [
// The user ID for the user.
'uid' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The user ID.',
],
// The number of sub-folders.
'nFolders' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The number of folders owned by the user.',
],
// The number of files.
'nFiles' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The number of files owned by the user.',
],
// The total storage used by the user.
'nBytes' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The amount of storage (in bytes) used by the user.',
],
],
// Primary Key.
'primary key' => [
'uid',
],
// No additional indexes are needed beyond the primary key.
];
return $schema;
}
/*--------------------------------------------------------------------
*
* Usage operations.
*
*-------------------------------------------------------------------*/
/**
* Clears usage statistics for all users.
*
* @see ::deleteFromUsage()
* @see ::getUsage()
* @see ::getAllUsage()
*/
public static function clearUsage() {
$connection = Database::getConnection();
$connection->delete(self::USAGE_TABLE)->execute();
}
/**
* Deletes the entries for a list of users.
*
* @param int[]|int $uids
* (optional, default = -1) With a -1, usage statistics are deleted
* for all users by forwarding to clearUsage(). Otherwise each user ID
* given is deleted from the usage table.
*
* @see ::clearUsage()
* @see ::getUsage()
* @see ::getAllUsage()
*/
public static function deleteFromUsage($uids = -1) {
if ($uids === -1 || $uids === '-1' || $uids === '') {
self::clearUsage();
return;
}
// User IDs are technically strings, but are often really integers and
// are sometimes cast as integers. Accept either.
if (is_int($uids) === TRUE || is_string[$uids] === TRUE) {
$uids = [$uids];
}
if (is_array($uids) === TRUE) {
for ($i = 0; $i < count($uids); ++$i) {
$uids[$i] = (string) $uids[$i];
}
$connection = Database::getConnection();
$query = $connection->delete(self::USAGE_TABLE);
$query->condition('uid', $uids, 'IN');
$query->execute();
}
}
/**
* Returns usage statistics of all users.
*
* The returned array has one entry for each user in the
* database. Array keys are user IDs, and array values are associative
* arrays with keys for specific metrics and values for those
* metrics. Supported array keys are:
*
* - nFolders: the number of folders.
* - nFiles: the number of files.
* - nBytes: the total storage of all files.
*
* All metrics are for the total number of items or bytes owned by
* the user.
*
* The returned values for bytes used is the current storage space use
* for each user. This value does not include any database storage space
* required for file and folder metadata.
*
* The returned array only contains records for those users that
* have current usage. Users who have no recorded metrics
* will not be listed in the returned array.
*
* @return array
* An array with user ID array keys. Each array value is an
* associative array with keys for each of the above usage.
*
* @see ::clearUsage()
* @see ::deleteFromUsage()
* @see ::getUsage()
* @see ::updateUsage()
*/
public static function getAllUsage() {
// Query the usage table for all entries.
$connection = Database::getConnection();
$select = $connection->select(self::USAGE_TABLE, 'u');
$select->addField('u', 'uid', 'uid');
$select->addField('u', 'nFolders', 'nFolders');
$select->addField('u', 'nFiles', 'nFiles');
$select->addField('u', 'nBytes', 'nBytes');
$records = $select->execute()->fetchAll();
// Build and return an array from the records. Array keys
// are user IDs, while values are usage info.
$usage = [];
foreach ($records as $record) {
$usage[$record->uid] = [
'nFolders' => $record->nFolders,
'nFiles' => $record->nFiles,
'nBytes' => $record->nBytes,
];
}
return $usage;
}
/**
* Returns the time of the last usage statistics update.
*
* @return string
* The last update time.
*/
public static function getLastUpdateTime() {
return \Drupal::state()->get('foldershare.usage_last');
}
/**
* Returns usage statistics for a user.
*
* The returned associative array has keys for specific metrics,
* and values for those metrics. Supported array keys are:
*
* - nFolders: the number of folders.
* - nFiles: the number of files.
* - nBytes: the total storage of all files.
*
* All metrics are for the total number of items or bytes owned by
* the user.
*
* The returned value for bytes used is the current storage space use
* for the user. This value does not include any database storage space
* required for file and folder metadata.
*
* If there is no recorded usage information for the user, an
* array is returned with all metric values zero.
*
* @param int|string $uid
* The user ID of the user whose usage is to be returned.
*
* @return array
* An associative array is returned that includes keys for each
* of the above usage.
*
* @see ::clearUsage()
* @see ::deleteFromUsage()
* @see ::getAllUsage()
* @see ::updateUsage()
*/
public static function getUsage($uid) {
// User IDs are technically strings, but are often really integers and
// are sometimes cast as integers. Accept either.
if (is_int($uid) === FALSE && is_string($uid) === FALSE) {
return [
'nFolders' => 0,
'nFiles' => 0,
'nBytes' => 0,
];
}
$uid = (string) $uid;
// Query the usage table for an entry for this user.
// There could be none, or one, but not multiple entries.
$connection = Database::getConnection();
$select = $connection->select(self::USAGE_TABLE, 'u');
$select->addField('u', 'uid', 'uid');
$select->addField('u', 'nFolders', 'nFolders');
$select->addField('u', 'nFiles', 'nFiles');
$select->addField('u', 'nBytes', 'nBytes');
$select->condition('u.uid', $uid, '=');
$select->range(0, 1);
$records = $select->execute()->fetchAll();
// If none, return an empty usage array.
if (count($records) === 0) {
return [
'nFolders' => 0,
'nFiles' => 0,
'nBytes' => 0,
];
}
// Otherwise return the usage.
$record = array_shift($records);
return [
'nFolders' => $record->nFolders,
'nFiles' => $record->nFiles,
'nBytes' => $record->nBytes,
];
}
/**
* Updates the usage table.
*
* All current usage information is deleted and a new set assembled
* and saved for all users at the site. Users that have no files or
* folders are not included.
*
* <B>Process locks</B>
* This method uses a process lock to insure that the usage table is
* updated by only one process at a time. If the table is found to be
* locked, this method returns immediately without updating the table.
*
* @return bool
* Returns FALSE if the update aborted because another update is already
* in progress. Otherwise returns TRUE.
*
* @see ::clearUsage()
* @see ::deleteFromUsage()
* @see ::getAllUsage()
* @see ::getUsage()
*/
public static function updateUsage() {
// LOCK DURING REBUILD.
if (\Drupal::lock()->acquire('foldershare_usage_lock', 30) === FALSE) {
// Another update is in progress.
return FALSE;
}
\Drupal::state()->set('foldershare.usage_last', 'pending');
// Empty the table.
$connection = Database::getConnection();
$connection->delete(self::USAGE_TABLE)->execute();
// For each user, count the number of files, folders, and bytes, then
// update the usage table.
$userIds = \Drupal::entityQuery('user')->execute();
foreach ($userIds as $uid) {
$nFolders = FolderShare::countNumberOfFolders($uid);
$nFiles = FolderShare::countNumberOfFiles($uid);
$nBytes = FolderShare::countNumberOfBytes($uid);
// Add a new entry.
$query = $connection->insert(self::USAGE_TABLE);
$query->fields(
[
'uid' => $uid,
'nFolders' => $nFolders,
'nFiles' => $nFiles,
'nBytes' => $nBytes,
]);
$query->execute();
}
// UNLOCK.
\Drupal::lock()->release('foldershare_usage_lock');
// Update the stored date.
\Drupal::state()->set('foldershare.usage_last', '@' . (string) time());
ManageLog::notice('Usage statistics updated');
return TRUE;
}
/*--------------------------------------------------------------------
*
* Totals.
*
*-------------------------------------------------------------------*/
/**
* Returns the total number of bytes.
*
* The returned value only includes storage space used for files.
* Any storage space required in the database for folder or file
* metadata is not included.
*
* @return int
* The total number of bytes.
*
* @see FolderShare::countNumberOfBytes()
*/
public static function getNumberOfBytes() {
return FolderShare::countNumberOfBytes();
}
/**
* Returns the total number of folders.
*
* @return int
* The total number of folders.
*
* @see FolderShare::countNumberOfFolders()
*/
public static function getNumberOfFolders() {
return FolderShare::countNumberOfFolders();
}
/**
* Returns the total number of files.
*
* @return int
* The total number of files.
*
* @see FolderShare::countNumberOfFiles()
*/
public static function getNumberOfFiles() {
return FolderShare::countNumberOfFiles();
}
/*---------------------------------------------------------------------
*
* Update scheduling.
*
*---------------------------------------------------------------------*/
/**
* Returns the usage update task run interval.
*
* The run interval indicates the time between execution of the usage
* updating scheduled task. Known values are:
*
* - 'manual' = the table is only updated manually.
* - 'hourly' = the table is updated hourly.
* - 'daily' = the table is updated daily.
* - 'weekly' = the table is updated weekly.
*
* @return string
* Returns the run interval setting.
*
* @see \Drupal\foldershare\Settings::getUsageUpdateInterval()
*/
public static function getIndexInterval() {
return Settings::getUsageUpdateInterval();
}
/**
* Returns the usage update task run interval in seconds.
*
* The current run interval is interpreted to compute and return the
* interval time in seconds.
*
* @return int
* Returns the run interval in seconds. Zero is returned if the
* interval is 'manual'.
*
* @see ::getIndexInterval()
* @see \Drupal\foldershare\Settings::getUsageUpdateInterval()
*/
private static function getIndexIntervalSeconds() {
switch (self::getIndexInterval()) {
default:
case 'manual':
// No update task.
return 0;
case 'hourly':
// 60 minutes/hour * 60 seconds/minute.
return (60 * 60);
case 'daily':
// 24 hours/day * 60 minutes/hour * 60 seconds/minute.
return (24 * 60 * 60);
case 'weekly':
// 7 days/week * 24 hours/day * 60 minutes/hour * 60 seconds/minute.
return (7 * 24 * 60 * 60);
}
}
/**
* Schedules usage statistics updates.
*
* <B>This method is internal and strictly for use by the FolderShare
* module itself.</B>
*
* The current usage updating task, if any, is deleted and a new task
* scheduled, if the current interval is not 'manual'.
*
* @see ::getIndexInterval()
* @see ::taskUpdateUsage()
* @see \Drupal\foldershare\Settings::getUsageUpdateInterval()
*/
public static function scheduleUsageUpdate() {
// If a task is already scheduled, delete it first.
FolderShareScheduledTask::deleteTasks(
'\Drupal\foldershare\ManageUsageStatistics::taskUpdateUsage');
$seconds = self::getIndexIntervalSeconds();
if ($seconds === 0) {
// There is no scheduled update task.
return;
}
// Schedule a new task with the current interval.
FolderShareScheduledTask::createTask(
time() + $seconds,
'\Drupal\foldershare\ManageUsageStatistics::taskUpdateUsage',
(int) \Drupal::currentUser()->id(),
NULL,
time(),
self::getIndexInterval() . ' usage table update',
0);
}
/*---------------------------------------------------------------------
*
* Usage update task.
*
*---------------------------------------------------------------------*/
/**
* Updates the usage table as a scheduled task.
*
* <B>This method is internal and strictly for use by the FolderShare
* module itself.</B>
*
* The task updates the usage table.
*
* @param int $requester
* The user ID of the user that requested the update. This is ignored.
* @param array $parameters
* The queued task's parameters. This is ignored.
* @param int $started
* The timestamp of the start date & time for an operation that causes
* a chain of tasks.
* @param string $comments
* A comment on the current task.
* @param int $executionTime
* The accumulated total execution time of the task chain, in seconds.
*/
public static function taskUpdateUsage(
int $requester,
array $parameters,
int $started,
string $comments,
int $executionTime) {
$seconds = self::getIndexIntervalSeconds();
if ($seconds === 0) {
// There is no scheduled update task.
return;
}
// Schedule the next task with the current interval.
FolderShareScheduledTask::createTask(
time() + $seconds,
'\Drupal\foldershare\ManageUsageStatistics::taskUpdateUsage',
$requester,
NULL,
time(),
self::getIndexInterval() . ' usage table update',
0);
// Update the usage table.
self::updateUsage();
}
}
