[update] Version 2.4.5

This commit is contained in:
Lkeme 2025-02-18 16:13:22 +08:00
parent 33fb76a747
commit 22833cc006
35 changed files with 1901 additions and 24 deletions

View File

@ -29,7 +29,7 @@
<p align=center>
<img src="https://img.shields.io/badge/Version-2.4.3.241231-orange.svg?longCache=true&style=for-the-badge" alt="">
<img src="https://img.shields.io/badge/Version-2.4.5.250218-orange.svg?longCache=true&style=for-the-badge" alt="">
<img src="https://img.shields.io/badge/PHP-8.1+-green.svg?longCache=true&style=for-the-badge" alt="">
<img src="https://img.shields.io/badge/Composer-latest-blueviolet.svg?longCache=true&style=for-the-badge" alt="">
<img src="https://img.shields.io/badge/License-mit-blue.svg?longCache=true&style=for-the-badge" alt="">

View File

@ -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": {

View File

@ -8,6 +8,25 @@
[comment]: <> (</details>)
## 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

View File

@ -0,0 +1 @@
github: fire015

4
packages/flintstone/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/vendor
composer.lock
.idea
.phpunit.result.cache

View File

@ -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

View File

@ -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

View File

@ -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.**

View File

@ -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
<?php
require 'vendor/autoload.php';
use Flintstone\Flintstone;
$users = new Flintstone('users', ['dir' => '/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
<?php
// Load a database
$users = new Flintstone('users', ['dir' => '/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
<?php
require 'vendor/autoload.php';
use Flintstone\Flintstone;
use Flintstone\Formatter\JsonFormatter;
$users = new Flintstone('users', [
'dir' => __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`.

View File

@ -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
<?php
require 'vendor/autoload.php';
use Flintstone\Flintstone;
use Flintstone\FlintstoneException;
try {
$users = Flintstone::load('users', array('dir' => '/path/to/database/dir/'));
}
catch (FlintstoneException $e) {
}
```
### Version 2.x:
```php
<?php
require 'vendor/autoload.php';
use Flintstone\Flintstone;
use Flintstone\Exception;
try {
$users = new Flintstone('users', array('dir' => '/path/to/database/dir/'));
}
catch (Exception $e) {
}
```
See CHANGELOG.md for further changes.

View File

@ -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"
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" verbose="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flintstone Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,53 @@
<?php
namespace Flintstone\Cache;
class ArrayCache implements CacheInterface
{
/**
* Cache data.
*
* @var array
*/
protected $cache = [];
/**
* {@inheritdoc}
*/
public function contains($key)
{
return array_key_exists($key, $this->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 = [];
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Flintstone\Cache;
interface CacheInterface
{
/**
* Check if a key exists in the cache.
*
* @param string $key
*
* @return bool
*/
public function contains($key);
/**
* Get a key from the cache.
*
* @param string $key
*
* @return mixed
*/
public function get($key);
/**
* Set a key in the cache.
*
* @param string $key
* @param mixed $data
*/
public function set($key, $data);
/**
* Delete a key from the cache.
*
* @param string $key
*/
public function delete($key);
/**
* Flush the cache.
*/
public function flush();
}

View File

@ -0,0 +1,209 @@
<?php
namespace Flintstone;
use Flintstone\Cache\ArrayCache;
use Flintstone\Cache\CacheInterface;
use Flintstone\Formatter\FormatterInterface;
use Flintstone\Formatter\SerializeFormatter;
class Config
{
/**
* Config.
*
* @var array
*/
protected $config = [];
/**
* Constructor.
*
* @param array $config
*/
public function __construct(array $config = [])
{
$config = $this->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;
}
}

View File

@ -0,0 +1,255 @@
<?php
namespace Flintstone;
use SplFileObject;
use SplTempFileObject;
class Database
{
/**
* File read flag.
*
* @var int
*/
const FILE_READ = 1;
/**
* File write flag.
*
* @var int
*/
const FILE_WRITE = 2;
/**
* File append flag.
*
* @var int
*/
const FILE_APPEND = 3;
/**
* File access mode.
*
* @var array
*/
protected $fileAccessMode = [
self::FILE_READ => [
'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;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Flintstone;
class Exception extends \Exception
{
}

View File

@ -0,0 +1,285 @@
<?php
namespace Flintstone;
class Flintstone
{
/**
* Flintstone version.
*
* @var string
*/
const VERSION = '2.3';
/**
* Database class.
*
* @var Database
*/
protected $database;
/**
* Config class.
*
* @var Config
*/
protected $config;
/**
* Constructor.
*
* @param Database|string $database
* @param Config|array $config
*/
public function __construct($database, $config)
{
if (is_string($database)) {
$database = new Database($database);
}
if (is_array($config)) {
$config = new Config($config);
}
$this->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);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Flintstone\Formatter;
interface FormatterInterface
{
/**
* Encode data into a string.
*
* @param mixed $data
*
* @return string
*/
public function encode($data): string;
/**
* Decode a string into data.
*
* @param string $data
*
* @return mixed
*/
public function decode(string $data);
}

View File

@ -0,0 +1,46 @@
<?php
namespace Flintstone\Formatter;
use Flintstone\Exception;
class JsonFormatter implements FormatterInterface
{
/**
* @var bool
*/
private $assoc;
public function __construct(bool $assoc = true)
{
$this->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());
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Flintstone\Formatter;
class SerializeFormatter implements FormatterInterface
{
/**
* {@inheritdoc}
*/
public function encode($data): string
{
return serialize($this->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;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Flintstone;
class Line
{
/**
* @var string
*/
protected $line;
/**
* @var array
*/
protected $pieces = [];
public function __construct(string $line)
{
$this->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];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Flintstone;
class Validation
{
/**
* Validate the key.
*
* @param string $key
*
* @throws Exception
*/
public static function validateKey(string $key)
{
if (empty($key) || !preg_match('/^[\w-]+$/', $key)) {
throw new Exception('Invalid characters in key');
}
}
/**
* Check the database name is valid.
*
* @param string $name
*
* @throws Exception
*/
public static function validateDatabaseName(string $name)
{
if (empty($name) || !preg_match('/^[\w-]+$/', $name)) {
throw new Exception('Invalid characters in database name');
}
}
}

View File

@ -0,0 +1,46 @@
<?php
use Flintstone\Cache\ArrayCache;
class ArrayCacheTest extends \PHPUnit\Framework\TestCase
{
/**
* @var ArrayCache
*/
private $cache;
protected function setUp(): void
{
$this->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'));
}
}

View File

@ -0,0 +1,85 @@
<?php
use Flintstone\Cache\ArrayCache;
use Flintstone\Config;
use Flintstone\Formatter\JsonFormatter;
use Flintstone\Formatter\SerializeFormatter;
class ConfigTest extends \PHPUnit\Framework\TestCase
{
/**
* @test
*/
public function defaultConfigIsSet()
{
$config = new Config();
$this->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());
}
}

View File

@ -0,0 +1,96 @@
<?php
use Flintstone\Config;
use Flintstone\Database;
use Flintstone\Line;
class DatabaseTest extends \PHPUnit\Framework\TestCase
{
/**
* @var Database
*/
private $db;
protected function setUp(): void
{
$config = new Config([
'dir' => __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()));
}
}

View File

@ -0,0 +1,97 @@
<?php
use Flintstone\Config;
use Flintstone\Database;
use Flintstone\Flintstone;
use Flintstone\Formatter\JsonFormatter;
class FlintstoneTest extends \PHPUnit\Framework\TestCase
{
public function testGetDatabaseAndConfig()
{
$db = new Flintstone('test', [
'dir' => __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());
}
}

View File

@ -0,0 +1,73 @@
<?php
use Flintstone\Formatter\JsonFormatter;
class JsonFormatterTest extends \PHPUnit\Framework\TestCase
{
/**
* @var JsonFormatter
*/
private $formatter;
protected function setUp(): void
{
$this->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"]'],
];
}
}

View File

@ -0,0 +1,44 @@
<?php
use Flintstone\Formatter\SerializeFormatter;
class SerializeFormatterTest extends \PHPUnit\Framework\TestCase
{
/**
* @var SerializeFormatter
*/
private $formatter;
protected function setUp(): void
{
$this->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";}'],
];
}
}

View File

@ -0,0 +1,50 @@
<?php
use Flintstone\Line;
class LineTest extends \PHPUnit\Framework\TestCase
{
/**
* @var Line
*/
private $line;
protected function setUp(): void
{
$this->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());
}
}

View File

@ -0,0 +1,24 @@
<?php
use Flintstone\Validation;
class ValidationTest extends \PHPUnit\Framework\TestCase
{
/**
* @test
*/
public function validateKey()
{
$this->expectException(\Flintstone\Exception::class);
Validation::validateKey('test!123');
}
/**
* @test
*/
public function validateDatabaseName()
{
$this->expectException(\Flintstone\Exception::class);
Validation::validateDatabaseName('test!123');
}
}

View File

@ -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 {

View File

@ -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="

View File

@ -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": "程序有更新,请及时线上查看更新哦~"
}

View File

@ -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),
};
}