44 Commits

Author SHA1 Message Date
a6ecf19932 fix: Fixing CI. Added directory listing 👷🔥
Some checks failed
CI / release (push) Successful in 13s
Create Distribution / Create Archive (push) Failing after 5s
2024-10-29 21:37:39 +13:00
942c4e8699 fix: Fixing CI. Separate flow for upload 👷🔥
Some checks failed
CI / release (push) Successful in 14s
Create Distribution / Create Archive (push) Failing after 4s
2024-10-29 21:31:10 +13:00
d9d01fcbb3 fix: Fixing CI. Separate flow for upload 👷🔥
Some checks failed
CI / release (push) Successful in 1m47s
Create Distribution / Create Archive (push) Failing after 2s
2024-10-29 21:10:53 +13:00
de1973025f fix: Exclude generated changelog 🔥
All checks were successful
CI / release (push) Successful in 16s
2024-10-24 23:03:56 +13:00
dede25ec32 fix: Do not include semrel folder 🔥
All checks were successful
CI / release (push) Successful in 16s
2024-10-24 22:59:47 +13:00
607d3a9004 fix: Relabelling namespace/package names ♻️
All checks were successful
CI / release (push) Successful in 24s
2024-10-24 22:38:37 +13:00
032be45f9f fix: Create .zip archive instead of .tar.gz for Gitea 💚👷
All checks were successful
CI / release (push) Successful in 42s
2024-10-24 22:25:21 +13:00
afbddabea2 chore: Updated repo path 🔨 2024-10-24 22:24:23 +13:00
2ec085419f fix: Updated build script 🔨💚
All checks were successful
CI / release (push) Successful in 22s
2024-10-24 16:58:31 +13:00
7657af0a55 fix: Updated build script 🔨💚
All checks were successful
CI / release (push) Successful in 19s
2024-10-24 16:55:02 +13:00
8b87324850 ci: Use gitea provider from go-semantic-release 💚
All checks were successful
CI / release (push) Successful in 21s
2024-10-24 16:41:41 +13:00
25c80c4baf chore: Updated gitignore 🙈
Some checks failed
CI / release (push) Failing after 2m21s
2024-10-24 16:36:53 +13:00
afc24185d3 ci: Added Gitea Actions config 👷 2024-10-24 16:36:25 +13:00
e1cac1e31d fix: Don't break on whitespace. Helpful for JSON 🐛 2024-10-24 16:26:31 +13:00
0c05079327 tests: Updated tests for later PHPUnit 🔥 2024-10-24 15:37:18 +13:00
7526e04e08 chore: Update namespace 2024-10-24 15:36:26 +13:00
b84bde262e ⬆️ chore: Change namespace 2024-10-24 15:34:40 +13:00
a2b204fa47 Update README.md 2015-10-13 15:49:15 -05:00
f03508bf3e First round of updates finished.
- Needs better test cases.
2015-09-30 15:12:35 -05:00
49677e8bf2 Merge branch 'master' of https://github.com/dcarbone/ugly-queue 2015-09-30 08:56:31 -05:00
50601bc3c8 First round of updates finished. 2015-09-30 08:55:18 -05:00
d935032ec3 Devil is in the details. 2015-09-29 12:08:32 -05:00
232e228475 Lots of cleanup, removing of dumb code. More to do. 2015-09-29 11:35:48 -05:00
d40357df73 Update LICENSE 2015-07-31 10:31:08 -05:00
4020de5223 Update .travis.yml
Updating to take advantage of Travis-CI containers.
2015-07-20 10:17:07 -05:00
13df907166 Updating README.md 2014-10-30 10:55:29 -05:00
b840f40a89 - Moving Notify things into "enum" class
- Adding \Countable interfaces to both UglyQueue and UglyQueueManager
2014-10-30 10:54:06 -05:00
d20106a021 Some more tests and such. 2014-10-01 08:10:39 -05:00
97dba637d9 Adding some test cases for UglyQueueManager 2014-09-29 17:23:43 -05:00
793edc2d60 Adding some test cases for UglyQueueManager 2014-09-29 17:18:14 -05:00
1e98b0cf7e Fixing some issues noticed in test cases. 2014-09-29 17:02:28 -05:00
89c51468eb Huge update. 2014-09-29 16:26:53 -05:00
d33fd6bcba Adding ability to get list of initialized queues. 2014-08-11 12:42:17 -05:00
89cb1ae4b1 Adding ability to check for queue group existence 2014-08-11 12:24:05 -05:00
12f6edf269 More tests and minor updates. 2014-08-10 14:27:00 -05:00
0b3b876b64 More tests and minor updates. 2014-08-10 13:56:15 -05:00
e07cb21821 Removing weird file deletion method. 2014-08-10 12:28:26 -05:00
9387bb8843 More test methods and slight modifications to UglyQueue 2014-08-10 11:46:06 -05:00
1825c09123 Removing test queue 2014-08-10 10:54:12 -05:00
728c6a5a7d More test methods and slight modifications to UglyQueue 2014-08-10 10:52:59 -05:00
d63950b5f5 Update README.md 2014-08-08 18:15:02 -05:00
6dcb89742e Adding .travis.yml 2014-08-08 18:11:20 -05:00
b166fd3497 Adding 2 methods and beginning work on test cases 2014-08-08 18:09:45 -05:00
734ec3d5c4 Couple of missed strings relating to it's internal dev name 2014-08-08 17:27:21 -05:00
16 changed files with 1639 additions and 183 deletions

23
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: CI
on:
push:
branches:
- "**"
tags:
- "!**"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Release Archive
id: semrelease
uses: go-semantic-release/action@v1
with:
# custom-arguments: --provider=gitea
hooks: exec
env:
GITEA_TOKEN: ${{ secrets.G_TOKEN }}
GITEA_HOST: ${{ secrets.G_SERVER_URL}}

