diff --git a/README.md b/README.md index b87acd8..ffc1b98 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@

- + diff --git a/composer.json b/composer.json index 4d648cc..eb55431 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,12 @@ "source": "https://github.com/lkeme/BiliHelper-personal" }, "repositories": [ + { + "description": "Development package", + "type": "path", + "url": "packages/flintstone", + "canonical": false + }, { "description": "PhpComposer", "type": "composer", @@ -51,17 +57,17 @@ "ext-mbstring": "*", "monolog/monolog": "dev-main", "bramus/monolog-colored-line-formatter": "dev-master", - "symfony/yaml": "^6.0", + "symfony/yaml": "^7.0", "toolkit/stdlib": "*", "adhocore/cli": "^v1.0.0", - "lkeme/data": "4.x-dev", + "jbzoo/data": "dev-master", "grasmash/expander": "dev-main", "amphp/amp": "3.x-dev", - "fire015/flintstone": "dev-master", + "fire015/flintstone-fix-php-84": "dev-master", "overtrue/pinyin": "dev-master", "guzzlehttp/guzzle": "^7.0", "toolkit/pflag": "dev-main", - "symfony/console": "^6.0", + "symfony/console": "^7.0", "malios/php-to-ascii-table": "dev-master" }, "autoload": { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bdbf8c1..49d3546 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,25 @@ [comment]: <> () +## v2.4.5.250218 alpha (2025-02-18) + +### Added + +- + +### Changed + +- 更新设备信息 + +### Fixed + +- 尝试修复(-403)账号异常,操作失败 +- Make database config type explicitely nullable (fix php84 deprecation) + +### Remarks + +- Goodbye 2024 + ## v2.4.3.241231 alpha (2024-12-31) ### Added diff --git a/packages/flintstone/.github/FUNDING.yml b/packages/flintstone/.github/FUNDING.yml new file mode 100644 index 0000000..52722cf --- /dev/null +++ b/packages/flintstone/.github/FUNDING.yml @@ -0,0 +1 @@ +github: fire015 diff --git a/packages/flintstone/.gitignore b/packages/flintstone/.gitignore new file mode 100644 index 0000000..c1d6dc4 --- /dev/null +++ b/packages/flintstone/.gitignore @@ -0,0 +1,4 @@ +/vendor +composer.lock +.idea +.phpunit.result.cache \ No newline at end of file diff --git a/packages/flintstone/.travis.yml b/packages/flintstone/.travis.yml new file mode 100644 index 0000000..3a4bd10 --- /dev/null +++ b/packages/flintstone/.travis.yml @@ -0,0 +1,14 @@ +language: php +sudo: false +dist: bionic + +php: + - 7.3 + - 7.4 + - 8.0 + - 8.1.0 + +install: + - travis_retry composer install --no-interaction --prefer-dist + +script: vendor/bin/phpunit diff --git a/packages/flintstone/CHANGELOG.md b/packages/flintstone/CHANGELOG.md new file mode 100644 index 0000000..8d4a97a --- /dev/null +++ b/packages/flintstone/CHANGELOG.md @@ -0,0 +1,67 @@ +Change Log +========== + +### 19/01/2021 - 2.3 +* Bump minimum PHP version to 7.3 +* Update PHPUnit to version 9 (ensure Flintstone is compatible with PHP 8) + +### 12/03/2019 - 2.2 +* Bump minimum PHP version to 7.0 +* Update PHPUnit to version 6 +* Removed data type validation for storing +* Added param and return types + +### 09/06/2017 - 2.1.1 +* Update `Database::writeTempToFile` to correctly close the file pointer and free up memory + +### 24/05/2017 - 2.1 +* Bump minimum PHP version to 5.6 +* Tidy up of Flintstone class, moved some code into `Database` +* Added `Line` and `Validation` classes +* Closed off public methods `Database::openFile` and `Database::closeFile` + +### 20/01/2016 - 2.0 +* Major refactor, class names have changed and the whole codebase is much more extensible +* Removed the static `load` and `unload` methods and the `FlinstoneDB` class +* The `replace` method is no longer public +* The `getFile` method has been removed +* Default swap memory limit has been increased to 2MB +* Ability to pass any instance for cache that implements `Flintstone\Cache\CacheInterface` + +### 25/03/2015 - 1.9 +* Added `getAll` method and some refactoring + +### 15/10/2014 - 1.8 +* Added formatter option so that you can control how data is encoded/decoded (default is serialize but also ships with json) + +### 09/10/2014 - 1.7 +* Moved from fopen to SplFileObject +* Moved composer loader from PSR-0 to PSR-4 +* Code is now PSR-2 compliant +* Added PHP 5.6 to travis + +### 30/09/2014 - 1.6 +* Updated limits on valid characters in key name and size +* Improved unit tests + +### 29/05/2014 - 1.5 +* Reduced some internal complexity +* Fixed gzip compression +* Unit tests now running against all options +* Removed `setOptions` method, must be passed into the `load` method + +### 11/03/2014 - 1.4 +* Now using Composer + +### 16/07/2013 - 1.3 +* Changed the load method to static so that multiple instances can be loaded without conflict (use Flintstone::load now instead of $db->load) +* Exception thrown is now FlintstoneException + +### 23/01/2013 - 1.2 +* Removed the multibyte unserialize method as it seems to work without + +### 22/06/2012 - 1.1 +* Added new method getKeys() to return an array of keys in the database + +### 17/06/2011 - 1.0 +* Initial release \ No newline at end of file diff --git a/packages/flintstone/LICENSE.md b/packages/flintstone/LICENSE.md new file mode 100644 index 0000000..fe14b13 --- /dev/null +++ b/packages/flintstone/LICENSE.md @@ -0,0 +1,18 @@ +# MIT License + +Copyright (c) 2010-2017 Jason M + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT +OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.** diff --git a/packages/flintstone/README.md b/packages/flintstone/README.md new file mode 100644 index 0000000..3c2661e --- /dev/null +++ b/packages/flintstone/README.md @@ -0,0 +1,105 @@ +Flintstone +========== + +[![Total Downloads](https://img.shields.io/packagist/dm/fire015/flintstone.svg)](https://packagist.org/packages/fire015/flintstone) +[![Build Status](https://travis-ci.com/fire015/flintstone.svg?branch=master)](https://travis-ci.com/github/fire015/flintstone) + +A key/value database store using flat files for PHP. + +Features include: + +* Memory efficient +* File locking +* Caching +* Gzip compression +* Easy to use + +### Installation + +The easiest way to install Flintstone is via [composer](http://getcomposer.org/). Run the following command to install it. + +``` +composer require fire015/flintstone +``` + +```php + '/path/to/database/dir/']); +``` + +### Requirements + +- PHP 7.3+ + +### Data types + +Flintstone can store any data type that can be formatted into a string. By default this uses `serialize()`. See [Changing the formatter](#changing-the-formatter) for more details. + +### Options + +|Name |Type |Default Value |Description | +|--- |--- |--- |--- | +|dir |string |the current working directory |The directory where the database files are stored (this should be somewhere that is not web accessible) e.g. /path/to/database/ | +|ext |string |.dat |The database file extension to use | +|gzip |boolean |false |Use gzip to compress the database | +|cache |boolean or object |true |Whether to cache `get()` results for faster data retrieval | +|formatter |null or object |null |The formatter class used to encode/decode data | +|swap_memory_limit |integer |2097152 |The amount of memory to use before writing to a temporary file | + + +### Usage examples + +```php + '/path/to/database/dir/']); + +// Set a key +$users->set('bob', ['email' => 'bob@site.com', 'password' => '123456']); + +// Get a key +$user = $users->get('bob'); +echo 'Bob, your email is ' . $user['email']; + +// Retrieve all key names +$keys = $users->getKeys(); // returns array('bob') + +// Retrieve all data +$data = $users->getAll(); // returns array('bob' => array('email' => 'bob@site.com', 'password' => '123456')); + +// Delete a key +$users->delete('bob'); + +// Flush the database +$users->flush(); +``` + +### Changing the formatter +By default Flintstone will encode/decode data using PHP's serialize functions, however you can override this with your own class if you prefer. + +Just make sure it implements `Flintstone\Formatter\FormatterInterface` and then you can provide it as the `formatter` option. + +If you wish to use JSON as the formatter, Flintstone already ships with this as per the example below: + +```php + __DIR__, + 'formatter' => new JsonFormatter() +]); +``` + +### Changing the cache +To speed up data retrieval Flintstone can store the results of `get()` in a cache store. By default this uses a simple array that only persist's for as long as the `Flintstone` object exists. + +If you want to use your own cache store (such as Memcached) you can pass a class as the `cache` option. Just make sure it implements `Flintstone\Cache\CacheInterface`. diff --git a/packages/flintstone/UPGRADE.md b/packages/flintstone/UPGRADE.md new file mode 100644 index 0000000..7d46b0e --- /dev/null +++ b/packages/flintstone/UPGRADE.md @@ -0,0 +1,42 @@ +Upgrading from version 1.x to 2.x +================================= + +As Flintstone is no longer loaded statically the major change required is to switch from using the static `load` method to just instantiating a new instance of Flinstone. + +The `FlinstoneDB` class has also been removed and `Flintstone\FlintstoneException` is now `Flintstone\Exception`. + +### Version 1.x: + +```php + '/path/to/database/dir/')); +} +catch (FlintstoneException $e) { + +} +``` + +### Version 2.x: + +```php + '/path/to/database/dir/')); +} +catch (Exception $e) { + +} +``` + +See CHANGELOG.md for further changes. \ No newline at end of file diff --git a/packages/flintstone/composer.json b/packages/flintstone/composer.json new file mode 100644 index 0000000..eb4a4e7 --- /dev/null +++ b/packages/flintstone/composer.json @@ -0,0 +1,25 @@ +{ + "name": "fire015/flintstone-fix-php-84", + "type": "library", + "description": "A key/value database store using flat files for PHP", + "keywords": ["flintstone", "database", "cache", "files", "memory"], + "homepage": "https://github.com/fire015/flintstone", + "license": "MIT", + "authors": [ + { + "name": "Jason M", + "email": "emailfire@gmail.com" + } + ], + "require": { + "php": ">=7.3" + }, + "autoload": { + "psr-4": { + "Flintstone\\": "src/" + } + }, + "require-dev" : { + "phpunit/phpunit": "^9" + } +} diff --git a/packages/flintstone/phpunit.xml.dist b/packages/flintstone/phpunit.xml.dist new file mode 100644 index 0000000..db726eb --- /dev/null +++ b/packages/flintstone/phpunit.xml.dist @@ -0,0 +1,13 @@ + + + + + ./src + + + + + ./tests + + + diff --git a/packages/flintstone/src/Cache/ArrayCache.php b/packages/flintstone/src/Cache/ArrayCache.php new file mode 100644 index 0000000..5d2fcdc --- /dev/null +++ b/packages/flintstone/src/Cache/ArrayCache.php @@ -0,0 +1,53 @@ +cache); + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + return $this->cache[$key]; + } + + /** + * {@inheritdoc} + */ + public function set($key, $data) + { + $this->cache[$key] = $data; + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + unset($this->cache[$key]); + } + + /** + * {@inheritdoc} + */ + public function flush() + { + $this->cache = []; + } +} diff --git a/packages/flintstone/src/Cache/CacheInterface.php b/packages/flintstone/src/Cache/CacheInterface.php new file mode 100644 index 0000000..9459e01 --- /dev/null +++ b/packages/flintstone/src/Cache/CacheInterface.php @@ -0,0 +1,44 @@ +normalizeConfig($config); + $this->setDir($config['dir']); + $this->setExt($config['ext']); + $this->setGzip($config['gzip']); + $this->setCache($config['cache']); + $this->setFormatter($config['formatter']); + $this->setSwapMemoryLimit($config['swap_memory_limit']); + } + + /** + * Normalize the user supplied config. + * + * @param array $config + * + * @return array + */ + protected function normalizeConfig(array $config): array + { + $defaultConfig = [ + 'dir' => getcwd(), + 'ext' => '.dat', + 'gzip' => false, + 'cache' => true, + 'formatter' => null, + 'swap_memory_limit' => 2097152, // 2MB + ]; + + return array_replace($defaultConfig, $config); + } + + /** + * Get the dir. + * + * @return string + */ + public function getDir(): string + { + return $this->config['dir']; + } + + /** + * Set the dir. + * + * @param string $dir + * + * @throws Exception + */ + public function setDir(string $dir) + { + if (!is_dir($dir)) { + throw new Exception('Directory does not exist: ' . $dir); + } + + $this->config['dir'] = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR; + } + + /** + * Get the ext. + * + * @return string + */ + public function getExt(): string + { + if ($this->useGzip()) { + return $this->config['ext'] . '.gz'; + } + + return $this->config['ext']; + } + + /** + * Set the ext. + * + * @param string $ext + */ + public function setExt(string $ext) + { + if (substr($ext, 0, 1) !== '.') { + $ext = '.' . $ext; + } + + $this->config['ext'] = $ext; + } + + /** + * Use gzip? + * + * @return bool + */ + public function useGzip(): bool + { + return $this->config['gzip']; + } + + /** + * Set gzip. + * + * @param bool $gzip + */ + public function setGzip(bool $gzip) + { + $this->config['gzip'] = $gzip; + } + + /** + * Get the cache. + * + * @return CacheInterface|false + */ + public function getCache() + { + return $this->config['cache']; + } + + /** + * Set the cache. + * + * @param mixed $cache + * + * @throws Exception + */ + public function setCache($cache) + { + if (!is_bool($cache) && !$cache instanceof CacheInterface) { + throw new Exception('Cache must be a boolean or an instance of Flintstone\Cache\CacheInterface'); + } + + if ($cache === true) { + $cache = new ArrayCache(); + } + + $this->config['cache'] = $cache; + } + + /** + * Get the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface + { + return $this->config['formatter']; + } + + /** + * Set the formatter. + * + * @param FormatterInterface|null $formatter + * + * @throws Exception + */ + public function setFormatter($formatter) + { + if ($formatter === null) { + $formatter = new SerializeFormatter(); + } + + if (!$formatter instanceof FormatterInterface) { + throw new Exception('Formatter must be an instance of Flintstone\Formatter\FormatterInterface'); + } + + $this->config['formatter'] = $formatter; + } + + /** + * Get the swap memory limit. + * + * @return int + */ + public function getSwapMemoryLimit(): int + { + return $this->config['swap_memory_limit']; + } + + /** + * Set the swap memory limit. + * + * @param int $limit + */ + public function setSwapMemoryLimit(int $limit) + { + $this->config['swap_memory_limit'] = $limit; + } +} diff --git a/packages/flintstone/src/Database.php b/packages/flintstone/src/Database.php new file mode 100644 index 0000000..f06aad7 --- /dev/null +++ b/packages/flintstone/src/Database.php @@ -0,0 +1,255 @@ + [ + 'mode' => 'rb', + 'operation' => LOCK_SH, + ], + self::FILE_WRITE => [ + 'mode' => 'wb', + 'operation' => LOCK_EX, + ], + self::FILE_APPEND => [ + 'mode' => 'ab', + 'operation' => LOCK_EX, + ], + ]; + + /** + * Database name. + * + * @var string + */ + protected $name; + + /** + * Config class. + * + * @var Config + */ + protected $config; + + /** + * Constructor. + * + * @param string $name + * @param Config|null $config + */ + public function __construct(string $name, Config|null $config = null) + { + $this->setName($name); + + if ($config) { + $this->setConfig($config); + } + } + + /** + * Get the database name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Set the database name. + * + * @param string $name + * + * @throws Exception + */ + public function setName(string $name) + { + Validation::validateDatabaseName($name); + $this->name = $name; + } + + /** + * Get the config. + * + * @return Config + */ + public function getConfig(): Config + { + return $this->config; + } + + /** + * Set the config. + * + * @param Config $config + */ + public function setConfig(Config $config) + { + $this->config = $config; + } + + /** + * Get the path to the database file. + * + * @return string + */ + public function getPath(): string + { + return $this->config->getDir() . $this->getName() . $this->config->getExt(); + } + + /** + * Open the database file. + * + * @param int $mode + * + * @throws Exception + * + * @return SplFileObject + */ + protected function openFile(int $mode): SplFileObject + { + $path = $this->getPath(); + + if (!is_file($path) && !@touch($path)) { + throw new Exception('Could not create file: ' . $path); + } + + if (!is_readable($path) || !is_writable($path)) { + throw new Exception('File does not have permission for read and write: ' . $path); + } + + if ($this->getConfig()->useGzip()) { + $path = 'compress.zlib://' . $path; + } + + $res = $this->fileAccessMode[$mode]; + $file = new SplFileObject($path, $res['mode']); + + if ($mode === self::FILE_READ) { + $file->setFlags(SplFileObject::DROP_NEW_LINE | SplFileObject::SKIP_EMPTY | SplFileObject::READ_AHEAD); + } + + if (!$this->getConfig()->useGzip() && !$file->flock($res['operation'])) { + $file = null; + throw new Exception('Could not lock file: ' . $path); + } + + return $file; + } + + /** + * Open a temporary file. + * + * @return SplTempFileObject + */ + public function openTempFile(): SplTempFileObject + { + return new SplTempFileObject($this->getConfig()->getSwapMemoryLimit()); + } + + /** + * Close the database file. + * + * @param SplFileObject $file + * + * @throws Exception + */ + protected function closeFile(SplFileObject &$file) + { + if (!$this->getConfig()->useGzip() && !$file->flock(LOCK_UN)) { + $file = null; + throw new Exception('Could not unlock file'); + } + + $file = null; + } + + /** + * Read lines from the database file. + * + * @return \Generator + */ + public function readFromFile(): \Generator + { + $file = $this->openFile(static::FILE_READ); + + try { + foreach ($file as $line) { + yield new Line($line); + } + } finally { + $this->closeFile($file); + } + } + + /** + * Append a line to the database file. + * + * @param string $line + */ + public function appendToFile(string $line) + { + $file = $this->openFile(static::FILE_APPEND); + $file->fwrite($line); + $this->closeFile($file); + } + + /** + * Flush the database file. + */ + public function flushFile() + { + $file = $this->openFile(static::FILE_WRITE); + $this->closeFile($file); + } + + /** + * Write temporary file contents to database file. + * + * @param SplTempFileObject $tmpFile + */ + public function writeTempToFile(SplTempFileObject &$tmpFile) + { + $file = $this->openFile(static::FILE_WRITE); + + foreach ($tmpFile as $line) { + $file->fwrite($line); + } + + $this->closeFile($file); + $tmpFile = null; + } +} diff --git a/packages/flintstone/src/Exception.php b/packages/flintstone/src/Exception.php new file mode 100644 index 0000000..e784197 --- /dev/null +++ b/packages/flintstone/src/Exception.php @@ -0,0 +1,7 @@ +setDatabase($database); + $this->setConfig($config); + } + + /** + * Get the database. + * + * @return Database + */ + public function getDatabase(): Database + { + return $this->database; + } + + /** + * Set the database. + * + * @param Database $database + */ + public function setDatabase(Database $database) + { + $this->database = $database; + } + + /** + * Get the config. + * + * @return Config + */ + public function getConfig(): Config + { + return $this->config; + } + + /** + * Set the config. + * + * @param Config $config + */ + public function setConfig(Config $config) + { + $this->config = $config; + $this->getDatabase()->setConfig($config); + } + + /** + * Get a key from the database. + * + * @param string $key + * + * @return mixed + */ + public function get(string $key) + { + Validation::validateKey($key); + + // Fetch the key from cache + if ($cache = $this->getConfig()->getCache()) { + if ($cache->contains($key)) { + return $cache->get($key); + } + } + + // Fetch the key from database + $file = $this->getDatabase()->readFromFile(); + $data = false; + + foreach ($file as $line) { + /** @var Line $line */ + if ($line->getKey() == $key) { + $data = $this->decodeData($line->getData()); + break; + } + } + + // Save the data to cache + if ($cache && $data !== false) { + $cache->set($key, $data); + } + + return $data; + } + + /** + * Set a key in the database. + * + * @param string $key + * @param mixed $data + */ + public function set(string $key, $data) + { + Validation::validateKey($key); + + // If the key already exists we need to replace it + if ($this->get($key) !== false) { + $this->replace($key, $data); + return; + } + + // Write the key to the database + $this->getDatabase()->appendToFile($this->getLineString($key, $data)); + + // Delete the key from cache + if ($cache = $this->getConfig()->getCache()) { + $cache->delete($key); + } + } + + /** + * Delete a key from the database. + * + * @param string $key + */ + public function delete(string $key) + { + Validation::validateKey($key); + + if ($this->get($key) !== false) { + $this->replace($key, false); + } + } + + /** + * Flush the database. + */ + public function flush() + { + $this->getDatabase()->flushFile(); + + // Flush the cache + if ($cache = $this->getConfig()->getCache()) { + $cache->flush(); + } + } + + /** + * Get all keys from the database. + * + * @return array + */ + public function getKeys(): array + { + $keys = []; + $file = $this->getDatabase()->readFromFile(); + + foreach ($file as $line) { + /** @var Line $line */ + $keys[] = $line->getKey(); + } + + return $keys; + } + + /** + * Get all data from the database. + * + * @return array + */ + public function getAll(): array + { + $data = []; + $file = $this->getDatabase()->readFromFile(); + + foreach ($file as $line) { + /** @var Line $line */ + $data[$line->getKey()] = $this->decodeData($line->getData()); + } + + return $data; + } + + /** + * Replace a key in the database. + * + * @param string $key + * @param mixed $data + */ + protected function replace(string $key, $data) + { + // Write a new database to a temporary file + $tmpFile = $this->getDatabase()->openTempFile(); + $file = $this->getDatabase()->readFromFile(); + + foreach ($file as $line) { + /** @var Line $line */ + if ($line->getKey() == $key) { + if ($data !== false) { + $tmpFile->fwrite($this->getLineString($key, $data)); + } + } else { + $tmpFile->fwrite($line->getLine() . "\n"); + } + } + + $tmpFile->rewind(); + + // Overwrite the database with the temporary file + $this->getDatabase()->writeTempToFile($tmpFile); + + // Delete the key from cache + if ($cache = $this->getConfig()->getCache()) { + $cache->delete($key); + } + } + + /** + * Get the line string to write. + * + * @param string $key + * @param mixed $data + * + * @return string + */ + protected function getLineString(string $key, $data): string + { + return $key . '=' . $this->encodeData($data) . "\n"; + } + + /** + * Decode a string into data. + * + * @param string $data + * + * @return mixed + */ + protected function decodeData(string $data) + { + return $this->getConfig()->getFormatter()->decode($data); + } + + /** + * Encode data into a string. + * + * @param mixed $data + * + * @return string + */ + protected function encodeData($data): string + { + return $this->getConfig()->getFormatter()->encode($data); + } +} diff --git a/packages/flintstone/src/Formatter/FormatterInterface.php b/packages/flintstone/src/Formatter/FormatterInterface.php new file mode 100644 index 0000000..8666dd6 --- /dev/null +++ b/packages/flintstone/src/Formatter/FormatterInterface.php @@ -0,0 +1,24 @@ +assoc = $assoc; + } + + /** + * {@inheritdoc} + */ + public function encode($data): string + { + $result = json_encode($data); + + if (json_last_error() === JSON_ERROR_NONE) { + return $result; + } + + throw new Exception(json_last_error_msg()); + } + + /** + * {@inheritdoc} + */ + public function decode(string $data) + { + $result = json_decode($data, $this->assoc); + + if (json_last_error() === JSON_ERROR_NONE) { + return $result; + } + + throw new Exception(json_last_error_msg()); + } +} diff --git a/packages/flintstone/src/Formatter/SerializeFormatter.php b/packages/flintstone/src/Formatter/SerializeFormatter.php new file mode 100644 index 0000000..c16087e --- /dev/null +++ b/packages/flintstone/src/Formatter/SerializeFormatter.php @@ -0,0 +1,52 @@ +preserveLines($data, false)); + } + + /** + * {@inheritdoc} + */ + public function decode(string $data) + { + return $this->preserveLines(unserialize($data), true); + } + + /** + * Preserve new lines, recursive function. + * + * @param mixed $data + * @param bool $reverse + * + * @return mixed + */ + protected function preserveLines($data, bool $reverse) + { + $search = ["\n", "\r"]; + $replace = ['\\n', '\\r']; + + if ($reverse) { + $search = ['\\n', '\\r']; + $replace = ["\n", "\r"]; + } + + if (is_string($data)) { + $data = str_replace($search, $replace, $data); + } elseif (is_array($data)) { + foreach ($data as &$value) { + $value = $this->preserveLines($value, $reverse); + } + unset($value); + } + + return $data; + } +} diff --git a/packages/flintstone/src/Line.php b/packages/flintstone/src/Line.php new file mode 100644 index 0000000..c9f52e0 --- /dev/null +++ b/packages/flintstone/src/Line.php @@ -0,0 +1,37 @@ +line = $line; + $this->pieces = explode('=', $line, 2); + } + + public function getLine(): string + { + return $this->line; + } + + public function getKey(): string + { + return $this->pieces[0]; + } + + public function getData(): string + { + return $this->pieces[1]; + } +} diff --git a/packages/flintstone/src/Validation.php b/packages/flintstone/src/Validation.php new file mode 100644 index 0000000..29e901e --- /dev/null +++ b/packages/flintstone/src/Validation.php @@ -0,0 +1,34 @@ +cache = new ArrayCache(); + } + + /** + * @test + */ + public function canGetAndSet() + { + $this->cache->set('foo', 'bar'); + $this->assertTrue($this->cache->contains('foo')); + $this->assertEquals('bar', $this->cache->get('foo')); + } + + /** + * @test + */ + public function canDelete() + { + $this->cache->set('foo', 'bar'); + $this->cache->delete('foo'); + $this->assertFalse($this->cache->contains('foo')); + } + + /** + * @test + */ + public function canFlush() + { + $this->cache->set('foo', 'bar'); + $this->cache->flush(); + $this->assertFalse($this->cache->contains('foo')); + } +} diff --git a/packages/flintstone/tests/ConfigTest.php b/packages/flintstone/tests/ConfigTest.php new file mode 100644 index 0000000..e0ef3c3 --- /dev/null +++ b/packages/flintstone/tests/ConfigTest.php @@ -0,0 +1,85 @@ +assertEquals(getcwd().DIRECTORY_SEPARATOR, $config->getDir()); + $this->assertEquals('.dat', $config->getExt()); + $this->assertFalse($config->useGzip()); + $this->assertInstanceOf(ArrayCache::class, $config->getCache()); + $this->assertInstanceOf(SerializeFormatter::class, $config->getFormatter()); + $this->assertEquals(2097152, $config->getSwapMemoryLimit()); + } + + /** + * @test + */ + public function constructorConfigOverride() + { + $config = new Config([ + 'dir' => __DIR__, + 'ext' => 'test', + 'gzip' => true, + 'cache' => false, + 'formatter' => null, + 'swap_memory_limit' => 100, + ]); + + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR, $config->getDir()); + $this->assertEquals('.test.gz', $config->getExt()); + $this->assertTrue($config->useGzip()); + $this->assertFalse($config->getCache()); + $this->assertInstanceOf(SerializeFormatter::class, $config->getFormatter()); + $this->assertEquals(100, $config->getSwapMemoryLimit()); + } + + /** + * @test + */ + public function setValidFormatter() + { + $config = new Config(); + $config->setFormatter(new JsonFormatter()); + $this->assertInstanceOf(JsonFormatter::class, $config->getFormatter()); + } + + /** + * @test + */ + public function setInvalidFormatter() + { + $this->expectException(\Flintstone\Exception::class); + $config = new Config(); + $config->setFormatter(new self()); + } + + /** + * @test + */ + public function invalidDirSet() + { + $this->expectException(\Flintstone\Exception::class); + $config = new Config(); + $config->setDir('/x/y/z/foo'); + } + + /** + * @test + */ + public function invalidCacheSet() + { + $this->expectException(\Flintstone\Exception::class); + $config = new Config(); + $config->setCache(new self()); + } +} diff --git a/packages/flintstone/tests/DatabaseTest.php b/packages/flintstone/tests/DatabaseTest.php new file mode 100644 index 0000000..2f274e0 --- /dev/null +++ b/packages/flintstone/tests/DatabaseTest.php @@ -0,0 +1,96 @@ + __DIR__, + ]); + + $this->db = new Database('test', $config); + } + + protected function tearDown(): void + { + if (is_file($this->db->getPath())) { + unlink($this->db->getPath()); + } + } + + /** + * @test + */ + public function databaseHasInvalidName() + { + $this->expectException(\Flintstone\Exception::class); + $config = new Config(); + new Database('test!123', $config); + } + + /** + * @test + */ + public function canGetDatabaseAndConfig() + { + $this->assertEquals('test', $this->db->getName()); + $this->assertInstanceOf(Config::class, $this->db->getConfig()); + $this->assertEquals(__DIR__ . DIRECTORY_SEPARATOR . 'test.dat', $this->db->getPath()); + } + + /** + * @test + */ + public function canAppendToFile() + { + $this->db->appendToFile('foo=bar'); + $this->assertEquals('foo=bar', file_get_contents($this->db->getPath())); + } + + /** + * @test + */ + public function canFlushFile() + { + $this->db->appendToFile('foo=bar'); + $this->db->flushFile(); + $this->assertEmpty(file_get_contents($this->db->getPath())); + } + + /** + * @test + */ + public function canReadFromFile() + { + $this->db->appendToFile('foo=bar'); + $file = $this->db->readFromFile(); + + foreach ($file as $line) { + $this->assertInstanceOf(Line::class, $line); + $this->assertEquals('foo', $line->getKey()); + $this->assertEquals('bar', $line->getData()); + } + } + + /** + * @test + */ + public function canWriteTempToFile() + { + $tmpFile = new SplTempFileObject(); + $tmpFile->fwrite('foo=bar'); + $tmpFile->rewind(); + + $this->db->writeTempToFile($tmpFile); + $this->assertEquals('foo=bar', file_get_contents($this->db->getPath())); + } +} diff --git a/packages/flintstone/tests/FlintstoneTest.php b/packages/flintstone/tests/FlintstoneTest.php new file mode 100644 index 0000000..21c28a2 --- /dev/null +++ b/packages/flintstone/tests/FlintstoneTest.php @@ -0,0 +1,97 @@ + __DIR__, + 'cache' => false, + ]); + + $this->assertInstanceOf(Database::class, $db->getDatabase()); + $this->assertInstanceOf(Config::class, $db->getConfig()); + } + + /** + * @test + */ + public function keyHasInvalidName() + { + $this->expectException(\Flintstone\Exception::class); + $db = new Flintstone('test', []); + $db->get('test!123'); + } + + /** + * @test + */ + public function canRunAllOperations() + { + $this->runOperationsTests([ + 'dir' => __DIR__, + 'cache' => false, + 'gzip' => false, + ]); + + $this->runOperationsTests([ + 'dir' => __DIR__, + 'cache' => true, + 'gzip' => true, + ]); + + $this->runOperationsTests([ + 'dir' => __DIR__, + 'cache' => false, + 'gzip' => false, + 'formatter' => new JsonFormatter(), + ]); + } + + private function runOperationsTests(array $config) + { + $db = new Flintstone('test', $config); + $arr = ['foo' => "new\nline"]; + + $this->assertFalse($db->get('foo')); + + $db->set('foo', 1); + $db->set('name', 'john'); + $db->set('arr', $arr); + $this->assertEquals(1, $db->get('foo')); + $this->assertEquals('john', $db->get('name')); + $this->assertEquals($arr, $db->get('arr')); + + $db->set('foo', 2); + $this->assertEquals(2, $db->get('foo')); + $this->assertEquals('john', $db->get('name')); + $this->assertEquals($arr, $db->get('arr')); + + $db->delete('name'); + $this->assertFalse($db->get('name')); + $this->assertEquals($arr, $db->get('arr')); + + $keys = $db->getKeys(); + $this->assertEquals(2, count($keys)); + $this->assertEquals('foo', $keys[0]); + $this->assertEquals('arr', $keys[1]); + + $data = $db->getAll(); + $this->assertEquals(2, count($data)); + $this->assertEquals(2, $data['foo']); + $this->assertEquals($arr, $data['arr']); + + $db->flush(); + $this->assertFalse($db->get('foo')); + $this->assertFalse($db->get('arr')); + $this->assertEquals(0, count($db->getKeys())); + $this->assertEquals(0, count($db->getAll())); + + unlink($db->getDatabase()->getPath()); + } +} diff --git a/packages/flintstone/tests/Formatter/JsonFormatterTest.php b/packages/flintstone/tests/Formatter/JsonFormatterTest.php new file mode 100644 index 0000000..39f4fef --- /dev/null +++ b/packages/flintstone/tests/Formatter/JsonFormatterTest.php @@ -0,0 +1,73 @@ +formatter = new JsonFormatter(); + } + + /** + * @test + * @dataProvider validData + */ + public function encodesValidData($originalValue, $encodedValue) + { + $this->assertSame($encodedValue, $this->formatter->encode($originalValue)); + } + + /** + * @test + * @dataProvider validData + */ + public function decodesValidData($originalValue, $encodedValue) + { + $this->assertSame($originalValue, $this->formatter->decode($encodedValue)); + } + + /** + * @test + */ + public function decodesAnObject() + { + $originalValue = (object)['foo' => 'bar']; + $formatter = new JsonFormatter(false); + $encodedValue = $formatter->encode($originalValue); + $this->assertEquals($originalValue, $formatter->decode($encodedValue)); + } + + /** + * @test + */ + public function encodingInvalidDataThrowsException() + { + $this->expectException(\Flintstone\Exception::class); + $this->formatter->encode(chr(241)); + } + + /** + * @test + */ + public function decodingInvalidDataThrowsException() + { + $this->expectException(\Flintstone\Exception::class); + $this->formatter->decode('{'); + } + + public function validData(): array + { + return [ + [null, 'null'], + [1, '1'], + ['foo', '"foo"'], + [["test", "new\nline"], '["test","new\nline"]'], + ]; + } +} diff --git a/packages/flintstone/tests/Formatter/SerializeFormatterTest.php b/packages/flintstone/tests/Formatter/SerializeFormatterTest.php new file mode 100644 index 0000000..2d8d06e --- /dev/null +++ b/packages/flintstone/tests/Formatter/SerializeFormatterTest.php @@ -0,0 +1,44 @@ +formatter = new SerializeFormatter(); + } + + /** + * @test + * @dataProvider validData + */ + public function encodesValidData($originalValue, $encodedValue) + { + $this->assertSame($encodedValue, $this->formatter->encode($originalValue)); + } + + /** + * @test + * @dataProvider validData + */ + public function decodesValidData($originalValue, $encodedValue) + { + $this->assertSame($originalValue, $this->formatter->decode($encodedValue)); + } + + public function validData(): array + { + return [ + [null, 'N;'], + [1, 'i:1;'], + ['foo', 's:3:"foo";'], + [["test", "new\nline"], 'a:2:{i:0;s:4:"test";i:1;s:9:"new\nline";}'], + ]; + } +} diff --git a/packages/flintstone/tests/LineTest.php b/packages/flintstone/tests/LineTest.php new file mode 100644 index 0000000..746d3db --- /dev/null +++ b/packages/flintstone/tests/LineTest.php @@ -0,0 +1,50 @@ +line = new Line('foo=bar'); + } + + /** + * @test + */ + public function canGetLine() + { + $this->assertEquals('foo=bar', $this->line->getLine()); + } + + /** + * @test + */ + public function canGetKey() + { + $this->assertEquals('foo', $this->line->getKey()); + } + + /** + * @test + */ + public function canGetData() + { + $this->assertEquals('bar', $this->line->getData()); + } + + /** + * @test + */ + public function canGetKeyAndDataWithMultipleEquals() + { + $line = new Line('foo=bar=baz'); + $this->assertEquals('foo', $line->getKey()); + $this->assertEquals('bar=baz', $line->getData()); + } +} diff --git a/packages/flintstone/tests/ValidationTest.php b/packages/flintstone/tests/ValidationTest.php new file mode 100644 index 0000000..1fe1ff1 --- /dev/null +++ b/packages/flintstone/tests/ValidationTest.php @@ -0,0 +1,24 @@ +expectException(\Flintstone\Exception::class); + Validation::validateKey('test!123'); + } + + /** + * @test + */ + public function validateDatabaseName() + { + $this->expectException(\Flintstone\Exception::class); + Validation::validateDatabaseName('test!123'); + } +} diff --git a/plugin/CheckUpdate/CheckUpdate.php b/plugin/CheckUpdate/CheckUpdate.php index f0c86a8..f65da77 100644 --- a/plugin/CheckUpdate/CheckUpdate.php +++ b/plugin/CheckUpdate/CheckUpdate.php @@ -89,11 +89,11 @@ class CheckUpdate extends BasePluginRW } // 比较版本 if ($this->compareVersion($offline->get('version'), $online->version)) { - // TODO 完善消息 支持markdown - $time = $online->time; + // $version = $online->version; - $des = $online->des; - $info = "请注意版本变动更新哦~\n\n版本号: $version\n\n更新日志: $des\n\n更新时间: $time\n\n"; + $time = $online->update_time; + $desc = $online->update_description; + $info = "请注意版本变动更新哦~\n\n版本号: $version\n\n更新日志: $desc\n\n更新时间: $time\n\n"; Log::notice($info); Notice::push('update', $info); } else { diff --git a/profile/example/device/device.yaml b/profile/example/device/device.yaml index 42a582b..3fae873 100644 --- a/profile/example/device/device.yaml +++ b/profile/example/device/device.yaml @@ -3,8 +3,8 @@ device_version: 0.0.1 app: bili_a: # Android package: "tv.danmaku.bili" - version: "8.27.0" - build: "8270400" + version: "8.33.0" + build: "8330200" channel: "bili" device: "phone" mobi_app: "android" @@ -15,7 +15,7 @@ app: secret_key: "NTYwYzUyY2NkMjg4ZmVkMDQ1ODU5ZWQxOGJmZmQ5NzM" app_key_n: "NzgzYmJiNzI2NDQ1MWQ4Mg==" secret_key_n: "MjY1MzU4M2M4ODczZGVhMjY4YWI5Mzg2OTE4YjFkNjU=" - statistics: '{"appId":1,"platform":3,"version":"8.27.0","abtest":""}' + statistics: '{"appId":1,"platform":3,"version":"8.33.0","abtest":""}' bili_i: # IOS app_key: "MjdlYjUzZmM5MDU4ZjhjMw==" secret_key: "YzJlZDUzYTc0ZWVlZmUzY2Y5OWZiZDAxZDhjOWMzNzU=" diff --git a/resources/version.json b/resources/version.json index 081832e..c480136 100644 --- a/resources/version.json +++ b/resources/version.json @@ -8,10 +8,7 @@ "dev_raw_url": "https://raw.githubusercontent.com/lkeme/BiliHelper-personal/dev/resources/version.json", "master_purge_url": "https://cdn.staticaly.com/gh/lkeme/BiliHelper-personal/master/resources/version.json", "dev_purge_url": "https://cdn.staticaly.com/gh/lkeme/BiliHelper-personal/dev/resources/version.json", - "version": "2.4.3.241231", - "des": "程序有更新,请及时线上查看更新哦~", - "time": "2024-12-31", - "ini_version": "0.0.1", - "ini_des": "配置有更新,请及时线上查看更新哦~", - "ini_time": "2024-12-31" + "version": "2.4.5.250218", + "update_time": "2025-02-18", + "update_description": "程序有更新,请及时线上查看更新哦~" } diff --git a/src/Util/Resource/Resource.php b/src/Util/Resource/Resource.php index d280cdf..1ec46d1 100644 --- a/src/Util/Resource/Resource.php +++ b/src/Util/Resource/Resource.php @@ -20,6 +20,11 @@ namespace Bhp\Util\Resource; use Grasmash\Expander\Expander; use Grasmash\Expander\Stringifier; use JBZoo\Data\Data; +use JBZoo\Data\Ini; +use JBZoo\Data\JSON; +use JBZoo\Data\PhpArray; +use JBZoo\Data\Yml; +use Symfony\Component\Yaml\Yaml; use function JBZoo\Data\data; use function JBZoo\Data\ini; use function JBZoo\Data\phpArray; @@ -35,9 +40,9 @@ class Resource extends Collection protected const FORMAT_JSON = 'json'; /** - * @var Data + * @var Ini|JSON|Yml|PhpArray|Data */ - protected Data $config; + protected Ini|JSON|Yml|PhpArray|Data $config; /** * @var string @@ -73,15 +78,15 @@ class Resource extends Collection * 切换解析器 * @param string $filepath * @param string $format - * @return Data + * @return Ini|JSON|Yml|PhpArray|Data */ - protected function switchParser(string $filepath, string $format): Data + protected function switchParser(string $filepath, string $format): Ini|Json|Yml|PhpArray|Data { return match ($format) { Resource::FORMAT_INI => ini($filepath), - Resource::FORMAT_PHP => phpArray($filepath), - Resource::FORMAT_YML, Resource::FORMAT_YAML => yml($filepath), Resource::FORMAT_JSON => json($filepath), + Resource::FORMAT_YML, Resource::FORMAT_YAML => yml($filepath), + Resource::FORMAT_PHP => phpArray($filepath), default => data($filepath), }; }