24
.github/workflows/dist.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Create Distribution
run-name: Distribute composer package with Gitea Actions 🚀
on:
push:
tags:
- 'v*'
jobs:
dist:
name: Create Archive
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # Checkout Sourcecode
- uses: https://hub.cybercinch.nz/cybercinch/composer-build-action@v1
- run: |
ls -lah
- uses: https://hub.cybercinch.nz/guisea/gitea-composer-upload-action@v1
with:
base_url: "${{ secrets.G_SERVER_URL}}"
access_token: "${{ secrets.G_TOKEN }}"
username: "${{ secrets.G_USERNAME }}"
owner: "cybercinch" # Override owner name for repository (Optional)
package_version: "${{ env.GITHUB_REF_NAME }}"

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
**/vendor
tests/misc/*
!tests/misc/cleanup.php
!tests/misc/index.html

21
.semrelrc Normal file
View File

@@ -0,0 +1,21 @@
{
"plugins": {
"provider": {
"name": "gitea"
},
"changelog-generator": {
"name": "default",
"options": {
"emojis": "true"
}
},
"hooks": {
"names": [
"exec"
],
"options": {
"exec_on_no_release": "echo {{.Reason}}: {{.Message}}"
}
}
}
}

17
.travis.yml Normal file
View File

@@ -0,0 +1,17 @@
language: php
sudo: false
php:
- 5.3.3
- 5.3
- 5.4
- 5.5
- 5.6
before_script:
- composer self-update 1.0.0-alpha10
- composer install --no-interaction --prefer-source
script:
- ./vendor/bin/phpunit

View File

@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
dcarbone/ugly-queue Copyright (C) 2015 Daniel Paul Carbone
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

View File

@@ -1,6 +1,91 @@
ugly-queue
==========
A simple file-based queue system for PHP 5.3.3+
A simple file-based FIFO queue system for PHP 5.3.3+
Documentation and Test suites forthcoming.
Build status: [![Build Status](https://travis-ci.org/Cybercinch/ugly-queue.svg?branch=master)](https://travis-ci.org/Cybercinch/ugly-queue)
## Installation
This library is designed to be installed into your app using [https://getcomposer.org/](Composer).
Simply copy-paste the following line into your `"requires:"` hash:
```json
"dcarbone/ugly-queue": "0.4.*"
```
## Basic Usage
Once installed, you must first initialize an instance of [src/UglyQueueManager.php](UglyQueueManager).
This is done as such:
```php
$queueBaseDir = 'path to where you would like queue files and directories to be stored';
$manager = new UglyQueueManager($queueBaseDir);
```
Once initialized, you can start adding queues!
```php
$sandwichQueue = $manager->createQueue('sandwiches');
$sandwichQueue->lock();
$sandwichQueue->addItems(array(
'bread',
'meat',
'cheese',
'lettuce',
'bread'
));
$sandwichQueue->unlock();
```
Once you have items added to the queue, you can either pull items out ad-hoc or set up some sort of cron
or schedule task to process items regularly.
If the base directory for all of your queues remains the same, each initialization
of `UglyQueueManager` will automatically find and initialize instances of pre-existing
UglyQueues.
In a subsequent request, simply do the following:
```php
$queueBaseDir = 'path to where you would like queue files and directories to be stored';
$manager = new UglyQueueManager($queueBaseDir);
$tastySandwich = $manager->getQueue('sandwiches');
// 'sandwiches' queue will exist now
$tastySandwich->lock();
// Specify the number of items you wish to retrieve from the queue
$items = $tastySandwich->retrieveItems(4);
// $items is now an array...
var_export($items);
/*
array (
4 => 'bread',
3 => 'lettuce',
2 => 'cheese',
1 => 'meat',
)
*/
```
The queue will then retain a single item, `0 => 'bread'` as the 5th item left in the queue.
At any time you can determine how many items remain in a queue by executing `count($queueObj);`
There are a few limitations currently:
1. This lib is designed for small values without much in the way of formatting or line breaks
2. It is designed to be atomic, meaning that only one process can be adding / retrieving items from
a queue at a time. Reading actions (count, searching, etc) are NOT atomic, however.

View File

@@ -1,5 +1,5 @@
{
"name" : "dcarbone/ugly-queue",
"name" : "cybercinch/ugly-queue",
"type" : "library",
"description" : "A simple file-based queue system for PHP 5.3.3+",
@@ -9,28 +9,32 @@
"file queue",
"ugly queue"
],
"homepage": "https://github.com/dcarbone/ugly-queue",
"homepage": "https://hub.cybercinch.nz/cybercinch/ugly-queue",
"license": "GPLv3",
"authors" : [
{
"name" : "Daniel Carbone",
"email" : "daniel.p.carbone@gmail.com"
},
{
"name" : "Aaron Guise",
"email" : "aaron-composer@guise.net.nz"
}
],
"require" : {
"php" : ">=5.3.3",
"dcarbone/helpers" : "6.1.*"
"php" : ">=7.1",
"dcarbone/helpers" : "~6.1"
},
"require-dev" : {
"phpunit/phpunit": "4.1.*"
"phpunit/phpunit": "~9.6.21"
},
"autoload" : {
"psr-4" : {
"DCarbone\\" : "src/"
"Cybercinch\\" : "src/"
}
}
}

View File

@@ -17,6 +17,11 @@
<exclude>./tests/misc</exclude>
</testsuite>
<testsuite name="UglyQueueManager">
<directory>./tests/UglyQueueManager</directory>
<exclude>./tests/misc</exclude>
</testsuite>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>

32
scripts/build.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
mkdir -p ./build/
echo "Made temporary directory"
# Package up the release (Needs to be .zip for upload to gitea)
zip -r "./build/Package.zip" \
. \
-x '.semrel/*' \
-x '.generated-go-semantic-release-changelog.md' \
-x './vendor/*' \
-x './tests/*' \
-x './build/*' \
-x './.git/*' \
-x './.idea/*' \
-x './.github/*' \
-x './scripts/*'
#RELEASE_ID=$(curl --silent -X 'GET' "${GITEA_HOST}/api/v1/repos/cybercinch/ugly-queue/releases/latest" \
#-H "accept: application/json" \
#-H "Authorization: token ${GITEA_TOKEN}" | jq -r .id)
#
## Attach to release
#curl --silent -X 'POST' "${GITEA_HOST}/api/v1/repos/cybercinch/ugly-queue/releases/${RELEASE_ID}/assets?name=ugly-queue-${1}.tar.gz" \
#-H "Authorization: token ${GITEA_TOKEN}" \
#--form attachment="@build/ugly-queue-${1}.tar.gz"
# Upload the artifact to composer registry
curl --user "cibot:${GITEA_TOKEN}" \
--upload-file "build/ugly-queue.zip" \
"${GITEA_HOST}/api/packages/cybercinch/composer?version=${1}"

View File

@@ -1,48 +1,74 @@
<?php namespace DCarbone;
<?php namespace Cybercinch;
use DCarbone\Helpers\FileHelper;
/**
* Class UglyQueue
* @package DCarbone
* @package Cybercinch
*/
class UglyQueue
class UglyQueue implements \Serializable, \SplSubject, \Countable
{
/** @var array */
protected $config;
const QUEUE_READONLY = 0;
const QUEUE_READWRITE = 1;
/** @var int */
private $_notifyStatus;
/** @var \SplObserver[] */
private $_observers = array();
/** @var int */
protected $mode = null;
/** @var string */
protected $queueBaseDir;
protected $baseDir;
/** @var string */
protected $queueGroup = null;
protected $name;
/** @var string */
protected $queueGroupDirPath = null;
protected $path;
/** @var bool */
protected $haveLock = false;
/** @var bool */
protected $init = false;
protected $locked = false;
/** @var resource */
protected $_tmpHandle;
protected $tmpHandle;
/** @var string */
protected $queueFile;
/** @var string */
protected $queueTmpFile;
/** @var string */
protected $lockFile;
/** @var string */
protected $serializeFile;
/**
* @param array $config
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @param string $baseDir
* @param string $name
* @param \SplObserver[] $observers
*/
public function __construct(array $config)
public function __construct($baseDir, $name, array $observers = array())
{
if (!isset($config['queue-base-dir']))
throw new \InvalidArgumentException('UglyQueue::__construct - "$config" parameter "queue-base-dir" not seen.');
$this->baseDir = realpath($baseDir);
$this->name = $name;
$this->_observers = $observers;
if (!is_dir($config['queue-base-dir']) || !is_writable($config['queue-base-dir']))
throw new \RuntimeException('UglyQueue::__construct - "$config[\'queue-base-dir\']" points to a directory that either doesn\'t exist or is not writable');
$path = sprintf('%s%s%s', $baseDir, DIRECTORY_SEPARATOR, $name);
if (!file_exists($path) && !@mkdir($path))
throw new \RuntimeException('Unable to initialize queue directory "'.$path.'". Please check permissions.');
$this->config = $config;
$this->path = $path;
$this->lockFile = sprintf('%s%squeue.lock', $path, DIRECTORY_SEPARATOR);
$this->queueFile = sprintf('%s%squeue.txt', $path, DIRECTORY_SEPARATOR);
$this->queueTmpFile = sprintf('%s%squeue.tmp', $path, DIRECTORY_SEPARATOR);
$this->serializeFile = sprintf('%s%sugly-queue.obj', $path, DIRECTORY_SEPARATOR);
$this->initialize();
}
/**
@@ -50,78 +76,147 @@ class UglyQueue
*/
public function __destruct()
{
$this->unlock();
$this->_populateQueue();
$this->unlock();
file_put_contents($this->path.'/ugly-queue.obj', serialize($this));
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return int
*/
public function getMode()
{
return $this->mode;
}
/**
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* @return string
*/
public function getBaseDir()
{
return $this->baseDir;
}
/**
* @return string
*/
public function getQueueFile()
{
return $this->queueFile;
}
/**
* @return string
*/
public function getQueueTmpFile()
{
return $this->queueTmpFile;
}
/**
* @return string
*/
public function getLockFile()
{
return $this->lockFile;
}
/**
* @return string
*/
public function getSerializeFile()
{
return $this->serializeFile;
}
/**
* @return boolean
*/
public function isLocked()
{
return $this->locked;
}
/**
* @param int $ttl Time to live in seconds
* @throws \InvalidArgumentException
* @return bool
*/
public function lock($ttl = 250)
{
$already_locked = $this->isLocked();
if (!is_int($ttl))
throw new \InvalidArgumentException('Argument 1 expected to be integer, "'.gettype($ttl).'" seen');
// If there is no lock, currently
if ($already_locked === false)
return $this->haveLock = $this->createQueueLock($ttl);
if ($ttl < 1)
throw new \InvalidArgumentException('Argument 1 expected to be greater than 0 "'.$ttl.'" seen');
// If there is currently no lock
if ($this->isAlreadyLocked() === false)
return $this->createLockFile($ttl);
// If we make it this far, there is already a lock in place.
return $this->haveLock = false;
$this->locked = false;
$this->_notifyStatus = UglyQueueEnum::QUEUE_LOCKED_BY_OTHER_PROCESS;
$this->notify();
return false;
}
/**
* @param int $ttl seconds to live
* @return bool
*/
protected function createQueueLock($ttl)
{
$ok = (bool)@file_put_contents(
$this->queueGroupDirPath.'queue.lock',
json_encode(array('ttl' => $ttl, 'born' => time())));
if ($ok !== true)
return false;
$this->haveLock = true;
return true;
}
/**
* Close file_queue, writing out contents to file.
* Close this ugly queue, writing out contents to file.
*/
public function unlock()
{
if ($this->haveLock === true)
if ($this->isLocked() === true)
{
@FileHelper::superUnlink($this->queueGroupDirPath.'queue.lock');
$this->haveLock = false;
unlink($this->lockFile);
$this->locked = false;
$this->_notifyStatus = UglyQueueEnum::QUEUE_UNLOCKED;
$this->notify();
}
}
/**
* @throws \RuntimeException
* @return bool
*/
public function isLocked()
public function isAlreadyLocked()
{
// First check for lock file
if (is_file($this->queueGroupDirPath.'queue.lock'))
if (is_file($this->lockFile))
{
$lock = json_decode(file_get_contents($this->queueGroupDirPath.'queue.lock'), true);
$lock = json_decode(file_get_contents($this->lockFile), true);
// If we have an invalid lock structure, THIS IS BAD.
if (!isset($lock['ttl']) || !isset($lock['born']))
return true;
// If the decoded lock file contains a ttl and born value...
if (isset($lock['ttl']) && isset($lock['born']))
{
$lock_ttl = ((int)$lock['born'] + (int)$lock['ttl']);
$lock_ttl = ((int)$lock['born'] + (int)$lock['ttl']);
// If we're within the TTL of the lock, assume another thread is already processing.
// We'll pick it up on the next go around.
if ($lock_ttl > time())
return true;
// If we're within the TTL of the lock, assume another thread is already processing.
// We'll pick it up on the next go around.
if ($lock_ttl > time())
return true;
}
// Else, remove lock file and assume we're good to go!
@FileHelper::superUnlink($this->queueGroupDirPath.'queue.lock');
unlink($this->lockFile);
return false;
}
@@ -129,96 +224,77 @@ class UglyQueue
return false;
}
/**
* @param string $queue_group
*/
public function initialize($queue_group)
{
$this->queueBaseDir = $this->config['queue-base-dir'];
$this->queueGroup = $queue_group;
$this->queueGroupDirPath = $this->queueBaseDir.$queue_group.DIRECTORY_SEPARATOR;
// Create directory for this queue group
if (!is_dir($this->queueGroupDirPath))
mkdir($this->queueGroupDirPath);
// Insert "don't look here" index.html file
if (!file_exists($this->queueGroupDirPath.'index.html'))
{
$html = <<<HTML
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>
HTML;
file_put_contents($this->queueGroupDirPath.'index.html', $html);
}
if (!file_exists($this->queueGroupDirPath.'queue.txt'))
file_put_contents($this->queueGroupDirPath.'queue.txt', '');
$this->init = true;
}
/**
* @param int $count
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @return bool|array
*/
public function processQueue($count = 1)
public function retrieveItems($count = 1)
{
if ($this->init === false)
throw new \RuntimeException('file_queue::load_queue_data - Must first initialize queue!');
if ($this->mode === self::QUEUE_READONLY)
throw new \RuntimeException('Queue "'.$this->name.'" cannot be processed. It was started in Read-Only mode (the user running this process does not have permission to write to the queue directory).');
// If we don't have a lock, assume issue and move on.
if ($this->haveLock === false || !file_exists($this->queueGroupDirPath.'queue.txt'))
return false;
if ($this->isLocked() === false)
throw new \RuntimeException('Cannot process queue named "'.$this->name.'". It is locked by another process.');
// If non-int valid is passed
if (!is_int($count))
throw new \InvalidArgumentException('Argument 1 expected to be integer greater than 0, "'.gettype($count).'" seen');
// If negative integer passed
if ($count <= 0)
throw new \InvalidArgumentException('Argument 1 expected to be integer greater than 0, "'.$count.'" seen');
if ($this->_notifyStatus !== UglyQueueEnum::QUEUE_PROCESSING)
{
$this->_notifyStatus = UglyQueueEnum::QUEUE_PROCESSING;
$this->notify();
}
// Find number of lines in the queue file
$line_count = FileHelper::getLineCount($this->queueGroupDirPath.'queue.txt');
$lineCount = count($this);
// If queue line count is 0, assume empty
if ($line_count === 0)
if ($lineCount === 0)
return false;
// Try to open the file for reading / writing.
$queue_file_handle = fopen($this->queueGroupDirPath.'queue.txt', 'r+');
if ($queue_file_handle === false)
$queueFileHandle = fopen($this->queueFile, 'r+');
if ($queueFileHandle === false)
$this->unlock();
// Get an array of the oldest $count data in the queue
$data = array();
$start_line = $line_count - $count;
$start_line = $lineCount - $count;
$i = 0;
while (($line = fscanf($queue_file_handle, "%s\t%s\n")) !== false && $i < $line_count)
while (($line = fscanf($queueFileHandle, "%s\t%[^\n]")) !== false && $i < $lineCount)
{
if ($i++ >= $start_line)
{
list ($key, $value) = $line;
$data[$key] = $value;
$data = array($key => $value) + $data;
}
}
// If we have consumed the rest of the file
if ($count >= $line_count)
if ($count >= $lineCount)
{
rewind($queue_file_handle);
ftruncate($queue_file_handle, 0);
fclose($queue_file_handle);
$this->unlock();
rewind($queueFileHandle);
ftruncate($queueFileHandle, 0);
fclose($queueFileHandle);
$this->_notifyStatus = UglyQueueEnum::QUEUE_REACHED_END;
$this->notify();
}
// Otherwise, create new queue file minus the processed lines.
else
{
$tmp = fopen($this->queueGroupDirPath.'queue.tmp', 'w+');
rewind($queue_file_handle);
$tmp = fopen($this->queueTmpFile, 'w+');
rewind($queueFileHandle);
$i = 0;
while (($line = fgets($queue_file_handle)) !== false && $i < $start_line)
while (($line = fgets($queueFileHandle)) !== false && $i < $start_line)
{
if ($line !== "\n" || $line !== "")
fwrite($tmp, $line);
@@ -226,10 +302,10 @@ HTML;
$i++;
}
fclose($queue_file_handle);
fclose($queueFileHandle);
fclose($tmp);
FileHelper::superUnlink($this->queueGroupDirPath.'queue.txt');
rename($this->queueGroupDirPath.'queue.tmp', $this->queueGroupDirPath.'queue.txt');
unlink($this->queueFile);
rename($this->queueTmpFile, $this->queueFile);
}
return $data;
@@ -241,71 +317,278 @@ HTML;
* @return bool
* @throws \RuntimeException
*/
public function addToQueue($key, $value)
public function addItem($key, $value)
{
if ($this->init === false)
throw new \RuntimeException('file_queue::add_to_queue - Must first initialize queue!');
if ($this->mode === self::QUEUE_READONLY)
throw new \RuntimeException('Cannot add item to queue "'.$this->name.'" as it is in read-only mode');
// If we don't have a lock, assume issue and move on.
if ($this->haveLock === false)
return false;
if ($this->isLocked() === false)
throw new \RuntimeException('Cannot add item to queue "'.$this->name.'". Queue is already locked by another process');
if (!is_resource($this->_tmpHandle))
if (!is_resource($this->tmpHandle))
{
$this->_tmpHandle = fopen($this->queueGroupDirPath.'queue.tmp', 'w+');
if ($this->_tmpHandle === false)
return false;
$this->tmpHandle = fopen($this->queueTmpFile, 'w+');
if ($this->tmpHandle === false)
throw new \RuntimeException('Unable to create "queue.tmp" file.');
}
if (is_array($value) || $value instanceof \stdClass)
$value = json_encode($value);
return (bool)fwrite(
$this->_tmpHandle,
$this->tmpHandle,
$key."\t".str_replace(array("\r\n", "\n"), ' ', $value)
."\n");
}
/**
* @param array $items
*/
public function addItems(array $items)
{
foreach($items as $k=>$v)
{
$this->addItem($k, $v);
}
}
/**
* If there is a tmp queue file, add it's contents to the beginning of a new queue file
*
* @return void
*/
protected function _populateQueue()
public function _populateQueue()
{
if (is_resource($this->_tmpHandle))
if (is_resource($this->tmpHandle))
{
if (file_exists($this->queueGroupDirPath.'queue.txt'))
if (file_exists($this->queueFile))
{
$queue_file_handle = fopen($this->queueGroupDirPath.'queue.txt', 'r+');
while (($line = fgets($queue_file_handle)) !== false)
$queueFileHandle = fopen($this->queueFile, 'r+');
while (($line = fgets($queueFileHandle)) !== false)
{
if ($line !== "\n" && $line !== "")
fwrite($this->_tmpHandle, $line);
fwrite($this->tmpHandle, $line);
}
fclose($queue_file_handle);
FileHelper::superUnlink($this->queueGroupDirPath.'queue.txt');
fclose($queueFileHandle);
unlink($this->queueFile);
}
fclose($this->_tmpHandle);
rename($this->queueGroupDirPath.'queue.tmp', $this->queueGroupDirPath.'queue.txt');
fclose($this->tmpHandle);
rename($this->queueTmpFile, $this->queueFile);
}
}
/**
* @return string
* @param string $key
* @return bool
* @throws \RuntimeException
*/
public function getQueueGroup()
public function keyExistsInQueue($key)
{
return $this->queueGroup;
$key = (string)$key;
// Try to open the file for reading / writing.
$queueFileHandle = fopen($this->queueFile, 'r');
while(($line = fscanf($queueFileHandle, "%s\t%s\n")) !== false)
{
if ($key === $line[0])
{
fclose($queueFileHandle);
return true;
}
}
fclose($queueFileHandle);
return false;
}
/**
* @return string
* (PHP 5 >= 5.1.0)
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
*
* @return int The custom count as an integer.
*/
public function getQueueBaseDir()
public function count()
{
return $this->queueBaseDir;
return (int)FileHelper::getLineCount($this->queueFile);
}
/**
* (PHP 5 >= 5.1.0)
* String representation of object
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or null
*/
public function serialize()
{
return serialize(
array(
$this->baseDir,
$this->name,
$this->path,
$this->queueFile,
$this->queueTmpFile,
$this->lockFile,
$this->serializeFile,
)
);
}
/**
* (PHP 5 >= 5.1.0)
* Constructs the object
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized The string representation of the object.
* @return void
*/
public function unserialize($serialized)
{
$data = unserialize($serialized);
$this->baseDir = $data[0];
$this->name = $data[1];
$this->path = $data[2];
$this->queueFile = $data[3];
$this->queueTmpFile = $data[4];
$this->lockFile = $data[5];
$this->serializeFile = $data[6];
$this->initialize();
}
/**
* (PHP 5 >= 5.1.0)
* Attach an SplObserver
* @link http://php.net/manual/en/splsubject.attach.php
*
* @param \SplObserver $observer The SplObserver to attach.
* @return void
*/
public function attach(\SplObserver $observer)
{
if (!in_array($observer, $this->_observers))
$this->_observers[] = $observer;
}
/**
* (PHP 5 >= 5.1.0)
* Detach an observer
* @link http://php.net/manual/en/splsubject.detach.php
*
* @param \SplObserver $observer The SplObserver to detach.
* @return void
*/
public function detach(\SplObserver $observer)
{
$idx = array_search($observer, $this->_observers, true);
if ($idx !== false)
unset($this->_observers[$idx]);
}
/**
* (PHP 5 >= 5.1.0)
* Notify an observer
* @link http://php.net/manual/en/splsubject.notify.php
*
* @return void
*/
public function notify()
{
foreach($this->_observers as $observer)
{
$observer->update($this);
}
}
/**
* This method is mostly intended to check the "validity" of a re-initialized queue
*
* Could probably stand to be improved.
*
* @return bool
*/
public function _valid()
{
return (
$this->baseDir !== null &&
$this->name !== null &&
$this->path !== null &&
$this->queueFile !== null &&
$this->queueTmpFile !== null &&
$this->lockFile !== null &&
$this->serializeFile !== null
);
}
// --------
/**
* @param int $ttl seconds to live
* @return bool
*/
protected function createLockFile($ttl)
{
$ok = (bool)@file_put_contents(
$this->lockFile,
json_encode(array('ttl' => $ttl, 'born' => time())));
if ($ok !== true)
{
$this->_notifyStatus = UglyQueueEnum::QUEUE_FAILED_TO_LOCK;
$this->notify();
return $this->locked = false;
}
$this->locked = true;
$this->_notifyStatus = UglyQueueEnum::QUEUE_LOCKED;
$this->notify();
return true;
}
/**
* Post-construct initialization method.
*
* Also used post-un-serialization
*/
protected function initialize()
{
if (is_readable($this->path) && is_writable($this->path))
$this->mode = self::QUEUE_READWRITE;
else if (is_readable($this->path))
$this->mode = self::QUEUE_READONLY;
if (!file_exists($this->path.'/index.html'))
{
if ($this->mode === self::QUEUE_READONLY)
throw new \RuntimeException('Cannot initialize queue with name "'.$this->name.'", the user lacks permission to create files.');
$html = <<<HTML
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>
HTML;
file_put_contents($this->path.'/index.html', $html);
}
if (!file_exists($this->queueFile))
{
if ($this->mode === self::QUEUE_READONLY)
throw new \RuntimeException('Cannot initialize queue with name "'.$this->name.'", the user lacks permission to create files.');
file_put_contents($this->queueFile, '');
}
$this->_notifyStatus = UglyQueueEnum::QUEUE_INITIALIZED;
$this->notify();
}
}

24
src/UglyQueueEnum.php Normal file
View File

@@ -0,0 +1,24 @@
<?php namespace Cybercinch;
/**
* Class UglyQueueEnum
* @package Cybercinch
*
* Pseudo-enum thing.
*/
abstract class UglyQueueEnum
{
// Typically used by UglyQueueManager
const MANAGER_INITIALIZED = 1;
const QUEUE_ADDED = 2;
const QUEUE_REMOVED = 3;
// Typically used by UglyQueues
const QUEUE_INITIALIZED = 100;
const QUEUE_LOCKED = 101;
const QUEUE_FAILED_TO_LOCK = 102;
const QUEUE_LOCKED_BY_OTHER_PROCESS = 103;
const QUEUE_UNLOCKED = 104;
const QUEUE_PROCESSING = 105;
const QUEUE_REACHED_END = 106;
}

193
src/UglyQueueManager.php Normal file
View File

@@ -0,0 +1,193 @@
<?php namespace Cybercinch;
use InvalidArgumentException;
/**
* Class UglyQueueManager
* @package Cybercinch
*/
class UglyQueueManager implements \SplObserver, \Countable
{
/** @var UglyQueue[] */
protected $queues = array();
/** @var string */
protected $baseDir;
/**
* Constructor
*
* @param string $baseDir
* @throws \RuntimeException
* @throws InvalidArgumentException
*/
public function __construct($baseDir)
{
if (false === is_string($baseDir))
throw new InvalidArgumentException('Argument 1 expected to be string, "'.gettype($baseDir).'" seen.');
if (false === is_dir($baseDir))
throw new \RuntimeException('"'.$baseDir.'" points to a directory that does not exist.');
if (false === is_readable($baseDir))
throw new \RuntimeException('"'.$baseDir.'" is not readable and/or writable .');
$this->baseDir = rtrim($baseDir, "/\\");
foreach(glob(sprintf('%s/*', $this->baseDir), GLOB_ONLYDIR) as $queueDir)
{
$this->addQueueAtPath($queueDir);
}
}
/**
* @param string $name
* @return UglyQueue
*/
public function getQueue($name)
{
if (isset($this->queues[$name]))
return $this->queues[$name];
$path = sprintf('%s/%s', $this->baseDir, $name);
if (file_exists($path))
return $this->addQueueAtPath($path);
return $this->createQueue($name);
}
/**
* @param UglyQueue $uglyQueue
* @return UglyQueueManager
* @throws \RuntimeException
*/
public function addQueue(UglyQueue $uglyQueue)
{
$name = $uglyQueue->getName();
if ($this->containsQueueWithName($name))
throw new \RuntimeException('Queue named "'.$name.'" already exists in this manager.');
$this->queues[$name] = $uglyQueue;
return $this;
}
/**
* @param string $name
* @return UglyQueue
*/
public function createQueue($name)
{
$queue = new UglyQueue($this->baseDir, $name, array($this));
$this->addQueue($queue);
return end($this->queues);
}
/**
* @param string $path
* @return \DCarbone\UglyQueueManager
*/
public function addQueueAtPath($path)
{
// Try to avoid looking at hidden directories or magic dirs such as '.' and '..'
$split = preg_split('#[/\\\]+#', $path);
$queueName = end($split);
if (0 === strpos($queueName, '.'))
return null;
$serializedFile = sprintf('%s/%s/ugly-queue.obj', $this->baseDir, $queueName);
/** @var \DCarbone\UglyQueue $uglyQueue */
if (file_exists($serializedFile))
$uglyQueue = unserialize(file_get_contents($serializedFile));
if (!isset($uglyQueue) || $uglyQueue->_valid() === false)
$uglyQueue = new UglyQueue($this->baseDir, $queueName, array($this));
$uglyQueue->attach($this);
return $this->addQueue($uglyQueue);
}
/**
* @param UglyQueue $uglyQueue
* @return UglyQueueManager
*/
public function removeQueue(UglyQueue $uglyQueue)
{
$name = $uglyQueue->getName();
if ($this->containsQueueWithName($name))
unset($this->queues[$name]);
return $this;
}
/**
* @param string $name
* @return UglyQueueManager
*/
public function removeQueueByName($name)
{
if ($this->containsQueueWithName($name))
unset($this->queues[$name]);
return $this;
}
/**
* @param string $name
* @return UglyQueue
* @throws InvalidArgumentException
*/
public function getQueueWithName($name)
{
if (isset($this->queues[$name]))
return $this->queues[$name];
throw new InvalidArgumentException('Argument 1 expected to be valid queue name.');
}
/**
* @param string $name
* @return bool
*/
public function containsQueueWithName($name)
{
return isset($this->queues[$name]);
}
/**
* @return array
*/
public function getQueueList()
{
return array_keys($this->queues);
}
/**
* (PHP 5 >= 5.1.0)
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
*
* @return int The custom count as an integer. The return value is cast to an integer.
*/
public function count()
{
return count($this->queues);
}
/**
* (PHP 5 >= 5.1.0)
* Receive update from subject
* @link http://php.net/manual/en/splobserver.update.php
*
* @param \SplSubject $subject The SplSubject notifying the observer of an update.
* @return void
*/
public function update(\SplSubject $subject)
{
// Nothing for now...
}
}

View File

@@ -1,48 +1,553 @@
<?php
date_default_timezone_set('UTC');
require_once __DIR__.'/../misc/cleanup.php';
/**
* Class UglyQueueTest
*/
class UglyQueueTest extends PHPUnit_Framework_TestCase
class UglyQueueTest extends PHPUnit\Framework\TestCase
{
/**
* @covers \DCarbone\UglyQueue::__construct
* @uses \DCarbone\UglyQueue
* @return \DCarbone\UglyQueue
*/
public function testCanConstructUglyQueueWithValidParameter()
{
$conf = array(
'queue-base-dir' => dirname(__DIR__).'/misc/',
);
protected $baseDir;
$uglyQueue = new \DCarbone\UglyQueue($conf);
/**
* @var array
*/
protected $tastySandwich = array(
'0' => 'unsalted butter',
'1' => 'all-purpose flour',
'2' => 'hot milk',
'3' => 'kosher salt',
'4' => 'freshly ground black pepper',
'5' => 'nutmeg',
'6' => 'grated Gruyere',
'7' => 'freshly grated Parmesan',
'8' => 'white sandwich bread, crust removed',
'9' => 'Dijon mustard',
'10' => 'Virginia baked ham, sliced',
);
protected function setUp(): void
{
$this->baseDir = realpath(__DIR__.'/../misc/queues');
}
/**
* @covers \Cybercinch\UglyQueue::__construct
* @uses \Cybercinch\UglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanInitializeObjectWithValidParameters()
{
$uglyQueue = new \Cybercinch\UglyQueue($this->baseDir, 'tasty-sandwich');
$this->assertInstanceOf('\\Cybercinch\\UglyQueue', $uglyQueue);
return $uglyQueue;
}
/**
* @covers \DCarbone\UglyQueue::__construct
* @uses \DCarbone\UglyQueue
* @expectedException \InvalidArgumentException
* @covers \Cybercinch\UglyQueue::retrieveItems
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testExceptionThrownWhenConstructingUglyQueueWithEmptyOrInvalidConf()
public function testExceptionThrownWhenTryingToProcessQueueAfterInitializationBeforeLock(\Cybercinch\UglyQueue $uglyQueue)
{
$conf = array();
$uglyQueue = new \DCarbone\UglyQueue($conf);
$this->expectException(\RuntimeException::class);
$uglyQueue->retrieveItems();
}
/**
* @covers \DCarbone\UglyQueue::__construct
* @uses \DCarbone\UglyQueue
* @expectedException \RuntimeException
* @covers \Cybercinch\UglyQueue::keyExistsInQueue
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testExceptionThrownWhenConstructingUglyQueueWithInvalidQueueBaseDirPath()
public function testKeyExistsInQueueReturnsFalseWithEmptyQueueAfterInitialization(\Cybercinch\UglyQueue $uglyQueue)
{
$conf = array(
'queue-base-dir' => 'sandwiches',
);
$exists = $uglyQueue->keyExistsInQueue(0);
$uglyQueue = new \DCarbone\UglyQueue($conf);
$this->assertFalse($exists);
}
}
/**
* @covers \Cybercinch\UglyQueue::addItem
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testExceptionThrownWhenTryingToAddItemsToQueueWithoutLock(\Cybercinch\UglyQueue $uglyQueue)
{
$this->expectException(\RuntimeException::class);
$uglyQueue->addItem('test', 'value');
}
/**
* @covers \Cybercinch\UglyQueue::getPath
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetQueueDirectory(\Cybercinch\UglyQueue $uglyQueue)
{
$queuePath = $uglyQueue->getPath();
$this->assertFileExists($queuePath);
}
/**
* @covers \Cybercinch\UglyQueue::getName
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetQueueName(\Cybercinch\UglyQueue $uglyQueue)
{
$queueName = $uglyQueue->getName();
$this->assertEquals('tasty-sandwich', $queueName);
}
/**
* @covers \Cybercinch\UglyQueue::getMode
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetQueueMode(\Cybercinch\UglyQueue $uglyQueue)
{
$mode = $uglyQueue->getMode();
$this->assertEquals(\Cybercinch\UglyQueue::QUEUE_READWRITE, $mode);
}
/**
* @covers \Cybercinch\UglyQueue::getBaseDir
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetBaseDirectory(\Cybercinch\UglyQueue $uglyQueue)
{
$baseDir = $uglyQueue->getBaseDir();
$this->assertEquals($this->baseDir, $baseDir);
}
/**
* @covers \Cybercinch\UglyQueue::getLockFile
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetLockFilePath(\Cybercinch\UglyQueue $uglyQueue)
{
$lockFile = $uglyQueue->getLockFile();
$this->assertEquals(
sprintf('%s%s%s%squeue.lock',
$this->baseDir,
DIRECTORY_SEPARATOR,
$uglyQueue->getName(),
DIRECTORY_SEPARATOR),
$lockFile
);
}
/**
* @covers \Cybercinch\UglyQueue::getQueueFile
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetQueueFilePath(\Cybercinch\UglyQueue $uglyQueue)
{
$queueFile = $uglyQueue->getQueueFile();
$this->assertEquals(
sprintf('%s%s%s%squeue.txt',
$this->baseDir,
DIRECTORY_SEPARATOR,
$uglyQueue->getName(),
DIRECTORY_SEPARATOR),
$queueFile
);
}
/**
* @covers \Cybercinch\UglyQueue::getQueueTmpFile
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetQueueTmpFilePath(\Cybercinch\UglyQueue $uglyQueue)
{
$queueTmpFile = $uglyQueue->getQueueTmpFile();
$this->assertEquals(
sprintf(
'%s%s%s%squeue.tmp',
$this->baseDir,
DIRECTORY_SEPARATOR,
$uglyQueue->getName(),
DIRECTORY_SEPARATOR),
$queueTmpFile
);
}
/**
* @covers \Cybercinch\UglyQueue::getSerializeFile
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetSerializeFilePath(\Cybercinch\UglyQueue $uglyQueue)
{
$serializeFile = $uglyQueue->getSerializeFile();
$this->assertEquals(
sprintf(
'%s%s%s%sugly-queue.obj',
$this->baseDir,
DIRECTORY_SEPARATOR,
$uglyQueue->getName(),
DIRECTORY_SEPARATOR),
$serializeFile
);
}
/**
* @covers \Cybercinch\isAlreadyLocked::isAlreadyLocked
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetQueueLockedStatus(\Cybercinch\UglyQueue $uglyQueue)
{
$locked = $uglyQueue->isAlreadyLocked();
$this->assertFalse($locked);
}
/**
* @covers \Cybercinch\UglyQueue::count
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testGetQueueItemCountReturnsZeroWithEmptyQueue(\Cybercinch\UglyQueue $uglyQueue)
{
$itemCount = count($uglyQueue);
$this->assertEquals(0, $itemCount);
}
/**
* @covers \Cybercinch\UglyQueue::__construct
* @uses \Cybercinch\UglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanInitializeExistingQueue()
{
$uglyQueue = new \Cybercinch\UglyQueue($this->baseDir, 'tasty-sandwich');
$this->assertInstanceOf('\\Cybercinch\\UglyQueue', $uglyQueue);
return $uglyQueue;
}
/**
* @covers \Cybercinch\UglyQueue::lock
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testExceptionThrownWhenPassingNonIntegerValueToLock(\Cybercinch\UglyQueue $uglyQueue)
{
$this->expectException(\InvalidArgumentException::class);
$uglyQueue->lock('7 billion');
}
/**
* @covers \Cybercinch\UglyQueue::lock
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testExceptionThrownWhenPassingNegativeIntegerValueToLock(\Cybercinch\UglyQueue $uglyQueue)
{
$this->expectException(\InvalidArgumentException::class);
$uglyQueue->lock(-73);
}
/**
* @covers \Cybercinch\UglyQueue::lock
* @covers \Cybercinch\isAlreadyLocked::isAlreadyLocked
* @covers \Cybercinch\UglyQueue::createLockFile
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidParameters
* @param \Cybercinch\UglyQueue $uglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanLockUglyQueueWithDefaultTTL(\Cybercinch\UglyQueue $uglyQueue)
{
$locked = $uglyQueue->lock();
$this->assertTrue($locked);
$this->assertFileExists($uglyQueue->getLockFile());
$decode = @json_decode(file_get_contents($uglyQueue->getLockFile()));
$this->assertTrue((json_last_error() === JSON_ERROR_NONE));
$this->assertObjectHasAttribute('ttl', $decode);
$this->assertObjectHasAttribute('born', $decode);
$this->assertEquals(250, $decode->ttl);
return $uglyQueue;
}
/**
* @covers \Cybercinch\UglyQueue::lock
* @covers \Cybercinch\isAlreadyLocked::isAlreadyLocked
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeExistingQueue
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCannotLockQueueThatIsAlreadyLocked(\Cybercinch\UglyQueue $uglyQueue)
{
$lock = $uglyQueue->lock();
$this->assertFalse($lock);
}
/**
* @covers \Cybercinch\isAlreadyLocked::isAlreadyLocked
* @uses \Cybercinch\UglyQueue
* @depends testCanLockUglyQueueWithDefaultTTL
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testIsLockedReturnsTrueAfterLocking(\Cybercinch\UglyQueue $uglyQueue)
{
$isLocked = $uglyQueue->isAlreadyLocked();
$this->assertTrue($isLocked);
}
/**
* @covers \Cybercinch\UglyQueue::unlock
* @uses \Cybercinch\UglyQueue
* @uses \Cybercinch\Helpers\FileHelper
* @depends testCanLockUglyQueueWithDefaultTTL
* @param \Cybercinch\UglyQueue $uglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanUnlockLockedQueue(\Cybercinch\UglyQueue $uglyQueue)
{
$uglyQueue->unlock();
$this->assertFileNotExists($uglyQueue->getLockFile());
return $uglyQueue;
}
/**
* @covers \Cybercinch\isAlreadyLocked::isAlreadyLocked
* @uses \Cybercinch\UglyQueue
* @depends testCanUnlockLockedQueue
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testIsLockedReturnsFalseAfterUnlockingQueue(\Cybercinch\UglyQueue $uglyQueue)
{
$isLocked = $uglyQueue->isAlreadyLocked();
$this->assertFalse($isLocked);
}
/**
* @covers \Cybercinch\UglyQueue::lock
* @covers \Cybercinch\isAlreadyLocked::isAlreadyLocked
* @uses \Cybercinch\UglyQueue
* @uses \Cybercinch\Helpers\FileHelper
* @depends testCanUnlockLockedQueue
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testIsLockedReturnsFalseWithStaleQueueLockFile(\Cybercinch\UglyQueue $uglyQueue)
{
$uglyQueue->lock(2);
$isLocked = $uglyQueue->isAlreadyLocked();
$this->assertTrue($isLocked);
sleep(3);
$isLocked = $uglyQueue->isAlreadyLocked();
$this->assertFalse($isLocked);
}
/**
* @covers \Cybercinch\UglyQueue::lock
* @covers \Cybercinch\isAlreadyLocked::isAlreadyLocked
* @uses \Cybercinch\UglyQueue
* @depends testCanUnlockLockedQueue
* @param \Cybercinch\UglyQueue $uglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanLockQueueWithValidIntegerValue(\Cybercinch\UglyQueue $uglyQueue)
{
$locked = $uglyQueue->lock(200);
$this->assertTrue($locked);
$this->assertFileExists($uglyQueue->getLockFile());
$decode = @json_decode(file_get_contents($uglyQueue->getLockFile()));
$this->assertTrue((json_last_error() === JSON_ERROR_NONE));
$this->assertObjectHasAttribute('ttl', $decode);
$this->assertObjectHasAttribute('born', $decode);
$this->assertEquals(200, $decode->ttl);
return $uglyQueue;
}
/**
* @covers \Cybercinch\UglyQueue::addItem
* @uses \Cybercinch\UglyQueue
* @uses \Cybercinch\Helpers\FileHelper
* @depends testCanLockQueueWithValidIntegerValue
* @param \Cybercinch\UglyQueue $uglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanPopulateQueueTempFileAfterInitializationAndAcquiringLock(\Cybercinch\UglyQueue $uglyQueue)
{
foreach(array_reverse($this->tastySandwich, true) as $k=>$v)
{
$added = $uglyQueue->addItem($k, $v);
$this->assertTrue($added);
}
$this->assertFileExists(
$uglyQueue->getQueueTmpFile(),
'queue.tmp file was not created!');
$lineCount = \Dcarbone\Helpers\FileHelper::getLineCount($uglyQueue->getQueueTmpFile());
$this->assertEquals(11, $lineCount);
return $uglyQueue;
}
/**
* @covers \Cybercinch\UglyQueue::_populateQueue
* @uses \Cybercinch\UglyQueue
* @depends testCanPopulateQueueTempFileAfterInitializationAndAcquiringLock
* @param \Cybercinch\UglyQueue $uglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanForciblyUpdateQueueFileFromTempFile(\Cybercinch\UglyQueue $uglyQueue)
{
$uglyQueue->_populateQueue();
$this->assertFileNotExists($uglyQueue->getQueueTmpFile());
$uglyQueue->_populateQueue();
return $uglyQueue;
}
/**
* @covers \Cybercinch\UglyQueue::count
* @uses \Cybercinch\UglyQueue
* @uses \Cybercinch\Helpers\FileHelper
* @depends testCanPopulateQueueTempFileAfterInitializationAndAcquiringLock
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testCanGetCountOfItemsInPopulatedQueue(\Cybercinch\UglyQueue $uglyQueue)
{
$itemCount = count($uglyQueue);
$this->assertEquals(11, $itemCount);
}
/**
* @covers \Cybercinch\UglyQueue::keyExistsInQueue
* @uses \Cybercinch\UglyQueue
* @depends testCanPopulateQueueTempFileAfterInitializationAndAcquiringLock
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testKeyExistsReturnsTrueWithPopulatedQueue(\Cybercinch\UglyQueue $uglyQueue)
{
$exists = $uglyQueue->keyExistsInQueue(5);
$this->assertTrue($exists);
}
/**
* @covers \Cybercinch\UglyQueue::retrieveItems
* @uses \Cybercinch\UglyQueue
* @depends testCanPopulateQueueTempFileAfterInitializationAndAcquiringLock
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testExceptionThrownWhenTryingToProcessLockedQueueWithNonInteger(\Cybercinch\UglyQueue $uglyQueue)
{
$this->expectException(\InvalidArgumentException::class);
$uglyQueue->retrieveItems('Eleventy Billion');
}
/**
* @covers \Cybercinch\UglyQueue::retrieveItems
* @uses \Cybercinch\UglyQueue
* @depends testCanPopulateQueueTempFileAfterInitializationAndAcquiringLock
* @expectedException
* @param \Cybercinch\UglyQueue $uglyQueue
*/
public function testExceptionThrownWhenTryingToProcessLockedQueueWithIntegerLessThan1(\Cybercinch\UglyQueue $uglyQueue)
{
$this->expectException(\InvalidArgumentException::class);
$uglyQueue->retrieveItems(0);
}
/**
* @covers \Cybercinch\UglyQueue::retrieveItems
* @covers \Cybercinch\UglyQueue::count
* @uses \Cybercinch\UglyQueue
* @uses \Cybercinch\Helpers\FileHelper
* @depends testCanPopulateQueueTempFileAfterInitializationAndAcquiringLock
* @param \Cybercinch\UglyQueue $uglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanGetPartialQueueContents(\Cybercinch\UglyQueue $uglyQueue)
{
$process = $uglyQueue->retrieveItems(5);
$this->assertEquals(5, count($process));
$this->assertArrayHasKey('0', $process);
$this->assertArrayHasKey('4', $process);
$this->assertEquals(6, count($uglyQueue));
return $uglyQueue;
}
/**
* @covers \Cybercinch\UglyQueue::retrieveItems
* @covers \Cybercinch\UglyQueue::count
* @uses \Cybercinch\UglyQueue
* @uses \Cybercinch\Helpers\FileHelper
* @depends testCanGetPartialQueueContents
* @param \Cybercinch\UglyQueue $uglyQueue
* @return \Cybercinch\UglyQueue
*/
public function testCanGetFullQueueContents(\Cybercinch\UglyQueue $uglyQueue)
{
$process = $uglyQueue->retrieveItems(6);
$this->assertEquals(6, count($process));
$this->assertArrayHasKey('10', $process);
$this->assertArrayHasKey('5', $process);
$this->assertEquals(0, count($uglyQueue));
return $uglyQueue;
}
}

View File

@@ -0,0 +1,214 @@
<?php
/**
* Class UglyQueueManagerTest
*/
class UglyQueueManagerTest extends PHPUnit\Framework\TestCase
{
protected $baseDir;
protected $reallyTastySandwich = array(
'0' => 'beef broth',
'1' => 'barbeque sauce',
'2' => 'boneless pork ribs',
);
protected function setUp(): void
{
$this->baseDir = realpath(__DIR__.'/../misc/queues');
}
/**
* @covers \Cybercinch\UglyQueueManager::__construct
* @covers \Cybercinch\UglyQueue::unserialize
* @covers \Cybercinch\UglyQueueManager::addQueue
* @covers \Cybercinch\UglyQueueManager::containsQueueWithName
* @uses \Cybercinch\UglyQueueManager
* @uses \Cybercinch\UglyQueue
* @return \Cybercinch\UglyQueueManager
*/
public function testCanInitializeObjectWithValidPath()
{
$manager = new \Cybercinch\UglyQueueManager($this->baseDir);
$this->assertInstanceOf('\\Cybercinch\\UglyQueueManager', $manager);
return $manager;
}
/**
* @covers \Cybercinch\UglyQueueManager::__construct
* @uses \Cybercinch\UglyQueueManager
*/
public function testExceptionThrownDuringConstructionWithInvalidBasePathValue()
{
$this->expectException(RuntimeException::class);
new \Cybercinch\UglyQueueManager('i do not exist!');
}
/**
* @covers \Cybercinch\UglyQueueManager::containsQueueWithName
* @uses \Cybercinch\UglyQueueManager
* @depends testCanInitializeObjectWithValidPath
* @param \Cybercinch\UglyQueueManager $manager
*/
public function testCanDetermineIfValidQueueExistsInManager(\Cybercinch\UglyQueueManager $manager)
{
$shouldBeTrue = $manager->containsQueueWithName('tasty-sandwich');
$this->assertTrue($shouldBeTrue);
}
/**
* @covers \Cybercinch\UglyQueueManager::containsQueueWithName
* @uses \Cybercinch\UglyQueueManager
* @depends testCanInitializeObjectWithValidPath
* @param \Cybercinch\UglyQueueManager $manager
*/
public function testCanDetermineQueueDoesNotExistInManager(\Cybercinch\UglyQueueManager $manager)
{
$shouldBeFalse = $manager->containsQueueWithName('i should not exist');
$this->assertFalse($shouldBeFalse);
}
/**
* @covers \Cybercinch\UglyQueueManager::getQueueWithName
* @uses \Cybercinch\UglyQueueManager
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidPath
* @param \Cybercinch\UglyQueueManager $manager
*/
public function testCanGetUglyQueueObjectFromManager(\Cybercinch\UglyQueueManager $manager)
{
$uglyQueue = $manager->getQueueWithName('tasty-sandwich');
$this->assertInstanceOf('\\Cybercinch\\UglyQueue', $uglyQueue);
$this->assertEquals('tasty-sandwich', $uglyQueue->getName());
}
/**
* @covers \Cybercinch\UglyQueueManager::getQueueWithName
* @uses \Cybercinch\UglyQueueManager
* @depends testCanInitializeObjectWithValidPath
* @param \Cybercinch\UglyQueueManager $manager
*/
public function testExceptionThrownWhenTryingToGetNonExistentQueueFromManager(\Cybercinch\UglyQueueManager $manager)
{
$this->expectException(InvalidArgumentException::class);
$manager->getQueueWithName('sandwiches');
}
/**
* @covers \Cybercinch\UglyQueueManager::getQueueList
* @uses \Cybercinch\UglyQueueManager
* @depends testCanInitializeObjectWithValidPath
* @param \Cybercinch\UglyQueueManager $manager
*/
public function testCanGetListOfQueuesInManager(\Cybercinch\UglyQueueManager $manager)
{
$queueList = $manager->getQueueList();
$this->assertIsArray($queueList);
$this->assertCount(1, $queueList);
$this->assertContains('tasty-sandwich', $queueList);
}
/**
* @covers \Cybercinch\UglyQueueManager::getQueueWithName
* @covers \Cybercinch\UglyQueueManager::addQueue
* @uses \Cybercinch\UglyQueueManager
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidPath
* @param \Cybercinch\UglyQueueManager $manager
*/
public function testExceptionThrownWhenReAddingQueueToManager(\Cybercinch\UglyQueueManager $manager)
{
$this->expectException(RuntimeException::class);
$uglyQueue = $manager->getQueueWithName('tasty-sandwich');
$manager->addQueue($uglyQueue);
}
/**
* @covers \Cybercinch\UglyQueueManager::addQueueAtPath
* @covers \Cybercinch\UglyQueueManager::addQueue
* @covers \Cybercinch\UglyQueueManager::getQueueWithName
* @uses \Cybercinch\UglyQueueManager
* @uses \Cybercinch\UglyQueue
* @depends testCanInitializeObjectWithValidPath
* @param \Cybercinch\UglyQueueManager $manager
*/
public function testCanInitializeNewQueueAndAddToManager(\Cybercinch\UglyQueueManager $manager)
{
$manager->addQueueAtPath($this->baseDir.'/really-tasty-sandwich');
$uglyQueue = $manager->getQueueWithName('really-tasty-sandwich');
$this->assertInstanceOf('\\Cybercinch\\UglyQueue', $uglyQueue);
$this->assertEquals('really-tasty-sandwich', $uglyQueue->getName());
$queueList = $manager->getQueueList();
$this->assertIsArray($queueList);
$this->assertCount(2, $queueList);
$this->assertContains('really-tasty-sandwich', $queueList);
}
/**
* @covers \Cybercinch\UglyQueueManager::removeQueueByName
* @uses \Cybercinch\UglyQueueManager
* @depends testCanInitializeObjectWithValidPath
* @param \Cybercinch\UglyQueueManager $manager
*/
public function testCanRemoveQueueFromManagerByName(\Cybercinch\UglyQueueManager $manager)
{
$manager->removeQueueByName('really-tasty-sandwich');
$queueList = $manager->getQueueList();
$this->assertCount(1, $queueList);
$this->assertNotContains('really-tasty-sandwich', $queueList);
}
// /**
// * @covers \Cybercinch\UglyQueue::queueExists
// * @uses \Cybercinch\UglyQueue
// * @depends testCanInitializeNewUglyQueue
// * @param \Cybercinch\UglyQueue $uglyQueue
// */
// public function testCanDetermineExistenceOfExistingQueue(\Cybercinch\UglyQueue $uglyQueue)
// {
// $exists = $uglyQueue->queueExists('tasty-sandwich');
//
// $this->assertTrue($exists);
// }
//
// /**
// * @covers \Cybercinch\UglyQueue::queueExists
// * @uses \Cybercinch\UglyQueue
// * @depends testCanInitializeNewUglyQueue
// * @param \Cybercinch\UglyQueue $uglyQueue
// */
// public function testCanDetermineExistenceOfNonExistingQueue(\Cybercinch\UglyQueue $uglyQueue)
// {
// $exists = $uglyQueue->queueExists('nasty-sandwich');
//
// $this->assertFalse($exists);
// }
//
// /**
// * @covers \Cybercinch\UglyQueue::getInitializedQueueList
// * @uses \Cybercinch\UglyQueue
// * @depends testCanInitializeNewUglyQueue
// * @param \Cybercinch\UglyQueue $uglyQueue
// */
// public function testCanGetListOfInitializedQueues(\Cybercinch\UglyQueue $uglyQueue)
// {
// $queueList = $uglyQueue->getInitializedQueueList();
//
// $this->assertEquals(1, count($queueList));
// $this->assertContains('tasty-sandwich', $queueList);
// }
}

22
tests/misc/cleanup.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
if (is_dir(__DIR__.'/queues/'))
{
foreach(glob(__DIR__.'/queues/*', GLOB_ONLYDIR) as $queueDir)
{
foreach(glob($queueDir.'/*') as $file)
{
$split = preg_split('#[/\\\]+#', $file);
if (strpos(end($split), '.') === 0)
continue;
unlink($file);
}
rmdir($queueDir);
}
}
else
{
mkdir(__DIR__.'/queues');
}